From 9ebab2db6eacbc5050e66de29f22c87ec69f0900 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 19 Jun 2026 00:22:17 +0800 Subject: [PATCH] =?UTF-8?q?feat(security):=20=E9=8E=96=E4=BD=8F=20Telegram?= =?UTF-8?q?=20=E9=80=9A=E7=9F=A5=E5=87=BA=E5=8F=A3=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E6=97=81=E8=B7=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/LOGBOOK.md | 38 + .../HIGH-VALUE-CONFIG-CONTROL-COVERAGE.md | 4 + .../IWOOOS-CONFIG-CONTROL-INVENTORY.md | 4 + .../SECURITY-SUPPLY-CHAIN-PROGRESS.md | 8 + ...NOTIFICATION-EGRESS-NO-NEW-BYPASS-GUARD.md | 79 + ...CATION-EGRESS-OWNER-RESPONSE-ACCEPTANCE.md | 95 + ...n-egress-no-new-bypass-guard.snapshot.json | 197 ++ ...ss-owner-response-acceptance.snapshot.json | 2473 +++++++++++++++++ ...026-06-04-iwooos-security-governance-p0.md | 2 +- .../security/iwooos-config-control-guard.py | 2 + .../security-mirror-progress-guard.py | 238 ++ ...notification-egress-no-new-bypass-guard.py | 282 ++ ...cation-egress-owner-response-acceptance.py | 388 +++ 13 files changed, 3809 insertions(+), 1 deletion(-) create mode 100644 docs/security/TELEGRAM-NOTIFICATION-EGRESS-NO-NEW-BYPASS-GUARD.md create mode 100644 docs/security/TELEGRAM-NOTIFICATION-EGRESS-OWNER-RESPONSE-ACCEPTANCE.md create mode 100644 docs/security/telegram-notification-egress-no-new-bypass-guard.snapshot.json create mode 100644 docs/security/telegram-notification-egress-owner-response-acceptance.snapshot.json create mode 100644 scripts/security/telegram-notification-egress-no-new-bypass-guard.py create mode 100644 scripts/security/telegram-notification-egress-owner-response-acceptance.py diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index 37eae57a..fc754ef1 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -1,3 +1,41 @@ +## 2026-06-19|Telegram 通知出口 owner response 驗收帳本與 no-new-bypass guard + +**背景**:Telegram 通知出口清冊已固定 repo 內 `18` 個 direct Bot API `sendMessage` call site,並完成 11 份 owner request 草稿與三個 no-runtime 遷移波次。但若只有清冊與遷移草稿,仍無法阻止新 direct Bot API 旁路繼續出現,也無法讓 reviewer 判斷 owner response 是否合格、是否夾帶 secret / raw payload、是否把 CD success / route 200 / UI 可見誤當 delivery receipt。 + +**完成內容**: +- 新增 `scripts/security/telegram-notification-egress-owner-response-acceptance.py`。 +- 新增 `docs/security/telegram-notification-egress-owner-response-acceptance.snapshot.json`。 +- 新增 `docs/security/TELEGRAM-NOTIFICATION-EGRESS-OWNER-RESPONSE-ACCEPTANCE.md`。 +- 新增 `scripts/security/telegram-notification-egress-no-new-bypass-guard.py`。 +- 新增 `docs/security/telegram-notification-egress-no-new-bypass-guard.snapshot.json`。 +- 新增 `docs/security/TELEGRAM-NOTIFICATION-EGRESS-NO-NEW-BYPASS-GUARD.md`。 +- `security-mirror-progress-guard.py` 已鎖住兩個 snapshot 的 schema、summary、false flags、source paths、blocked actions 與 runtime gate。 +- `iwooos-config-control-guard.py` 已把兩份新 Markdown 納入高價值配置控管文件清單。 +- `docs/security/IWOOOS-CONFIG-CONTROL-INVENTORY.md`、`docs/security/HIGH-VALUE-CONFIG-CONTROL-COVERAGE.md`、`docs/security/SECURITY-SUPPLY-CHAIN-PROGRESS.md` 與 P0 工作表已同步更新。 +- Owner response acceptance 固定 `candidates=11`、workflow `6`、ops script `4`、API direct `1`、`acceptance_fields=32`、`owner_fields=19`、`reviewer_checks=22`、`outcome_lanes=10`、`forbidden_payloads=14`、`blocked_actions=35`。 +- No-new-bypass guard 固定 baseline `18`、current direct call `18`、current files `11`、guarded methods `9`、`sendMessage=18`、`sendDocument/sendPhoto/sendMediaGroup/editMessageText=0`、`new_bypass=0`。 + +**驗證**: +- `python3 -m json.tool docs/security/telegram-notification-egress-owner-response-acceptance.snapshot.json` 通過。 +- `python3 -m json.tool docs/security/telegram-notification-egress-no-new-bypass-guard.snapshot.json` 通過。 +- `python3 -m py_compile scripts/security/telegram-notification-egress-owner-response-acceptance.py scripts/security/telegram-notification-egress-no-new-bypass-guard.py scripts/security/security-mirror-progress-guard.py` 通過。 +- `python3 scripts/security/telegram-notification-egress-owner-response-acceptance.py --root .` 通過,輸出 `TELEGRAM_NOTIFICATION_EGRESS_OWNER_RESPONSE_ACCEPTANCE_OK candidates=11 workflow=6 ops=4 api=1 accepted=0 runtime_gate=0`。 +- `python3 scripts/security/telegram-notification-egress-no-new-bypass-guard.py --root .` 通過,輸出 `TELEGRAM_NOTIFICATION_EGRESS_NO_NEW_BYPASS_GUARD_OK current=18 baseline=18 new=0 sendDocument=0 runtime_gate=0`。 +- `python3 scripts/security/security-mirror-progress-guard.py --root .`:`SECURITY_MIRROR_PROGRESS_GUARD_OK`。 +- `python3 scripts/security/source-control-owner-response-guard.py --root .`:`SOURCE_CONTROL_OWNER_RESPONSE_GUARD_OK`。 +- `python3 scripts/security/iwooos-config-control-guard.py --root .`:`IWOOOS_CONFIG_CONTROL_GUARD_OK`。 +- `python3 scripts/ops/doc-secrets-sanity-check.py docs .gitea scripts/security`:`DOC_SECRET_SANITY_OK scanned_files=924`。 +- `git diff --check`:通過。 + +**完成度同步**: +- Telegram notification egress owner response acceptance artifact:`100%`。 +- Telegram notification egress no-new-bypass guard:`100%`。 +- Direct Bot API convergence:仍 `0%`,尚未修改 workflow / script / API sender。 +- owner response dispatch / received / accepted:仍 `0%`。 +- IwoooS headline 仍維持 `64%`,active runtime gate 仍 `0`。 + +**邊界**:本段是 repo-only metadata / guard / snapshot / 文件收斂,沒有送 Telegram、沒有呼叫 Bot API、沒有 workflow dispatch、沒有觸發 CD、沒有修改 workflow / ops script / API sender、沒有讀 secret store、沒有收 secret value / hash / partial token、沒有保存 raw payload、沒有 production write、沒有 runtime gate、沒有 action button。不能把 no-new-bypass `pass` 誤判成既有 18 個 direct send 已批准或已收斂。 + ## 2026-06-19|P2-110E 報表資料源缺口接入 AwoooP Work Items owner review **背景**:P2-110D 已讓 Reports 頁顯示三個 `report-source-gap:*` 的 PlayBook 草案與 Verifier 計畫,但操作員仍需要離開 AwoooP 工作鏈路才能看到報表資料源缺口;這會讓 KM / PlayBook / 腳本 / 排程 / Verifier 的沉澱結果看起來像「只存在 API 或文件裡」,沒有形成可追蹤工作項。 diff --git a/docs/security/HIGH-VALUE-CONFIG-CONTROL-COVERAGE.md b/docs/security/HIGH-VALUE-CONFIG-CONTROL-COVERAGE.md index 19059208..399b0b3b 100644 --- a/docs/security/HIGH-VALUE-CONFIG-CONTROL-COVERAGE.md +++ b/docs/security/HIGH-VALUE-CONFIG-CONTROL-COVERAGE.md @@ -54,6 +54,10 @@ 同日再新增 `docs/security/TELEGRAM-NOTIFICATION-EGRESS-MIGRATION-PLAN-DRAFT.md` 與 `docs/security/telegram-notification-egress-migration-plan-draft.snapshot.json`,將 11 份草稿排成三個 no-runtime 遷移波次。固定 `migration_candidate_count=11`、workflow `6`、ops script `4`、API direct `1`、`proposed_wave_count=3`、`reviewer_check_count=15`、`blocked_action_count=21`;owner response、migration authorized、workflow / script modification、API sender refactor、Telegram send、Bot API call、secret collection、production write、runtime gate 與 action button 仍全部為 `0 / false`。 +2026-06-19 再新增 `docs/security/TELEGRAM-NOTIFICATION-EGRESS-NO-NEW-BYPASS-GUARD.md` 與 `docs/security/telegram-notification-egress-no-new-bypass-guard.snapshot.json`,把既有 18 個 direct send 固定為 no-new-bypass baseline。固定 `guarded_method_count=9`、`current_direct_bot_api_call_count=18`、`new_bypass_count=0`、`sendDocument_call_count=0`、`sendPhoto_call_count=0`、`sendMediaGroup_call_count=0`、`runtime_gate_count=0`。這是 repo source 防新增旁路 guard,不代表既有 direct send 已收斂。 + +同日再新增 `docs/security/TELEGRAM-NOTIFICATION-EGRESS-OWNER-RESPONSE-ACCEPTANCE.md` 與 `docs/security/telegram-notification-egress-owner-response-acceptance.snapshot.json`,把 11 份 direct egress 檔案轉成 owner response acceptance 候選。固定 `acceptance_candidate_count=11`、workflow `6`、ops script `4`、API direct `1`、`acceptance_field_count=32`、`required_owner_field_count=19`、`reviewer_check_count=22`、`outcome_lane_count=10`、`blocked_action_count=35`;owner response received / accepted、formatter convergence accepted、redaction contract accepted、delivery receipt accepted、migration authorized、workflow / script / API sender modification、Telegram send、Bot API call、secret collection、production write、runtime gate 與 action button 仍全部為 `0 / false`。 + ## 1.2c 2026-06-18 Backup / Restore / Escrow 事故後回讀計畫 已新增 `docs/security/BACKUP-RESTORE-POST-INCIDENT-READBACK-PLAN.md` 與 `docs/security/backup-restore-post-incident-readback-plan.snapshot.json`,將 38 個 backup / restore / escrow / retention surface 轉成事故後回讀計畫。固定 `readback_candidate_count=38`、`write_capable_readback_candidate_count=27`、`live_evidence_required_readback_candidate_count=38`、`restore_drill_readback_required_candidate_count=38`、`offsite_or_escrow_readback_required_candidate_count=20`、`retention_or_remote_delete_readback_required_candidate_count=17`、`required_readback_field_count=34`、`reviewer_check_count=32`、`outcome_lane_count=11`、`blocked_action_count=51`,讓 `backup_restore_credential` 從 `64%` 推進到 `66%`,高價值配置平均維持 `71%`,需 live evidence 類別仍為 `9`。 diff --git a/docs/security/IWOOOS-CONFIG-CONTROL-INVENTORY.md b/docs/security/IWOOOS-CONFIG-CONTROL-INVENTORY.md index 5513ee79..7d776626 100644 --- a/docs/security/IWOOOS-CONFIG-CONTROL-INVENTORY.md +++ b/docs/security/IWOOOS-CONFIG-CONTROL-INVENTORY.md @@ -93,6 +93,10 @@ 同日再新增 `telegram_notification_egress_migration_plan_draft_v1`,將 11 份 owner request 草稿排成 workflow notification wrapper、ops notification wrapper、API sender gateway 三個遷移波次。固定 `migration_candidate_count=11`、`workflow_migration_candidate_count=6`、`ops_script_migration_candidate_count=4`、`api_direct_migration_candidate_count=1`、`proposed_wave_count=3`、`owner_response_required_count=11`、`maintenance_window_required_count=11`、`rollback_owner_required_count=11`。owner response、migration authorized、workflow / script modification、API sender refactor、Telegram send、Bot API call、secret collection、raw payload storage、production write、runtime gate 仍全部為 `0 / false`。 +2026-06-19 再新增 `telegram_notification_egress_no_new_bypass_guard_v1`,將既有 18 個 direct send 固定成 baseline signature,並掃描 `sendMessage`、`sendDocument`、`sendPhoto`、`sendMediaGroup`、`editMessageText`、`sendAnimation`、`sendVideo`、`sendAudio`、`sendVoice` 等 9 類 Bot API method。固定 `baseline_signature_count=18`、`current_direct_bot_api_call_count=18`、`new_bypass_count=0`、`sendDocument_call_count=0`、`runtime_gate_count=0`。此更新只代表 repo source 目前沒有新增未登記 Telegram 直送旁路;既有 18 個 direct send 仍未遷移,owner response、migration authorized、workflow / script modification、API sender refactor、Telegram send、Bot API call、secret collection、raw payload storage、production write、runtime gate 仍全部為 `0 / false`。 + +同日再新增 `telegram_notification_egress_owner_response_acceptance_v1`,把 11 份 owner request draft 與 11 份 migration candidate 轉成 owner response acceptance 帳本。固定 `acceptance_candidate_count=11`、workflow `6`、ops script `4`、API direct `1`、`acceptance_field_count=32`、`required_owner_field_count=19`、`reviewer_check_count=22`、`outcome_lane_count=10`、`forbidden_payload_count=14`、`blocked_action_count=35`。owner response received / accepted / rejected / quarantined、supplement requested、formatter convergence accepted、redaction contract accepted、delivery receipt accepted、break-glass fallback accepted、maintenance / rollback / postcheck accepted、migration authorized、workflow / script / API sender modification、Telegram send、Bot API call、workflow dispatch、production deploy、secret collection、raw payload storage、runtime gate 仍全部為 `0 / false`。 + ### 0.3d 2026-06-15 Public / Admin / API runtime config 變更證據驗收 `public_runtime_config_change_evidence_acceptance_v1` 已把公開產品頁、AwoooP 後台、API / CORS、frontend env、Sentry tunnel、webhook / callback 與跨產品 runtime route 轉成 metadata-only 變更證據驗收只讀帳本。固定 `candidates=6`、`c0=5`、`c1=1`、`write_capable=6`、`source_refs=20`、`required_evidence_fields=21`、`reviewer_checks=21`、`outcome_lanes=8`、`blocked_actions=32`。 diff --git a/docs/security/SECURITY-SUPPLY-CHAIN-PROGRESS.md b/docs/security/SECURITY-SUPPLY-CHAIN-PROGRESS.md index d51d5a89..9d13dac4 100644 --- a/docs/security/SECURITY-SUPPLY-CHAIN-PROGRESS.md +++ b/docs/security/SECURITY-SUPPLY-CHAIN-PROGRESS.md @@ -98,6 +98,14 @@ 同日再新增 `telegram_notification_egress_migration_plan_draft_v1`,把 11 份草稿排成 workflow notification wrapper、ops notification wrapper、API sender gateway 三個 no-runtime 遷移波次。固定 `migration_candidate_count=11`、workflow `6`、ops script `4`、API direct `1`、`proposed_wave_count=3`、`owner_response_required_count=11`、`maintenance_window_required_count=11`、`rollback_owner_required_count=11`、`migration_authorized_count=0`、`runtime_gate_count=0`。這是遷移計畫草稿,不是 workflow / script / API sender 變更,也不是 Telegram send、Bot API call、secret collection 或 production write。 +## 0.00aaaa2 2026-06-19 Telegram 通知出口防新增旁路與 owner response acceptance + +本輪新增 `telegram_notification_egress_no_new_bypass_guard_v1`,把既有 18 個 direct send 固定成 no-new-bypass baseline,並把 `sendDocument`、`sendPhoto`、`sendMediaGroup`、`editMessageText` 等附件 / 編輯型 Bot API method 一併納入 repo source guard。固定 `current_direct_bot_api_call_count=18`、`guarded_method_count=9`、`new_bypass_count=0`、`sendDocument_call_count=0`、`removed_baseline_call_count=0`、`runtime_gate_count=0`。 + +同步新增 `telegram_notification_egress_owner_response_acceptance_v1`,把 11 個 direct egress 檔案轉成 reviewer 可驗收的 owner response acceptance 候選。固定 `acceptance_candidate_count=11`、workflow `6`、ops script `4`、API direct `1`、`acceptance_field_count=32`、`required_owner_field_count=19`、`reviewer_check_count=22`、`outcome_lane_count=10`、`forbidden_payload_count=14`、`blocked_action_count=35`。 + +同步邊界:IwoooS headline 維持 `64%`,active runtime gate 維持 `0`;既有 direct Bot API 收斂仍為 `0%`,owner response received / accepted、migration authorized、workflow / script / API sender modification、Telegram send、Bot API call、workflow dispatch、production deploy、secret value collection、raw payload storage、runtime gate 與 action buttons 全部仍為 `0 / false`。本段只更新文件、snapshot 與 guard,不送 Telegram、不讀 Bot token、不改 workflow、不改 host、不 dispatch workflow、不觸發部署。 + ## 0.00aaa 2026-06-15 K8s / ArgoCD GitOps 變更證據驗收 本輪把 K8s / ArgoCD 從 owner response acceptance 推進到 GitOps 變更證據驗收只讀帳本:`k8s_argocd_change_evidence_acceptance_v1` 固定 `candidates=4`、`c0=3`、`write_capable=4`、`required_evidence_fields=18`、`reviewer_checks=18`、`outcome_lanes=8`、`blocked_actions=28`,並讓 `k8s_production_gitops` 只讀治理成熟度 `62% -> 64%`,高價值配置平均只讀成熟度仍維持 `68%`。這是 metadata-only 收件驗收,不是 change evidence received / accepted、runtime approval package、ArgoCD API read、ArgoCD sync、kubectl action、Helm upgrade、NetworkPolicy apply、NodePort change、RBAC change、live cluster read、production write 或 runtime gate。 diff --git a/docs/security/TELEGRAM-NOTIFICATION-EGRESS-NO-NEW-BYPASS-GUARD.md b/docs/security/TELEGRAM-NOTIFICATION-EGRESS-NO-NEW-BYPASS-GUARD.md new file mode 100644 index 00000000..ed4ee2a1 --- /dev/null +++ b/docs/security/TELEGRAM-NOTIFICATION-EGRESS-NO-NEW-BYPASS-GUARD.md @@ -0,0 +1,79 @@ +# Telegram 通知出口 No-New-Bypass Guard + +| 項目 | 內容 | +|------|------| +| 日期 | 2026-06-19 | +| 狀態 | `pass_no_new_bypass` | +| 工具 | `scripts/security/telegram-notification-egress-no-new-bypass-guard.py` | +| Snapshot | `docs/security/telegram-notification-egress-no-new-bypass-guard.snapshot.json` | +| Source snapshot | `docs/security/telegram-notification-egress-inventory.snapshot.json` | +| 模式 | repo source scan;不讀 secret、不送 Telegram、不修改 workflow / script / API sender | +| runtime gate | `0` | + +## 1. 目的 + +`telegram_notification_egress_no_new_bypass_guard_v1` 用 committed inventory 當 baseline,掃描 repo source 是否新增未登記的 Telegram Bot API direct endpoint。既有 `18` 個 `sendMessage` call site 仍是待 owner response 與 migration review 的基線;本 guard 的目的不是批准它們,而是防止新旁路在治理收斂期間繼續增加。 + +## 2. 掃描範圍 + +| 範圍 | 說明 | +|------|------| +| `.gitea/workflows` | Gitea Actions workflow 中的 direct Bot API | +| `scripts/ops` | 主機 / 備份 / DR 類 ops script | +| `scripts/ci` | CI helper script | +| `apps/api/src` | API sender 與 notification 相關 source | + +Guard 目前保護的方法包含 `sendMessage`、`sendDocument`、`sendPhoto`、`sendMediaGroup`、`editMessageText`、`sendAnimation`、`sendVideo`、`sendAudio`、`sendVoice`。 + +## 3. 固定數字 + +| 指標 | 數值 | 解讀 | +|------|------|------| +| `source_direct_bot_api_call_count` | `18` | committed inventory 的 direct call baseline | +| `source_direct_bot_api_file_count` | `11` | committed inventory 的 direct file baseline | +| `baseline_signature_count` | `18` | baseline signature 數 | +| `current_direct_bot_api_call_count` | `18` | 目前掃描到的 direct call 數 | +| `current_direct_bot_api_file_count` | `11` | 目前掃描到的 direct file 數 | +| `guarded_method_count` | `9` | 受保護 Bot API method 數 | +| `sendMessage_call_count` | `18` | 目前全部都是既有 `sendMessage` | +| `sendDocument_call_count` | `0` | 不允許新增附件型 direct send | +| `sendPhoto_call_count` | `0` | 不允許新增圖片型 direct send | +| `sendMediaGroup_call_count` | `0` | 不允許新增 media group direct send | +| `editMessageText_call_count` | `0` | 不允許新增 edit direct call | +| `new_bypass_count` | `0` | 新增未登記旁路必須維持 0 | +| `removed_baseline_call_count` | `0` | baseline 移除需另外走 migration evidence | +| `runtime_gate_count` | `0` | 不提供 runtime 執行授權 | + +## 4. 判讀規則 + +- `new_bypass_count=0` 才代表沒有新增未登記 direct Bot API 旁路。 +- 既有 `18` 個 direct `sendMessage` 只是治理基線,不代表已批准保留。 +- 若新增 `sendDocument`、`sendPhoto`、`sendMediaGroup` 等附件型出口,guard 會阻擋,必須先進 inventory、owner request、migration plan 與 acceptance ledger。 +- 若移除既有 baseline,也不能只靠 guard 結果宣稱完成;仍需 migration evidence、owner response、delivery receipt 與 postcheck。 + +## 5. 禁止事項 + +本 guard 不得被解讀成以下授權: + +- 送 Telegram 或呼叫 Bot API。 +- 修改 workflow、ops script、API sender、chat route 或 Bot token。 +- 讀 secret store、收 secret value / hash / partial token。 +- 保存 raw payload、raw log 或未脫敏截圖。 +- 開 runtime gate 或新增 action button。 + +## 6. 驗證指令 + +```bash +python3 scripts/security/telegram-notification-egress-no-new-bypass-guard.py --root . +python3 -m json.tool docs/security/telegram-notification-egress-no-new-bypass-guard.snapshot.json >/dev/null +python3 -m py_compile scripts/security/telegram-notification-egress-no-new-bypass-guard.py +python3 scripts/security/security-mirror-progress-guard.py --root . +``` + +## 7. 完成度 + +| 項目 | 完成度 | 邊界 | +|------|--------|------| +| No-new-bypass source guard | `100%` | 目前 `new_bypass_count=0` | +| Existing direct Bot API convergence | `0%` | 18 個既有 direct send 尚未 migration | +| Runtime execution | `0%` | 不送 Telegram、不呼叫 Bot API、不改 live route | diff --git a/docs/security/TELEGRAM-NOTIFICATION-EGRESS-OWNER-RESPONSE-ACCEPTANCE.md b/docs/security/TELEGRAM-NOTIFICATION-EGRESS-OWNER-RESPONSE-ACCEPTANCE.md new file mode 100644 index 00000000..4d6d723b --- /dev/null +++ b/docs/security/TELEGRAM-NOTIFICATION-EGRESS-OWNER-RESPONSE-ACCEPTANCE.md @@ -0,0 +1,95 @@ +# Telegram 通知出口 Owner Response 驗收帳本 + +| 項目 | 內容 | +|------|------| +| 日期 | 2026-06-19 | +| 狀態 | `owner_response_acceptance_ledger_ready_no_runtime_action` | +| 工具 | `scripts/security/telegram-notification-egress-owner-response-acceptance.py` | +| Snapshot | `docs/security/telegram-notification-egress-owner-response-acceptance.snapshot.json` | +| Source snapshots | `docs/security/telegram-notification-egress-owner-request-draft.snapshot.json`、`docs/security/telegram-notification-egress-migration-plan-draft.snapshot.json` | +| 模式 | metadata-only;不讀 secret、不送 Telegram、不呼叫 Bot API、不改 workflow / script / API sender | +| runtime gate | `0` | + +## 1. 目的 + +`telegram_notification_egress_owner_response_acceptance_v1` 把 Telegram 通知出口 owner request 草稿與 migration plan 草稿轉成 reviewer 可驗收的帳本。它處理的是既有 `18` 個 direct Bot API `sendMessage` call site 的 owner response 收件規則,避免把「CD success、route 200、UI 可見或 Telegram sent」誤判為 delivery receipt 或自動化閉環完成。 + +這份帳本不是 owner response received,也不是 migration authorized。workflow、ops script 或 API sender 要真的改走 wrapper / gateway 時,仍需要獨立 change evidence、維護窗口、rollback owner 與 runtime approval。 + +## 2. 固定數字 + +| 指標 | 數值 | 解讀 | +|------|------|------| +| `source_request_draft_count` | `11` | 來源 owner request 草稿 | +| `source_migration_candidate_count` | `11` | 來源 migration candidate | +| `source_direct_bot_api_call_count` | `18` | 既有 direct Bot API call site | +| `acceptance_candidate_count` | `11` | 每個 direct egress 檔案一個驗收候選 | +| `workflow_acceptance_candidate_count` | `6` | Gitea workflow 類 | +| `ops_script_acceptance_candidate_count` | `4` | Ops script 類 | +| `api_direct_acceptance_candidate_count` | `1` | API direct sender 類 | +| `acceptance_field_count` | `32` | 每個 candidate 的驗收欄位 | +| `required_owner_field_count` | `19` | owner response 必填欄位 | +| `reviewer_check_count` | `22` | reviewer 必檢規則 | +| `outcome_lane_count` | `10` | 收件結果分流 | +| `forbidden_payload_count` | `14` | 禁止出現在回覆中的 payload 類型 | +| `blocked_action_count` | `35` | 帳本階段禁止動作 | + +所有 request sent、recipient confirmed、audit event emitted、owner response received / accepted / rejected / quarantined、formatter convergence accepted、redaction contract accepted、delivery receipt accepted、break-glass fallback accepted、maintenance window accepted、rollback owner accepted、postcheck evidence accepted、dedup / fingerprint accepted、no-false-green accepted、migration authorized、workflow / script / API sender modification、Telegram send、Bot API call、workflow dispatch、production deploy、secret collection、raw payload storage、production write、runtime gate、action button 全部維持 `0 / false`。 + +## 3. Reviewer checks + +Reviewer 必須確認: + +- source owner request draft 與 migration plan 都是目前版本。 +- owner identity、decision、decision reason、affected scope 與 followup owner 完整。 +- 所有 evidence refs 都是脫敏 metadata,不包含 secret value、hash、partial token、raw message payload 或 raw workflow log。 +- message shape contract、redaction contract、formatter convergence、gateway / Alertmanager target、break-glass fallback、delivery receipt、dedup / fingerprint、maintenance window、rollback owner 與 postcheck evidence 都明確存在。 +- `no_secret_value_attestation`、`no_raw_payload_attestation`、`no_false_green_attestation` 都存在。 +- migration authorization 與 runtime approval 必須分離;此帳本不能直接批准 workflow / script / API sender 修改。 +- runtime gate 維持 `0`。 + +## 4. Outcome lanes + +| Lane | 說明 | +|------|------| +| `waiting_owner_response` | 等待合格 owner response | +| `quarantine_secret_or_raw_payload` | 收到 secret、raw payload 或 raw log 時隔離 | +| `reject_execution_request` | 回覆夾帶立即執行要求時拒收 | +| `request_owner_route_supplement` | owner、route 或 scope 不完整 | +| `request_formatter_convergence_supplement` | formatter convergence 或 target 不完整 | +| `request_redaction_or_receipt_supplement` | redaction contract、delivery receipt 或 dedup 不完整 | +| `request_maintenance_or_rollback_supplement` | maintenance window 或 rollback owner 不完整 | +| `ready_for_migration_review` | metadata 完整,可另開 migration review | +| `owner_review_only_update` | 只更新 owner review metadata | +| `waiting_runtime_gate` | owner review 完成後仍等待 runtime gate | + +## 5. 禁止動作 + +此帳本階段禁止: + +- 標記 owner response received / accepted。 +- 送 Telegram、呼叫 Bot API、dispatch workflow、觸發 CD 或 production deploy。 +- 修改 workflow、ops script、API sender、chat route、Bot token 或 secret。 +- 讀 secret store、收 secret value / hash / partial token / chat id secret。 +- 保存 raw message payload、raw workflow log、raw action log、內部協作逐字稿或未脫敏截圖。 +- 把 CD success、route `200`、UI 可見或 Telegram sent 當作 delivery receipt。 +- 跳過 formatter convergence、redaction contract、dedup / fingerprint、break-glass fallback 或 no-false-green review。 +- 開 runtime gate 或新增 action button。 + +## 6. 驗證指令 + +```bash +python3 scripts/security/telegram-notification-egress-owner-response-acceptance.py --root . +python3 -m json.tool docs/security/telegram-notification-egress-owner-response-acceptance.snapshot.json >/dev/null +python3 -m py_compile scripts/security/telegram-notification-egress-owner-response-acceptance.py +python3 scripts/security/security-mirror-progress-guard.py --root . +``` + +## 7. 完成度 + +| 項目 | 完成度 | 邊界 | +|------|--------|------| +| Owner response acceptance artifact | `100%` | 11 個 candidate 已可 reviewer 驗收 | +| Owner response dispatch / received / accepted | `0%` | 尚未送件、尚未收件、尚未接受 | +| Direct Bot API convergence | `0%` | 尚未修改 workflow / script / API sender | +| Runtime execution | `0%` | 不送 Telegram、不呼叫 Bot API、不開 runtime gate | diff --git a/docs/security/telegram-notification-egress-no-new-bypass-guard.snapshot.json b/docs/security/telegram-notification-egress-no-new-bypass-guard.snapshot.json new file mode 100644 index 00000000..7e976dcc --- /dev/null +++ b/docs/security/telegram-notification-egress-no-new-bypass-guard.snapshot.json @@ -0,0 +1,197 @@ +{ + "current_direct_bot_api_calls": [ + { + "line": 54, + "method": "sendMessage", + "path": ".gitea/workflows/cd-dev.yaml", + "sanitized_excerpt": "printf '%b' \"$MSG\" | curl -fS -X POST \"https://api.telegram.org/bot/sendMessage\" \\", + "signature": ".gitea/workflows/cd-dev.yaml::sendmessage::printf '%b' \"$MSG\" | curl -fS -X POST \"https://api.telegram.org/bot/sendMessage\" \\" + }, + { + "line": 241, + "method": "sendMessage", + "path": ".gitea/workflows/cd-dev.yaml", + "sanitized_excerpt": "printf '%b' \"$MSG\" | curl -fS -X POST \"https://api.telegram.org/bot/sendMessage\" \\", + "signature": ".gitea/workflows/cd-dev.yaml::sendmessage::printf '%b' \"$MSG\" | curl -fS -X POST \"https://api.telegram.org/bot/sendMessage\" \\" + }, + { + "line": 262, + "method": "sendMessage", + "path": ".gitea/workflows/cd-dev.yaml", + "sanitized_excerpt": "printf '%b' \"$MSG\" | curl -fS -X POST \"https://api.telegram.org/bot/sendMessage\" \\", + "signature": ".gitea/workflows/cd-dev.yaml::sendmessage::printf '%b' \"$MSG\" | curl -fS -X POST \"https://api.telegram.org/bot/sendMessage\" \\" + }, + { + "line": 113, + "method": "sendMessage", + "path": ".gitea/workflows/cd.yaml", + "sanitized_excerpt": "curl -fS -X POST \"https://api.telegram.org/bot/sendMessage\" \\", + "signature": ".gitea/workflows/cd.yaml::sendmessage::curl -fS -X POST \"https://api.telegram.org/bot/sendMessage\" \\" + }, + { + "line": 305, + "method": "sendMessage", + "path": ".gitea/workflows/cd.yaml", + "sanitized_excerpt": "curl -fS -X POST \"https://api.telegram.org/bot/sendMessage\" \\", + "signature": ".gitea/workflows/cd.yaml::sendmessage::curl -fS -X POST \"https://api.telegram.org/bot/sendMessage\" \\" + }, + { + "line": 1203, + "method": "sendMessage", + "path": ".gitea/workflows/cd.yaml", + "sanitized_excerpt": "curl -fS -X POST \"https://api.telegram.org/bot/sendMessage\" \\", + "signature": ".gitea/workflows/cd.yaml::sendmessage::curl -fS -X POST \"https://api.telegram.org/bot/sendMessage\" \\" + }, + { + "line": 1552, + "method": "sendMessage", + "path": ".gitea/workflows/cd.yaml", + "sanitized_excerpt": "printf '%b' \"$TG_MSG\" | curl -fS -X POST \"https://api.telegram.org/bot/sendMessage\" \\", + "signature": ".gitea/workflows/cd.yaml::sendmessage::printf '%b' \"$TG_MSG\" | curl -fS -X POST \"https://api.telegram.org/bot/sendMessage\" \\" + }, + { + "line": 1575, + "method": "sendMessage", + "path": ".gitea/workflows/cd.yaml", + "sanitized_excerpt": "curl -fS -X POST \"https://api.telegram.org/bot/sendMessage\" \\", + "signature": ".gitea/workflows/cd.yaml::sendmessage::curl -fS -X POST \"https://api.telegram.org/bot/sendMessage\" \\" + }, + { + "line": 137, + "method": "sendMessage", + "path": ".gitea/workflows/code-review.yaml", + "sanitized_excerpt": "curl -fsS -X POST \"https://api.telegram.org/bot/sendMessage\" \\", + "signature": ".gitea/workflows/code-review.yaml::sendmessage::curl -fsS -X POST \"https://api.telegram.org/bot/sendMessage\" \\" + }, + { + "line": 216, + "method": "sendMessage", + "path": ".gitea/workflows/code-review.yaml", + "sanitized_excerpt": "curl -fsS -X POST \"https://api.telegram.org/bot/sendMessage\" \\", + "signature": ".gitea/workflows/code-review.yaml::sendmessage::curl -fsS -X POST \"https://api.telegram.org/bot/sendMessage\" \\" + }, + { + "line": 69, + "method": "sendMessage", + "path": ".gitea/workflows/deploy-alerts.yaml", + "sanitized_excerpt": "curl -fS -X POST \"https://api.telegram.org/bot/sendMessage\" \\", + "signature": ".gitea/workflows/deploy-alerts.yaml::sendmessage::curl -fS -X POST \"https://api.telegram.org/bot/sendMessage\" \\" + }, + { + "line": 98, + "method": "sendMessage", + "path": ".gitea/workflows/e2e-health.yaml", + "sanitized_excerpt": "curl -s -X POST \"https://api.telegram.org/bot/sendMessage\" \\", + "signature": ".gitea/workflows/e2e-health.yaml::sendmessage::curl -s -X POST \"https://api.telegram.org/bot/sendMessage\" \\" + }, + { + "line": 210, + "method": "sendMessage", + "path": ".gitea/workflows/run-migration.yml", + "sanitized_excerpt": "curl -s -X POST \"https://api.telegram.org/bot/sendMessage\" \\", + "signature": ".gitea/workflows/run-migration.yml::sendmessage::curl -s -X POST \"https://api.telegram.org/bot/sendMessage\" \\" + }, + { + "line": 1138, + "method": "sendMessage", + "path": "apps/api/src/services/channel_hub.py", + "sanitized_excerpt": "f\"https://api.telegram.org/bot/sendMessage\",", + "signature": "apps/api/src/services/channel_hub.py::sendmessage::f\"https://api.telegram.org/bot/sendMessage\"," + }, + { + "line": 64, + "method": "sendMessage", + "path": "scripts/ops/backup-from-110.sh", + "sanitized_excerpt": "curl -s -X POST \"https://api.telegram.org/bot/sendMessage\" \\", + "signature": "scripts/ops/backup-from-110.sh::sendmessage::curl -s -X POST \"https://api.telegram.org/bot/sendMessage\" \\" + }, + { + "line": 90, + "method": "sendMessage", + "path": "scripts/ops/docker-health-monitor.sh", + "sanitized_excerpt": "curl -s -X POST \"https://api.telegram.org/bot/sendMessage\" \\", + "signature": "scripts/ops/docker-health-monitor.sh::sendmessage::curl -s -X POST \"https://api.telegram.org/bot/sendMessage\" \\" + }, + { + "line": 63, + "method": "sendMessage", + "path": "scripts/ops/dr-drill.sh", + "sanitized_excerpt": "curl -s -X POST \"https://api.telegram.org/bot/sendMessage\" \\", + "signature": "scripts/ops/dr-drill.sh::sendmessage::curl -s -X POST \"https://api.telegram.org/bot/sendMessage\" \\" + }, + { + "line": 52, + "method": "sendMessage", + "path": "scripts/ops/pg-backup.sh", + "sanitized_excerpt": "curl -s -X POST \"https://api.telegram.org/bot/sendMessage\" \\", + "signature": "scripts/ops/pg-backup.sh::sendmessage::curl -s -X POST \"https://api.telegram.org/bot/sendMessage\" \\" + } + ], + "execution_boundaries": { + "action_buttons_allowed": false, + "api_sender_refactor_authorized": false, + "bot_api_call_authorized": false, + "bot_token_change_authorized": false, + "chat_route_change_authorized": false, + "not_authorization": true, + "partial_token_collection_allowed": false, + "production_write_authorized": false, + "raw_payload_storage_allowed": false, + "runtime_execution_authorized": false, + "script_modification_authorized": false, + "secret_hash_collection_allowed": false, + "secret_value_collection_allowed": false, + "telegram_send_authorized": false, + "workflow_modification_authorized": false + }, + "generated_at": "2026-06-19T09:40:00+08:00", + "git_commit": "4d0150e1", + "guarded_bot_methods": [ + "sendMessage", + "sendDocument", + "sendPhoto", + "sendMediaGroup", + "editMessageText", + "sendAnimation", + "sendVideo", + "sendAudio", + "sendVoice" + ], + "guarded_roots": [ + ".gitea/workflows", + "scripts/ops", + "scripts/ci", + "apps/api/src" + ], + "mode": "repo_source_scan_no_secret_value_no_telegram_send", + "new_bypass_findings": [], + "operator_interpretation": [ + "new_bypass_count 維持 0 才代表沒有新增未登記 Telegram Bot API 直送旁路。", + "既有 18 個 sendMessage 旁路仍是待 owner response 的基線,不代表已批准或已收斂。", + "sendDocument / sendPhoto / sendMediaGroup 等附件型出口若出現在 repo source,會被視為新增旁路並阻擋。", + "本 guard 只讀 repo source 與 committed snapshot,不送 Telegram、不讀 Bot token、不修改 workflow / script / API sender。" + ], + "removed_baseline_signatures": [], + "schema_version": "telegram_notification_egress_no_new_bypass_guard_v1", + "source_snapshot": "docs/security/telegram-notification-egress-inventory.snapshot.json", + "status": "pass_no_new_bypass", + "summary": { + "action_button_count": 0, + "baseline_signature_count": 18, + "current_direct_bot_api_call_count": 18, + "current_direct_bot_api_file_count": 11, + "editMessageText_call_count": 0, + "guarded_method_count": 9, + "new_bypass_count": 0, + "new_bypass_file_count": 0, + "other_guarded_method_call_count": 0, + "removed_baseline_call_count": 0, + "runtime_gate_count": 0, + "sendDocument_call_count": 0, + "sendMediaGroup_call_count": 0, + "sendMessage_call_count": 18, + "sendPhoto_call_count": 0, + "source_direct_bot_api_call_count": 18, + "source_direct_bot_api_file_count": 11 + } +} diff --git a/docs/security/telegram-notification-egress-owner-response-acceptance.snapshot.json b/docs/security/telegram-notification-egress-owner-response-acceptance.snapshot.json new file mode 100644 index 00000000..52cba7b8 --- /dev/null +++ b/docs/security/telegram-notification-egress-owner-response-acceptance.snapshot.json @@ -0,0 +1,2473 @@ +{ + "schema_version": "telegram_notification_egress_owner_response_acceptance_v1", + "generated_at": "2026-06-19T09:45:00+08:00", + "git_commit": "4d0150e1", + "status": "owner_response_acceptance_ledger_ready_no_runtime_action", + "mode": "metadata_only_no_secret_value_no_telegram_send_no_workflow_script_api_change", + "source_owner_request_snapshot": "docs/security/telegram-notification-egress-owner-request-draft.snapshot.json", + "source_owner_request_schema_version": "telegram_notification_egress_owner_request_draft_v1", + "source_owner_request_status": "owner_request_draft_ready_no_dispatch_no_runtime_action", + "source_migration_plan_snapshot": "docs/security/telegram-notification-egress-migration-plan-draft.snapshot.json", + "source_migration_plan_schema_version": "telegram_notification_egress_migration_plan_draft_v1", + "source_migration_plan_status": "migration_plan_draft_ready_no_runtime_action", + "summary": { + "source_request_draft_count": 11, + "source_migration_candidate_count": 11, + "source_direct_bot_api_call_count": 18, + "acceptance_candidate_count": 11, + "workflow_acceptance_candidate_count": 6, + "ops_script_acceptance_candidate_count": 4, + "api_direct_acceptance_candidate_count": 1, + "acceptance_field_count": 32, + "required_owner_field_count": 19, + "reviewer_check_count": 22, + "outcome_lane_count": 10, + "forbidden_payload_count": 14, + "blocked_action_count": 35, + "request_sent_count": 0, + "recipient_confirmed_count": 0, + "audit_event_emitted_count": 0, + "owner_response_received_count": 0, + "owner_response_accepted_count": 0, + "owner_response_rejected_count": 0, + "owner_response_quarantined_count": 0, + "supplement_requested_count": 0, + "formatter_convergence_accepted_count": 0, + "redaction_contract_accepted_count": 0, + "delivery_receipt_accepted_count": 0, + "break_glass_fallback_accepted_count": 0, + "maintenance_window_accepted_count": 0, + "rollback_owner_accepted_count": 0, + "postcheck_evidence_accepted_count": 0, + "dedup_or_fingerprint_accepted_count": 0, + "no_false_green_accepted_count": 0, + "direct_bot_api_migration_authorized_count": 0, + "workflow_modification_authorized_count": 0, + "script_modification_authorized_count": 0, + "api_sender_refactor_authorized_count": 0, + "telegram_send_authorized_count": 0, + "bot_api_call_authorized_count": 0, + "workflow_dispatch_authorized_count": 0, + "production_deploy_authorized_count": 0, + "secret_value_collection_allowed_count": 0, + "raw_payload_storage_allowed_count": 0, + "production_write_authorized_count": 0, + "runtime_gate_count": 0, + "action_button_count": 0 + }, + "execution_boundaries": { + "runtime_execution_authorized": false, + "owner_response_mark_received_authorized": false, + "owner_response_mark_accepted_authorized": false, + "direct_bot_api_migration_authorized": false, + "workflow_modification_authorized": false, + "script_modification_authorized": false, + "api_sender_refactor_authorized": false, + "telegram_send_authorized": false, + "bot_api_call_authorized": false, + "workflow_dispatch_authorized": false, + "production_deploy_authorized": false, + "secret_value_collection_allowed": false, + "raw_payload_storage_allowed": false, + "production_write_authorized": false, + "action_buttons_allowed": false, + "not_authorization": true + }, + "acceptance_candidates": [ + { + "acceptance_candidate_id": "telegram_notification_egress_owner_response_acceptance:.gitea/workflows/cd-dev.yaml", + "status": "waiting_owner_response", + "source_request_draft_id": "telegram_notification_egress_owner_request:_gitea_workflows_cd_dev_yaml", + "source_migration_candidate_id": "telegram_notification_egress_migration:.gitea/workflows/cd-dev.yaml", + "source_path": ".gitea/workflows/cd-dev.yaml", + "surface_kind": "gitea_workflow_direct_bot_api", + "direct_call_count": 3, + "line_refs": [ + 54, + 241, + 262 + ], + "line_hash_refs": [ + "f503c2c0f61100a9", + "c41f88fbca91a4b9", + "eaa9a6cb8326dc79" + ], + "proposed_wave": "wave_1_workflow_notification_wrapper", + "proposed_target": "scripts/ci/notify-awoooi-cicd.sh or AWOOI Alertmanager webhook", + "proposed_change_summary": "Replace direct workflow Bot API send with normalized CI/CD notification wrapper after owner approval.", + "owner_response_ref": null, + "owner_role_or_team": "pending_owner_response", + "decision": "pending_owner_response", + "decision_reason": "pending_owner_response", + "affected_scope": "pending_owner_response", + "redacted_evidence_refs": [], + "message_shape_contract_ref": null, + "redaction_contract_ref": null, + "formatter_convergence_decision": "pending_owner_response", + "gateway_or_alertmanager_target": "pending_owner_response", + "break_glass_fallback_decision": "pending_owner_response", + "delivery_receipt_ref": null, + "dedup_or_fingerprint_plan": "pending_owner_response", + "fallback_or_degraded_mode": "pending_owner_response", + "migration_or_exception_reason": "pending_owner_response", + "maintenance_window": "pending_owner_response", + "rollback_owner": "pending_owner_response", + "postcheck_evidence_ref": null, + "no_secret_value_attestation": "pending_owner_response", + "no_raw_payload_attestation": "pending_owner_response", + "no_false_green_attestation": "pending_owner_response", + "reviewer_outcome": "waiting_owner_response", + "followup_owner": "pending_owner_response", + "acceptance_fields": [ + "acceptance_candidate_id", + "source_request_draft_id", + "source_migration_candidate_id", + "source_path", + "surface_kind", + "direct_call_count", + "proposed_wave", + "proposed_target", + "owner_response_ref", + "owner_role_or_team", + "decision", + "decision_reason", + "affected_scope", + "redacted_evidence_refs", + "message_shape_contract_ref", + "redaction_contract_ref", + "formatter_convergence_decision", + "gateway_or_alertmanager_target", + "break_glass_fallback_decision", + "delivery_receipt_ref", + "dedup_or_fingerprint_plan", + "fallback_or_degraded_mode", + "migration_or_exception_reason", + "maintenance_window", + "rollback_owner", + "postcheck_evidence_ref", + "no_secret_value_attestation", + "no_raw_payload_attestation", + "no_false_green_attestation", + "reviewer_outcome", + "followup_owner", + "not_authorization" + ], + "required_owner_fields": [ + "owner_role_or_team", + "routing_purpose", + "current_sender", + "target_chat_route", + "message_shape_contract", + "redaction_contract", + "formatter_convergence_decision", + "gateway_or_alertmanager_target", + "break_glass_fallback_decision", + "delivery_receipt_ref", + "dedup_or_fingerprint_plan", + "fallback_or_degraded_mode", + "migration_or_exception_reason", + "maintenance_window", + "rollback_owner", + "postcheck_evidence_ref", + "no_secret_value_attestation", + "no_raw_payload_attestation", + "no_false_green_attestation" + ], + "reviewer_checks": [ + "source_owner_request_current", + "source_migration_plan_current", + "owner_identity_present", + "decision_reason_present", + "affected_scope_matches_source", + "redacted_refs_only", + "no_secret_or_token_value", + "no_raw_message_payload", + "message_shape_contract_present", + "redaction_contract_present", + "formatter_convergence_explicit", + "gateway_or_alertmanager_target_valid", + "break_glass_fallback_explicit", + "delivery_receipt_metadata_only", + "dedup_or_fingerprint_present", + "maintenance_window_present", + "rollback_owner_present", + "postcheck_evidence_present", + "no_false_green_attested", + "migration_authorization_separate", + "counts_transition_safe", + "runtime_gate_stays_zero" + ], + "outcome_lanes": [ + "waiting_owner_response", + "quarantine_secret_or_raw_payload", + "reject_execution_request", + "request_owner_route_supplement", + "request_formatter_convergence_supplement", + "request_redaction_or_receipt_supplement", + "request_maintenance_or_rollback_supplement", + "ready_for_migration_review", + "owner_review_only_update", + "waiting_runtime_gate" + ], + "forbidden_payloads": [ + "bot_token_value", + "chat_secret_value", + "secret_hash", + "partial_token", + "masked_token", + "authorization_header", + "raw_message_payload", + "raw_workflow_log", + "raw_action_log", + "raw_screenshot_with_secret", + "internal_work_window_transcript", + "private_namespace", + "unredacted_internal_path", + "unredacted_private_ip" + ], + "blocked_actions": [ + "mark_owner_response_received_without_record", + "mark_owner_response_accepted_without_reviewer_record", + "send_telegram", + "call_bot_api", + "modify_workflow", + "modify_ops_script", + "refactor_api_sender", + "dispatch_workflow", + "trigger_cd", + "deploy_production", + "change_chat_route", + "change_bot_token", + "rotate_secret", + "read_secret_store", + "collect_secret_value", + "collect_secret_hash", + "collect_partial_token", + "collect_chat_id_secret", + "store_raw_message_payload", + "store_unredacted_log", + "store_internal_work_window_transcript", + "accept_cd_success_as_delivery_receipt", + "accept_route_200_as_notification_delivery", + "accept_ui_visible_as_notification_acceptance", + "accept_telegram_sent_without_delivery_receipt", + "skip_formatter_convergence", + "skip_redaction_contract", + "skip_dedup_or_fingerprint_review", + "skip_break_glass_fallback_review", + "authorize_migration", + "authorize_workflow_modification", + "authorize_script_modification", + "authorize_api_sender_refactor", + "open_runtime_gate", + "add_action_button" + ], + "not_authorization": true, + "request_sent": false, + "recipient_confirmed": false, + "audit_event_emitted": false, + "owner_response_received": false, + "owner_response_accepted": false, + "owner_response_rejected": false, + "owner_response_quarantined": false, + "supplement_requested": false, + "formatter_convergence_accepted": false, + "redaction_contract_accepted": false, + "delivery_receipt_accepted": false, + "break_glass_fallback_accepted": false, + "maintenance_window_accepted": false, + "rollback_owner_accepted": false, + "postcheck_evidence_accepted": false, + "dedup_or_fingerprint_accepted": false, + "no_false_green_accepted": false, + "direct_bot_api_migration_authorized": false, + "workflow_modification_authorized": false, + "script_modification_authorized": false, + "api_sender_refactor_authorized": false, + "telegram_send_authorized": false, + "bot_api_call_authorized": false, + "workflow_dispatch_authorized": false, + "production_deploy_authorized": false, + "secret_value_collection_allowed": false, + "raw_payload_storage_allowed": false, + "production_write_authorized": false, + "runtime_gate": false, + "action_buttons_allowed": false + }, + { + "acceptance_candidate_id": "telegram_notification_egress_owner_response_acceptance:.gitea/workflows/cd.yaml", + "status": "waiting_owner_response", + "source_request_draft_id": "telegram_notification_egress_owner_request:_gitea_workflows_cd_yaml", + "source_migration_candidate_id": "telegram_notification_egress_migration:.gitea/workflows/cd.yaml", + "source_path": ".gitea/workflows/cd.yaml", + "surface_kind": "gitea_workflow_direct_bot_api", + "direct_call_count": 5, + "line_refs": [ + 113, + 305, + 1203, + 1552, + 1575 + ], + "line_hash_refs": [ + "b57e6587a106976b", + "19c1de8d67af874a", + "6ef020c2b6eac91f", + "d029b95242f39c03", + "d62c45a8595984fc" + ], + "proposed_wave": "wave_1_workflow_notification_wrapper", + "proposed_target": "scripts/ci/notify-awoooi-cicd.sh or AWOOI Alertmanager webhook", + "proposed_change_summary": "Replace direct workflow Bot API send with normalized CI/CD notification wrapper after owner approval.", + "owner_response_ref": null, + "owner_role_or_team": "pending_owner_response", + "decision": "pending_owner_response", + "decision_reason": "pending_owner_response", + "affected_scope": "pending_owner_response", + "redacted_evidence_refs": [], + "message_shape_contract_ref": null, + "redaction_contract_ref": null, + "formatter_convergence_decision": "pending_owner_response", + "gateway_or_alertmanager_target": "pending_owner_response", + "break_glass_fallback_decision": "pending_owner_response", + "delivery_receipt_ref": null, + "dedup_or_fingerprint_plan": "pending_owner_response", + "fallback_or_degraded_mode": "pending_owner_response", + "migration_or_exception_reason": "pending_owner_response", + "maintenance_window": "pending_owner_response", + "rollback_owner": "pending_owner_response", + "postcheck_evidence_ref": null, + "no_secret_value_attestation": "pending_owner_response", + "no_raw_payload_attestation": "pending_owner_response", + "no_false_green_attestation": "pending_owner_response", + "reviewer_outcome": "waiting_owner_response", + "followup_owner": "pending_owner_response", + "acceptance_fields": [ + "acceptance_candidate_id", + "source_request_draft_id", + "source_migration_candidate_id", + "source_path", + "surface_kind", + "direct_call_count", + "proposed_wave", + "proposed_target", + "owner_response_ref", + "owner_role_or_team", + "decision", + "decision_reason", + "affected_scope", + "redacted_evidence_refs", + "message_shape_contract_ref", + "redaction_contract_ref", + "formatter_convergence_decision", + "gateway_or_alertmanager_target", + "break_glass_fallback_decision", + "delivery_receipt_ref", + "dedup_or_fingerprint_plan", + "fallback_or_degraded_mode", + "migration_or_exception_reason", + "maintenance_window", + "rollback_owner", + "postcheck_evidence_ref", + "no_secret_value_attestation", + "no_raw_payload_attestation", + "no_false_green_attestation", + "reviewer_outcome", + "followup_owner", + "not_authorization" + ], + "required_owner_fields": [ + "owner_role_or_team", + "routing_purpose", + "current_sender", + "target_chat_route", + "message_shape_contract", + "redaction_contract", + "formatter_convergence_decision", + "gateway_or_alertmanager_target", + "break_glass_fallback_decision", + "delivery_receipt_ref", + "dedup_or_fingerprint_plan", + "fallback_or_degraded_mode", + "migration_or_exception_reason", + "maintenance_window", + "rollback_owner", + "postcheck_evidence_ref", + "no_secret_value_attestation", + "no_raw_payload_attestation", + "no_false_green_attestation" + ], + "reviewer_checks": [ + "source_owner_request_current", + "source_migration_plan_current", + "owner_identity_present", + "decision_reason_present", + "affected_scope_matches_source", + "redacted_refs_only", + "no_secret_or_token_value", + "no_raw_message_payload", + "message_shape_contract_present", + "redaction_contract_present", + "formatter_convergence_explicit", + "gateway_or_alertmanager_target_valid", + "break_glass_fallback_explicit", + "delivery_receipt_metadata_only", + "dedup_or_fingerprint_present", + "maintenance_window_present", + "rollback_owner_present", + "postcheck_evidence_present", + "no_false_green_attested", + "migration_authorization_separate", + "counts_transition_safe", + "runtime_gate_stays_zero" + ], + "outcome_lanes": [ + "waiting_owner_response", + "quarantine_secret_or_raw_payload", + "reject_execution_request", + "request_owner_route_supplement", + "request_formatter_convergence_supplement", + "request_redaction_or_receipt_supplement", + "request_maintenance_or_rollback_supplement", + "ready_for_migration_review", + "owner_review_only_update", + "waiting_runtime_gate" + ], + "forbidden_payloads": [ + "bot_token_value", + "chat_secret_value", + "secret_hash", + "partial_token", + "masked_token", + "authorization_header", + "raw_message_payload", + "raw_workflow_log", + "raw_action_log", + "raw_screenshot_with_secret", + "internal_work_window_transcript", + "private_namespace", + "unredacted_internal_path", + "unredacted_private_ip" + ], + "blocked_actions": [ + "mark_owner_response_received_without_record", + "mark_owner_response_accepted_without_reviewer_record", + "send_telegram", + "call_bot_api", + "modify_workflow", + "modify_ops_script", + "refactor_api_sender", + "dispatch_workflow", + "trigger_cd", + "deploy_production", + "change_chat_route", + "change_bot_token", + "rotate_secret", + "read_secret_store", + "collect_secret_value", + "collect_secret_hash", + "collect_partial_token", + "collect_chat_id_secret", + "store_raw_message_payload", + "store_unredacted_log", + "store_internal_work_window_transcript", + "accept_cd_success_as_delivery_receipt", + "accept_route_200_as_notification_delivery", + "accept_ui_visible_as_notification_acceptance", + "accept_telegram_sent_without_delivery_receipt", + "skip_formatter_convergence", + "skip_redaction_contract", + "skip_dedup_or_fingerprint_review", + "skip_break_glass_fallback_review", + "authorize_migration", + "authorize_workflow_modification", + "authorize_script_modification", + "authorize_api_sender_refactor", + "open_runtime_gate", + "add_action_button" + ], + "not_authorization": true, + "request_sent": false, + "recipient_confirmed": false, + "audit_event_emitted": false, + "owner_response_received": false, + "owner_response_accepted": false, + "owner_response_rejected": false, + "owner_response_quarantined": false, + "supplement_requested": false, + "formatter_convergence_accepted": false, + "redaction_contract_accepted": false, + "delivery_receipt_accepted": false, + "break_glass_fallback_accepted": false, + "maintenance_window_accepted": false, + "rollback_owner_accepted": false, + "postcheck_evidence_accepted": false, + "dedup_or_fingerprint_accepted": false, + "no_false_green_accepted": false, + "direct_bot_api_migration_authorized": false, + "workflow_modification_authorized": false, + "script_modification_authorized": false, + "api_sender_refactor_authorized": false, + "telegram_send_authorized": false, + "bot_api_call_authorized": false, + "workflow_dispatch_authorized": false, + "production_deploy_authorized": false, + "secret_value_collection_allowed": false, + "raw_payload_storage_allowed": false, + "production_write_authorized": false, + "runtime_gate": false, + "action_buttons_allowed": false + }, + { + "acceptance_candidate_id": "telegram_notification_egress_owner_response_acceptance:.gitea/workflows/code-review.yaml", + "status": "waiting_owner_response", + "source_request_draft_id": "telegram_notification_egress_owner_request:_gitea_workflows_code_review_yaml", + "source_migration_candidate_id": "telegram_notification_egress_migration:.gitea/workflows/code-review.yaml", + "source_path": ".gitea/workflows/code-review.yaml", + "surface_kind": "gitea_workflow_direct_bot_api", + "direct_call_count": 2, + "line_refs": [ + 137, + 216 + ], + "line_hash_refs": [ + "a7ebbd735dad5ab2", + "f10f7782dc7c8125" + ], + "proposed_wave": "wave_1_workflow_notification_wrapper", + "proposed_target": "scripts/ci/notify-awoooi-cicd.sh or AWOOI Alertmanager webhook", + "proposed_change_summary": "Replace direct workflow Bot API send with normalized CI/CD notification wrapper after owner approval.", + "owner_response_ref": null, + "owner_role_or_team": "pending_owner_response", + "decision": "pending_owner_response", + "decision_reason": "pending_owner_response", + "affected_scope": "pending_owner_response", + "redacted_evidence_refs": [], + "message_shape_contract_ref": null, + "redaction_contract_ref": null, + "formatter_convergence_decision": "pending_owner_response", + "gateway_or_alertmanager_target": "pending_owner_response", + "break_glass_fallback_decision": "pending_owner_response", + "delivery_receipt_ref": null, + "dedup_or_fingerprint_plan": "pending_owner_response", + "fallback_or_degraded_mode": "pending_owner_response", + "migration_or_exception_reason": "pending_owner_response", + "maintenance_window": "pending_owner_response", + "rollback_owner": "pending_owner_response", + "postcheck_evidence_ref": null, + "no_secret_value_attestation": "pending_owner_response", + "no_raw_payload_attestation": "pending_owner_response", + "no_false_green_attestation": "pending_owner_response", + "reviewer_outcome": "waiting_owner_response", + "followup_owner": "pending_owner_response", + "acceptance_fields": [ + "acceptance_candidate_id", + "source_request_draft_id", + "source_migration_candidate_id", + "source_path", + "surface_kind", + "direct_call_count", + "proposed_wave", + "proposed_target", + "owner_response_ref", + "owner_role_or_team", + "decision", + "decision_reason", + "affected_scope", + "redacted_evidence_refs", + "message_shape_contract_ref", + "redaction_contract_ref", + "formatter_convergence_decision", + "gateway_or_alertmanager_target", + "break_glass_fallback_decision", + "delivery_receipt_ref", + "dedup_or_fingerprint_plan", + "fallback_or_degraded_mode", + "migration_or_exception_reason", + "maintenance_window", + "rollback_owner", + "postcheck_evidence_ref", + "no_secret_value_attestation", + "no_raw_payload_attestation", + "no_false_green_attestation", + "reviewer_outcome", + "followup_owner", + "not_authorization" + ], + "required_owner_fields": [ + "owner_role_or_team", + "routing_purpose", + "current_sender", + "target_chat_route", + "message_shape_contract", + "redaction_contract", + "formatter_convergence_decision", + "gateway_or_alertmanager_target", + "break_glass_fallback_decision", + "delivery_receipt_ref", + "dedup_or_fingerprint_plan", + "fallback_or_degraded_mode", + "migration_or_exception_reason", + "maintenance_window", + "rollback_owner", + "postcheck_evidence_ref", + "no_secret_value_attestation", + "no_raw_payload_attestation", + "no_false_green_attestation" + ], + "reviewer_checks": [ + "source_owner_request_current", + "source_migration_plan_current", + "owner_identity_present", + "decision_reason_present", + "affected_scope_matches_source", + "redacted_refs_only", + "no_secret_or_token_value", + "no_raw_message_payload", + "message_shape_contract_present", + "redaction_contract_present", + "formatter_convergence_explicit", + "gateway_or_alertmanager_target_valid", + "break_glass_fallback_explicit", + "delivery_receipt_metadata_only", + "dedup_or_fingerprint_present", + "maintenance_window_present", + "rollback_owner_present", + "postcheck_evidence_present", + "no_false_green_attested", + "migration_authorization_separate", + "counts_transition_safe", + "runtime_gate_stays_zero" + ], + "outcome_lanes": [ + "waiting_owner_response", + "quarantine_secret_or_raw_payload", + "reject_execution_request", + "request_owner_route_supplement", + "request_formatter_convergence_supplement", + "request_redaction_or_receipt_supplement", + "request_maintenance_or_rollback_supplement", + "ready_for_migration_review", + "owner_review_only_update", + "waiting_runtime_gate" + ], + "forbidden_payloads": [ + "bot_token_value", + "chat_secret_value", + "secret_hash", + "partial_token", + "masked_token", + "authorization_header", + "raw_message_payload", + "raw_workflow_log", + "raw_action_log", + "raw_screenshot_with_secret", + "internal_work_window_transcript", + "private_namespace", + "unredacted_internal_path", + "unredacted_private_ip" + ], + "blocked_actions": [ + "mark_owner_response_received_without_record", + "mark_owner_response_accepted_without_reviewer_record", + "send_telegram", + "call_bot_api", + "modify_workflow", + "modify_ops_script", + "refactor_api_sender", + "dispatch_workflow", + "trigger_cd", + "deploy_production", + "change_chat_route", + "change_bot_token", + "rotate_secret", + "read_secret_store", + "collect_secret_value", + "collect_secret_hash", + "collect_partial_token", + "collect_chat_id_secret", + "store_raw_message_payload", + "store_unredacted_log", + "store_internal_work_window_transcript", + "accept_cd_success_as_delivery_receipt", + "accept_route_200_as_notification_delivery", + "accept_ui_visible_as_notification_acceptance", + "accept_telegram_sent_without_delivery_receipt", + "skip_formatter_convergence", + "skip_redaction_contract", + "skip_dedup_or_fingerprint_review", + "skip_break_glass_fallback_review", + "authorize_migration", + "authorize_workflow_modification", + "authorize_script_modification", + "authorize_api_sender_refactor", + "open_runtime_gate", + "add_action_button" + ], + "not_authorization": true, + "request_sent": false, + "recipient_confirmed": false, + "audit_event_emitted": false, + "owner_response_received": false, + "owner_response_accepted": false, + "owner_response_rejected": false, + "owner_response_quarantined": false, + "supplement_requested": false, + "formatter_convergence_accepted": false, + "redaction_contract_accepted": false, + "delivery_receipt_accepted": false, + "break_glass_fallback_accepted": false, + "maintenance_window_accepted": false, + "rollback_owner_accepted": false, + "postcheck_evidence_accepted": false, + "dedup_or_fingerprint_accepted": false, + "no_false_green_accepted": false, + "direct_bot_api_migration_authorized": false, + "workflow_modification_authorized": false, + "script_modification_authorized": false, + "api_sender_refactor_authorized": false, + "telegram_send_authorized": false, + "bot_api_call_authorized": false, + "workflow_dispatch_authorized": false, + "production_deploy_authorized": false, + "secret_value_collection_allowed": false, + "raw_payload_storage_allowed": false, + "production_write_authorized": false, + "runtime_gate": false, + "action_buttons_allowed": false + }, + { + "acceptance_candidate_id": "telegram_notification_egress_owner_response_acceptance:.gitea/workflows/deploy-alerts.yaml", + "status": "waiting_owner_response", + "source_request_draft_id": "telegram_notification_egress_owner_request:_gitea_workflows_deploy_alerts_yaml", + "source_migration_candidate_id": "telegram_notification_egress_migration:.gitea/workflows/deploy-alerts.yaml", + "source_path": ".gitea/workflows/deploy-alerts.yaml", + "surface_kind": "gitea_workflow_direct_bot_api", + "direct_call_count": 1, + "line_refs": [ + 69 + ], + "line_hash_refs": [ + "79e83190f68f27c9" + ], + "proposed_wave": "wave_1_workflow_notification_wrapper", + "proposed_target": "scripts/ci/notify-awoooi-cicd.sh or AWOOI Alertmanager webhook", + "proposed_change_summary": "Replace direct workflow Bot API send with normalized CI/CD notification wrapper after owner approval.", + "owner_response_ref": null, + "owner_role_or_team": "pending_owner_response", + "decision": "pending_owner_response", + "decision_reason": "pending_owner_response", + "affected_scope": "pending_owner_response", + "redacted_evidence_refs": [], + "message_shape_contract_ref": null, + "redaction_contract_ref": null, + "formatter_convergence_decision": "pending_owner_response", + "gateway_or_alertmanager_target": "pending_owner_response", + "break_glass_fallback_decision": "pending_owner_response", + "delivery_receipt_ref": null, + "dedup_or_fingerprint_plan": "pending_owner_response", + "fallback_or_degraded_mode": "pending_owner_response", + "migration_or_exception_reason": "pending_owner_response", + "maintenance_window": "pending_owner_response", + "rollback_owner": "pending_owner_response", + "postcheck_evidence_ref": null, + "no_secret_value_attestation": "pending_owner_response", + "no_raw_payload_attestation": "pending_owner_response", + "no_false_green_attestation": "pending_owner_response", + "reviewer_outcome": "waiting_owner_response", + "followup_owner": "pending_owner_response", + "acceptance_fields": [ + "acceptance_candidate_id", + "source_request_draft_id", + "source_migration_candidate_id", + "source_path", + "surface_kind", + "direct_call_count", + "proposed_wave", + "proposed_target", + "owner_response_ref", + "owner_role_or_team", + "decision", + "decision_reason", + "affected_scope", + "redacted_evidence_refs", + "message_shape_contract_ref", + "redaction_contract_ref", + "formatter_convergence_decision", + "gateway_or_alertmanager_target", + "break_glass_fallback_decision", + "delivery_receipt_ref", + "dedup_or_fingerprint_plan", + "fallback_or_degraded_mode", + "migration_or_exception_reason", + "maintenance_window", + "rollback_owner", + "postcheck_evidence_ref", + "no_secret_value_attestation", + "no_raw_payload_attestation", + "no_false_green_attestation", + "reviewer_outcome", + "followup_owner", + "not_authorization" + ], + "required_owner_fields": [ + "owner_role_or_team", + "routing_purpose", + "current_sender", + "target_chat_route", + "message_shape_contract", + "redaction_contract", + "formatter_convergence_decision", + "gateway_or_alertmanager_target", + "break_glass_fallback_decision", + "delivery_receipt_ref", + "dedup_or_fingerprint_plan", + "fallback_or_degraded_mode", + "migration_or_exception_reason", + "maintenance_window", + "rollback_owner", + "postcheck_evidence_ref", + "no_secret_value_attestation", + "no_raw_payload_attestation", + "no_false_green_attestation" + ], + "reviewer_checks": [ + "source_owner_request_current", + "source_migration_plan_current", + "owner_identity_present", + "decision_reason_present", + "affected_scope_matches_source", + "redacted_refs_only", + "no_secret_or_token_value", + "no_raw_message_payload", + "message_shape_contract_present", + "redaction_contract_present", + "formatter_convergence_explicit", + "gateway_or_alertmanager_target_valid", + "break_glass_fallback_explicit", + "delivery_receipt_metadata_only", + "dedup_or_fingerprint_present", + "maintenance_window_present", + "rollback_owner_present", + "postcheck_evidence_present", + "no_false_green_attested", + "migration_authorization_separate", + "counts_transition_safe", + "runtime_gate_stays_zero" + ], + "outcome_lanes": [ + "waiting_owner_response", + "quarantine_secret_or_raw_payload", + "reject_execution_request", + "request_owner_route_supplement", + "request_formatter_convergence_supplement", + "request_redaction_or_receipt_supplement", + "request_maintenance_or_rollback_supplement", + "ready_for_migration_review", + "owner_review_only_update", + "waiting_runtime_gate" + ], + "forbidden_payloads": [ + "bot_token_value", + "chat_secret_value", + "secret_hash", + "partial_token", + "masked_token", + "authorization_header", + "raw_message_payload", + "raw_workflow_log", + "raw_action_log", + "raw_screenshot_with_secret", + "internal_work_window_transcript", + "private_namespace", + "unredacted_internal_path", + "unredacted_private_ip" + ], + "blocked_actions": [ + "mark_owner_response_received_without_record", + "mark_owner_response_accepted_without_reviewer_record", + "send_telegram", + "call_bot_api", + "modify_workflow", + "modify_ops_script", + "refactor_api_sender", + "dispatch_workflow", + "trigger_cd", + "deploy_production", + "change_chat_route", + "change_bot_token", + "rotate_secret", + "read_secret_store", + "collect_secret_value", + "collect_secret_hash", + "collect_partial_token", + "collect_chat_id_secret", + "store_raw_message_payload", + "store_unredacted_log", + "store_internal_work_window_transcript", + "accept_cd_success_as_delivery_receipt", + "accept_route_200_as_notification_delivery", + "accept_ui_visible_as_notification_acceptance", + "accept_telegram_sent_without_delivery_receipt", + "skip_formatter_convergence", + "skip_redaction_contract", + "skip_dedup_or_fingerprint_review", + "skip_break_glass_fallback_review", + "authorize_migration", + "authorize_workflow_modification", + "authorize_script_modification", + "authorize_api_sender_refactor", + "open_runtime_gate", + "add_action_button" + ], + "not_authorization": true, + "request_sent": false, + "recipient_confirmed": false, + "audit_event_emitted": false, + "owner_response_received": false, + "owner_response_accepted": false, + "owner_response_rejected": false, + "owner_response_quarantined": false, + "supplement_requested": false, + "formatter_convergence_accepted": false, + "redaction_contract_accepted": false, + "delivery_receipt_accepted": false, + "break_glass_fallback_accepted": false, + "maintenance_window_accepted": false, + "rollback_owner_accepted": false, + "postcheck_evidence_accepted": false, + "dedup_or_fingerprint_accepted": false, + "no_false_green_accepted": false, + "direct_bot_api_migration_authorized": false, + "workflow_modification_authorized": false, + "script_modification_authorized": false, + "api_sender_refactor_authorized": false, + "telegram_send_authorized": false, + "bot_api_call_authorized": false, + "workflow_dispatch_authorized": false, + "production_deploy_authorized": false, + "secret_value_collection_allowed": false, + "raw_payload_storage_allowed": false, + "production_write_authorized": false, + "runtime_gate": false, + "action_buttons_allowed": false + }, + { + "acceptance_candidate_id": "telegram_notification_egress_owner_response_acceptance:.gitea/workflows/e2e-health.yaml", + "status": "waiting_owner_response", + "source_request_draft_id": "telegram_notification_egress_owner_request:_gitea_workflows_e2e_health_yaml", + "source_migration_candidate_id": "telegram_notification_egress_migration:.gitea/workflows/e2e-health.yaml", + "source_path": ".gitea/workflows/e2e-health.yaml", + "surface_kind": "gitea_workflow_direct_bot_api", + "direct_call_count": 1, + "line_refs": [ + 98 + ], + "line_hash_refs": [ + "d73ce94678f970a9" + ], + "proposed_wave": "wave_1_workflow_notification_wrapper", + "proposed_target": "scripts/ci/notify-awoooi-cicd.sh or AWOOI Alertmanager webhook", + "proposed_change_summary": "Replace direct workflow Bot API send with normalized CI/CD notification wrapper after owner approval.", + "owner_response_ref": null, + "owner_role_or_team": "pending_owner_response", + "decision": "pending_owner_response", + "decision_reason": "pending_owner_response", + "affected_scope": "pending_owner_response", + "redacted_evidence_refs": [], + "message_shape_contract_ref": null, + "redaction_contract_ref": null, + "formatter_convergence_decision": "pending_owner_response", + "gateway_or_alertmanager_target": "pending_owner_response", + "break_glass_fallback_decision": "pending_owner_response", + "delivery_receipt_ref": null, + "dedup_or_fingerprint_plan": "pending_owner_response", + "fallback_or_degraded_mode": "pending_owner_response", + "migration_or_exception_reason": "pending_owner_response", + "maintenance_window": "pending_owner_response", + "rollback_owner": "pending_owner_response", + "postcheck_evidence_ref": null, + "no_secret_value_attestation": "pending_owner_response", + "no_raw_payload_attestation": "pending_owner_response", + "no_false_green_attestation": "pending_owner_response", + "reviewer_outcome": "waiting_owner_response", + "followup_owner": "pending_owner_response", + "acceptance_fields": [ + "acceptance_candidate_id", + "source_request_draft_id", + "source_migration_candidate_id", + "source_path", + "surface_kind", + "direct_call_count", + "proposed_wave", + "proposed_target", + "owner_response_ref", + "owner_role_or_team", + "decision", + "decision_reason", + "affected_scope", + "redacted_evidence_refs", + "message_shape_contract_ref", + "redaction_contract_ref", + "formatter_convergence_decision", + "gateway_or_alertmanager_target", + "break_glass_fallback_decision", + "delivery_receipt_ref", + "dedup_or_fingerprint_plan", + "fallback_or_degraded_mode", + "migration_or_exception_reason", + "maintenance_window", + "rollback_owner", + "postcheck_evidence_ref", + "no_secret_value_attestation", + "no_raw_payload_attestation", + "no_false_green_attestation", + "reviewer_outcome", + "followup_owner", + "not_authorization" + ], + "required_owner_fields": [ + "owner_role_or_team", + "routing_purpose", + "current_sender", + "target_chat_route", + "message_shape_contract", + "redaction_contract", + "formatter_convergence_decision", + "gateway_or_alertmanager_target", + "break_glass_fallback_decision", + "delivery_receipt_ref", + "dedup_or_fingerprint_plan", + "fallback_or_degraded_mode", + "migration_or_exception_reason", + "maintenance_window", + "rollback_owner", + "postcheck_evidence_ref", + "no_secret_value_attestation", + "no_raw_payload_attestation", + "no_false_green_attestation" + ], + "reviewer_checks": [ + "source_owner_request_current", + "source_migration_plan_current", + "owner_identity_present", + "decision_reason_present", + "affected_scope_matches_source", + "redacted_refs_only", + "no_secret_or_token_value", + "no_raw_message_payload", + "message_shape_contract_present", + "redaction_contract_present", + "formatter_convergence_explicit", + "gateway_or_alertmanager_target_valid", + "break_glass_fallback_explicit", + "delivery_receipt_metadata_only", + "dedup_or_fingerprint_present", + "maintenance_window_present", + "rollback_owner_present", + "postcheck_evidence_present", + "no_false_green_attested", + "migration_authorization_separate", + "counts_transition_safe", + "runtime_gate_stays_zero" + ], + "outcome_lanes": [ + "waiting_owner_response", + "quarantine_secret_or_raw_payload", + "reject_execution_request", + "request_owner_route_supplement", + "request_formatter_convergence_supplement", + "request_redaction_or_receipt_supplement", + "request_maintenance_or_rollback_supplement", + "ready_for_migration_review", + "owner_review_only_update", + "waiting_runtime_gate" + ], + "forbidden_payloads": [ + "bot_token_value", + "chat_secret_value", + "secret_hash", + "partial_token", + "masked_token", + "authorization_header", + "raw_message_payload", + "raw_workflow_log", + "raw_action_log", + "raw_screenshot_with_secret", + "internal_work_window_transcript", + "private_namespace", + "unredacted_internal_path", + "unredacted_private_ip" + ], + "blocked_actions": [ + "mark_owner_response_received_without_record", + "mark_owner_response_accepted_without_reviewer_record", + "send_telegram", + "call_bot_api", + "modify_workflow", + "modify_ops_script", + "refactor_api_sender", + "dispatch_workflow", + "trigger_cd", + "deploy_production", + "change_chat_route", + "change_bot_token", + "rotate_secret", + "read_secret_store", + "collect_secret_value", + "collect_secret_hash", + "collect_partial_token", + "collect_chat_id_secret", + "store_raw_message_payload", + "store_unredacted_log", + "store_internal_work_window_transcript", + "accept_cd_success_as_delivery_receipt", + "accept_route_200_as_notification_delivery", + "accept_ui_visible_as_notification_acceptance", + "accept_telegram_sent_without_delivery_receipt", + "skip_formatter_convergence", + "skip_redaction_contract", + "skip_dedup_or_fingerprint_review", + "skip_break_glass_fallback_review", + "authorize_migration", + "authorize_workflow_modification", + "authorize_script_modification", + "authorize_api_sender_refactor", + "open_runtime_gate", + "add_action_button" + ], + "not_authorization": true, + "request_sent": false, + "recipient_confirmed": false, + "audit_event_emitted": false, + "owner_response_received": false, + "owner_response_accepted": false, + "owner_response_rejected": false, + "owner_response_quarantined": false, + "supplement_requested": false, + "formatter_convergence_accepted": false, + "redaction_contract_accepted": false, + "delivery_receipt_accepted": false, + "break_glass_fallback_accepted": false, + "maintenance_window_accepted": false, + "rollback_owner_accepted": false, + "postcheck_evidence_accepted": false, + "dedup_or_fingerprint_accepted": false, + "no_false_green_accepted": false, + "direct_bot_api_migration_authorized": false, + "workflow_modification_authorized": false, + "script_modification_authorized": false, + "api_sender_refactor_authorized": false, + "telegram_send_authorized": false, + "bot_api_call_authorized": false, + "workflow_dispatch_authorized": false, + "production_deploy_authorized": false, + "secret_value_collection_allowed": false, + "raw_payload_storage_allowed": false, + "production_write_authorized": false, + "runtime_gate": false, + "action_buttons_allowed": false + }, + { + "acceptance_candidate_id": "telegram_notification_egress_owner_response_acceptance:.gitea/workflows/run-migration.yml", + "status": "waiting_owner_response", + "source_request_draft_id": "telegram_notification_egress_owner_request:_gitea_workflows_run_migration_yml", + "source_migration_candidate_id": "telegram_notification_egress_migration:.gitea/workflows/run-migration.yml", + "source_path": ".gitea/workflows/run-migration.yml", + "surface_kind": "gitea_workflow_direct_bot_api", + "direct_call_count": 1, + "line_refs": [ + 210 + ], + "line_hash_refs": [ + "934a8dd69fca99b3" + ], + "proposed_wave": "wave_1_workflow_notification_wrapper", + "proposed_target": "scripts/ci/notify-awoooi-cicd.sh or AWOOI Alertmanager webhook", + "proposed_change_summary": "Replace direct workflow Bot API send with normalized CI/CD notification wrapper after owner approval.", + "owner_response_ref": null, + "owner_role_or_team": "pending_owner_response", + "decision": "pending_owner_response", + "decision_reason": "pending_owner_response", + "affected_scope": "pending_owner_response", + "redacted_evidence_refs": [], + "message_shape_contract_ref": null, + "redaction_contract_ref": null, + "formatter_convergence_decision": "pending_owner_response", + "gateway_or_alertmanager_target": "pending_owner_response", + "break_glass_fallback_decision": "pending_owner_response", + "delivery_receipt_ref": null, + "dedup_or_fingerprint_plan": "pending_owner_response", + "fallback_or_degraded_mode": "pending_owner_response", + "migration_or_exception_reason": "pending_owner_response", + "maintenance_window": "pending_owner_response", + "rollback_owner": "pending_owner_response", + "postcheck_evidence_ref": null, + "no_secret_value_attestation": "pending_owner_response", + "no_raw_payload_attestation": "pending_owner_response", + "no_false_green_attestation": "pending_owner_response", + "reviewer_outcome": "waiting_owner_response", + "followup_owner": "pending_owner_response", + "acceptance_fields": [ + "acceptance_candidate_id", + "source_request_draft_id", + "source_migration_candidate_id", + "source_path", + "surface_kind", + "direct_call_count", + "proposed_wave", + "proposed_target", + "owner_response_ref", + "owner_role_or_team", + "decision", + "decision_reason", + "affected_scope", + "redacted_evidence_refs", + "message_shape_contract_ref", + "redaction_contract_ref", + "formatter_convergence_decision", + "gateway_or_alertmanager_target", + "break_glass_fallback_decision", + "delivery_receipt_ref", + "dedup_or_fingerprint_plan", + "fallback_or_degraded_mode", + "migration_or_exception_reason", + "maintenance_window", + "rollback_owner", + "postcheck_evidence_ref", + "no_secret_value_attestation", + "no_raw_payload_attestation", + "no_false_green_attestation", + "reviewer_outcome", + "followup_owner", + "not_authorization" + ], + "required_owner_fields": [ + "owner_role_or_team", + "routing_purpose", + "current_sender", + "target_chat_route", + "message_shape_contract", + "redaction_contract", + "formatter_convergence_decision", + "gateway_or_alertmanager_target", + "break_glass_fallback_decision", + "delivery_receipt_ref", + "dedup_or_fingerprint_plan", + "fallback_or_degraded_mode", + "migration_or_exception_reason", + "maintenance_window", + "rollback_owner", + "postcheck_evidence_ref", + "no_secret_value_attestation", + "no_raw_payload_attestation", + "no_false_green_attestation" + ], + "reviewer_checks": [ + "source_owner_request_current", + "source_migration_plan_current", + "owner_identity_present", + "decision_reason_present", + "affected_scope_matches_source", + "redacted_refs_only", + "no_secret_or_token_value", + "no_raw_message_payload", + "message_shape_contract_present", + "redaction_contract_present", + "formatter_convergence_explicit", + "gateway_or_alertmanager_target_valid", + "break_glass_fallback_explicit", + "delivery_receipt_metadata_only", + "dedup_or_fingerprint_present", + "maintenance_window_present", + "rollback_owner_present", + "postcheck_evidence_present", + "no_false_green_attested", + "migration_authorization_separate", + "counts_transition_safe", + "runtime_gate_stays_zero" + ], + "outcome_lanes": [ + "waiting_owner_response", + "quarantine_secret_or_raw_payload", + "reject_execution_request", + "request_owner_route_supplement", + "request_formatter_convergence_supplement", + "request_redaction_or_receipt_supplement", + "request_maintenance_or_rollback_supplement", + "ready_for_migration_review", + "owner_review_only_update", + "waiting_runtime_gate" + ], + "forbidden_payloads": [ + "bot_token_value", + "chat_secret_value", + "secret_hash", + "partial_token", + "masked_token", + "authorization_header", + "raw_message_payload", + "raw_workflow_log", + "raw_action_log", + "raw_screenshot_with_secret", + "internal_work_window_transcript", + "private_namespace", + "unredacted_internal_path", + "unredacted_private_ip" + ], + "blocked_actions": [ + "mark_owner_response_received_without_record", + "mark_owner_response_accepted_without_reviewer_record", + "send_telegram", + "call_bot_api", + "modify_workflow", + "modify_ops_script", + "refactor_api_sender", + "dispatch_workflow", + "trigger_cd", + "deploy_production", + "change_chat_route", + "change_bot_token", + "rotate_secret", + "read_secret_store", + "collect_secret_value", + "collect_secret_hash", + "collect_partial_token", + "collect_chat_id_secret", + "store_raw_message_payload", + "store_unredacted_log", + "store_internal_work_window_transcript", + "accept_cd_success_as_delivery_receipt", + "accept_route_200_as_notification_delivery", + "accept_ui_visible_as_notification_acceptance", + "accept_telegram_sent_without_delivery_receipt", + "skip_formatter_convergence", + "skip_redaction_contract", + "skip_dedup_or_fingerprint_review", + "skip_break_glass_fallback_review", + "authorize_migration", + "authorize_workflow_modification", + "authorize_script_modification", + "authorize_api_sender_refactor", + "open_runtime_gate", + "add_action_button" + ], + "not_authorization": true, + "request_sent": false, + "recipient_confirmed": false, + "audit_event_emitted": false, + "owner_response_received": false, + "owner_response_accepted": false, + "owner_response_rejected": false, + "owner_response_quarantined": false, + "supplement_requested": false, + "formatter_convergence_accepted": false, + "redaction_contract_accepted": false, + "delivery_receipt_accepted": false, + "break_glass_fallback_accepted": false, + "maintenance_window_accepted": false, + "rollback_owner_accepted": false, + "postcheck_evidence_accepted": false, + "dedup_or_fingerprint_accepted": false, + "no_false_green_accepted": false, + "direct_bot_api_migration_authorized": false, + "workflow_modification_authorized": false, + "script_modification_authorized": false, + "api_sender_refactor_authorized": false, + "telegram_send_authorized": false, + "bot_api_call_authorized": false, + "workflow_dispatch_authorized": false, + "production_deploy_authorized": false, + "secret_value_collection_allowed": false, + "raw_payload_storage_allowed": false, + "production_write_authorized": false, + "runtime_gate": false, + "action_buttons_allowed": false + }, + { + "acceptance_candidate_id": "telegram_notification_egress_owner_response_acceptance:apps/api/src/services/channel_hub.py", + "status": "waiting_owner_response", + "source_request_draft_id": "telegram_notification_egress_owner_request:apps_api_src_services_channel_hub_py", + "source_migration_candidate_id": "telegram_notification_egress_migration:apps/api/src/services/channel_hub.py", + "source_path": "apps/api/src/services/channel_hub.py", + "surface_kind": "api_direct_bot_api", + "direct_call_count": 1, + "line_refs": [ + 1138 + ], + "line_hash_refs": [ + "9e418fa74c83815a" + ], + "proposed_wave": "wave_3_api_sender_gateway", + "proposed_target": "TelegramGateway final-exit formatter", + "proposed_change_summary": "Route API interim sender through TelegramGateway or equivalent final-exit normalization and mirror contract.", + "owner_response_ref": null, + "owner_role_or_team": "pending_owner_response", + "decision": "pending_owner_response", + "decision_reason": "pending_owner_response", + "affected_scope": "pending_owner_response", + "redacted_evidence_refs": [], + "message_shape_contract_ref": null, + "redaction_contract_ref": null, + "formatter_convergence_decision": "pending_owner_response", + "gateway_or_alertmanager_target": "pending_owner_response", + "break_glass_fallback_decision": "pending_owner_response", + "delivery_receipt_ref": null, + "dedup_or_fingerprint_plan": "pending_owner_response", + "fallback_or_degraded_mode": "pending_owner_response", + "migration_or_exception_reason": "pending_owner_response", + "maintenance_window": "pending_owner_response", + "rollback_owner": "pending_owner_response", + "postcheck_evidence_ref": null, + "no_secret_value_attestation": "pending_owner_response", + "no_raw_payload_attestation": "pending_owner_response", + "no_false_green_attestation": "pending_owner_response", + "reviewer_outcome": "waiting_owner_response", + "followup_owner": "pending_owner_response", + "acceptance_fields": [ + "acceptance_candidate_id", + "source_request_draft_id", + "source_migration_candidate_id", + "source_path", + "surface_kind", + "direct_call_count", + "proposed_wave", + "proposed_target", + "owner_response_ref", + "owner_role_or_team", + "decision", + "decision_reason", + "affected_scope", + "redacted_evidence_refs", + "message_shape_contract_ref", + "redaction_contract_ref", + "formatter_convergence_decision", + "gateway_or_alertmanager_target", + "break_glass_fallback_decision", + "delivery_receipt_ref", + "dedup_or_fingerprint_plan", + "fallback_or_degraded_mode", + "migration_or_exception_reason", + "maintenance_window", + "rollback_owner", + "postcheck_evidence_ref", + "no_secret_value_attestation", + "no_raw_payload_attestation", + "no_false_green_attestation", + "reviewer_outcome", + "followup_owner", + "not_authorization" + ], + "required_owner_fields": [ + "owner_role_or_team", + "routing_purpose", + "current_sender", + "target_chat_route", + "message_shape_contract", + "redaction_contract", + "formatter_convergence_decision", + "gateway_or_alertmanager_target", + "break_glass_fallback_decision", + "delivery_receipt_ref", + "dedup_or_fingerprint_plan", + "fallback_or_degraded_mode", + "migration_or_exception_reason", + "maintenance_window", + "rollback_owner", + "postcheck_evidence_ref", + "no_secret_value_attestation", + "no_raw_payload_attestation", + "no_false_green_attestation" + ], + "reviewer_checks": [ + "source_owner_request_current", + "source_migration_plan_current", + "owner_identity_present", + "decision_reason_present", + "affected_scope_matches_source", + "redacted_refs_only", + "no_secret_or_token_value", + "no_raw_message_payload", + "message_shape_contract_present", + "redaction_contract_present", + "formatter_convergence_explicit", + "gateway_or_alertmanager_target_valid", + "break_glass_fallback_explicit", + "delivery_receipt_metadata_only", + "dedup_or_fingerprint_present", + "maintenance_window_present", + "rollback_owner_present", + "postcheck_evidence_present", + "no_false_green_attested", + "migration_authorization_separate", + "counts_transition_safe", + "runtime_gate_stays_zero" + ], + "outcome_lanes": [ + "waiting_owner_response", + "quarantine_secret_or_raw_payload", + "reject_execution_request", + "request_owner_route_supplement", + "request_formatter_convergence_supplement", + "request_redaction_or_receipt_supplement", + "request_maintenance_or_rollback_supplement", + "ready_for_migration_review", + "owner_review_only_update", + "waiting_runtime_gate" + ], + "forbidden_payloads": [ + "bot_token_value", + "chat_secret_value", + "secret_hash", + "partial_token", + "masked_token", + "authorization_header", + "raw_message_payload", + "raw_workflow_log", + "raw_action_log", + "raw_screenshot_with_secret", + "internal_work_window_transcript", + "private_namespace", + "unredacted_internal_path", + "unredacted_private_ip" + ], + "blocked_actions": [ + "mark_owner_response_received_without_record", + "mark_owner_response_accepted_without_reviewer_record", + "send_telegram", + "call_bot_api", + "modify_workflow", + "modify_ops_script", + "refactor_api_sender", + "dispatch_workflow", + "trigger_cd", + "deploy_production", + "change_chat_route", + "change_bot_token", + "rotate_secret", + "read_secret_store", + "collect_secret_value", + "collect_secret_hash", + "collect_partial_token", + "collect_chat_id_secret", + "store_raw_message_payload", + "store_unredacted_log", + "store_internal_work_window_transcript", + "accept_cd_success_as_delivery_receipt", + "accept_route_200_as_notification_delivery", + "accept_ui_visible_as_notification_acceptance", + "accept_telegram_sent_without_delivery_receipt", + "skip_formatter_convergence", + "skip_redaction_contract", + "skip_dedup_or_fingerprint_review", + "skip_break_glass_fallback_review", + "authorize_migration", + "authorize_workflow_modification", + "authorize_script_modification", + "authorize_api_sender_refactor", + "open_runtime_gate", + "add_action_button" + ], + "not_authorization": true, + "request_sent": false, + "recipient_confirmed": false, + "audit_event_emitted": false, + "owner_response_received": false, + "owner_response_accepted": false, + "owner_response_rejected": false, + "owner_response_quarantined": false, + "supplement_requested": false, + "formatter_convergence_accepted": false, + "redaction_contract_accepted": false, + "delivery_receipt_accepted": false, + "break_glass_fallback_accepted": false, + "maintenance_window_accepted": false, + "rollback_owner_accepted": false, + "postcheck_evidence_accepted": false, + "dedup_or_fingerprint_accepted": false, + "no_false_green_accepted": false, + "direct_bot_api_migration_authorized": false, + "workflow_modification_authorized": false, + "script_modification_authorized": false, + "api_sender_refactor_authorized": false, + "telegram_send_authorized": false, + "bot_api_call_authorized": false, + "workflow_dispatch_authorized": false, + "production_deploy_authorized": false, + "secret_value_collection_allowed": false, + "raw_payload_storage_allowed": false, + "production_write_authorized": false, + "runtime_gate": false, + "action_buttons_allowed": false + }, + { + "acceptance_candidate_id": "telegram_notification_egress_owner_response_acceptance:scripts/ops/backup-from-110.sh", + "status": "waiting_owner_response", + "source_request_draft_id": "telegram_notification_egress_owner_request:scripts_ops_backup_from_110_sh", + "source_migration_candidate_id": "telegram_notification_egress_migration:scripts/ops/backup-from-110.sh", + "source_path": "scripts/ops/backup-from-110.sh", + "surface_kind": "ops_script_direct_bot_api", + "direct_call_count": 1, + "line_refs": [ + 64 + ], + "line_hash_refs": [ + "ff5ffbeca44d679b" + ], + "proposed_wave": "wave_2_ops_notification_wrapper", + "proposed_target": "scripts/ops/notify-awoooi-ops.sh or AWOOI Alertmanager webhook", + "proposed_change_summary": "Replace direct ops fallback send with normalized ops notification wrapper or documented break-glass fallback.", + "owner_response_ref": null, + "owner_role_or_team": "pending_owner_response", + "decision": "pending_owner_response", + "decision_reason": "pending_owner_response", + "affected_scope": "pending_owner_response", + "redacted_evidence_refs": [], + "message_shape_contract_ref": null, + "redaction_contract_ref": null, + "formatter_convergence_decision": "pending_owner_response", + "gateway_or_alertmanager_target": "pending_owner_response", + "break_glass_fallback_decision": "pending_owner_response", + "delivery_receipt_ref": null, + "dedup_or_fingerprint_plan": "pending_owner_response", + "fallback_or_degraded_mode": "pending_owner_response", + "migration_or_exception_reason": "pending_owner_response", + "maintenance_window": "pending_owner_response", + "rollback_owner": "pending_owner_response", + "postcheck_evidence_ref": null, + "no_secret_value_attestation": "pending_owner_response", + "no_raw_payload_attestation": "pending_owner_response", + "no_false_green_attestation": "pending_owner_response", + "reviewer_outcome": "waiting_owner_response", + "followup_owner": "pending_owner_response", + "acceptance_fields": [ + "acceptance_candidate_id", + "source_request_draft_id", + "source_migration_candidate_id", + "source_path", + "surface_kind", + "direct_call_count", + "proposed_wave", + "proposed_target", + "owner_response_ref", + "owner_role_or_team", + "decision", + "decision_reason", + "affected_scope", + "redacted_evidence_refs", + "message_shape_contract_ref", + "redaction_contract_ref", + "formatter_convergence_decision", + "gateway_or_alertmanager_target", + "break_glass_fallback_decision", + "delivery_receipt_ref", + "dedup_or_fingerprint_plan", + "fallback_or_degraded_mode", + "migration_or_exception_reason", + "maintenance_window", + "rollback_owner", + "postcheck_evidence_ref", + "no_secret_value_attestation", + "no_raw_payload_attestation", + "no_false_green_attestation", + "reviewer_outcome", + "followup_owner", + "not_authorization" + ], + "required_owner_fields": [ + "owner_role_or_team", + "routing_purpose", + "current_sender", + "target_chat_route", + "message_shape_contract", + "redaction_contract", + "formatter_convergence_decision", + "gateway_or_alertmanager_target", + "break_glass_fallback_decision", + "delivery_receipt_ref", + "dedup_or_fingerprint_plan", + "fallback_or_degraded_mode", + "migration_or_exception_reason", + "maintenance_window", + "rollback_owner", + "postcheck_evidence_ref", + "no_secret_value_attestation", + "no_raw_payload_attestation", + "no_false_green_attestation" + ], + "reviewer_checks": [ + "source_owner_request_current", + "source_migration_plan_current", + "owner_identity_present", + "decision_reason_present", + "affected_scope_matches_source", + "redacted_refs_only", + "no_secret_or_token_value", + "no_raw_message_payload", + "message_shape_contract_present", + "redaction_contract_present", + "formatter_convergence_explicit", + "gateway_or_alertmanager_target_valid", + "break_glass_fallback_explicit", + "delivery_receipt_metadata_only", + "dedup_or_fingerprint_present", + "maintenance_window_present", + "rollback_owner_present", + "postcheck_evidence_present", + "no_false_green_attested", + "migration_authorization_separate", + "counts_transition_safe", + "runtime_gate_stays_zero" + ], + "outcome_lanes": [ + "waiting_owner_response", + "quarantine_secret_or_raw_payload", + "reject_execution_request", + "request_owner_route_supplement", + "request_formatter_convergence_supplement", + "request_redaction_or_receipt_supplement", + "request_maintenance_or_rollback_supplement", + "ready_for_migration_review", + "owner_review_only_update", + "waiting_runtime_gate" + ], + "forbidden_payloads": [ + "bot_token_value", + "chat_secret_value", + "secret_hash", + "partial_token", + "masked_token", + "authorization_header", + "raw_message_payload", + "raw_workflow_log", + "raw_action_log", + "raw_screenshot_with_secret", + "internal_work_window_transcript", + "private_namespace", + "unredacted_internal_path", + "unredacted_private_ip" + ], + "blocked_actions": [ + "mark_owner_response_received_without_record", + "mark_owner_response_accepted_without_reviewer_record", + "send_telegram", + "call_bot_api", + "modify_workflow", + "modify_ops_script", + "refactor_api_sender", + "dispatch_workflow", + "trigger_cd", + "deploy_production", + "change_chat_route", + "change_bot_token", + "rotate_secret", + "read_secret_store", + "collect_secret_value", + "collect_secret_hash", + "collect_partial_token", + "collect_chat_id_secret", + "store_raw_message_payload", + "store_unredacted_log", + "store_internal_work_window_transcript", + "accept_cd_success_as_delivery_receipt", + "accept_route_200_as_notification_delivery", + "accept_ui_visible_as_notification_acceptance", + "accept_telegram_sent_without_delivery_receipt", + "skip_formatter_convergence", + "skip_redaction_contract", + "skip_dedup_or_fingerprint_review", + "skip_break_glass_fallback_review", + "authorize_migration", + "authorize_workflow_modification", + "authorize_script_modification", + "authorize_api_sender_refactor", + "open_runtime_gate", + "add_action_button" + ], + "not_authorization": true, + "request_sent": false, + "recipient_confirmed": false, + "audit_event_emitted": false, + "owner_response_received": false, + "owner_response_accepted": false, + "owner_response_rejected": false, + "owner_response_quarantined": false, + "supplement_requested": false, + "formatter_convergence_accepted": false, + "redaction_contract_accepted": false, + "delivery_receipt_accepted": false, + "break_glass_fallback_accepted": false, + "maintenance_window_accepted": false, + "rollback_owner_accepted": false, + "postcheck_evidence_accepted": false, + "dedup_or_fingerprint_accepted": false, + "no_false_green_accepted": false, + "direct_bot_api_migration_authorized": false, + "workflow_modification_authorized": false, + "script_modification_authorized": false, + "api_sender_refactor_authorized": false, + "telegram_send_authorized": false, + "bot_api_call_authorized": false, + "workflow_dispatch_authorized": false, + "production_deploy_authorized": false, + "secret_value_collection_allowed": false, + "raw_payload_storage_allowed": false, + "production_write_authorized": false, + "runtime_gate": false, + "action_buttons_allowed": false + }, + { + "acceptance_candidate_id": "telegram_notification_egress_owner_response_acceptance:scripts/ops/docker-health-monitor.sh", + "status": "waiting_owner_response", + "source_request_draft_id": "telegram_notification_egress_owner_request:scripts_ops_docker_health_monitor_sh", + "source_migration_candidate_id": "telegram_notification_egress_migration:scripts/ops/docker-health-monitor.sh", + "source_path": "scripts/ops/docker-health-monitor.sh", + "surface_kind": "ops_script_direct_bot_api", + "direct_call_count": 1, + "line_refs": [ + 90 + ], + "line_hash_refs": [ + "6611406903bc7d65" + ], + "proposed_wave": "wave_2_ops_notification_wrapper", + "proposed_target": "scripts/ops/notify-awoooi-ops.sh or AWOOI Alertmanager webhook", + "proposed_change_summary": "Replace direct ops fallback send with normalized ops notification wrapper or documented break-glass fallback.", + "owner_response_ref": null, + "owner_role_or_team": "pending_owner_response", + "decision": "pending_owner_response", + "decision_reason": "pending_owner_response", + "affected_scope": "pending_owner_response", + "redacted_evidence_refs": [], + "message_shape_contract_ref": null, + "redaction_contract_ref": null, + "formatter_convergence_decision": "pending_owner_response", + "gateway_or_alertmanager_target": "pending_owner_response", + "break_glass_fallback_decision": "pending_owner_response", + "delivery_receipt_ref": null, + "dedup_or_fingerprint_plan": "pending_owner_response", + "fallback_or_degraded_mode": "pending_owner_response", + "migration_or_exception_reason": "pending_owner_response", + "maintenance_window": "pending_owner_response", + "rollback_owner": "pending_owner_response", + "postcheck_evidence_ref": null, + "no_secret_value_attestation": "pending_owner_response", + "no_raw_payload_attestation": "pending_owner_response", + "no_false_green_attestation": "pending_owner_response", + "reviewer_outcome": "waiting_owner_response", + "followup_owner": "pending_owner_response", + "acceptance_fields": [ + "acceptance_candidate_id", + "source_request_draft_id", + "source_migration_candidate_id", + "source_path", + "surface_kind", + "direct_call_count", + "proposed_wave", + "proposed_target", + "owner_response_ref", + "owner_role_or_team", + "decision", + "decision_reason", + "affected_scope", + "redacted_evidence_refs", + "message_shape_contract_ref", + "redaction_contract_ref", + "formatter_convergence_decision", + "gateway_or_alertmanager_target", + "break_glass_fallback_decision", + "delivery_receipt_ref", + "dedup_or_fingerprint_plan", + "fallback_or_degraded_mode", + "migration_or_exception_reason", + "maintenance_window", + "rollback_owner", + "postcheck_evidence_ref", + "no_secret_value_attestation", + "no_raw_payload_attestation", + "no_false_green_attestation", + "reviewer_outcome", + "followup_owner", + "not_authorization" + ], + "required_owner_fields": [ + "owner_role_or_team", + "routing_purpose", + "current_sender", + "target_chat_route", + "message_shape_contract", + "redaction_contract", + "formatter_convergence_decision", + "gateway_or_alertmanager_target", + "break_glass_fallback_decision", + "delivery_receipt_ref", + "dedup_or_fingerprint_plan", + "fallback_or_degraded_mode", + "migration_or_exception_reason", + "maintenance_window", + "rollback_owner", + "postcheck_evidence_ref", + "no_secret_value_attestation", + "no_raw_payload_attestation", + "no_false_green_attestation" + ], + "reviewer_checks": [ + "source_owner_request_current", + "source_migration_plan_current", + "owner_identity_present", + "decision_reason_present", + "affected_scope_matches_source", + "redacted_refs_only", + "no_secret_or_token_value", + "no_raw_message_payload", + "message_shape_contract_present", + "redaction_contract_present", + "formatter_convergence_explicit", + "gateway_or_alertmanager_target_valid", + "break_glass_fallback_explicit", + "delivery_receipt_metadata_only", + "dedup_or_fingerprint_present", + "maintenance_window_present", + "rollback_owner_present", + "postcheck_evidence_present", + "no_false_green_attested", + "migration_authorization_separate", + "counts_transition_safe", + "runtime_gate_stays_zero" + ], + "outcome_lanes": [ + "waiting_owner_response", + "quarantine_secret_or_raw_payload", + "reject_execution_request", + "request_owner_route_supplement", + "request_formatter_convergence_supplement", + "request_redaction_or_receipt_supplement", + "request_maintenance_or_rollback_supplement", + "ready_for_migration_review", + "owner_review_only_update", + "waiting_runtime_gate" + ], + "forbidden_payloads": [ + "bot_token_value", + "chat_secret_value", + "secret_hash", + "partial_token", + "masked_token", + "authorization_header", + "raw_message_payload", + "raw_workflow_log", + "raw_action_log", + "raw_screenshot_with_secret", + "internal_work_window_transcript", + "private_namespace", + "unredacted_internal_path", + "unredacted_private_ip" + ], + "blocked_actions": [ + "mark_owner_response_received_without_record", + "mark_owner_response_accepted_without_reviewer_record", + "send_telegram", + "call_bot_api", + "modify_workflow", + "modify_ops_script", + "refactor_api_sender", + "dispatch_workflow", + "trigger_cd", + "deploy_production", + "change_chat_route", + "change_bot_token", + "rotate_secret", + "read_secret_store", + "collect_secret_value", + "collect_secret_hash", + "collect_partial_token", + "collect_chat_id_secret", + "store_raw_message_payload", + "store_unredacted_log", + "store_internal_work_window_transcript", + "accept_cd_success_as_delivery_receipt", + "accept_route_200_as_notification_delivery", + "accept_ui_visible_as_notification_acceptance", + "accept_telegram_sent_without_delivery_receipt", + "skip_formatter_convergence", + "skip_redaction_contract", + "skip_dedup_or_fingerprint_review", + "skip_break_glass_fallback_review", + "authorize_migration", + "authorize_workflow_modification", + "authorize_script_modification", + "authorize_api_sender_refactor", + "open_runtime_gate", + "add_action_button" + ], + "not_authorization": true, + "request_sent": false, + "recipient_confirmed": false, + "audit_event_emitted": false, + "owner_response_received": false, + "owner_response_accepted": false, + "owner_response_rejected": false, + "owner_response_quarantined": false, + "supplement_requested": false, + "formatter_convergence_accepted": false, + "redaction_contract_accepted": false, + "delivery_receipt_accepted": false, + "break_glass_fallback_accepted": false, + "maintenance_window_accepted": false, + "rollback_owner_accepted": false, + "postcheck_evidence_accepted": false, + "dedup_or_fingerprint_accepted": false, + "no_false_green_accepted": false, + "direct_bot_api_migration_authorized": false, + "workflow_modification_authorized": false, + "script_modification_authorized": false, + "api_sender_refactor_authorized": false, + "telegram_send_authorized": false, + "bot_api_call_authorized": false, + "workflow_dispatch_authorized": false, + "production_deploy_authorized": false, + "secret_value_collection_allowed": false, + "raw_payload_storage_allowed": false, + "production_write_authorized": false, + "runtime_gate": false, + "action_buttons_allowed": false + }, + { + "acceptance_candidate_id": "telegram_notification_egress_owner_response_acceptance:scripts/ops/dr-drill.sh", + "status": "waiting_owner_response", + "source_request_draft_id": "telegram_notification_egress_owner_request:scripts_ops_dr_drill_sh", + "source_migration_candidate_id": "telegram_notification_egress_migration:scripts/ops/dr-drill.sh", + "source_path": "scripts/ops/dr-drill.sh", + "surface_kind": "ops_script_direct_bot_api", + "direct_call_count": 1, + "line_refs": [ + 63 + ], + "line_hash_refs": [ + "b7cf532f3c5509fc" + ], + "proposed_wave": "wave_2_ops_notification_wrapper", + "proposed_target": "scripts/ops/notify-awoooi-ops.sh or AWOOI Alertmanager webhook", + "proposed_change_summary": "Replace direct ops fallback send with normalized ops notification wrapper or documented break-glass fallback.", + "owner_response_ref": null, + "owner_role_or_team": "pending_owner_response", + "decision": "pending_owner_response", + "decision_reason": "pending_owner_response", + "affected_scope": "pending_owner_response", + "redacted_evidence_refs": [], + "message_shape_contract_ref": null, + "redaction_contract_ref": null, + "formatter_convergence_decision": "pending_owner_response", + "gateway_or_alertmanager_target": "pending_owner_response", + "break_glass_fallback_decision": "pending_owner_response", + "delivery_receipt_ref": null, + "dedup_or_fingerprint_plan": "pending_owner_response", + "fallback_or_degraded_mode": "pending_owner_response", + "migration_or_exception_reason": "pending_owner_response", + "maintenance_window": "pending_owner_response", + "rollback_owner": "pending_owner_response", + "postcheck_evidence_ref": null, + "no_secret_value_attestation": "pending_owner_response", + "no_raw_payload_attestation": "pending_owner_response", + "no_false_green_attestation": "pending_owner_response", + "reviewer_outcome": "waiting_owner_response", + "followup_owner": "pending_owner_response", + "acceptance_fields": [ + "acceptance_candidate_id", + "source_request_draft_id", + "source_migration_candidate_id", + "source_path", + "surface_kind", + "direct_call_count", + "proposed_wave", + "proposed_target", + "owner_response_ref", + "owner_role_or_team", + "decision", + "decision_reason", + "affected_scope", + "redacted_evidence_refs", + "message_shape_contract_ref", + "redaction_contract_ref", + "formatter_convergence_decision", + "gateway_or_alertmanager_target", + "break_glass_fallback_decision", + "delivery_receipt_ref", + "dedup_or_fingerprint_plan", + "fallback_or_degraded_mode", + "migration_or_exception_reason", + "maintenance_window", + "rollback_owner", + "postcheck_evidence_ref", + "no_secret_value_attestation", + "no_raw_payload_attestation", + "no_false_green_attestation", + "reviewer_outcome", + "followup_owner", + "not_authorization" + ], + "required_owner_fields": [ + "owner_role_or_team", + "routing_purpose", + "current_sender", + "target_chat_route", + "message_shape_contract", + "redaction_contract", + "formatter_convergence_decision", + "gateway_or_alertmanager_target", + "break_glass_fallback_decision", + "delivery_receipt_ref", + "dedup_or_fingerprint_plan", + "fallback_or_degraded_mode", + "migration_or_exception_reason", + "maintenance_window", + "rollback_owner", + "postcheck_evidence_ref", + "no_secret_value_attestation", + "no_raw_payload_attestation", + "no_false_green_attestation" + ], + "reviewer_checks": [ + "source_owner_request_current", + "source_migration_plan_current", + "owner_identity_present", + "decision_reason_present", + "affected_scope_matches_source", + "redacted_refs_only", + "no_secret_or_token_value", + "no_raw_message_payload", + "message_shape_contract_present", + "redaction_contract_present", + "formatter_convergence_explicit", + "gateway_or_alertmanager_target_valid", + "break_glass_fallback_explicit", + "delivery_receipt_metadata_only", + "dedup_or_fingerprint_present", + "maintenance_window_present", + "rollback_owner_present", + "postcheck_evidence_present", + "no_false_green_attested", + "migration_authorization_separate", + "counts_transition_safe", + "runtime_gate_stays_zero" + ], + "outcome_lanes": [ + "waiting_owner_response", + "quarantine_secret_or_raw_payload", + "reject_execution_request", + "request_owner_route_supplement", + "request_formatter_convergence_supplement", + "request_redaction_or_receipt_supplement", + "request_maintenance_or_rollback_supplement", + "ready_for_migration_review", + "owner_review_only_update", + "waiting_runtime_gate" + ], + "forbidden_payloads": [ + "bot_token_value", + "chat_secret_value", + "secret_hash", + "partial_token", + "masked_token", + "authorization_header", + "raw_message_payload", + "raw_workflow_log", + "raw_action_log", + "raw_screenshot_with_secret", + "internal_work_window_transcript", + "private_namespace", + "unredacted_internal_path", + "unredacted_private_ip" + ], + "blocked_actions": [ + "mark_owner_response_received_without_record", + "mark_owner_response_accepted_without_reviewer_record", + "send_telegram", + "call_bot_api", + "modify_workflow", + "modify_ops_script", + "refactor_api_sender", + "dispatch_workflow", + "trigger_cd", + "deploy_production", + "change_chat_route", + "change_bot_token", + "rotate_secret", + "read_secret_store", + "collect_secret_value", + "collect_secret_hash", + "collect_partial_token", + "collect_chat_id_secret", + "store_raw_message_payload", + "store_unredacted_log", + "store_internal_work_window_transcript", + "accept_cd_success_as_delivery_receipt", + "accept_route_200_as_notification_delivery", + "accept_ui_visible_as_notification_acceptance", + "accept_telegram_sent_without_delivery_receipt", + "skip_formatter_convergence", + "skip_redaction_contract", + "skip_dedup_or_fingerprint_review", + "skip_break_glass_fallback_review", + "authorize_migration", + "authorize_workflow_modification", + "authorize_script_modification", + "authorize_api_sender_refactor", + "open_runtime_gate", + "add_action_button" + ], + "not_authorization": true, + "request_sent": false, + "recipient_confirmed": false, + "audit_event_emitted": false, + "owner_response_received": false, + "owner_response_accepted": false, + "owner_response_rejected": false, + "owner_response_quarantined": false, + "supplement_requested": false, + "formatter_convergence_accepted": false, + "redaction_contract_accepted": false, + "delivery_receipt_accepted": false, + "break_glass_fallback_accepted": false, + "maintenance_window_accepted": false, + "rollback_owner_accepted": false, + "postcheck_evidence_accepted": false, + "dedup_or_fingerprint_accepted": false, + "no_false_green_accepted": false, + "direct_bot_api_migration_authorized": false, + "workflow_modification_authorized": false, + "script_modification_authorized": false, + "api_sender_refactor_authorized": false, + "telegram_send_authorized": false, + "bot_api_call_authorized": false, + "workflow_dispatch_authorized": false, + "production_deploy_authorized": false, + "secret_value_collection_allowed": false, + "raw_payload_storage_allowed": false, + "production_write_authorized": false, + "runtime_gate": false, + "action_buttons_allowed": false + }, + { + "acceptance_candidate_id": "telegram_notification_egress_owner_response_acceptance:scripts/ops/pg-backup.sh", + "status": "waiting_owner_response", + "source_request_draft_id": "telegram_notification_egress_owner_request:scripts_ops_pg_backup_sh", + "source_migration_candidate_id": "telegram_notification_egress_migration:scripts/ops/pg-backup.sh", + "source_path": "scripts/ops/pg-backup.sh", + "surface_kind": "ops_script_direct_bot_api", + "direct_call_count": 1, + "line_refs": [ + 52 + ], + "line_hash_refs": [ + "8268f3d6f14d66a3" + ], + "proposed_wave": "wave_2_ops_notification_wrapper", + "proposed_target": "scripts/ops/notify-awoooi-ops.sh or AWOOI Alertmanager webhook", + "proposed_change_summary": "Replace direct ops fallback send with normalized ops notification wrapper or documented break-glass fallback.", + "owner_response_ref": null, + "owner_role_or_team": "pending_owner_response", + "decision": "pending_owner_response", + "decision_reason": "pending_owner_response", + "affected_scope": "pending_owner_response", + "redacted_evidence_refs": [], + "message_shape_contract_ref": null, + "redaction_contract_ref": null, + "formatter_convergence_decision": "pending_owner_response", + "gateway_or_alertmanager_target": "pending_owner_response", + "break_glass_fallback_decision": "pending_owner_response", + "delivery_receipt_ref": null, + "dedup_or_fingerprint_plan": "pending_owner_response", + "fallback_or_degraded_mode": "pending_owner_response", + "migration_or_exception_reason": "pending_owner_response", + "maintenance_window": "pending_owner_response", + "rollback_owner": "pending_owner_response", + "postcheck_evidence_ref": null, + "no_secret_value_attestation": "pending_owner_response", + "no_raw_payload_attestation": "pending_owner_response", + "no_false_green_attestation": "pending_owner_response", + "reviewer_outcome": "waiting_owner_response", + "followup_owner": "pending_owner_response", + "acceptance_fields": [ + "acceptance_candidate_id", + "source_request_draft_id", + "source_migration_candidate_id", + "source_path", + "surface_kind", + "direct_call_count", + "proposed_wave", + "proposed_target", + "owner_response_ref", + "owner_role_or_team", + "decision", + "decision_reason", + "affected_scope", + "redacted_evidence_refs", + "message_shape_contract_ref", + "redaction_contract_ref", + "formatter_convergence_decision", + "gateway_or_alertmanager_target", + "break_glass_fallback_decision", + "delivery_receipt_ref", + "dedup_or_fingerprint_plan", + "fallback_or_degraded_mode", + "migration_or_exception_reason", + "maintenance_window", + "rollback_owner", + "postcheck_evidence_ref", + "no_secret_value_attestation", + "no_raw_payload_attestation", + "no_false_green_attestation", + "reviewer_outcome", + "followup_owner", + "not_authorization" + ], + "required_owner_fields": [ + "owner_role_or_team", + "routing_purpose", + "current_sender", + "target_chat_route", + "message_shape_contract", + "redaction_contract", + "formatter_convergence_decision", + "gateway_or_alertmanager_target", + "break_glass_fallback_decision", + "delivery_receipt_ref", + "dedup_or_fingerprint_plan", + "fallback_or_degraded_mode", + "migration_or_exception_reason", + "maintenance_window", + "rollback_owner", + "postcheck_evidence_ref", + "no_secret_value_attestation", + "no_raw_payload_attestation", + "no_false_green_attestation" + ], + "reviewer_checks": [ + "source_owner_request_current", + "source_migration_plan_current", + "owner_identity_present", + "decision_reason_present", + "affected_scope_matches_source", + "redacted_refs_only", + "no_secret_or_token_value", + "no_raw_message_payload", + "message_shape_contract_present", + "redaction_contract_present", + "formatter_convergence_explicit", + "gateway_or_alertmanager_target_valid", + "break_glass_fallback_explicit", + "delivery_receipt_metadata_only", + "dedup_or_fingerprint_present", + "maintenance_window_present", + "rollback_owner_present", + "postcheck_evidence_present", + "no_false_green_attested", + "migration_authorization_separate", + "counts_transition_safe", + "runtime_gate_stays_zero" + ], + "outcome_lanes": [ + "waiting_owner_response", + "quarantine_secret_or_raw_payload", + "reject_execution_request", + "request_owner_route_supplement", + "request_formatter_convergence_supplement", + "request_redaction_or_receipt_supplement", + "request_maintenance_or_rollback_supplement", + "ready_for_migration_review", + "owner_review_only_update", + "waiting_runtime_gate" + ], + "forbidden_payloads": [ + "bot_token_value", + "chat_secret_value", + "secret_hash", + "partial_token", + "masked_token", + "authorization_header", + "raw_message_payload", + "raw_workflow_log", + "raw_action_log", + "raw_screenshot_with_secret", + "internal_work_window_transcript", + "private_namespace", + "unredacted_internal_path", + "unredacted_private_ip" + ], + "blocked_actions": [ + "mark_owner_response_received_without_record", + "mark_owner_response_accepted_without_reviewer_record", + "send_telegram", + "call_bot_api", + "modify_workflow", + "modify_ops_script", + "refactor_api_sender", + "dispatch_workflow", + "trigger_cd", + "deploy_production", + "change_chat_route", + "change_bot_token", + "rotate_secret", + "read_secret_store", + "collect_secret_value", + "collect_secret_hash", + "collect_partial_token", + "collect_chat_id_secret", + "store_raw_message_payload", + "store_unredacted_log", + "store_internal_work_window_transcript", + "accept_cd_success_as_delivery_receipt", + "accept_route_200_as_notification_delivery", + "accept_ui_visible_as_notification_acceptance", + "accept_telegram_sent_without_delivery_receipt", + "skip_formatter_convergence", + "skip_redaction_contract", + "skip_dedup_or_fingerprint_review", + "skip_break_glass_fallback_review", + "authorize_migration", + "authorize_workflow_modification", + "authorize_script_modification", + "authorize_api_sender_refactor", + "open_runtime_gate", + "add_action_button" + ], + "not_authorization": true, + "request_sent": false, + "recipient_confirmed": false, + "audit_event_emitted": false, + "owner_response_received": false, + "owner_response_accepted": false, + "owner_response_rejected": false, + "owner_response_quarantined": false, + "supplement_requested": false, + "formatter_convergence_accepted": false, + "redaction_contract_accepted": false, + "delivery_receipt_accepted": false, + "break_glass_fallback_accepted": false, + "maintenance_window_accepted": false, + "rollback_owner_accepted": false, + "postcheck_evidence_accepted": false, + "dedup_or_fingerprint_accepted": false, + "no_false_green_accepted": false, + "direct_bot_api_migration_authorized": false, + "workflow_modification_authorized": false, + "script_modification_authorized": false, + "api_sender_refactor_authorized": false, + "telegram_send_authorized": false, + "bot_api_call_authorized": false, + "workflow_dispatch_authorized": false, + "production_deploy_authorized": false, + "secret_value_collection_allowed": false, + "raw_payload_storage_allowed": false, + "production_write_authorized": false, + "runtime_gate": false, + "action_buttons_allowed": false + } + ], + "operator_interpretation": [ + "此帳本只是 reviewer 驗收模板;owner response received / accepted 仍維持 0。", + "CD success、route 200、UI 可見或 Telegram sent 狀態本身都不是 delivery receipt。", + "workflow、script 與 API sender 收斂仍需獨立 runtime approval 與 change evidence。" + ] +} diff --git a/docs/workplans/2026-06-04-iwooos-security-governance-p0.md b/docs/workplans/2026-06-04-iwooos-security-governance-p0.md index 25b6bf30..57cec47a 100644 --- a/docs/workplans/2026-06-04-iwooos-security-governance-p0.md +++ b/docs/workplans/2026-06-04-iwooos-security-governance-p0.md @@ -82,7 +82,7 @@ | P0-3 | AwoooP 同步封包 | 100% | 已送至 AwoooP 平行工作 thread `019e9154-7d5e-7b72-85be-c9d97e43ecc9`;後續仍需每次推版前重新 fetch / fast-forward | 本文件、thread send readback、mirror checklist readback | | P0-4 | production live sanity 節點 | 100% | desktop / mobile / 展開區塊 / overflow / action href 檢查已完成 | Playwright production sanity 通過 | | P0-5 | LOGBOOK 與完成度更新 | 100% | D2 comments-only、D2 AIOps sample、D2 Code Review 候選分類與 D2 AwoooP Runs fallback 皆已回填;可見 / bundle 變更皆已補 local / production desktop + mobile smoke | `docs/LOGBOOK.md` readback | -| P0-6 | Telegram 監控告警 / 通知出口治理 | outbound 主鏈路 100%;靜音 / recurrence slice 88%;通知出口清冊 100%;owner request draft 100%;migration plan draft 100%;direct Bot API convergence 0% | Alertmanager 缺 project context、既有 approval 收斂告警靜音、AI 分析中重複告警靜音皆已修復並正式 smoke;`TelegramGateway` final-exit formatter 已完成 host / multi-signal 卡片化;本輪新增 direct Bot API egress inventory,固定 workflow 13、ops script 4、API direct 1,並聚成 11 份 owner request 草稿與 3 個遷移波次;後續需 owner response 後分批收斂,不得把 API formatter 完成誤判成全域完成 | API health、Telegram health、API pod Alertmanager smoke、production logs `converged_alert_recurrence_sent`、`telegram-notification-egress-inventory.snapshot.json`、`telegram-notification-egress-owner-request-draft.snapshot.json`、`telegram-notification-egress-migration-plan-draft.snapshot.json` | +| P0-6 | Telegram 監控告警 / 通知出口治理 | outbound 主鏈路 100%;靜音 / recurrence slice 88%;通知出口清冊 100%;owner request draft 100%;migration plan draft 100%;防新增旁路 guard 100%;owner response acceptance 100%;direct Bot API convergence 0% | Alertmanager 缺 project context、既有 approval 收斂告警靜音、AI 分析中重複告警靜音皆已修復並正式 smoke;`TelegramGateway` final-exit formatter 已完成 host / multi-signal 卡片化;direct Bot API egress inventory 固定 workflow 13、ops script 4、API direct 1,並聚成 11 份 owner request 草稿與 3 個遷移波次;本輪新增 no-new-bypass guard,覆蓋 `sendMessage` / `sendDocument` / `sendPhoto` / `sendMediaGroup` / `editMessageText` 等 9 類 Bot API method,並新增 11 份 owner response acceptance 候選;後續需 owner response 後分批收斂,不得把 API formatter 或防新增 guard 完成誤判成既有旁路已收斂 | API health、Telegram health、API pod Alertmanager smoke、production logs `converged_alert_recurrence_sent`、`telegram-notification-egress-inventory.snapshot.json`、`telegram-notification-egress-owner-request-draft.snapshot.json`、`telegram-notification-egress-migration-plan-draft.snapshot.json`、`telegram-notification-egress-no-new-bypass-guard.snapshot.json`、`telegram-notification-egress-owner-response-acceptance.snapshot.json` | | P0-7 | Telegram 批准後執行真相鏈止血 | 100% | no-action approval 不再顯示批准 / 執行中;可執行修復 approval 會寫入 `auto_repair_executions`、KM 與 verifier;下一步補 MCP evidence / PlayBook trust 產生真正修復候選 | 目標 pytest `125 passed`、py_compile、guard、production health、API / worker rollout、production pod classifier readback | | P0-8 | Telegram no-action 人工處置包與操作入口 | 100% | no-action 卡片已新增人工處置包、證據補齊清單、AwoooP 修復候選建立步驟、verifier / KM / PlayBook 回寫提醒,並改成 `處置包`、`重診`、`歷史`、`靜默`、`真相鏈`、`Runs` 鍵盤;舊訊息不 retroactive 改寫 | 目標 pytest `64 passed + 44 passed`、py_compile、guard、production health、API / worker rollout、production pod render / keyboard smoke | | P0-9 | MCP evidence -> PlayBook 修復候選產生 | D5 `88%`;Approvals ledger `100%`;Runs ledger desktop `100%`;Alerts ledger desktop / mobile `100%` | 已補 webhook fallback 先建立 incident,再收 MCP evidence、查 approved PlayBook、檢查 trust / command safety、產生 medium approval candidate 與 verifier plan;D1 追加通用兜底 PlayBook / 診斷型命令不可誤當修復、阻擋理由繁中化;D2 在缺候選時產生 `repair_candidate_draft_package_v1`、`playbook_draft_required`、下一步與必填欄位;D3 新增 `awooop_repair_candidate_draft_work_item_v1` read-only projection 與 Telegram `工作項目` deeplink;D4 讓 AwoooP Work Items 詳細呈現 PlayBook 草案處置板、必填欄位、阻擋原因、下一步、Runs / 審批連結;D5 新增 `repair_candidate_coverage_gap_v1`,讓 blocked result 帶出 coverage key、target kind、blocking stage、必收 MCP evidence refs、PlayBook template fields 與 runtime 0 / false 邊界;Approvals / Runs / Alerts 已新增 `資產沉澱` 欄或焦點矩陣,可直接看到 KM / PlayBook / 腳本 / 排程 / Verifier 的完成與卡點;下一步要補 Runs mobile smoke,並把同一總帳接到正式 Telegram 告警卡、Observability 與 Tenants,用真實告警驗證 approval -> execution -> verifier -> KM / PlayBook 回寫 | Approvals code `dafe5342` 已隨 deploy marker `42c08ece` 正式站 desktop / mobile smoke;Runs code `11c2b5d4` 已隨 deploy marker `8b6ab87c` 正式站 desktop DOM smoke;Alerts code `10cd6167` 已隨 deploy marker `d36d764a` 正式站 desktop / mobile DOM smoke;P2-407 API production readback `overall_completion_percent=100`;status-chain 後續仍必須看到 tool call、PlayBook id、risk gate、repair candidate、verifier plan | diff --git a/scripts/security/iwooos-config-control-guard.py b/scripts/security/iwooos-config-control-guard.py index 6e599abb..c6383d95 100644 --- a/scripts/security/iwooos-config-control-guard.py +++ b/scripts/security/iwooos-config-control-guard.py @@ -74,6 +74,8 @@ REQUIRED_CONTROL_DOCS = [ "docs/security/SECURITY-ASSET-CONTROL-LEDGER.md", "docs/security/AI-PROVIDER-OWNER-RESPONSE-ACCEPTANCE.md", "docs/security/AGENT-BOUNTY-OWNER-REQUEST-DRAFT.md", + "docs/security/TELEGRAM-NOTIFICATION-EGRESS-NO-NEW-BYPASS-GUARD.md", + "docs/security/TELEGRAM-NOTIFICATION-EGRESS-OWNER-RESPONSE-ACCEPTANCE.md", "docs/security/SECURITY-SUPPLY-CHAIN-CONTRACT-MANIFEST.md", ] diff --git a/scripts/security/security-mirror-progress-guard.py b/scripts/security/security-mirror-progress-guard.py index 066f0faa..3b4fe79a 100755 --- a/scripts/security/security-mirror-progress-guard.py +++ b/scripts/security/security-mirror-progress-guard.py @@ -231,12 +231,18 @@ def validate(root: Path) -> None: telegram_notification_egress_inventory = load_json( security_dir / "telegram-notification-egress-inventory.snapshot.json" ) + telegram_notification_egress_no_new_bypass_guard = load_json( + security_dir / "telegram-notification-egress-no-new-bypass-guard.snapshot.json" + ) telegram_notification_egress_owner_request_draft = load_json( security_dir / "telegram-notification-egress-owner-request-draft.snapshot.json" ) telegram_notification_egress_migration_plan_draft = load_json( security_dir / "telegram-notification-egress-migration-plan-draft.snapshot.json" ) + telegram_notification_egress_owner_response_acceptance = load_json( + security_dir / "telegram-notification-egress-owner-response-acceptance.snapshot.json" + ) public_runtime_config_change_evidence_acceptance = load_json( security_dir / "public-runtime-config-change-evidence-acceptance.snapshot.json" ) @@ -21894,6 +21900,238 @@ def validate(root: Path) -> None: f"telegram_notification_egress_migration_plan_draft.{item['migration_candidate_id']}.{false_key}", item[false_key], ) + assert_equal( + "telegram_notification_egress_no_new_bypass_guard.schema", + telegram_notification_egress_no_new_bypass_guard["schema_version"], + "telegram_notification_egress_no_new_bypass_guard_v1", + ) + assert_equal( + "telegram_notification_egress_no_new_bypass_guard.status", + telegram_notification_egress_no_new_bypass_guard["status"], + "pass_no_new_bypass", + ) + assert_equal( + "telegram_notification_egress_no_new_bypass_guard.mode", + telegram_notification_egress_no_new_bypass_guard["mode"], + "repo_source_scan_no_secret_value_no_telegram_send", + ) + expected_telegram_egress_no_new_bypass_summary = { + "source_direct_bot_api_call_count": 18, + "source_direct_bot_api_file_count": 11, + "baseline_signature_count": 18, + "current_direct_bot_api_call_count": 18, + "current_direct_bot_api_file_count": 11, + "guarded_method_count": 9, + "sendMessage_call_count": 18, + "sendDocument_call_count": 0, + "sendPhoto_call_count": 0, + "sendMediaGroup_call_count": 0, + "editMessageText_call_count": 0, + "other_guarded_method_call_count": 0, + "new_bypass_count": 0, + "new_bypass_file_count": 0, + "removed_baseline_call_count": 0, + "runtime_gate_count": 0, + "action_button_count": 0, + } + for key, expected in expected_telegram_egress_no_new_bypass_summary.items(): + assert_equal( + f"telegram_notification_egress_no_new_bypass_guard.summary.{key}", + telegram_notification_egress_no_new_bypass_guard["summary"][key], + expected, + ) + assert_equal( + "telegram_notification_egress_no_new_bypass_guard.current_paths", + [item["path"] for item in telegram_notification_egress_no_new_bypass_guard["current_direct_bot_api_calls"]], + [ + ".gitea/workflows/cd-dev.yaml", + ".gitea/workflows/cd-dev.yaml", + ".gitea/workflows/cd-dev.yaml", + ".gitea/workflows/cd.yaml", + ".gitea/workflows/cd.yaml", + ".gitea/workflows/cd.yaml", + ".gitea/workflows/cd.yaml", + ".gitea/workflows/cd.yaml", + ".gitea/workflows/code-review.yaml", + ".gitea/workflows/code-review.yaml", + ".gitea/workflows/deploy-alerts.yaml", + ".gitea/workflows/e2e-health.yaml", + ".gitea/workflows/run-migration.yml", + "apps/api/src/services/channel_hub.py", + "scripts/ops/backup-from-110.sh", + "scripts/ops/docker-health-monitor.sh", + "scripts/ops/dr-drill.sh", + "scripts/ops/pg-backup.sh", + ], + ) + assert_equal( + "telegram_notification_egress_no_new_bypass_guard.new_bypass_findings", + telegram_notification_egress_no_new_bypass_guard["new_bypass_findings"], + [], + ) + assert_equal( + "telegram_notification_egress_no_new_bypass_guard.removed_baseline_signatures", + telegram_notification_egress_no_new_bypass_guard["removed_baseline_signatures"], + [], + ) + for key, value in telegram_notification_egress_no_new_bypass_guard["execution_boundaries"].items(): + if key == "not_authorization": + assert_true(f"telegram_notification_egress_no_new_bypass_guard.execution_boundaries.{key}", value) + else: + assert_false(f"telegram_notification_egress_no_new_bypass_guard.execution_boundaries.{key}", value) + assert_equal( + "telegram_notification_egress_owner_response_acceptance.schema", + telegram_notification_egress_owner_response_acceptance["schema_version"], + "telegram_notification_egress_owner_response_acceptance_v1", + ) + assert_equal( + "telegram_notification_egress_owner_response_acceptance.status", + telegram_notification_egress_owner_response_acceptance["status"], + "owner_response_acceptance_ledger_ready_no_runtime_action", + ) + assert_equal( + "telegram_notification_egress_owner_response_acceptance.mode", + telegram_notification_egress_owner_response_acceptance["mode"], + "metadata_only_no_secret_value_no_telegram_send_no_workflow_script_api_change", + ) + expected_telegram_egress_owner_response_acceptance_summary = { + "source_request_draft_count": 11, + "source_migration_candidate_count": 11, + "source_direct_bot_api_call_count": 18, + "acceptance_candidate_count": 11, + "workflow_acceptance_candidate_count": 6, + "ops_script_acceptance_candidate_count": 4, + "api_direct_acceptance_candidate_count": 1, + "acceptance_field_count": 32, + "required_owner_field_count": 19, + "reviewer_check_count": 22, + "outcome_lane_count": 10, + "forbidden_payload_count": 14, + "blocked_action_count": 35, + "request_sent_count": 0, + "recipient_confirmed_count": 0, + "audit_event_emitted_count": 0, + "owner_response_received_count": 0, + "owner_response_accepted_count": 0, + "owner_response_rejected_count": 0, + "owner_response_quarantined_count": 0, + "supplement_requested_count": 0, + "formatter_convergence_accepted_count": 0, + "redaction_contract_accepted_count": 0, + "delivery_receipt_accepted_count": 0, + "break_glass_fallback_accepted_count": 0, + "maintenance_window_accepted_count": 0, + "rollback_owner_accepted_count": 0, + "postcheck_evidence_accepted_count": 0, + "dedup_or_fingerprint_accepted_count": 0, + "no_false_green_accepted_count": 0, + "direct_bot_api_migration_authorized_count": 0, + "workflow_modification_authorized_count": 0, + "script_modification_authorized_count": 0, + "api_sender_refactor_authorized_count": 0, + "telegram_send_authorized_count": 0, + "bot_api_call_authorized_count": 0, + "workflow_dispatch_authorized_count": 0, + "production_deploy_authorized_count": 0, + "secret_value_collection_allowed_count": 0, + "raw_payload_storage_allowed_count": 0, + "production_write_authorized_count": 0, + "runtime_gate_count": 0, + "action_button_count": 0, + } + for key, expected in expected_telegram_egress_owner_response_acceptance_summary.items(): + assert_equal( + f"telegram_notification_egress_owner_response_acceptance.summary.{key}", + telegram_notification_egress_owner_response_acceptance["summary"][key], + expected, + ) + assert_equal( + "telegram_notification_egress_owner_response_acceptance.source_paths", + [item["source_path"] for item in telegram_notification_egress_owner_response_acceptance["acceptance_candidates"]], + expected_telegram_egress_request_paths, + ) + for key, value in telegram_notification_egress_owner_response_acceptance["execution_boundaries"].items(): + if key == "not_authorization": + assert_true( + f"telegram_notification_egress_owner_response_acceptance.execution_boundaries.{key}", + value, + ) + else: + assert_false( + f"telegram_notification_egress_owner_response_acceptance.execution_boundaries.{key}", + value, + ) + for item in telegram_notification_egress_owner_response_acceptance["acceptance_candidates"]: + assert_equal( + f"telegram_notification_egress_owner_response_acceptance.{item['acceptance_candidate_id']}.acceptance_fields", + len(item["acceptance_fields"]), + 32, + ) + assert_equal( + f"telegram_notification_egress_owner_response_acceptance.{item['acceptance_candidate_id']}.required_owner_fields", + len(item["required_owner_fields"]), + 19, + ) + assert_equal( + f"telegram_notification_egress_owner_response_acceptance.{item['acceptance_candidate_id']}.reviewer_checks", + len(item["reviewer_checks"]), + 22, + ) + assert_equal( + f"telegram_notification_egress_owner_response_acceptance.{item['acceptance_candidate_id']}.outcome_lanes", + len(item["outcome_lanes"]), + 10, + ) + assert_equal( + f"telegram_notification_egress_owner_response_acceptance.{item['acceptance_candidate_id']}.forbidden_payloads", + len(item["forbidden_payloads"]), + 14, + ) + assert_equal( + f"telegram_notification_egress_owner_response_acceptance.{item['acceptance_candidate_id']}.blocked_actions", + len(item["blocked_actions"]), + 35, + ) + assert_true( + f"telegram_notification_egress_owner_response_acceptance.{item['acceptance_candidate_id']}.not_authorization", + item["not_authorization"], + ) + for false_key in [ + "request_sent", + "recipient_confirmed", + "audit_event_emitted", + "owner_response_received", + "owner_response_accepted", + "owner_response_rejected", + "owner_response_quarantined", + "supplement_requested", + "formatter_convergence_accepted", + "redaction_contract_accepted", + "delivery_receipt_accepted", + "break_glass_fallback_accepted", + "maintenance_window_accepted", + "rollback_owner_accepted", + "postcheck_evidence_accepted", + "dedup_or_fingerprint_accepted", + "no_false_green_accepted", + "direct_bot_api_migration_authorized", + "workflow_modification_authorized", + "script_modification_authorized", + "api_sender_refactor_authorized", + "telegram_send_authorized", + "bot_api_call_authorized", + "workflow_dispatch_authorized", + "production_deploy_authorized", + "secret_value_collection_allowed", + "raw_payload_storage_allowed", + "production_write_authorized", + "runtime_gate", + "action_buttons_allowed", + ]: + assert_false( + f"telegram_notification_egress_owner_response_acceptance.{item['acceptance_candidate_id']}.{false_key}", + item[false_key], + ) assert_equal( "public_runtime_config_change_evidence_acceptance.schema", public_runtime_config_change_evidence_acceptance["schema_version"], diff --git a/scripts/security/telegram-notification-egress-no-new-bypass-guard.py b/scripts/security/telegram-notification-egress-no-new-bypass-guard.py new file mode 100644 index 00000000..3d3d94be --- /dev/null +++ b/scripts/security/telegram-notification-egress-no-new-bypass-guard.py @@ -0,0 +1,282 @@ +#!/usr/bin/env python3 +"""檢查 Telegram 通知出口不可新增未登記 direct Bot API 旁路。 + +本 guard 只掃描 repo 原始碼與 committed snapshot,不讀 secret、不呼叫 +Telegram、不修改 workflow / script / API sender。既有 direct send 仍是待 +owner response 的基線;任何新增或變形的 direct Bot API endpoint 都必須先 +進 inventory / owner request / migration plan,而不是直接合併。 +""" + +from __future__ import annotations + +import argparse +import json +import re +import subprocess +import sys +from collections import Counter +from datetime import datetime, timedelta, timezone +from pathlib import Path +from typing import Any + + +TAIPEI = timezone(timedelta(hours=8)) + +SOURCE_SNAPSHOT = Path("docs/security/telegram-notification-egress-inventory.snapshot.json") +SCAN_ROOTS = ( + Path(".gitea/workflows"), + Path("scripts/ops"), + Path("scripts/ci"), + Path("apps/api/src"), +) +SCAN_SUFFIXES = {".py", ".sh", ".js", ".yml", ".yaml"} +GUARDED_BOT_METHODS = ( + "sendMessage", + "sendDocument", + "sendPhoto", + "sendMediaGroup", + "editMessageText", + "sendAnimation", + "sendVideo", + "sendAudio", + "sendVoice", +) + +BOT_ENDPOINT_RE = re.compile( + r"api\.telegram\.org/bot.*?/(?P" + + "|".join(re.escape(method) for method in GUARDED_BOT_METHODS) + + r")\b", + re.IGNORECASE, +) +SECRET_INTERPOLATION_RE = re.compile(r"\$\{\{\s*secrets\.[^}]+\}\}") +BOT_TOKEN_URL_RE = re.compile( + r"api\.telegram\.org/bot.*?/(?P" + + "|".join(re.escape(method) for method in GUARDED_BOT_METHODS) + + r")\b", + re.IGNORECASE, +) + + +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 iter_scannable_files(root: Path) -> list[Path]: + files: list[Path] = [] + for scan_root in SCAN_ROOTS: + absolute_root = root / scan_root + if not absolute_root.exists(): + continue + for path in absolute_root.rglob("*"): + if path.is_file() and path.suffix in SCAN_SUFFIXES: + files.append(path) + return sorted(files) + + +def sanitize_excerpt(line: str) -> str: + excerpt = line.strip() + excerpt = SECRET_INTERPOLATION_RE.sub("${{ secrets. }}", excerpt) + excerpt = BOT_TOKEN_URL_RE.sub( + lambda match: f"api.telegram.org/bot/{match.group('method')}", + excerpt, + ) + return excerpt[:180] + + +def signature(path: str, method: str, sanitized_excerpt: str) -> str: + return f"{path}::{method.lower()}::{sanitized_excerpt}" + + +def load_source_snapshot(root: Path) -> dict[str, Any]: + snapshot_path = root / SOURCE_SNAPSHOT + return json.loads(snapshot_path.read_text(encoding="utf-8")) + + +def build_baseline(source_snapshot: dict[str, Any]) -> Counter[str]: + baseline: Counter[str] = Counter() + for item in source_snapshot.get("direct_bot_api_calls", []): + excerpt = item.get("sanitized_excerpt", "") + match = BOT_ENDPOINT_RE.search(excerpt) + method = match.group("method") if match else "sendMessage" + baseline[signature(item["path"], method, excerpt)] += 1 + return baseline + + +def scan_current_direct_endpoints(root: Path) -> list[dict[str, Any]]: + findings: list[dict[str, Any]] = [] + for path in iter_scannable_files(root): + relative_path = path.relative_to(root).as_posix() + text = path.read_text(encoding="utf-8", errors="replace") + for line_number, line in enumerate(text.splitlines(), start=1): + for match in BOT_ENDPOINT_RE.finditer(line): + method = match.group("method") + sanitized = sanitize_excerpt(line) + findings.append( + { + "path": relative_path, + "line": line_number, + "method": method, + "sanitized_excerpt": sanitized, + "signature": signature(relative_path, method, sanitized), + } + ) + return findings + + +def method_counts(findings: list[dict[str, Any]]) -> dict[str, int]: + counts = {method: 0 for method in GUARDED_BOT_METHODS} + for item in findings: + for method in GUARDED_BOT_METHODS: + if item["method"].lower() == method.lower(): + counts[method] += 1 + break + return counts + + +def build_report(root: Path, generated_at: str | None = None) -> dict[str, Any]: + generated = generated_at or datetime.now(TAIPEI).isoformat(timespec="seconds") + source_snapshot = load_source_snapshot(root) + baseline = build_baseline(source_snapshot) + current_findings = scan_current_direct_endpoints(root) + remaining_baseline = baseline.copy() + new_bypass_findings: list[dict[str, Any]] = [] + + for item in current_findings: + item_signature = item["signature"] + if remaining_baseline[item_signature] > 0: + remaining_baseline[item_signature] -= 1 + continue + new_bypass_findings.append(item) + + removed_baseline_signatures = [ + {"signature": item_signature, "removed_count": count} + for item_signature, count in sorted(remaining_baseline.items()) + if count > 0 + ] + current_files = sorted({item["path"] for item in current_findings}) + new_bypass_files = sorted({item["path"] for item in new_bypass_findings}) + counts_by_method = method_counts(current_findings) + source_summary = source_snapshot["summary"] + + return { + "schema_version": "telegram_notification_egress_no_new_bypass_guard_v1", + "generated_at": generated, + "git_commit": git_short_sha(root), + "status": "pass_no_new_bypass" if not new_bypass_findings else "blocked_new_bypass_detected", + "mode": "repo_source_scan_no_secret_value_no_telegram_send", + "source_snapshot": SOURCE_SNAPSHOT.as_posix(), + "guarded_roots": [path.as_posix() for path in SCAN_ROOTS], + "guarded_bot_methods": list(GUARDED_BOT_METHODS), + "summary": { + "source_direct_bot_api_call_count": source_summary["direct_bot_api_call_count"], + "source_direct_bot_api_file_count": source_summary["direct_bot_api_file_count"], + "baseline_signature_count": sum(baseline.values()), + "current_direct_bot_api_call_count": len(current_findings), + "current_direct_bot_api_file_count": len(current_files), + "guarded_method_count": len(GUARDED_BOT_METHODS), + "sendMessage_call_count": counts_by_method["sendMessage"], + "sendDocument_call_count": counts_by_method["sendDocument"], + "sendPhoto_call_count": counts_by_method["sendPhoto"], + "sendMediaGroup_call_count": counts_by_method["sendMediaGroup"], + "editMessageText_call_count": counts_by_method["editMessageText"], + "other_guarded_method_call_count": sum( + count + for method, count in counts_by_method.items() + if method + not in { + "sendMessage", + "sendDocument", + "sendPhoto", + "sendMediaGroup", + "editMessageText", + } + ), + "new_bypass_count": len(new_bypass_findings), + "new_bypass_file_count": len(new_bypass_files), + "removed_baseline_call_count": sum(item["removed_count"] for item in removed_baseline_signatures), + "runtime_gate_count": 0, + "action_button_count": 0, + }, + "execution_boundaries": { + "runtime_execution_authorized": False, + "telegram_send_authorized": False, + "bot_api_call_authorized": False, + "workflow_modification_authorized": False, + "script_modification_authorized": False, + "api_sender_refactor_authorized": False, + "secret_value_collection_allowed": False, + "secret_hash_collection_allowed": False, + "partial_token_collection_allowed": False, + "chat_route_change_authorized": False, + "bot_token_change_authorized": False, + "raw_payload_storage_allowed": False, + "production_write_authorized": False, + "action_buttons_allowed": False, + "not_authorization": True, + }, + "current_direct_bot_api_calls": current_findings, + "new_bypass_findings": new_bypass_findings, + "removed_baseline_signatures": removed_baseline_signatures, + "operator_interpretation": [ + "new_bypass_count 維持 0 才代表沒有新增未登記 Telegram Bot API 直送旁路。", + "既有 18 個 sendMessage 旁路仍是待 owner response 的基線,不代表已批准或已收斂。", + "sendDocument / sendPhoto / sendMediaGroup 等附件型出口若出現在 repo source,會被視為新增旁路並阻擋。", + "本 guard 只讀 repo source 與 committed snapshot,不送 Telegram、不讀 Bot token、不修改 workflow / script / API sender。", + ], + } + + +def validate(root: Path) -> None: + report = build_report(root) + errors: list[str] = [] + if report["summary"]["new_bypass_count"]: + for item in report["new_bypass_findings"]: + errors.append( + f"{item['path']}:{item['line']}: 新增未登記 Telegram Bot API 旁路 {item['method']}" + ) + + if errors: + raise SystemExit( + "BLOCKED telegram notification egress no-new-bypass guard:\n" + + "\n".join(f"- {error}" for error in errors) + ) + + +def main() -> None: + parser = argparse.ArgumentParser(description="檢查 Telegram 通知出口不可新增未登記 direct Bot API 旁路") + parser.add_argument("--root", default=".", help="repository root") + parser.add_argument("--output", help="寫出 JSON 報告") + parser.add_argument("--generated-at", help="固定 generated_at 時間,供 committed snapshot 使用") + args = parser.parse_args() + + root = Path(args.root).resolve() + report = build_report(root, args.generated_at) + if args.output: + output = Path(args.output) + output.parent.mkdir(parents=True, exist_ok=True) + output.write_text(json.dumps(report, ensure_ascii=False, indent=2, sort_keys=True) + "\n", encoding="utf-8") + + validate(root) + summary = report["summary"] + print( + "TELEGRAM_NOTIFICATION_EGRESS_NO_NEW_BYPASS_GUARD_OK " + f"current={summary['current_direct_bot_api_call_count']} " + f"baseline={summary['baseline_signature_count']} " + f"new={summary['new_bypass_count']} " + f"sendDocument={summary['sendDocument_call_count']} " + f"runtime_gate={summary['runtime_gate_count']}" + ) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/security/telegram-notification-egress-owner-response-acceptance.py b/scripts/security/telegram-notification-egress-owner-response-acceptance.py new file mode 100644 index 00000000..345baedb --- /dev/null +++ b/scripts/security/telegram-notification-egress-owner-response-acceptance.py @@ -0,0 +1,388 @@ +#!/usr/bin/env python3 +"""建立 Telegram 通知出口 owner response 驗收帳本。 + +此帳本把 Telegram 通知出口 owner request 草稿與 migration plan 草稿轉成 +reviewer 可驗收的候選項。它不送 Telegram、不呼叫 Bot API、不讀 secret, +也不修改 workflow、script、API sender、runtime config 或 production。 +""" + +from __future__ import annotations + +import argparse +import json +import subprocess +import sys +from datetime import datetime, timedelta, timezone +from pathlib import Path +from typing import Any + + +TAIPEI = timezone(timedelta(hours=8)) + +OWNER_REQUEST_SNAPSHOT = Path("docs/security/telegram-notification-egress-owner-request-draft.snapshot.json") +MIGRATION_PLAN_SNAPSHOT = Path("docs/security/telegram-notification-egress-migration-plan-draft.snapshot.json") + +ACCEPTANCE_FIELDS = [ + "acceptance_candidate_id", + "source_request_draft_id", + "source_migration_candidate_id", + "source_path", + "surface_kind", + "direct_call_count", + "proposed_wave", + "proposed_target", + "owner_response_ref", + "owner_role_or_team", + "decision", + "decision_reason", + "affected_scope", + "redacted_evidence_refs", + "message_shape_contract_ref", + "redaction_contract_ref", + "formatter_convergence_decision", + "gateway_or_alertmanager_target", + "break_glass_fallback_decision", + "delivery_receipt_ref", + "dedup_or_fingerprint_plan", + "fallback_or_degraded_mode", + "migration_or_exception_reason", + "maintenance_window", + "rollback_owner", + "postcheck_evidence_ref", + "no_secret_value_attestation", + "no_raw_payload_attestation", + "no_false_green_attestation", + "reviewer_outcome", + "followup_owner", + "not_authorization", +] + +REVIEWER_CHECKS = [ + "source_owner_request_current", + "source_migration_plan_current", + "owner_identity_present", + "decision_reason_present", + "affected_scope_matches_source", + "redacted_refs_only", + "no_secret_or_token_value", + "no_raw_message_payload", + "message_shape_contract_present", + "redaction_contract_present", + "formatter_convergence_explicit", + "gateway_or_alertmanager_target_valid", + "break_glass_fallback_explicit", + "delivery_receipt_metadata_only", + "dedup_or_fingerprint_present", + "maintenance_window_present", + "rollback_owner_present", + "postcheck_evidence_present", + "no_false_green_attested", + "migration_authorization_separate", + "counts_transition_safe", + "runtime_gate_stays_zero", +] + +OUTCOME_LANES = [ + "waiting_owner_response", + "quarantine_secret_or_raw_payload", + "reject_execution_request", + "request_owner_route_supplement", + "request_formatter_convergence_supplement", + "request_redaction_or_receipt_supplement", + "request_maintenance_or_rollback_supplement", + "ready_for_migration_review", + "owner_review_only_update", + "waiting_runtime_gate", +] + +FORBIDDEN_PAYLOADS = [ + "bot_token_value", + "chat_secret_value", + "secret_hash", + "partial_token", + "masked_token", + "authorization_header", + "raw_message_payload", + "raw_workflow_log", + "raw_action_log", + "raw_screenshot_with_secret", + "internal_work_window_transcript", + "private_namespace", + "unredacted_internal_path", + "unredacted_private_ip", +] + +BLOCKED_ACTIONS = [ + "mark_owner_response_received_without_record", + "mark_owner_response_accepted_without_reviewer_record", + "send_telegram", + "call_bot_api", + "modify_workflow", + "modify_ops_script", + "refactor_api_sender", + "dispatch_workflow", + "trigger_cd", + "deploy_production", + "change_chat_route", + "change_bot_token", + "rotate_secret", + "read_secret_store", + "collect_secret_value", + "collect_secret_hash", + "collect_partial_token", + "collect_chat_id_secret", + "store_raw_message_payload", + "store_unredacted_log", + "store_internal_work_window_transcript", + "accept_cd_success_as_delivery_receipt", + "accept_route_200_as_notification_delivery", + "accept_ui_visible_as_notification_acceptance", + "accept_telegram_sent_without_delivery_receipt", + "skip_formatter_convergence", + "skip_redaction_contract", + "skip_dedup_or_fingerprint_review", + "skip_break_glass_fallback_review", + "authorize_migration", + "authorize_workflow_modification", + "authorize_script_modification", + "authorize_api_sender_refactor", + "open_runtime_gate", + "add_action_button", +] + + +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 build_candidate(request: dict[str, Any], migration: dict[str, Any]) -> dict[str, Any]: + return { + "acceptance_candidate_id": f"telegram_notification_egress_owner_response_acceptance:{request['source_path']}", + "status": "waiting_owner_response", + "source_request_draft_id": request["request_draft_id"], + "source_migration_candidate_id": migration["migration_candidate_id"], + "source_path": request["source_path"], + "surface_kind": request["surface_kind"], + "direct_call_count": request["direct_call_count"], + "line_refs": request["line_refs"], + "line_hash_refs": request["line_hash_refs"], + "proposed_wave": migration["proposed_wave"], + "proposed_target": migration["proposed_target"], + "proposed_change_summary": migration["proposed_change_summary"], + "owner_response_ref": None, + "owner_role_or_team": "pending_owner_response", + "decision": "pending_owner_response", + "decision_reason": "pending_owner_response", + "affected_scope": "pending_owner_response", + "redacted_evidence_refs": [], + "message_shape_contract_ref": None, + "redaction_contract_ref": None, + "formatter_convergence_decision": "pending_owner_response", + "gateway_or_alertmanager_target": "pending_owner_response", + "break_glass_fallback_decision": "pending_owner_response", + "delivery_receipt_ref": None, + "dedup_or_fingerprint_plan": "pending_owner_response", + "fallback_or_degraded_mode": "pending_owner_response", + "migration_or_exception_reason": "pending_owner_response", + "maintenance_window": "pending_owner_response", + "rollback_owner": "pending_owner_response", + "postcheck_evidence_ref": None, + "no_secret_value_attestation": "pending_owner_response", + "no_raw_payload_attestation": "pending_owner_response", + "no_false_green_attestation": "pending_owner_response", + "reviewer_outcome": "waiting_owner_response", + "followup_owner": "pending_owner_response", + "acceptance_fields": ACCEPTANCE_FIELDS, + "required_owner_fields": request["required_owner_fields"], + "reviewer_checks": REVIEWER_CHECKS, + "outcome_lanes": OUTCOME_LANES, + "forbidden_payloads": FORBIDDEN_PAYLOADS, + "blocked_actions": BLOCKED_ACTIONS, + "not_authorization": True, + "request_sent": False, + "recipient_confirmed": False, + "audit_event_emitted": False, + "owner_response_received": False, + "owner_response_accepted": False, + "owner_response_rejected": False, + "owner_response_quarantined": False, + "supplement_requested": False, + "formatter_convergence_accepted": False, + "redaction_contract_accepted": False, + "delivery_receipt_accepted": False, + "break_glass_fallback_accepted": False, + "maintenance_window_accepted": False, + "rollback_owner_accepted": False, + "postcheck_evidence_accepted": False, + "dedup_or_fingerprint_accepted": False, + "no_false_green_accepted": False, + "direct_bot_api_migration_authorized": False, + "workflow_modification_authorized": False, + "script_modification_authorized": False, + "api_sender_refactor_authorized": False, + "telegram_send_authorized": False, + "bot_api_call_authorized": False, + "workflow_dispatch_authorized": False, + "production_deploy_authorized": False, + "secret_value_collection_allowed": False, + "raw_payload_storage_allowed": False, + "production_write_authorized": False, + "runtime_gate": False, + "action_buttons_allowed": False, + } + + +def build_report(root: Path, generated_at: str | None = None) -> dict[str, Any]: + generated = generated_at or datetime.now(TAIPEI).isoformat(timespec="seconds") + owner_request = load_json(root / OWNER_REQUEST_SNAPSHOT) + migration_plan = load_json(root / MIGRATION_PLAN_SNAPSHOT) + migration_by_request_id = { + item["source_request_draft_id"]: item for item in migration_plan["migration_candidates"] + } + + candidates = [ + build_candidate(request, migration_by_request_id[request["request_draft_id"]]) + for request in owner_request["request_drafts"] + ] + workflow = [item for item in candidates if item["surface_kind"] == "gitea_workflow_direct_bot_api"] + ops = [item for item in candidates if item["surface_kind"] == "ops_script_direct_bot_api"] + api = [item for item in candidates if item["surface_kind"] == "api_direct_bot_api"] + + return { + "schema_version": "telegram_notification_egress_owner_response_acceptance_v1", + "generated_at": generated, + "git_commit": git_short_sha(root), + "status": "owner_response_acceptance_ledger_ready_no_runtime_action", + "mode": "metadata_only_no_secret_value_no_telegram_send_no_workflow_script_api_change", + "source_owner_request_snapshot": OWNER_REQUEST_SNAPSHOT.as_posix(), + "source_owner_request_schema_version": owner_request["schema_version"], + "source_owner_request_status": owner_request["status"], + "source_migration_plan_snapshot": MIGRATION_PLAN_SNAPSHOT.as_posix(), + "source_migration_plan_schema_version": migration_plan["schema_version"], + "source_migration_plan_status": migration_plan["status"], + "summary": { + "source_request_draft_count": owner_request["summary"]["request_draft_count"], + "source_migration_candidate_count": migration_plan["summary"]["migration_candidate_count"], + "source_direct_bot_api_call_count": owner_request["summary"]["source_direct_bot_api_call_count"], + "acceptance_candidate_count": len(candidates), + "workflow_acceptance_candidate_count": len(workflow), + "ops_script_acceptance_candidate_count": len(ops), + "api_direct_acceptance_candidate_count": len(api), + "acceptance_field_count": len(ACCEPTANCE_FIELDS), + "required_owner_field_count": len(owner_request["request_drafts"][0]["required_owner_fields"]), + "reviewer_check_count": len(REVIEWER_CHECKS), + "outcome_lane_count": len(OUTCOME_LANES), + "forbidden_payload_count": len(FORBIDDEN_PAYLOADS), + "blocked_action_count": len(BLOCKED_ACTIONS), + "request_sent_count": 0, + "recipient_confirmed_count": 0, + "audit_event_emitted_count": 0, + "owner_response_received_count": 0, + "owner_response_accepted_count": 0, + "owner_response_rejected_count": 0, + "owner_response_quarantined_count": 0, + "supplement_requested_count": 0, + "formatter_convergence_accepted_count": 0, + "redaction_contract_accepted_count": 0, + "delivery_receipt_accepted_count": 0, + "break_glass_fallback_accepted_count": 0, + "maintenance_window_accepted_count": 0, + "rollback_owner_accepted_count": 0, + "postcheck_evidence_accepted_count": 0, + "dedup_or_fingerprint_accepted_count": 0, + "no_false_green_accepted_count": 0, + "direct_bot_api_migration_authorized_count": 0, + "workflow_modification_authorized_count": 0, + "script_modification_authorized_count": 0, + "api_sender_refactor_authorized_count": 0, + "telegram_send_authorized_count": 0, + "bot_api_call_authorized_count": 0, + "workflow_dispatch_authorized_count": 0, + "production_deploy_authorized_count": 0, + "secret_value_collection_allowed_count": 0, + "raw_payload_storage_allowed_count": 0, + "production_write_authorized_count": 0, + "runtime_gate_count": 0, + "action_button_count": 0, + }, + "execution_boundaries": { + "runtime_execution_authorized": False, + "owner_response_mark_received_authorized": False, + "owner_response_mark_accepted_authorized": False, + "direct_bot_api_migration_authorized": False, + "workflow_modification_authorized": False, + "script_modification_authorized": False, + "api_sender_refactor_authorized": False, + "telegram_send_authorized": False, + "bot_api_call_authorized": False, + "workflow_dispatch_authorized": False, + "production_deploy_authorized": False, + "secret_value_collection_allowed": False, + "raw_payload_storage_allowed": False, + "production_write_authorized": False, + "action_buttons_allowed": False, + "not_authorization": True, + }, + "acceptance_candidates": candidates, + "operator_interpretation": [ + "此帳本只是 reviewer 驗收模板;owner response received / accepted 仍維持 0。", + "CD success、route 200、UI 可見或 Telegram sent 狀態本身都不是 delivery receipt。", + "workflow、script 與 API sender 收斂仍需獨立 runtime approval 與 change evidence。", + ], + } + + +def validate(root: Path) -> None: + report = build_report(root) + summary = report["summary"] + if summary["acceptance_candidate_count"] != summary["source_request_draft_count"]: + raise SystemExit("BLOCKED telegram egress owner response acceptance: candidate/request count mismatch") + if summary["acceptance_candidate_count"] != summary["source_migration_candidate_count"]: + raise SystemExit("BLOCKED telegram egress owner response acceptance: candidate/migration count mismatch") + if summary["runtime_gate_count"] != 0: + raise SystemExit("BLOCKED telegram egress owner response acceptance: runtime gate must stay 0") + + +def main() -> None: + parser = argparse.ArgumentParser(description="建立 Telegram 通知出口 owner response 驗收帳本") + parser.add_argument("--root", default=".", help="repository root") + parser.add_argument("--output", help="write JSON snapshot") + parser.add_argument("--generated-at", help="fixed generated_at timestamp") + args = parser.parse_args() + + root = Path(args.root).resolve() + report = build_report(root, args.generated_at) + payload = json.dumps(report, ensure_ascii=False, indent=2) + "\n" + if args.output: + Path(args.output).write_text(payload, encoding="utf-8") + else: + sys.stdout.write(payload) + + print( + "TELEGRAM_NOTIFICATION_EGRESS_OWNER_RESPONSE_ACCEPTANCE_OK " + f"candidates={report['summary']['acceptance_candidate_count']} " + f"workflow={report['summary']['workflow_acceptance_candidate_count']} " + f"ops={report['summary']['ops_script_acceptance_candidate_count']} " + f"api={report['summary']['api_direct_acceptance_candidate_count']} " + f"accepted={report['summary']['owner_response_accepted_count']} " + f"runtime_gate={report['summary']['runtime_gate_count']}", + file=sys.stderr, + ) + + +if __name__ == "__main__": + main()