docs(ops): add blocked product response intake preflight [skip ci]

This commit is contained in:
Your Name
2026-06-24 15:07:27 +08:00
parent 413a0dc864
commit 5649d89b80
5 changed files with 623 additions and 0 deletions

View File

@@ -1,3 +1,26 @@
## 2026-06-24Blocked products owner response intake preflight
**背景**blocked products 已有 owner decision packages `8/8`、response templates `8/8`、acceptance ledgers `8/8`,但仍缺真正 owner response。為避免下一輪人工判斷混亂本輪新增可執行 intake preflight讓未來收件先走機器檢查與 lanes 分流。
**新增 / 更新**
- `scripts/security/blocked-products-owner-response-intake-preflight.py`
- `docs/operations/blocked-product-owner-responses/README.md`
- `docs/operations/codex-gitea-blocked-products-owner-response-intake-preflight.snapshot.json`
- `docs/operations/CODEX-GITEA-BLOCKED-PRODUCTS-OWNER-RESPONSE-INTAKE-PREFLIGHT-2026-06-24.md`
**固定口徑**
- blocked products`8`
- response files`0`
- intake lanes`6`
- required owner response fields`14`
- acceptance checks`16`
- rejection guards`15`
- owner response received / accepted / rejected`0 / 0 / 0`
- review branch ready / remote dev ready / remote dev created`0 / 0 / 0`
- product repo write / runtime write / secret collection`0`
**邊界**:這是 repo-only intake preflight不是 owner response、review branch、remote `dev` branch、Gitea repo write、runtime write、secret collection、raw `.git` sync 或 raw conversation sync。
## 2026-06-24Codex Start Here acceptance ledger sync readback
**背景**blocked products owner response acceptance ledger 已推上 Gitea 後Mac Mini 本機 `~/.codex/CODEX-START-HERE.md``~/.codex/codex-workstation-sync-dashboard.snapshot.json` 需要再次同步到 MacBook Pro避免外出開新 Codex 視窗時讀不到 acceptance ledger gate。

View File

@@ -0,0 +1,56 @@
# Codex Gitea Blocked Products Owner Response Intake Preflight
- generated_at: `2026-06-24T15:05:00+08:00`
- blocked_products: `8`
- response_files: `0`
- owner_response_received: `0 / 8`
- owner_response_accepted: `0 / 8`
- remote_dev_ready: `0 / 8`
- runtime_write_authorized: `0`
## 目的
本階段把 blocked products 的 owner response 從「文件模板」推進成「可執行的 intake preflight」。未來如果收到產品級、已脫敏的 owner response JSON先由 `scripts/security/blocked-products-owner-response-intake-preflight.py` 掃描、分類、拒收或轉 reviewer checklist。
目前尚未收到任何 owner response所以所有 accepted / remote dev / runtime gate 仍維持 `0`
## 收件位置
只允許放置 metadata-only JSON
- `docs/operations/blocked-product-owner-responses/`
目前此目錄只有 README沒有 response JSON。
## Intake Lanes
| Lane | 用途 | Gate effect |
|------|------|-------------|
| `keep_waiting_owner_response` | 沒有產品級回覆或只有一般批准語句 | received / accepted / remote dev 仍為 0 |
| `quarantine_sensitive_payload` | 疑似 secret / raw payload / `.env` / raw `.git` | 不保存 raw payload不進 review |
| `reject_execution_request` | 夾帶 repo write / deploy / restart / runtime 操作 | 拒收,不建立 action button |
| `request_supplement` | 欄位不足或 evidence refs 不足 | 補件,不增加 accepted |
| `ready_for_acceptance_review` | 欄位完整且脫敏 | 只進 reviewer checklist |
| `metadata_accepted_waiting_branch_confirmation` | metadata accepted 後仍需分支最終確認 | runtime write 仍 false |
## 驗證指令
```bash
python3 scripts/security/blocked-products-owner-response-intake-preflight.py \
--root . \
--generated-at 2026-06-24T15:05:00+08:00
```
預期 readback
```text
BLOCKED_PRODUCTS_OWNER_RESPONSE_INTAKE_PREFLIGHT_OK products=8 responses=0 accepted=0 remote_dev_ready=0 runtime_write=0
```
## 邊界
- 沒有讀 secret、`.env`、raw conversation、raw `.git` 或 runtime volume。
- 沒有修改任何產品 repo。
- 沒有建立 review branch、remote `dev` branch 或 Gitea repo。
- 沒有部署、restart、reload、DB / K8s / host / firewall / Nginx runtime write。
- 一般「批准繼續」仍不等於產品級 owner response。

View File

@@ -0,0 +1,14 @@
# Blocked Product Owner Responses
本目錄只允許放「已脫敏、metadata-only」owner response JSON。
禁止放入:
- secret value、token、password、private key、cookie、session、authorization header
- `.env` 內容、hash、partial token、credential derivative
- raw Codex / ChatGPT conversation
- raw `.git` directory 或 git bundle
- runtime volume payload
- 未脫敏 log、backup archive、browser profile、database dump
目前尚未收到任何可驗收的 owner response所以此目錄只保留本 README。

View File

@@ -0,0 +1,267 @@
{
"schema_version": "codex_gitea_blocked_products_owner_response_intake_preflight_v1",
"generated_at": "2026-06-24T15:05:00+08:00",
"git_commit": "413a0dc8",
"source_ledger_schema_version": "codex_gitea_blocked_products_owner_response_acceptance_v1",
"status": "waiting_owner_response",
"source_ledger": "docs/operations/codex-gitea-blocked-products-owner-response-acceptance.snapshot.json",
"response_directory": "docs/operations/blocked-product-owner-responses",
"summary": {
"blocked_product_count": 8,
"intake_candidate_count": 8,
"response_file_count": 0,
"parsed_response_file_count": 0,
"quarantined_response_file_count": 0,
"valid_redacted_response_file_count": 0,
"required_owner_response_field_count": 14,
"acceptance_check_count": 16,
"rejection_guard_count": 15,
"intake_lane_count": 6,
"owner_response_received_count": 0,
"owner_response_accepted_count": 0,
"owner_response_rejected_count": 0,
"supplement_requested_count": 0,
"review_branch_ready_count": 0,
"remote_dev_branch_ready_count": 0,
"remote_dev_branch_created_count": 0,
"product_repo_write_authorized_count": 0,
"runtime_write_authorized_count": 0,
"secret_values_collected_count": 0,
"action_button_count": 0
},
"intake_lanes": [
{
"lane_id": "keep_waiting_owner_response",
"instruction": "沒有產品級 redacted owner response或只有一般批准語句時維持等待。",
"gate_effect": "received / accepted / remote_dev_ready 全部維持 0。"
},
{
"lane_id": "quarantine_sensitive_payload",
"instruction": "疑似包含 secret、.env、raw conversation、raw .git、runtime volume 或未脫敏 payload 時隔離。",
"gate_effect": "不得保存 raw payload不得渲染到前端不得進 acceptance review。"
},
{
"lane_id": "reject_execution_request",
"instruction": "夾帶 repo write、remote dev branch、deploy、restart、DB、K8s、host 或 runtime 執行要求時拒收。",
"gate_effect": "不得建立 action button不得轉成執行批准。"
},
{
"lane_id": "request_supplement",
"instruction": "欄位不足、scope 不清、include / exclude 模糊或 evidence refs 不足時補件。",
"gate_effect": "不得增加 accepted 或 remote_dev_ready。"
},
{
"lane_id": "ready_for_acceptance_review",
"instruction": "欄位完整、證據脫敏、無敏感 payload 且無執行要求時才可進 reviewer checklist。",
"gate_effect": "仍不是 accepted也不是 remote dev 授權。"
},
{
"lane_id": "metadata_accepted_waiting_branch_confirmation",
"instruction": "即使 metadata accepted也還需要逐產品 branch / baseline final confirmation。",
"gate_effect": "runtime_write 仍必須是 falseremote dev 仍需獨立最終確認。"
}
],
"required_owner_response_fields": [
"owner_role_or_team",
"decision",
"decision_reason",
"accepted_baseline_source",
"include_groups",
"exclude_groups",
"quarantined_paths_ack",
"env_secret_policy_ack",
"generated_artifact_policy_ack",
"review_branch_allowed",
"remote_dev_branch_allowed",
"runtime_write_allowed",
"followup_owner",
"evidence_refs"
],
"acceptance_checks": [
"owner_role_or_team_is_present",
"decision_is_product_specific",
"decision_reason_is_present",
"baseline_source_is_explicit",
"include_groups_are_specific",
"exclude_groups_are_specific",
"quarantined_paths_ack_is_true",
"env_secret_policy_ack_is_true",
"generated_artifact_policy_ack_is_true",
"review_branch_allowed_is_explicit",
"remote_dev_branch_allowed_is_explicit",
"runtime_write_allowed_is_false",
"evidence_refs_are_redacted",
"followup_owner_is_present",
"no_secret_value_or_partial_secret_present",
"no_raw_conversation_or_raw_git_sync_requested"
],
"rejection_guards": [
"generic_approval_phrase_only",
"missing_owner_role_or_team",
"missing_decision_reason",
"missing_baseline_source",
"ambiguous_include_or_exclude_groups",
"requests_secret_value_or_env_content",
"requests_raw_git_directory_sync",
"requests_raw_codex_or_chatgpt_history_sync",
"requests_runtime_volume_sync",
"requests_runtime_write",
"requests_product_repo_write_without_review_branch",
"requests_remote_dev_branch_without_explicit_product_decision",
"includes_generated_outputs_without_sanitized_policy",
"includes_logs_or_backup_archives_without_policy",
"missing_redacted_evidence_refs"
],
"products": [
{
"product_id": "clawbot-openclaw",
"status": "waiting_owner_response",
"decision_package": "docs/operations/CLAWBOT-OPENCLAW-DEV-BASELINE-OWNER-DECISION-2026-06-24.md",
"response_template_section": "P1-1 ClawBot / OpenClaw",
"default_blockers": ["owner_response_missing", "two_file_drift_not_accepted"],
"intake_lane": "keep_waiting_owner_response",
"owner_response_received": false,
"owner_response_accepted": false,
"owner_response_rejected": false,
"supplement_requested": false,
"quarantined": false,
"review_branch_ready": false,
"remote_dev_branch_ready": false,
"runtime_write_authorized": false,
"secret_values_collected": false
},
{
"product_id": "tsenyang-website",
"status": "waiting_owner_response",
"decision_package": "docs/operations/TSENYANG-WEBSITE-DEV-BASELINE-OWNER-DECISION-2026-06-24.md",
"response_template_section": "P1-2 Tsenyang Website",
"default_blockers": ["owner_response_missing", "presentation_output_policy_missing"],
"intake_lane": "keep_waiting_owner_response",
"owner_response_received": false,
"owner_response_accepted": false,
"owner_response_rejected": false,
"supplement_requested": false,
"quarantined": false,
"review_branch_ready": false,
"remote_dev_branch_ready": false,
"runtime_write_authorized": false,
"secret_values_collected": false
},
{
"product_id": "agent-bounty-protocol",
"status": "waiting_owner_response",
"decision_package": "docs/operations/AGENT-BOUNTY-DEV-BASELINE-OWNER-DECISION-2026-06-24.md",
"response_template_section": "P1-3 Agent Bounty",
"default_blockers": ["owner_response_missing", "a2a_treasury_scope_not_accepted", "backup_archive_policy_missing"],
"intake_lane": "keep_waiting_owner_response",
"owner_response_received": false,
"owner_response_accepted": false,
"owner_response_rejected": false,
"supplement_requested": false,
"quarantined": false,
"review_branch_ready": false,
"remote_dev_branch_ready": false,
"runtime_write_authorized": false,
"secret_values_collected": false
},
{
"product_id": "2026fifa",
"status": "waiting_owner_response",
"decision_package": "docs/operations/2026FIFA-DEV-BASELINE-OWNER-DECISION-2026-06-24.md",
"response_template_section": "P1-4 2026FIFA",
"default_blockers": ["owner_response_missing", "narrow_scanner_not_completed"],
"intake_lane": "keep_waiting_owner_response",
"owner_response_received": false,
"owner_response_accepted": false,
"owner_response_rejected": false,
"supplement_requested": false,
"quarantined": false,
"review_branch_ready": false,
"remote_dev_branch_ready": false,
"runtime_write_authorized": false,
"secret_values_collected": false
},
{
"product_id": "vibework",
"status": "waiting_owner_response",
"decision_package": "docs/operations/VIBEWORK-DEV-BASELINE-OWNER-DECISION-2026-06-24.md",
"response_template_section": "P1-5 VibeWork",
"default_blockers": ["owner_response_missing", "release_scope_not_split_or_accepted", "diff_check_debt_unresolved"],
"intake_lane": "keep_waiting_owner_response",
"owner_response_received": false,
"owner_response_accepted": false,
"owner_response_rejected": false,
"supplement_requested": false,
"quarantined": false,
"review_branch_ready": false,
"remote_dev_branch_ready": false,
"runtime_write_authorized": false,
"secret_values_collected": false
},
{
"product_id": "stockplatform-v2",
"status": "waiting_owner_response",
"decision_package": "docs/operations/STOCKPLATFORM-V2-DEV-BASELINE-OWNER-DECISION-2026-06-24.md",
"response_template_section": "P1-6 StockPlatform v2",
"default_blockers": ["owner_response_missing", "tmp_generated_outputs_not_excluded", "source_candidate_policy_missing"],
"intake_lane": "keep_waiting_owner_response",
"owner_response_received": false,
"owner_response_accepted": false,
"owner_response_rejected": false,
"supplement_requested": false,
"quarantined": false,
"review_branch_ready": false,
"remote_dev_branch_ready": false,
"runtime_write_authorized": false,
"secret_values_collected": false
},
{
"product_id": "bitan-pharmacy",
"status": "waiting_owner_response",
"decision_package": "docs/operations/BITAN-PHARMACY-DEV-BASELINE-OWNER-DECISION-2026-06-24.md",
"response_template_section": "P1-7 Bitan Pharmacy",
"default_blockers": ["owner_response_missing", "internal_inventory_missing", "public_content_cleanliness_evidence_policy_missing"],
"intake_lane": "keep_waiting_owner_response",
"owner_response_received": false,
"owner_response_accepted": false,
"owner_response_rejected": false,
"supplement_requested": false,
"quarantined": false,
"review_branch_ready": false,
"remote_dev_branch_ready": false,
"runtime_write_authorized": false,
"secret_values_collected": false
},
{
"product_id": "vtuber",
"status": "waiting_owner_response",
"decision_package": "docs/operations/VTUBER-DEV-BASELINE-OWNER-DECISION-2026-06-24.md",
"response_template_section": "P1-8 VTuber",
"default_blockers": ["owner_response_missing", "repository_identity_unresolved", "remote_repo_missing"],
"intake_lane": "keep_waiting_owner_response",
"owner_response_received": false,
"owner_response_accepted": false,
"owner_response_rejected": false,
"supplement_requested": false,
"quarantined": false,
"review_branch_ready": false,
"remote_dev_branch_ready": false,
"runtime_write_authorized": false,
"secret_values_collected": false
}
],
"inspected_response_files": [],
"hard_gates": [
"Only redacted owner response metadata JSON files may be inspected.",
"Generic approval text is not a product-specific owner response.",
"Accepted count stays 0 until reviewer acceptance is recorded.",
"Remote dev branch readiness stays 0 until product-specific final confirmation.",
"Runtime write, product repo write, secret collection, raw .git sync, raw conversation sync, .env read, and runtime volume sync remain forbidden."
],
"secret_values_collected": false,
"env_file_content_read": false,
"raw_git_sync_allowed": false,
"raw_conversation_sync_allowed": false,
"runtime_write_authorized": false,
"product_repo_write_authorized": false
}

View File

@@ -0,0 +1,263 @@
#!/usr/bin/env python3
"""
Codex / Gitea blocked products owner response intake preflight.
This is a repo-only metadata gate. It reads the committed acceptance ledger and
optionally scans redacted owner response metadata files. It must never read
secret values, .env contents, raw Codex conversations, raw .git directories, or
runtime volumes.
"""
from __future__ import annotations
import argparse
import json
import subprocess
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Any
TAIPEI = timezone(timedelta(hours=8))
DEFAULT_LEDGER = Path("docs/operations/codex-gitea-blocked-products-owner-response-acceptance.snapshot.json")
DEFAULT_RESPONSE_DIR = Path("docs/operations/blocked-product-owner-responses")
INTAKE_LANES = [
{
"lane_id": "keep_waiting_owner_response",
"instruction": "沒有產品級 redacted owner response或只有一般批准語句時維持等待。",
"gate_effect": "received / accepted / remote_dev_ready 全部維持 0。",
},
{
"lane_id": "quarantine_sensitive_payload",
"instruction": "疑似包含 secret、.env、raw conversation、raw .git、runtime volume 或未脫敏 payload 時隔離。",
"gate_effect": "不得保存 raw payload不得渲染到前端不得進 acceptance review。",
},
{
"lane_id": "reject_execution_request",
"instruction": "夾帶 repo write、remote dev branch、deploy、restart、DB、K8s、host 或 runtime 執行要求時拒收。",
"gate_effect": "不得建立 action button不得轉成執行批准。",
},
{
"lane_id": "request_supplement",
"instruction": "欄位不足、scope 不清、include / exclude 模糊或 evidence refs 不足時補件。",
"gate_effect": "不得增加 accepted 或 remote_dev_ready。",
},
{
"lane_id": "ready_for_acceptance_review",
"instruction": "欄位完整、證據脫敏、無敏感 payload 且無執行要求時才可進 reviewer checklist。",
"gate_effect": "仍不是 accepted也不是 remote dev 授權。",
},
{
"lane_id": "metadata_accepted_waiting_branch_confirmation",
"instruction": "即使 metadata accepted也還需要逐產品 branch / baseline final confirmation。",
"gate_effect": "runtime_write 仍必須是 falseremote dev 仍需獨立最終確認。",
},
]
FORBIDDEN_KEYS = [
"secret",
"token",
"password",
"private_key",
"cookie",
"session",
"authorization",
"env_content",
"raw_git",
"raw_conversation",
"runtime_volume",
]
def git_short_sha(root: Path) -> str:
try:
result = subprocess.run(
["git", "rev-parse", "--short", "HEAD"],
cwd=root,
check=True,
capture_output=True,
text=True,
)
return result.stdout.strip()
except Exception:
return "unknown"
def load_json(path: Path) -> dict[str, Any]:
return json.loads(path.read_text(encoding="utf-8"))
def response_files(response_dir: Path) -> list[Path]:
if not response_dir.exists():
return []
return sorted(path for path in response_dir.glob("*.json") if path.is_file())
def has_forbidden_key(payload: Any) -> bool:
if isinstance(payload, dict):
for key, value in payload.items():
normalized = str(key).lower()
if any(forbidden in normalized for forbidden in FORBIDDEN_KEYS):
return True
if has_forbidden_key(value):
return True
elif isinstance(payload, list):
return any(has_forbidden_key(item) for item in payload)
return False
def inspect_responses(response_paths: list[Path]) -> list[dict[str, Any]]:
inspected: list[dict[str, Any]] = []
for path in response_paths:
try:
payload = load_json(path)
parse_ok = True
parse_error = ""
except Exception as exc:
payload = {}
parse_ok = False
parse_error = str(exc)
product_id = payload.get("product_id", path.stem) if isinstance(payload, dict) else path.stem
inspected.append(
{
"path": str(path),
"product_id": product_id,
"parse_ok": parse_ok,
"parse_error": parse_error,
"contains_forbidden_key": has_forbidden_key(payload),
"received": parse_ok,
"accepted": False,
"review_branch_ready": False,
"remote_dev_branch_ready": False,
"runtime_write_authorized": False,
}
)
return inspected
def repo_relative(root: Path, path: Path) -> str:
try:
return path.relative_to(root).as_posix()
except ValueError:
return str(path)
def candidate_from_product(product: dict[str, Any]) -> dict[str, Any]:
return {
"product_id": product["product_id"],
"status": "waiting_owner_response",
"decision_package": product["decision_package"],
"response_template_section": product["response_template_section"],
"default_blockers": product["default_blockers"],
"intake_lane": "keep_waiting_owner_response",
"owner_response_received": False,
"owner_response_accepted": False,
"owner_response_rejected": False,
"supplement_requested": False,
"quarantined": False,
"review_branch_ready": False,
"remote_dev_branch_ready": False,
"runtime_write_authorized": False,
"secret_values_collected": False,
}
def build_report(root: Path, ledger: dict[str, Any], response_dir: Path, generated_at: str | None) -> dict[str, Any]:
report_time = generated_at or datetime.now(TAIPEI).isoformat(timespec="seconds")
products = ledger.get("products", [])
response_paths = response_files(response_dir)
inspected = inspect_responses(response_paths)
parsed_received = [item for item in inspected if item["received"] and not item["contains_forbidden_key"]]
quarantined = [item for item in inspected if item["contains_forbidden_key"] or not item["parse_ok"]]
return {
"schema_version": "codex_gitea_blocked_products_owner_response_intake_preflight_v1",
"generated_at": report_time,
"git_commit": git_short_sha(root),
"source_ledger_schema_version": ledger.get("schema_version"),
"status": "waiting_owner_response",
"source_ledger": str(DEFAULT_LEDGER),
"response_directory": repo_relative(root, response_dir),
"summary": {
"blocked_product_count": len(products),
"intake_candidate_count": len(products),
"response_file_count": len(response_paths),
"parsed_response_file_count": sum(1 for item in inspected if item["parse_ok"]),
"quarantined_response_file_count": len(quarantined),
"valid_redacted_response_file_count": len(parsed_received),
"required_owner_response_field_count": len(ledger.get("required_owner_response_fields", [])),
"acceptance_check_count": len(ledger.get("acceptance_checks", [])),
"rejection_guard_count": len(ledger.get("rejection_guards", [])),
"intake_lane_count": len(INTAKE_LANES),
"owner_response_received_count": 0,
"owner_response_accepted_count": 0,
"owner_response_rejected_count": 0,
"supplement_requested_count": 0,
"review_branch_ready_count": 0,
"remote_dev_branch_ready_count": 0,
"remote_dev_branch_created_count": 0,
"product_repo_write_authorized_count": 0,
"runtime_write_authorized_count": 0,
"secret_values_collected_count": 0,
"action_button_count": 0,
},
"intake_lanes": INTAKE_LANES,
"required_owner_response_fields": ledger.get("required_owner_response_fields", []),
"acceptance_checks": ledger.get("acceptance_checks", []),
"rejection_guards": ledger.get("rejection_guards", []),
"products": [candidate_from_product(product) for product in products],
"inspected_response_files": inspected,
"hard_gates": [
"Only redacted owner response metadata JSON files may be inspected.",
"Generic approval text is not a product-specific owner response.",
"Accepted count stays 0 until reviewer acceptance is recorded.",
"Remote dev branch readiness stays 0 until product-specific final confirmation.",
"Runtime write, product repo write, secret collection, raw .git sync, raw conversation sync, .env read, and runtime volume sync remain forbidden.",
],
"secret_values_collected": False,
"env_file_content_read": False,
"raw_git_sync_allowed": False,
"raw_conversation_sync_allowed": False,
"runtime_write_authorized": False,
"product_repo_write_authorized": False,
}
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--root", default=".", help="Repository root.")
parser.add_argument("--ledger", default=str(DEFAULT_LEDGER), help="Acceptance ledger snapshot path.")
parser.add_argument("--response-dir", default=str(DEFAULT_RESPONSE_DIR), help="Redacted response metadata directory.")
parser.add_argument("--generated-at", default=None, help="Override generated_at timestamp.")
parser.add_argument("--output", default=None, help="Optional output JSON path.")
args = parser.parse_args()
root = Path(args.root).resolve()
ledger_path = (root / args.ledger).resolve()
response_dir = (root / args.response_dir).resolve()
ledger = load_json(ledger_path)
report = build_report(root, ledger, response_dir, args.generated_at)
text = json.dumps(report, ensure_ascii=False, indent=2) + "\n"
if args.output:
Path(args.output).write_text(text, encoding="utf-8")
else:
print(text, end="")
summary = report["summary"]
print(
"BLOCKED_PRODUCTS_OWNER_RESPONSE_INTAKE_PREFLIGHT_OK "
f"products={summary['blocked_product_count']} "
f"responses={summary['response_file_count']} "
f"accepted={summary['owner_response_accepted_count']} "
f"remote_dev_ready={summary['remote_dev_branch_ready_count']} "
f"runtime_write={summary['runtime_write_authorized_count']}"
)
return 0
if __name__ == "__main__":
raise SystemExit(main())