diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index ab00f3294..68a95bca0 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -17838,7 +17838,7 @@ "items": { "nginxGateway": { "title": "Nginx 公開入口", - "body": "最優先控管公開入口、上游服務、管理路由、WebSocket、ACME 與 TLS;手動或緊急變更回補已要求變更意圖、事前批准或緊急破窗理由、路由健康影響、回滾驗證與變更後監控窗口,但 live conf、rendered diff、nginx -t、route smoke、維護窗口與回滾負責人仍為 0。" + "body": "最優先控管公開入口、上游服務、管理路由、WebSocket、ACME 與 TLS;手動或緊急變更回補後已新增事故後回讀計畫,固定 3 個 gateway 範圍、30 個必填欄位、28 個審查檢查項、10 條結果分流與 41 類禁止動作。目前 readback received / accepted、live conf、rendered diff、nginx -t、reload、route smoke、DNS / TLS probe、certbot renew、維護窗口、回滾負責人與 runtime gate 仍全部為 0。" }, "dnsTls": { "title": "DNS / TLS / certbot", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index ab00f3294..68a95bca0 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -17838,7 +17838,7 @@ "items": { "nginxGateway": { "title": "Nginx 公開入口", - "body": "最優先控管公開入口、上游服務、管理路由、WebSocket、ACME 與 TLS;手動或緊急變更回補已要求變更意圖、事前批准或緊急破窗理由、路由健康影響、回滾驗證與變更後監控窗口,但 live conf、rendered diff、nginx -t、route smoke、維護窗口與回滾負責人仍為 0。" + "body": "最優先控管公開入口、上游服務、管理路由、WebSocket、ACME 與 TLS;手動或緊急變更回補後已新增事故後回讀計畫,固定 3 個 gateway 範圍、30 個必填欄位、28 個審查檢查項、10 條結果分流與 41 類禁止動作。目前 readback received / accepted、live conf、rendered diff、nginx -t、reload、route smoke、DNS / TLS probe、certbot renew、維護窗口、回滾負責人與 runtime gate 仍全部為 0。" }, "dnsTls": { "title": "DNS / TLS / certbot", diff --git a/apps/web/src/app/[locale]/iwooos/page.tsx b/apps/web/src/app/[locale]/iwooos/page.tsx index 743adb310..cbf807f72 100644 --- a/apps/web/src/app/[locale]/iwooos/page.tsx +++ b/apps/web/src/app/[locale]/iwooos/page.tsx @@ -2111,7 +2111,7 @@ const highValueConfigControlCoverageBoundaries = [ 'high_value_config_control_coverage_owner_response_accepted_count=0', 'high_value_config_control_coverage_runtime_gate_count=0', 'high_value_config_control_coverage_action_button_count=0', - 'nginx_public_gateway_coverage_percent=90', + 'nginx_public_gateway_coverage_percent=92', 'public_gateway_owner_response_acceptance_required_owner_response_field_count=22', 'public_gateway_owner_response_acceptance_reviewer_check_count=22', 'public_gateway_owner_response_acceptance_outcome_lane_count=8', @@ -2224,6 +2224,18 @@ const highValueConfigControlCoverageBoundaries = [ 'public_gateway_rendered_diff_acceptance_candidate_count=3', 'public_gateway_rendered_diff_acceptance_reviewer_check_count=15', 'public_gateway_rendered_diff_acceptance_runtime_gate_count=0', + 'public_gateway_post_incident_readback_plan_candidate_count=3', + 'public_gateway_post_incident_readback_plan_c0_candidate_count=2', + 'public_gateway_post_incident_readback_plan_required_readback_field_count=30', + 'public_gateway_post_incident_readback_plan_reviewer_check_count=28', + 'public_gateway_post_incident_readback_plan_outcome_lane_count=10', + 'public_gateway_post_incident_readback_plan_blocked_action_count=41', + 'public_gateway_post_incident_readback_plan_post_incident_readback_received_count=0', + 'public_gateway_post_incident_readback_plan_post_incident_readback_accepted_count=0', + 'public_gateway_post_incident_readback_plan_nginx_test_authorized_count=0', + 'public_gateway_post_incident_readback_plan_nginx_reload_authorized_count=0', + 'public_gateway_post_incident_readback_plan_route_smoke_authorized_count=0', + 'public_gateway_post_incident_readback_plan_runtime_gate_count=0', 'public_gateway_preflight_inventory_source_config_count=3', 'public_gateway_preflight_inventory_route_impact_count=14', 'public_gateway_preflight_inventory_preflight_gate_count=12', @@ -2466,7 +2478,7 @@ const criticalConfigPrioritySummary = [ ] as const const criticalConfigPriorityItems: CriticalConfigPriorityItem[] = [ - { key: 'nginxGateway', rank: 'P0-1', state: '90% / live 0', icon: Server, tone: 'warn' }, + { key: 'nginxGateway', rank: 'P0-1', state: '92% / readback 0', icon: Server, tone: 'warn' }, { key: 'dnsTls', rank: 'P0-2', state: '78% / probe 0', icon: Route, tone: 'warn' }, { key: 'k8sArgocd', rank: 'P0-3', state: '66% / readback 0', icon: Cloud, tone: 'warn' }, { key: 'workflowSecret', rank: 'P0-4', state: '68% / 72%', icon: GitBranch, tone: 'warn' }, @@ -2487,6 +2499,9 @@ const criticalConfigPriorityBoundaries = [ 'nginx_public_gateway_live_conf_evidence_received=false', 'nginx_public_gateway_rendered_diff_accepted=false', 'nginx_public_gateway_rendered_diff_acceptance_candidate_count=3', + 'nginx_public_gateway_post_incident_readback_plan_candidate_count=3', + 'nginx_public_gateway_post_incident_readback_plan_blocked_action_count=41', + 'nginx_public_gateway_post_incident_readback_plan_runtime_gate_count=0', 'nginx_public_gateway_nginx_test_evidence_count=0', 'public_admin_api_runtime_config_coverage_percent=66', 'public_frontend_sensitive_surface_guard_file_count=225', diff --git a/docs/schemas/iwooos_posture_projection_v1.schema.json b/docs/schemas/iwooos_posture_projection_v1.schema.json index 33b948bc9..78ccace9f 100644 --- a/docs/schemas/iwooos_posture_projection_v1.schema.json +++ b/docs/schemas/iwooos_posture_projection_v1.schema.json @@ -483,7 +483,46 @@ "k8s_argocd_post_incident_readback_plan_runtime_gate_count", "k8s_argocd_post_incident_readback_plan_action_button_count", "k8s_argocd_post_incident_readback_plan_coverage_percent_after_readback_plan", - "k8s_production_gitops_coverage_percent" + "k8s_production_gitops_coverage_percent", + "nginx_public_gateway_coverage_percent", + "public_gateway_post_incident_readback_plan_candidate_count", + "public_gateway_post_incident_readback_plan_c0_candidate_count", + "public_gateway_post_incident_readback_plan_write_capable_candidate_count", + "public_gateway_post_incident_readback_plan_required_readback_field_count", + "public_gateway_post_incident_readback_plan_reviewer_check_count", + "public_gateway_post_incident_readback_plan_outcome_lane_count", + "public_gateway_post_incident_readback_plan_blocked_action_count", + "public_gateway_post_incident_readback_plan_post_incident_readback_received_count", + "public_gateway_post_incident_readback_plan_post_incident_readback_accepted_count", + "public_gateway_post_incident_readback_plan_actor_attribution_accepted_count", + "public_gateway_post_incident_readback_plan_before_after_route_state_accepted_count", + "public_gateway_post_incident_readback_plan_source_live_diff_state_accepted_count", + "public_gateway_post_incident_readback_plan_nginx_test_readback_accepted_count", + "public_gateway_post_incident_readback_plan_nginx_reload_or_no_reload_accepted_count", + "public_gateway_post_incident_readback_plan_route_smoke_readback_accepted_count", + "public_gateway_post_incident_readback_plan_tls_acme_readback_accepted_count", + "public_gateway_post_incident_readback_plan_websocket_readback_accepted_count", + "public_gateway_post_incident_readback_plan_upstream_health_accepted_count", + "public_gateway_post_incident_readback_plan_public_admin_api_route_impact_accepted_count", + "public_gateway_post_incident_readback_plan_ai_provider_impact_accepted_count", + "public_gateway_post_incident_readback_plan_monitoring_alert_accepted_count", + "public_gateway_post_incident_readback_plan_operator_notification_accepted_count", + "public_gateway_post_incident_readback_plan_cross_project_sync_accepted_count", + "public_gateway_post_incident_readback_plan_rollback_validation_accepted_count", + "public_gateway_post_incident_readback_plan_post_change_monitoring_accepted_count", + "public_gateway_post_incident_readback_plan_postcheck_readback_accepted_count", + "public_gateway_post_incident_readback_plan_recurrence_guard_accepted_count", + "public_gateway_post_incident_readback_plan_no_false_green_accepted_count", + "public_gateway_post_incident_readback_plan_host_live_conf_read_authorized_count", + "public_gateway_post_incident_readback_plan_nginx_test_authorized_count", + "public_gateway_post_incident_readback_plan_nginx_reload_authorized_count", + "public_gateway_post_incident_readback_plan_public_gateway_reload_authorized_count", + "public_gateway_post_incident_readback_plan_route_smoke_authorized_count", + "public_gateway_post_incident_readback_plan_dns_tls_probe_authorized_count", + "public_gateway_post_incident_readback_plan_certbot_renew_authorized_count", + "public_gateway_post_incident_readback_plan_runtime_gate_count", + "public_gateway_post_incident_readback_plan_action_button_count", + "public_gateway_post_incident_readback_plan_coverage_percent_after_readback_plan" ], "properties": { "route_path": { @@ -2121,6 +2160,162 @@ "k8s_production_gitops_coverage_percent": { "type": "integer", "const": 66 + }, + "nginx_public_gateway_coverage_percent": { + "type": "integer", + "const": 92 + }, + "public_gateway_post_incident_readback_plan_candidate_count": { + "type": "integer", + "const": 3 + }, + "public_gateway_post_incident_readback_plan_c0_candidate_count": { + "type": "integer", + "const": 2 + }, + "public_gateway_post_incident_readback_plan_write_capable_candidate_count": { + "type": "integer", + "const": 3 + }, + "public_gateway_post_incident_readback_plan_required_readback_field_count": { + "type": "integer", + "const": 30 + }, + "public_gateway_post_incident_readback_plan_reviewer_check_count": { + "type": "integer", + "const": 28 + }, + "public_gateway_post_incident_readback_plan_outcome_lane_count": { + "type": "integer", + "const": 10 + }, + "public_gateway_post_incident_readback_plan_blocked_action_count": { + "type": "integer", + "const": 41 + }, + "public_gateway_post_incident_readback_plan_post_incident_readback_received_count": { + "type": "integer", + "const": 0 + }, + "public_gateway_post_incident_readback_plan_post_incident_readback_accepted_count": { + "type": "integer", + "const": 0 + }, + "public_gateway_post_incident_readback_plan_actor_attribution_accepted_count": { + "type": "integer", + "const": 0 + }, + "public_gateway_post_incident_readback_plan_before_after_route_state_accepted_count": { + "type": "integer", + "const": 0 + }, + "public_gateway_post_incident_readback_plan_source_live_diff_state_accepted_count": { + "type": "integer", + "const": 0 + }, + "public_gateway_post_incident_readback_plan_nginx_test_readback_accepted_count": { + "type": "integer", + "const": 0 + }, + "public_gateway_post_incident_readback_plan_nginx_reload_or_no_reload_accepted_count": { + "type": "integer", + "const": 0 + }, + "public_gateway_post_incident_readback_plan_route_smoke_readback_accepted_count": { + "type": "integer", + "const": 0 + }, + "public_gateway_post_incident_readback_plan_tls_acme_readback_accepted_count": { + "type": "integer", + "const": 0 + }, + "public_gateway_post_incident_readback_plan_websocket_readback_accepted_count": { + "type": "integer", + "const": 0 + }, + "public_gateway_post_incident_readback_plan_upstream_health_accepted_count": { + "type": "integer", + "const": 0 + }, + "public_gateway_post_incident_readback_plan_public_admin_api_route_impact_accepted_count": { + "type": "integer", + "const": 0 + }, + "public_gateway_post_incident_readback_plan_ai_provider_impact_accepted_count": { + "type": "integer", + "const": 0 + }, + "public_gateway_post_incident_readback_plan_monitoring_alert_accepted_count": { + "type": "integer", + "const": 0 + }, + "public_gateway_post_incident_readback_plan_operator_notification_accepted_count": { + "type": "integer", + "const": 0 + }, + "public_gateway_post_incident_readback_plan_cross_project_sync_accepted_count": { + "type": "integer", + "const": 0 + }, + "public_gateway_post_incident_readback_plan_rollback_validation_accepted_count": { + "type": "integer", + "const": 0 + }, + "public_gateway_post_incident_readback_plan_post_change_monitoring_accepted_count": { + "type": "integer", + "const": 0 + }, + "public_gateway_post_incident_readback_plan_postcheck_readback_accepted_count": { + "type": "integer", + "const": 0 + }, + "public_gateway_post_incident_readback_plan_recurrence_guard_accepted_count": { + "type": "integer", + "const": 0 + }, + "public_gateway_post_incident_readback_plan_no_false_green_accepted_count": { + "type": "integer", + "const": 0 + }, + "public_gateway_post_incident_readback_plan_host_live_conf_read_authorized_count": { + "type": "integer", + "const": 0 + }, + "public_gateway_post_incident_readback_plan_nginx_test_authorized_count": { + "type": "integer", + "const": 0 + }, + "public_gateway_post_incident_readback_plan_nginx_reload_authorized_count": { + "type": "integer", + "const": 0 + }, + "public_gateway_post_incident_readback_plan_public_gateway_reload_authorized_count": { + "type": "integer", + "const": 0 + }, + "public_gateway_post_incident_readback_plan_route_smoke_authorized_count": { + "type": "integer", + "const": 0 + }, + "public_gateway_post_incident_readback_plan_dns_tls_probe_authorized_count": { + "type": "integer", + "const": 0 + }, + "public_gateway_post_incident_readback_plan_certbot_renew_authorized_count": { + "type": "integer", + "const": 0 + }, + "public_gateway_post_incident_readback_plan_runtime_gate_count": { + "type": "integer", + "const": 0 + }, + "public_gateway_post_incident_readback_plan_action_button_count": { + "type": "integer", + "const": 0 + }, + "public_gateway_post_incident_readback_plan_coverage_percent_after_readback_plan": { + "type": "integer", + "const": 92 } }, "additionalProperties": false diff --git a/docs/security/HIGH-VALUE-CONFIG-CONTROL-COVERAGE.md b/docs/security/HIGH-VALUE-CONFIG-CONTROL-COVERAGE.md index f0b841256..4759701ee 100644 --- a/docs/security/HIGH-VALUE-CONFIG-CONTROL-COVERAGE.md +++ b/docs/security/HIGH-VALUE-CONFIG-CONTROL-COVERAGE.md @@ -72,6 +72,8 @@ 固定數字為 `diff_acceptance_candidate_count=3`、`c0_diff_acceptance_candidate_count=2`、`c1_diff_acceptance_candidate_count=1`、`diff_acceptance_field_count=25`、`required_evidence_field_count=14`、`reviewer_check_count=15`、`outcome_lane_count=8`、`blocked_action_count=22`、`rendered_diff_accepted_count=0`、`nginx_test_evidence_accepted_count=0`、`route_smoke_result_accepted_count=0`、`runtime_gate_count=0`。此更新只讓 `nginx_public_gateway` 從 `86%` 推進到 `88%`;owner response accepted、redacted live conf accepted、rendered diff accepted、`nginx -t`、reload、route smoke、DNS / TLS probe、certbot renew、production write、runtime gate 與 action button 仍全部為 `0 / false`。 +2026-06-15 再新增 `docs/security/PUBLIC-GATEWAY-POST-INCIDENT-READBACK-PLAN.md` 與 `docs/security/public-gateway-post-incident-readback-plan.snapshot.json`,把同一批三份 Public Gateway config 轉成 Nginx / gateway 事故後回讀計畫。固定 `readback_candidate_count=3`、`c0_readback_candidate_count=2`、`c1_readback_candidate_count=1`、`write_capable_readback_candidate_count=3`、`required_readback_field_count=30`、`reviewer_check_count=28`、`outcome_lane_count=10`、`blocked_action_count=41`,讓 `nginx_public_gateway` 從 `90%` 推進到 `92%`,高價值配置平均維持 `71%`,需 live evidence 類別仍為 `9`。此更新只補上 actor、變更時間窗、change intent / break-glass、改前改後 route、source-to-live diff、`nginx -t` readback、reload / no-reload、route smoke、TLS / ACME、WebSocket、upstream、AI provider、monitoring、跨專案同步、回滾、防再發與 no-false-green 的脫敏回讀欄位;readback received / accepted、live conf read、`nginx -t`、reload、route smoke、DNS / TLS probe、certbot renew、host write、runtime gate 與 action button 仍全部為 `0 / false`。 + ## 1.5 2026-06-14 DNS / TLS / certbot owner response acceptance 只讀帳本 已新增 `docs/security/DOMAIN-TLS-CERTBOT-OWNER-RESPONSE-ACCEPTANCE.md` 與 `docs/security/domain-tls-certbot-owner-response-acceptance.snapshot.json`,將 4 份 DNS / TLS / certbot owner confirmation request 轉成 metadata-only owner response acceptance 只讀帳本。 @@ -267,6 +269,7 @@ python3 scripts/security/iwooos-config-control-guard.py --root . | Backup / restore / escrow owner response acceptance backfill | `100%` | 已將 `backup_restore_owner_response_acceptance_v1` 補強為 38 個 candidate、27 個 write-capable、23 個 owner 必填欄位、22 個 reviewer checks、9 條 outcome lanes、31 類 blocked action;backup / restore / credential 成熟度 `62% -> 64%` | | Monitoring / alerting / observability owner response acceptance backfill | `100%` | 已將 `monitoring_owner_response_acceptance_v1` 補強為 60 個 candidate、11 個 write-capable、38 個 acceptance fields、23 個 reviewer checks、12 條 outcome lanes、34 類 blocked action;monitoring / alerting / observability 成熟度 `66% -> 68%` | | AI provider / model routing owner response acceptance | `100%` | 已新增 `ai_provider_owner_response_acceptance_v1`,8 個 candidate、5 個 write-capable、24 個 owner 必填欄位、24 個 reviewer checks、10 條 outcome lanes、38 類 blocked action;AI provider / model routing 成熟度 `60% -> 64%` | +| Public Gateway / Nginx post-incident readback plan | `100%` | 已新增 `public_gateway_post_incident_readback_plan_v1`,3 個事故回讀 candidate、2 個 C0、30 個必填欄位、28 個 reviewer checks、10 條 outcome lanes、41 類 blocked action;成熟度 `90% -> 92%`,readback received / accepted、`nginx -t`、reload、route smoke、DNS / TLS、certbot 與 runtime gate 仍為 `0` | | SSH / firewall / network post-incident readback plan | `100%` | 已新增 `ssh_network_post_incident_readback_plan_v1`,14 個事故回讀 candidate、6 個 write-capable、24 個必填欄位、24 個 reviewer checks、10 條 outcome lanes、34 類 blocked action;成熟度 `62% -> 64%`,readback received / accepted、actor、before / after、impact、通知、同步、恢復、防再發與 runtime gate 仍為 `0` | | Docker / systemd / host service post-incident readback plan | `100%` | 已新增 `host_service_post_incident_readback_plan_v1`,9 個事故回讀 candidate、3 個 write-capable、28 個必填欄位、28 個 reviewer checks、10 條 outcome lanes、41 類 blocked action;成熟度 `62% -> 64%`,readback received / accepted、Docker daemon、compose、systemd、failed unit、port binding、public/admin route、AI provider、monitoring、同步、防再發與 runtime gate 仍為 `0` | | K8s / ArgoCD post-incident readback plan | `100%` | 已新增 `k8s_argocd_post_incident_readback_plan_v1`,4 個事故回讀 candidate、3 個 C0、4 個 write-capable、31 個必填欄位、28 個 reviewer checks、10 條 outcome lanes、41 類 blocked action;成熟度 `64% -> 66%`,readback received / accepted、ArgoCD health / sync、Pending、image pull / scheduling、drift、impact、同步、防再發與 runtime gate 仍為 `0` | @@ -324,6 +327,8 @@ python3 scripts/security/iwooos-config-control-guard.py --root . 2026-06-14 再新增 `public_gateway_rendered_diff_acceptance_v1`,把 3 份 public gateway config 轉成 `diff_acceptance_candidate_count=3`、`c0_diff_acceptance_candidate_count=2`、`required_evidence_field_count=14`、`reviewer_check_count=15`、`outcome_lane_count=8`、`blocked_action_count=22` 的 rendered diff evidence acceptance 只讀帳本。此更新只讓 `nginx_public_gateway` 從 `86%` 推進到 `88%`;owner response accepted、rendered diff accepted、owner-provided `nginx -t` evidence accepted、route smoke evidence accepted、reload、DNS / TLS probe、certbot renew、runtime gate 與 action button 仍全部為 `0`。 +2026-06-15 再新增 `public_gateway_post_incident_readback_plan_v1`,把 3 份 public gateway config 轉成 `readback_candidate_count=3`、`c0_readback_candidate_count=2`、`required_readback_field_count=30`、`reviewer_check_count=28`、`outcome_lane_count=10`、`blocked_action_count=41` 的事故後回讀計畫。此更新讓 `nginx_public_gateway` 從 `90%` 推進到 `92%`;readback received / accepted、actor attribution accepted、before / after route state accepted、source-live diff accepted、owner-provided `nginx -t` readback accepted、reload / no-reload accepted、route smoke readback accepted、TLS / ACME accepted、WebSocket accepted、upstream accepted、AI provider impact accepted、monitoring accepted、cross-project sync accepted、no-false-green accepted、`nginx -t`、reload、route smoke、DNS / TLS probe、certbot renew、host write、runtime gate 與 action button 仍全部為 `0`。 + ## 13. P0 DNS / TLS / certbot owner response acceptance 更新 `domain_tls_certbot_owner_response_acceptance_v1` 已把 4 份 DNS / TLS / certbot owner confirmation request 轉成 owner response acceptance 只讀帳本。四份候選全部為 `C0`,固定 `acceptance_candidate_count=4`、`required_owner_response_field_count=13`、`reviewer_check_count=13`、`outcome_lane_count=7`、`blocked_action_count=20`,讓 `dns_tls_certbot` 從 `74%` 推進到 `78%`。 diff --git a/docs/security/IWOOOS-CONFIG-CONTROL-INVENTORY.md b/docs/security/IWOOOS-CONFIG-CONTROL-INVENTORY.md index c62f7fe03..ea4838c56 100644 --- a/docs/security/IWOOOS-CONFIG-CONTROL-INVENTORY.md +++ b/docs/security/IWOOOS-CONFIG-CONTROL-INVENTORY.md @@ -123,6 +123,8 @@ 2026-06-14 再新增 `public_gateway_rendered_diff_acceptance_v1`,把 3 份 Public Gateway config 轉成 rendered diff evidence acceptance 只讀帳本。固定 `candidates=3`、`c0=2`、`required_evidence_fields=14`、`reviewer_checks=15`、`outcome_lanes=8`、`blocked_actions=22`,讓 Nginx public gateway 類別成熟度從 `86%` 推進到 `88%`。此更新只表示未來 owner-provided rendered diff、`nginx -t` readback、route smoke evidence、TLS / ACME impact、maintenance window、rollback owner 與 post-check evidence 有收件驗收規則;owner response accepted、rendered diff accepted、nginx test evidence accepted、route smoke evidence accepted、reload、DNS / TLS probe、certbot renew、host write、runtime gate 仍全部為 `0 / false`。 +2026-06-15 再新增 `public_gateway_post_incident_readback_plan_v1`,把同一批 3 份 Public Gateway config 補成事故後回讀計畫。固定 `readback_candidates=3`、`c0=2`、`c1=1`、`write_capable=3`、`readback_fields=36`、`required_readback_fields=30`、`reviewer_checks=28`、`outcome_lanes=10`、`blocked_actions=41`,讓 Nginx public gateway 類別成熟度從 `90%` 推進到 `92%`。此更新只表示 actor、變更時間窗、change intent / break-glass、改前改後 route、source-to-live diff、`nginx -t` readback、reload / no-reload、route smoke、TLS / ACME、WebSocket、upstream、AI provider、monitoring、跨專案同步、回滾、防再發與 no-false-green 已有脫敏回讀欄位與審查分流;readback received / accepted、live conf read、`nginx -t`、reload、route smoke、DNS / TLS probe、certbot renew、host write、runtime gate 仍全部為 `0 / false`。 + ### 0.7 2026-06-14 DNS / TLS / certbot owner response acceptance 只讀帳本 `domain_tls_certbot_owner_response_acceptance_v1` 已把 4 份 DNS / TLS / certbot owner confirmation request 轉成 owner response acceptance 只讀帳本。固定 `candidates=4`、`c0=4`、`owner_fields=13`、`reviewer_checks=13`、`outcome_lanes=7`、`blocked_actions=20`,讓 DNS / TLS / certbot 類別成熟度從 `74%` 推進到 `78%`。 @@ -157,7 +159,7 @@ | 優先 | 項目 | 現況 | 風險 | 本階段處置 | |------|------|------|------|------------| -| P0 | Nginx public gateway | 已有 Ansible source-of-truth、repo-only drift detector、DNS / TLS 清冊、public gateway preflight Gate 與 owner response acceptance 只讀帳本,但尚缺 owner-provided live conf、rendered diff、`nginx -t` evidence、route smoke、maintenance window 與 rollback owner | 手改 live conf 會讓公開網站、admin route、TLS、API、WebSocket 或 ACME 被改壞,且不易追責 | 已新增高價值配置 Hard Rule、drift detector、preflight 清冊與 owner response acceptance 帳本;仍不得 SSH 或 reload | +| P0 | Nginx public gateway | 已有 Ansible source-of-truth、repo-only drift detector、DNS / TLS 清冊、public gateway preflight Gate、owner response acceptance、rendered diff evidence acceptance 與事故後回讀計畫,但尚缺 owner-provided live conf、rendered diff、`nginx -t` evidence、route smoke、maintenance window 與 rollback owner | 手改 live conf 會讓公開網站、admin route、TLS、API、WebSocket 或 ACME 被改壞,且不易追責 | 已新增高價值配置 Hard Rule、drift detector、preflight 清冊、owner response acceptance 與 post-incident readback plan;仍不得 SSH 或 reload | | P0 | `docs/runbooks/SECRETS-MANAGEMENT.md` Gitea token 範例 | 文件內存在可疑 token 範例 | 可能造成 Gitea API 權限外洩或複製貼上事故 | 已改為 owner-managed token env,不保存 value | | P0 | `k8s/monitoring/docker-compose-110.yml` Grafana admin 密碼 | compose 內有固定密碼常值 | 若被當作 live 密碼或複製使用,會造成監控後台弱控管 | 已改為 `GRAFANA_ADMIN_PASSWORD` owner secret store 注入 | | P0 | `ops/monitoring/discover_docker.py` SSH host key 驗證 | 仍使用關閉 host key 驗證的參數 | MITM 或錯誤主機信任風險 | 已改為 `BatchMode=yes` + `accept-new`;後續升級 pinned known_hosts | @@ -246,6 +248,7 @@ Nginx 是目前必須最先資安控管的配置,原因是它同時控制公 10. Public / admin / API / frontend runtime config 變更必須先通過 affected route、auth boundary、API readback、CORS diff、frontend env diff、i18n redaction、desktop / mobile smoke、sensitive string scan、rollback owner 與 post-check evidence;前台不得顯示 raw owner namespace、repo slug、內部狀態碼、內部協作內容或未脫敏截圖。 11. 高價值配置控管必須能由 `scripts/security/iwooos-config-control-guard.py` 集中驗證;guard 通過只代表 repo snapshot 基線完整,不代表 owner response、live evidence、reload、restart、workflow / secret / runner 變更、backup / restore、scan、runtime 或 deploy 授權。 12. Package / Docker 供應鏈修復前必須先通過 owner policy gate;Python lockfile、requirements pinning、Docker digest pinning、compose digest、CVE / license / SBOM 只能先收脫敏 owner metadata,不得因 baseline 或 gate 通過而 install、upgrade、rewrite lockfile、pull / build / push image、登入 registry、修改 workflow、部署或開 runtime gate。 +13. Public Gateway / Nginx 事故後回讀只能收脫敏 evidence ref,不得保存 raw live conf、完整 diff、secret、憑證內容、cookie、token 或未脫敏截圖;不得把 route 200、Nginx active、dashboard up、CD success 或 UI 可見當成事故已驗收。 ## 5. 需要調整的既有規範 @@ -268,6 +271,7 @@ Nginx 是目前必須最先資安控管的配置,原因是它同時控制公 | source-control P0 止血 | `100%` | 已清掉本波掃到的 token 範例、Grafana 密碼常值與 SSH host key 關閉 | | repo-only Nginx drift detector | `100%` | 已新增 `scripts/security/nginx-config-drift-detector.py` 與 repo source-of-truth snapshot | | public gateway preflight 清冊 | `100%` | 已新增 `public_gateway_preflight_inventory_v1`,固定 12 個 reload / route change 前置 Gate;成熟度 `78% -> 84%` | +| Public Gateway / Nginx 事故後回讀計畫 | `100%` | 已新增 `public_gateway_post_incident_readback_plan_v1`,固定 3 份事故後回讀 candidate、2 份 C0、30 個必填欄位、28 個 reviewer checks、10 條 outcome lanes、41 類 blocked action;成熟度 `90% -> 92%`,readback accepted、`nginx -t`、reload、route smoke 與 runtime gate 仍為 `0` | | 高價值配置變更分類 Gate | `100%` | 已新增 `scripts/security/high-value-config-change-gate.py`,可用 git diff 或手動檔案分類 C0/C1/C2/C3 並列出 owner / rollback / evidence / 驗證欄位 | | DNS / TLS / certbot owner response acceptance | `100%` | 已新增 `domain_tls_certbot_owner_response_acceptance_v1`,4 個 C0 candidate、13 個 owner 必填欄位、13 個 reviewer checks、7 條 outcome lanes、20 類 blocked action;成熟度 `74% -> 78%` | | owner response evidence JSON 欄位檢查 | `70%` | Gate 可檢查必要欄位與 false flags;尚未接正式收件 API 或 AwoooP queue | diff --git a/docs/security/IWOOOS-POSTURE-PROJECTION.md b/docs/security/IWOOOS-POSTURE-PROJECTION.md index acda81f59..95126196c 100644 --- a/docs/security/IWOOOS-POSTURE-PROJECTION.md +++ b/docs/security/IWOOOS-POSTURE-PROJECTION.md @@ -3,7 +3,7 @@ | 項目 | 內容 | |------|------| | 日期 | 2026-06-15 | -| 狀態 | 草案;P0 governance 總帳已建立;高價值配置 Owner Packet count sync 與 K8s / ArgoCD 事故後回讀投影已完成 | +| 狀態 | 草案;P0 governance 總帳已建立;高價值配置 Owner Packet count sync、K8s / ArgoCD 與 Public Gateway / Nginx 事故後回讀投影已完成 | | Schema | `docs/schemas/iwooos_posture_projection_v1.schema.json` | | Snapshot | `docs/security/iwooos-posture-projection.snapshot.json` | | 模式 | `mirror_only` | @@ -29,6 +29,12 @@ 此同步只代表前端可以顯示 K8s / ArgoCD 事故後回讀計畫與邊界;`post_incident_readback_received_count`、`post_incident_readback_accepted_count`、`argocd_api_read_authorized_count`、`argocd_sync_authorized_count`、`kubectl_action_authorized_count`、`runtime_gate_count` 與 `action_button_count` 仍全部維持 `0`。不得把 ArgoCD `Synced`、route 200、Pod Running、CD success 或 smoke pass 視為資安驗收。 +## 1.3 2026-06-15 Public Gateway / Nginx 事故後回讀投影 + +`public_gateway_post_incident_readback_plan_v1` 已投影到 `iwooos-posture-projection.snapshot.json` 與前台 marker。固定 `candidate_count=3`、`c0_candidate_count=2`、`write_capable_candidate_count=3`、`required_readback_field_count=30`、`reviewer_check_count=28`、`outcome_lane_count=10`、`blocked_action_count=41`,並讓 `nginx_public_gateway_coverage_percent=92`。 + +此同步只代表前端可以顯示 Nginx / Public Gateway 事故後回讀計畫與邊界;`post_incident_readback_received_count`、`post_incident_readback_accepted_count`、`host_live_conf_read_authorized_count`、`nginx_test_authorized_count`、`nginx_reload_authorized_count`、`route_smoke_authorized_count`、`dns_tls_probe_authorized_count`、`certbot_renew_authorized_count`、`runtime_gate_count` 與 `action_button_count` 仍全部維持 `0`。不得把 route `200`、Nginx active、dashboard up、CD success 或 UI 可見視為資安驗收。 + ## 2. 來源 IwoooS 首版只讀取或對齊以下已提交 evidence: @@ -39,6 +45,7 @@ IwoooS 首版只讀取或對齊以下已提交 evidence: | `security_rollout_policy_v1` | 7 條 low-friction non-blocking lanes | | `source_control_owner_response_validation_rollup_v1` | owner response 仍為 0、S4.9 下一個收件候選 | | `source_control_primary_readiness_gate_v1` | GitHub primary readiness 仍為 0、候選 repo 與切換前置缺口 | +| `public_gateway_post_incident_readback_plan_v1` | Public Gateway / Nginx 事故後回讀計畫、92% 子項成熟度、30 個必填欄位、41 類 blocked action、runtime gate 0 | | `k8s_argocd_post_incident_readback_plan_v1` | K8s / ArgoCD 事故後回讀計畫、66% 子項成熟度、31 個必填欄位、41 類 blocked action、runtime gate 0 | | `kali_integration_status_v1` | Kali 112 observe-only 整合態勢 | | `vibework_iwooos_onboarding_handoff_v1` | VibeWork repo / product / surface / owner / evidence refs / 獨立產品邊界只讀 handoff | diff --git a/docs/security/PUBLIC-GATEWAY-POST-INCIDENT-READBACK-PLAN.md b/docs/security/PUBLIC-GATEWAY-POST-INCIDENT-READBACK-PLAN.md new file mode 100644 index 000000000..f4b8b48b1 --- /dev/null +++ b/docs/security/PUBLIC-GATEWAY-POST-INCIDENT-READBACK-PLAN.md @@ -0,0 +1,164 @@ +# IwoooS Public Gateway / Nginx 事故後回讀計畫 + +| 項目 | 內容 | +|------|------| +| 日期 | 2026-06-15 | +| 狀態 | `post_incident_readback_plan_ready_no_runtime_action` | +| 工具 | `scripts/security/public-gateway-post-incident-readback-plan.py` | +| 輸入 | `docs/security/public-gateway-rendered-diff-acceptance.snapshot.json` | +| Snapshot | `docs/security/public-gateway-post-incident-readback-plan.snapshot.json` | +| runtime gate | `0` | + +## 1. 目的 + +Nginx public gateway 是公開網站、API、Webhook、WebSocket、TLS、ACME 與 AI provider proxy 的共同入口。若 live Nginx conf 被手動或緊急變更,IwoooS 不能只看 route `200`、Nginx active、dashboard up、CD success 或 UI 可見就判定事故已驗收。 + +本文件補上事故後回讀計畫:未來若發生 gateway / Nginx 變更或事故,必須用脫敏 evidence ref 回填 actor、時間窗、變更意圖或 break-glass、改前改後 route 狀態、source-to-live diff、`nginx -t` readback、reload / no-reload 判定、route smoke、TLS / ACME、WebSocket、upstream、AI provider、monitoring、跨專案同步、回滾、防再發與 no-false-green attestation。 + +這個計畫不是 live conf received、不是 rendered diff accepted、不是 `nginx -t` 授權、不是 Nginx reload、不是 route smoke、不是 DNS / TLS probe、不是 certbot renew、不是 host write,也不是 production write 或 runtime gate。 + +## 2. 摘要 + +| 指標 | 值 | 說明 | +|------|----|------| +| readback candidate count | `3` | 對應三份 public gateway config | +| C0 readback candidate count | `2` | 188 all sites、188 internal tools HTTPS | +| C1 readback candidate count | `1` | 110 Ollama proxy | +| readback field count | `36` | 每份事故後回讀欄位 | +| required readback field count | `30` | 未來 owner evidence 必填欄位 | +| reviewer check count | `28` | 補件、隔離、拒收、review 與 no-false-green 檢查 | +| outcome lane count | `10` | 從等待回讀包到等待 runtime gate | +| blocked action count | `41` | 不可直接執行或不可誤讀的動作 | +| post-incident readback received / accepted | `0 / 0` | 尚未收到 / 驗收 | +| nginx test / reload / route smoke authorized | `0 / 0 / 0` | 尚未授權且未執行 | +| runtime gate / action button | `0 / 0` | 未開啟 | +| coverage after readback plan | `92%` | 只代表只讀控管成熟度,不代表可 reload | + +## 3. Readback 欄位 + +| 欄位 | 內容規則 | +|------|----------| +| `gateway_incident_or_change_ref` | incident、change、ticket 或 maintenance ref | +| `actor_attribution_ref` | actor role / team,不接受匿名操作 | +| `change_time_window_ref` | 變更 / 事故時間窗 | +| `change_intent_or_break_glass_ref` | change intent 或 break-glass reason;break-glass 不等於事前批准 | +| `before_route_state_ref` | 改前 route / upstream / TLS / WebSocket 摘要 ref | +| `after_route_state_ref` | 改後 route / upstream / TLS / WebSocket 摘要 ref | +| `source_live_diff_state_ref` | source-to-live diff 狀態 ref,不保存 raw conf 或完整 diff | +| `nginx_test_readback_ref` | owner-provided `nginx -t` readback ref;本工具不得執行 | +| `nginx_reload_or_no_reload_ref` | 是否 reload 與原因 ref | +| `route_smoke_readback_ref` | affected routes、status、TLS、WebSocket、ACME 或不適用原因 | +| `tls_acme_readback_ref` | TLS / ACME impact ref | +| `websocket_readback_ref` | WebSocket / streaming route impact ref | +| `upstream_health_ref` | upstream health、port、dependency impact ref | +| `public_admin_api_route_impact_ref` | public / admin / API / callback / webhook 影響 ref | +| `ai_provider_impact_ref` | Ollama、AI provider、model route 或 proxy 影響 ref | +| `monitoring_alert_ref` | monitoring、alert、incident 或 dashboard ref | +| `operator_notification_ref` | 已通知受影響產品 / owner / Session 的脫敏 ref | +| `cross_project_sync_ref` | 跨專案同步 ref | +| `rollback_validation_ref` | rollback owner 與回滾後驗證 ref | +| `post_change_monitoring_ref` | post-change monitoring window ref | +| `recovery_or_still_degraded_ref` | 已恢復或仍 degraded 的狀態 ref | +| `postcheck_readback_ref` | 獨立 post-check readback ref | +| `recurrence_guard_ref` | 防再發 guard、change freeze、owner review 或 automation block | +| `maintenance_window` | 後續 runtime action 的維護窗口或明確禁止窗口 | +| `rollback_owner` | rollback owner / team | +| `followup_owner` | 補件或下一階段 owner | +| `not_approval` | 必須為 `true` | + +## 4. Reviewer Checks + +| Check | 規則 | +|-------|------| +| `source_diff_acceptance_current` | 來源 rendered diff acceptance snapshot 必須是目前版本 | +| `incident_or_change_ref_present` | 必須有 incident / change ref | +| `actor_attribution_present` | 必須標示 actor role / team | +| `change_time_window_present` | 必須有變更 / 事故時間窗 | +| `intent_or_break_glass_present` | 必須有 intent 或 break-glass reason | +| `before_after_route_state_present` | 必須有 before / after route state | +| `source_live_diff_state_present` | 必須有 source-to-live diff 狀態 | +| `nginx_test_readback_is_owner_provided` | `nginx -t` 只能是 owner-provided readback | +| `reload_or_no_reload_called_out` | 必須說明是否 reload | +| `route_smoke_readback_present` | 必須列 route smoke 或不適用原因 | +| `tls_acme_readback_present` | TLS / ACME 不可被 route 200 取代 | +| `websocket_readback_present` | WebSocket / streaming route 需獨立回讀 | +| `upstream_health_present` | upstream health / port / dependency 影響必須列 ref | +| `public_admin_api_route_impact_present` | public / admin / API route 影響需列 ref | +| `ai_provider_impact_present` | AI provider / proxy 影響需列 ref | +| `monitoring_alert_present` | 需有 monitoring / alert / incident ref | +| `operator_notification_present` | 需有通知 ref | +| `cross_project_sync_present` | 需有跨專案同步 ref | +| `rollback_validation_present` | 需有 rollback validation ref | +| `post_change_monitoring_present` | 需有變更後監控窗口 | +| `recovery_or_still_degraded_present` | 需說明恢復或仍 degraded | +| `postcheck_independent` | post-check 必須獨立於原操作人與 UI | +| `recurrence_guard_present` | 需有防再發措施 | +| `maintenance_window_present` | 後續 runtime action 需有維護窗口 | +| `no_false_green` | 不得把 route 200、Nginx active、dashboard up、CD success 或 UI 可見當驗收 | +| `raw_payload_absent` | 不得保存 raw conf、完整 diff、private key、憑證內容、cookie、token 或未脫敏截圖 | +| `runtime_stays_zero` | 不得觸發 `nginx -t`、reload、route smoke、DNS / TLS probe、certbot renew 或 host write | +| `counts_transition_safe` | accepted count 只能由 reviewer record 更新,且不得同時開 runtime gate | + +## 5. Outcome Lanes + +| Lane | 意義 | +|------|------| +| `waiting_post_incident_readback` | 尚未收到事故回讀包 | +| `request_actor_or_time_supplement` | 缺 actor、時間窗、intent 或 break-glass reason | +| `request_route_state_supplement` | 缺 before / after route、upstream、WebSocket、TLS / ACME 或 route smoke | +| `request_diff_test_supplement` | 缺 source-live diff、`nginx -t` readback、reload / no-reload 或 rollback validation | +| `request_dependency_supplement` | 缺 AI provider、monitoring、public/admin/API route 或跨專案影響 | +| `quarantine_raw_payload` | raw conf、完整 diff、secret、憑證、cookie、token 或未脫敏截圖只能隔離 | +| `reject_false_green_claim` | route 200、Nginx active、dashboard up、CD success 或 UI 可見不能當驗收 | +| `ready_for_gateway_post_incident_review` | metadata 合格後進 reviewer review | +| `recurrence_guard_backfill_required` | 需補防再發 guard | +| `waiting_runtime_gate` | 即使 readback accepted,runtime gate 仍需獨立人工批准 | + +## 6. Blocked Actions + +| Action | 邊界 | +|--------|------| +| `ssh_read_live_nginx_conf` | 未授權不得讀 live Nginx conf | +| `store_raw_live_conf` | 不得保存 raw live conf | +| `store_full_rendered_diff_payload` | 不得保存完整 diff payload | +| `collect_secret_value` | 不得收 secret value | +| `collect_private_key` | 不得收 private key | +| `collect_certificate_body` | 不得收完整憑證內容 | +| `run_nginx_test` | 不得由本計畫執行 `nginx -t` | +| `reload_nginx` / `restart_nginx` | 不得由本計畫執行 | +| `change_public_route` / `change_admin_route` / `change_api_route` | 不得改 route | +| `change_websocket_route` / `change_acme_challenge_route` / `change_upstream` | 不得改路由、ACME 或 upstream | +| `change_dns_record` / `tls_probe` / `dns_probe` / `certbot_renew` | 不得改 DNS / TLS 或 renew cert | +| `route_smoke` / `websocket_smoke` | 不得由本計畫執行 smoke | +| `host_write` / `firewall_change` / `docker_restart` / `systemd_restart` | 不得寫主機或重啟 | +| `accept_route_200_as_all_green` / `accept_nginx_active_as_all_green` | 不得假性驗收 | +| `mark_readback_accepted_without_reviewer_record` | 不得跳過 reviewer record | +| `open_runtime_gate` / `add_action_button` | 不得開執行閘門或按鈕 | + +## 7. 指令 + +產生 committed snapshot: + +```bash +python3 scripts/security/public-gateway-post-incident-readback-plan.py \ + --root . \ + --source-report docs/security/public-gateway-rendered-diff-acceptance.snapshot.json \ + --output docs/security/public-gateway-post-incident-readback-plan.snapshot.json \ + --generated-at 2026-06-15T22:10:00+08:00 +``` + +集中驗證 guard: + +```bash +python3 scripts/security/security-mirror-progress-guard.py --root . +``` + +## 8. 完成度 + +| 工作 | 完成度 | 說明 | +|------|--------|------| +| Public Gateway / Nginx post-incident readback plan | `100%` | 產生器、snapshot 與文件已固定 | +| post-incident readback received / accepted | `0%` | 尚未收到 / 接受 | +| nginx test / reload / route smoke | `0%` | 未授權且未執行 | +| DNS / TLS / certbot | `0%` | 未授權且未執行 | +| runtime reload / host write | `0%` | 未授權且未執行 | diff --git a/docs/security/SECURITY-SUPPLY-CHAIN-PROGRESS.md b/docs/security/SECURITY-SUPPLY-CHAIN-PROGRESS.md index 4a9382e75..d3cc52f2f 100644 --- a/docs/security/SECURITY-SUPPLY-CHAIN-PROGRESS.md +++ b/docs/security/SECURITY-SUPPLY-CHAIN-PROGRESS.md @@ -3,7 +3,7 @@ | 項目 | 內容 | |------|------| | 日期 | 2026-06-15 | -| 狀態 | IwoooS 64% 只讀治理推進中;高價值配置集中 guard、Package / Docker 供應鏈 repo-only baseline 與 Package / Docker 供應鏈 owner policy gate 已完成;Frontend public sensitive surface guard、Backup / Restore / Escrow owner response acceptance backfill、Monitoring / Alerting / Observability no-false-green owner response acceptance、Host Service change evidence acceptance、Host Service post-incident readback plan、AI provider / model routing owner response acceptance、CD / Runner / Secret 注入變更證據驗收、Public / Admin / API runtime config 變更證據驗收與 K8s / ArgoCD post-incident readback plan 已本地完成;端口 / 防火牆變更證據驗收、主機服務事故回補與 K8s / ArgoCD GitOps 變更證據驗收已正式部署驗證;S4.9 owner response gate 仍是第一優先 | +| 狀態 | IwoooS 64% 只讀治理推進中;高價值配置集中 guard、Package / Docker 供應鏈 repo-only baseline 與 Package / Docker 供應鏈 owner policy gate 已完成;Frontend public sensitive surface guard、Backup / Restore / Escrow owner response acceptance backfill、Monitoring / Alerting / Observability no-false-green owner response acceptance、Host Service change evidence acceptance、Host Service post-incident readback plan、AI provider / model routing owner response acceptance、CD / Runner / Secret 注入變更證據驗收、Public / Admin / API runtime config 變更證據驗收、K8s / ArgoCD post-incident readback plan 與 Public Gateway / Nginx post-incident readback plan 已本地完成;端口 / 防火牆變更證據驗收、主機服務事故回補與 K8s / ArgoCD GitOps 變更證據驗收已正式部署驗證;S4.9 owner response gate 仍是第一優先 | | 本階段完成 | 資安供應鏈 contract manifest + Source Control Approval Board + Draft Reconcile Plan + Ref Detail Diff + Ref Truth Classification + Source Control Ref Truth Owner Response 收件包 + GitHub Primary Readiness Gate + GitHub Primary Rollback ADR + GitHub Target Owner Decision Response 收件包 + Gitea 認證清冊匯出請求 + Gitea 認證清冊匯入驗收契約 + Gitea 清冊覆蓋 Owner Attestation + Gitea Owner Attestation Approval Lane 對齊 + Gitea Owner Attestation Response 收件包 + Workflow / Secret Name Inventory + Workflow / Secret Name Local Evidence + Workflow / Secret Name Redacted Export Request + Workflow / Secret Name Owner Response 收件包 + Source Control Owner Response Validation Rollup + Kali 112 live integration status + Security Finding contract + Kali scan scope approval package + Security Approval Queue + S3 人工批准 Gate + S3 人工決策紀錄 + S3 人工審查封包 + S3 人工決策狀態轉移 + S3 後續 runtime gate 準備契約 + 鏡像 readiness index + 鏡像接收計畫 + 鏡像事件信封 + 鏡像路由矩陣 + 鏡像驗收契約 + 鏡像隔離契約 + 鏡像 dry-run 報告契約 + 鏡像狀態彙整契約 + IwoooS 前端態勢入口 + IwoooS posture projection contract + IwoooS 既有前端資安頁面整合 + IwoooS 覆蓋與邊界矩陣 + IwoooS 只讀資安處理旅程 + IwoooS owner evidence readiness board + IwoooS host coverage view + IwoooS host action gate matrix + IwoooS host evidence readiness board + IwoooS host evidence collection order + IwoooS host evidence intake preflight + IwoooS host evidence review outcome lanes + IwoooS host evidence review handoff packets + IwoooS host evidence reviewer checklist + IwoooS host evidence reviewer outcome lanes + IwoooS host owner decision candidate packets + IwoooS host owner decision review checklist + IwoooS host owner decision review outcome lanes + IwoooS host owner decision record draft packets + IwoooS host owner decision record draft review checklist + IwoooS host owner decision record draft review outcome lanes + IwoooS host owner decision record write-up packets + IwoooS host owner decision record write-up review checklist + IwoooS host owner decision record write-up review outcome lanes + IwoooS host owner decision record formal candidate packets + IwoooS host owner decision record formal candidate review checklist + IwoooS host owner decision record formal candidate review outcome lanes + IwoooS host owner decision record formal record queue packets + IwoooS host owner decision record formal record queue review checklist + IwoooS host owner decision record formal record queue review outcome lanes + IwoooS host owner decision record human handoff readiness packets + IwoooS host owner decision record human handoff readiness review checklist + IwoooS host owner decision record human handoff readiness review outcome lanes + IwoooS host owner decision record human record owner review candidate packets + IwoooS host owner decision record human record owner review candidate checklist + IwoooS host owner decision record human record owner review candidate outcome lanes + IwoooS host owner decision record human record owner review preparation packets + IwoooS host owner decision record human record owner review preparation checklist + IwoooS progress acceleration lanes + IwoooS owner response next-action focus + IwoooS S4.9 owner response preflight + IwoooS S4.9 owner response request templates + IwoooS progress hold movement gates + IwoooS AwoooP read-only landing readiness + IwoooS AwoooP cross-session handoff packets + AwoooP 首頁 IwoooS 資安鏡像候選 + AwoooP 工作鏈路 IwoooS 資安鏡像候選 + AwoooP 審批佇列 IwoooS owner response 只讀焦點 | | 本階段追加 | AwoooP 合約儀表板 IwoooS 資安契約只讀候選 + AwoooP 租戶管理 IwoooS 資安租戶範圍只讀候選 + AwoooP 執行監控 IwoooS 執行狀態只讀候選 + 既有安全 / 合規頁面 IwoooS 只讀反向橋接 + 告警 / 錯誤 / 授權 / 治理頁面 IwoooS 只讀反向橋接 + 稽核 / 工程審查頁面 IwoooS 深色只讀反向橋接 + IwoooS 前端資安頁面連接狀態板 + IwoooS GitHub 主要來源就緒度只讀狀態板 + AwoooP 工作鏈路 GitHub 主要來源就緒度只讀工作項 + AwoooP 合約儀表板 GitHub 主要來源就緒度合約只讀候選 + AwoooP 審批佇列 GitHub 主要來源就緒度審批邊界 + AwoooP 首頁 GitHub 主要來源就緒度只讀摘要 + AwoooP 租戶管理 GitHub 主要來源就緒度租戶範圍 + AwoooP 執行監控 GitHub 主要來源就緒度執行邊界 + IwoooS / AwoooP 資安可視區塊繁體中文呈現防護檢查 + AwoooP 執行詳情 / 審批詳情繁體中文呈現防護檢查 + AwoooP 首頁負責人回覆驗收總覽 + AwoooP 工作鏈路負責人回覆驗收只讀工作項 + AwoooP 合約儀表板負責人回覆驗收契約只讀候選 + AwoooP 審批佇列負責人回覆驗收只讀審查邊界 + AwoooP 租戶管理負責人回覆驗收租戶範圍 + AwoooP 執行監控負責人回覆驗收執行邊界 + AwoooP 執行詳情負責人回覆驗收詳情邊界 + AwoooP 審批決策負責人回覆驗收審批邊界 + IwoooS AwoooP 資安入口覆蓋狀態板 + IwoooS 階段式資安收斂節奏圖 + IwoooS 下一步人工收件作戰板 + IwoooS 人工回覆安全驗收閘道 + IwoooS 人工回覆審查結果分流 + IwoooS 人工決策準備佇列 + IwoooS 人工決策紀錄草稿防誤用 + IwoooS 人工決策正式紀錄負責人指派確認準備包 + IwoooS 人工決策正式紀錄負責人指派確認清單 + IwoooS 人工決策正式紀錄負責人指派確認結果分流 + IwoooS 人工決策正式紀錄負責人指派決策準備包 + IwoooS 人工決策正式紀錄負責人指派決策檢查清單 + IwoooS S4.9 負責人回覆封套欄位 + IwoooS S4.9 負責人回覆封套送件前檢查 + IwoooS S4.9 負責人回覆封套送件前結果分流 + IwoooS S4.9 負責人回覆送件請求草稿 + IwoooS S4.9 負責人回覆送件鏈路摘要 + IwoooS 低摩擦分階段收斂主控 + IwoooS 低摩擦下一步行動邊界 + IwoooS 64% 進度移動訊號驗收條 + IwoooS 第一個進度解鎖路徑 + IwoooS 第一解鎖證據包 + IwoooS 第一解鎖證據包預檢分流 + IwoooS 第一解鎖證據包補件路徑 + IwoooS 第一解鎖證據包補件送審前檢查 + IwoooS 第一解鎖證據包補件送審結果分流 + IwoooS 第一解鎖證據包 reviewer 指派準備包 + IwoooS 第一解鎖證據包 reviewer 指派前檢查 + IwoooS 第一解鎖證據包 reviewer 指派前檢查結果分流 + IwoooS 正式只讀 landing 與 Kali 112 只讀證據進度重估 | | 本階段追加補充 | IwoooS 目前具體工作地圖 + IwoooS 目前具體交付清單 + IwoooS 目前阻塞與解除條件 + IwoooS 三軸進度與全產品套用範圍 + IwoooS 全產品分階段套用台帳 + IwoooS 全產品 rollout 波次驗收門檻 + IwoooS 全產品 rollout 驗收結果分流 + IwoooS 全產品證據接線地圖 + IwoooS 全產品證據接線預檢 + IwoooS 全產品證據接線預檢結果分流 + IwoooS 全產品預檢補件回收台帳 + IwoooS 全產品補件重試門檻 + IwoooS 全產品重試結果分流 + IwoooS 全產品人工審查候選準備 + IwoooS 全產品人工審查候選預檢 + IwoooS 全產品人工審查候選預檢結果分流 + IwoooS 全產品人工審查候選預檢補件回收台帳 + IwoooS 全產品人工審查候選預檢補件重試門檻 + IwoooS 全產品只讀套用快照 + P2-145 owner response acceptance gate 正式驗證完成 | @@ -14,6 +14,12 @@ | 原則 | 低摩擦分階段;文件、schema、read-only evidence 優先;不做 runtime enforcement、不切 primary | | P0 主控板 | `docs/workplans/2026-06-04-iwooos-security-governance-p0.md` | +## 0.1 2026-06-15 Public Gateway / Nginx 事故後回讀計畫 + +本輪把 Public Gateway / Nginx 從 rendered diff evidence acceptance 再補一層事故後回讀計畫:`public_gateway_post_incident_readback_plan_v1` 固定 `candidates=3`、`c0=2`、`c1=1`、`write_capable=3`、`readback_fields=36`、`required_readback_fields=30`、`reviewer_checks=28`、`outcome_lanes=10`、`blocked_actions=41`,並讓 `nginx_public_gateway` 只讀治理成熟度 `90% -> 92%`,高價值配置平均成熟度維持 `71%`。新增要求包含 actor、變更時間窗、change intent / break-glass、改前改後 route、source-to-live diff、`nginx -t` readback、reload / no-reload、route smoke、TLS / ACME、WebSocket、upstream、AI provider、monitoring、operator notification、cross-project sync、rollback validation、post-change monitoring、postcheck、recurrence guard 與 no-false-green attestation。 + +同步邊界:IwoooS headline 維持 `64%`,active runtime gate 維持 `0`;readback received / accepted、actor accepted、before / after route state accepted、source-live diff accepted、`nginx -t` readback accepted、reload / no-reload accepted、route smoke accepted、TLS / ACME accepted、WebSocket accepted、upstream accepted、AI provider accepted、monitoring accepted、cross-project sync accepted、no-false-green accepted、live conf read、`nginx -t`、reload、route smoke、DNS / TLS probe、certbot renew、host write、runtime gate 與 action buttons 全部仍為 `0 / false`。本段只更新文件、snapshot、guard、schema、投影契約與前端 marker,不 SSH、不讀 live Nginx conf、不執行 `nginx -t`、不 reload、不做 route smoke、不改 DNS / TLS、不 renew cert、不寫主機。 + ## 0.00aaaaaaaaaa 2026-06-15 K8s / ArgoCD post-incident readback plan 本輪把 K8s / ArgoCD 從 GitOps 變更證據驗收再補一層事故後回讀計畫:`k8s_argocd_post_incident_readback_plan_v1` 固定 `candidates=4`、`c0=3`、`c1=1`、`write_capable=4`、`readback_fields=36`、`required_readback_fields=31`、`reviewer_checks=28`、`outcome_lanes=10`、`blocked_actions=41`,並讓 `k8s_production_gitops` 只讀治理成熟度 `64% -> 66%`,高價值配置平均成熟度維持 `71%`。新增要求包含 actor、ArgoCD app health、sync status、Degraded / Pending、image pull / scheduling、rollout before / after、event / metrics / alert、drift scanner、CronJob schedule、NetworkPolicy / RBAC / Secret metadata、public/admin route、AI provider / monitoring、backup / restore、operator notification、cross-project sync、postcheck、recurrence guard 與 no-false-green attestation。 @@ -471,6 +477,7 @@ headline 要再往上,需要 S4.9 / S4.10 / S4.11 / S4.12 任一 owner respons | Public Gateway redacted export 收件預檢 | 本地完成 | `public-gateway-redacted-export-intake-preflight.snapshot.json` 已固定 `intake_candidate_count=3`、`c0_intake_candidate_count=2`、`validation_check_count=10`、`rejection_guard_count=12`、`reviewer_intake_lane_count=5`、`redacted_export_received_count=0`、`accepted_redacted_export_count=0`,並由 `security-mirror-progress-guard.py` 鎖住 intake ids、validation checks、rejection guards、reviewer lanes 與 false flags | 這是 redacted export 收件前預檢,不是 request sent、recipient confirmed、redacted export received / accepted、raw live conf stored、rendered diff ready、`nginx -t`、Nginx reload、route smoke、DNS / TLS probe、certbot renew、host write、production write 或 runtime gate;不需要 production browser smoke | | Public Gateway rendered diff / nginx gate 草稿 | 本地完成 | `public-gateway-rendered-diff-gate-draft.snapshot.json` 已固定 `diff_gate_candidate_count=3`、`c0_diff_gate_candidate_count=2`、`preflight_stage_count=7`、`blocked_action_count=14`、`redacted_export_accepted_count=0`、`rendered_diff_ready_count=0`、`nginx_test_executed_count=0`、`runtime_gate_count=0`,並由 `security-mirror-progress-guard.py` 鎖住 diff gate ids、preflight stages、blocked actions 與 false flags | 這是 rendered diff / nginx / route smoke 分階段 gate 草稿,不是 redacted export accepted、rendered diff ready、`nginx -t`、Nginx reload、route smoke、DNS / TLS probe、certbot renew、host write、production write 或 runtime gate;不需要 production browser smoke | | Public Gateway rendered diff evidence acceptance | 本地完成 | `public-gateway-rendered-diff-acceptance.snapshot.json` 已固定 `diff_acceptance_candidate_count=3`、`c0_diff_acceptance_candidate_count=2`、`required_evidence_field_count=14`、`reviewer_check_count=15`、`outcome_lane_count=8`、`blocked_action_count=22`、`rendered_diff_accepted_count=0`、`nginx_test_evidence_accepted_count=0`、`route_smoke_result_accepted_count=0`、`runtime_gate_count=0`,並由 `security-mirror-progress-guard.py` 鎖住 acceptance ids、reviewer checks、outcome lanes、blocked actions 與 false flags | 這是 owner-provided rendered diff / nginx test / route smoke evidence 的只讀驗收帳本,不是 owner response accepted、rendered diff accepted、`nginx -t`、Nginx reload、route smoke、DNS / TLS probe、certbot renew、host write、production write 或 runtime gate;前端數字會隨本輪 commit / deploy 另行 smoke | +| Public Gateway / Nginx post-incident readback plan | 本地完成 | `public-gateway-post-incident-readback-plan.snapshot.json` 已固定 `readback_candidate_count=3`、`c0_readback_candidate_count=2`、`required_readback_field_count=30`、`reviewer_check_count=28`、`outcome_lane_count=10`、`blocked_action_count=41`、`post_incident_readback_received_count=0`、`post_incident_readback_accepted_count=0`、`nginx_test_authorized_count=0`、`nginx_reload_authorized_count=0`、`route_smoke_authorized_count=0`、`runtime_gate_count=0`,並由 `security-mirror-progress-guard.py` 鎖住 readback ids、reviewer checks、outcome lanes、blocked actions 與 false flags | 這是 Nginx / Public Gateway 事故後回讀計畫,不是 live conf read、`nginx -t`、Nginx reload、route smoke、DNS / TLS probe、certbot renew、host write、production write 或 runtime gate;前端數字會隨本輪 commit / deploy 另行 smoke | | DNS / TLS / certbot Owner Confirmation Request | 本地完成 | `domain-tls-certbot-owner-confirmation-request.snapshot.json` 已固定 `owner_confirmation_request_count=4`、`c0_owner_confirmation_request_count=4`、`required_owner_field_count=9`、`confirmation_question_count=5`、`rejection_guard_count=12`、`owner_response_received_count=0`、`owner_response_accepted_count=0`、`runtime_gate_count=0`,並由 `security-mirror-progress-guard.py` 鎖住 request ids、confirmation questions、rejection guards 與 false flags | 這是 SAN / wildcard / 共用憑證覆蓋關係的 owner confirmation request 草稿,不是 request sent、recipient confirmed、owner response received / accepted、DNS query、TLS probe、certbot renew、Nginx reload、route smoke、host write、production write 或 runtime gate;不需要 production browser smoke | | K8s / ArgoCD manifest repo-only 清冊 | 本地完成 | `k8s-argocd-manifest-inventory.snapshot.json` 已固定 `file_count=49`、`c0_file_count=36`、`yaml_manifest_file_count=45`、`unique_kind_count=20`、`top_level_kind_marker_count=56`、`required_owner_field_count=11`、`evidence_gap_count=8`、`blocked_action_count=13`,並由 `security-mirror-progress-guard.py` 鎖住 group ids、kind counts、blocked actions 與 false flags | 這是 repo-only manifest inventory,不是 live cluster read、ArgoCD API read、ArgoCD sync、kubectl apply / patch / delete、Helm upgrade、secret collection、manual pod restart、scale workload、RBAC / NetworkPolicy change、restore backup、production write 或 runtime gate;不需要 production browser smoke | | K8s / ArgoCD Owner Request Draft | 已推送 / 已同步 | `e8de19d7` 已進 `gitea/main`;`k8s-argocd-owner-request-draft.snapshot.json` 已固定 `request_draft_count=4`、`c0_request_draft_count=3`、`request_field_count=20`、`required_owner_field_count=11`、`evidence_gap_count=8`、`blocked_action_count=13`、`request_sent_count=0`、`owner_response_received_count=0`、`runtime_gate_count=0`,並由 `security-mirror-progress-guard.py` 鎖住 request ids、blocked actions 與 false flags;AwoooP 平行 Session 已同步 | 這是人工送件前 request draft,不是 request sent、recipient confirmed、owner response received / accepted、rendered manifest diff、ArgoCD API read、ArgoCD sync、kubectl action、live cluster read、secret collection、production write 或 runtime gate;不需要 production browser smoke | diff --git a/docs/security/high-value-config-control-coverage.snapshot.json b/docs/security/high-value-config-control-coverage.snapshot.json index 292dbf217..1035194b5 100644 --- a/docs/security/high-value-config-control-coverage.snapshot.json +++ b/docs/security/high-value-config-control-coverage.snapshot.json @@ -4,9 +4,9 @@ "action_buttons_allowed": false, "category_id": "nginx_public_gateway", "control_tier": "C0", - "coverage_percent": 90, - "coverage_status": "emergency_change_backfill_ready_needs_owner_live_diff", - "current_gap": "已固定 owner response acceptance、手動 / 緊急 gateway 變更回補欄位與 rendered diff evidence acceptance 只讀帳本;owner response、live conf、rendered diff evidence、nginx -t evidence、route smoke evidence、maintenance window 與 rollback owner 仍全部為 0。", + "coverage_percent": 92, + "coverage_status": "post_incident_readback_plan_ready_needs_public_gateway_owner_evidence", + "current_gap": "已固定 owner response acceptance、手動 / 緊急 gateway 變更回補欄位、rendered diff evidence acceptance 與 post-incident readback plan 只讀帳本;actor、時間窗、改前改後 route、source-to-live diff、nginx -t readback、reload / no-reload、route smoke、TLS / ACME、WebSocket、upstream、AI provider、monitoring、跨專案同步、防再發與 no-false-green 已納入;owner response、live conf、rendered diff evidence、nginx -t evidence、route smoke evidence、事故回讀包、maintenance window 與 rollback owner 仍全部為 0。", "evidence_refs": [ "docs/security/NGINX-CONFIG-DRIFT-DETECTOR.md", "docs/security/nginx-config-drift-repo.snapshot.json", @@ -17,10 +17,12 @@ "docs/security/public-gateway-owner-response-acceptance.snapshot.json", "docs/security/PUBLIC-GATEWAY-RENDERED-DIFF-ACCEPTANCE.md", "docs/security/public-gateway-rendered-diff-acceptance.snapshot.json", + "docs/security/PUBLIC-GATEWAY-POST-INCIDENT-READBACK-PLAN.md", + "docs/security/public-gateway-post-incident-readback-plan.snapshot.json", "docs/schemas/public_gateway_preflight_inventory_v1.schema.json" ], "label": "Nginx / reverse proxy / public route", - "next_owner_action": "補 public gateway owner 回覆、owner-provided live conf、source-to-live rendered diff ref、nginx -t evidence ref、route smoke evidence ref、change intent / break-glass、route health impact、rollback validation、maintenance window 與 rollback owner。", + "next_owner_action": "補 Public Gateway / Nginx 事故回讀包:owner 回覆、owner-provided live conf、source-to-live rendered diff ref、nginx -t evidence ref、route smoke evidence ref、change intent / break-glass、actor role / team、變更時間窗、改前改後 route state、upstream / WebSocket / TLS / ACME 影響、AI provider / monitoring 影響、跨專案同步、route health impact、rollback validation、post-change monitoring、recurrence guard、maintenance window 與 rollback owner。", "owner_response_accepted": false, "owner_response_received": false, "owner_response_required": true, @@ -645,8 +647,8 @@ "websocket_route_change_authorized": false, "workflow_modification_authorized": false }, - "generated_at": "2026-06-15T20:46:00+08:00", - "git_commit": "18cf5263", + "generated_at": "2026-06-15T22:10:00+08:00", + "git_commit": "915cbaac", "lowest_coverage_categories": [ { "category_id": "backup_restore_credential", diff --git a/docs/security/iwooos-posture-projection.snapshot.json b/docs/security/iwooos-posture-projection.snapshot.json index f5f5858c8..548348a73 100644 --- a/docs/security/iwooos-posture-projection.snapshot.json +++ b/docs/security/iwooos-posture-projection.snapshot.json @@ -8372,6 +8372,8 @@ "docs/security/PUBLIC-GATEWAY-OWNER-RESPONSE-ACCEPTANCE.md", "docs/security/public-gateway-rendered-diff-acceptance.snapshot.json", "docs/security/PUBLIC-GATEWAY-RENDERED-DIFF-ACCEPTANCE.md", + "docs/security/public-gateway-post-incident-readback-plan.snapshot.json", + "docs/security/PUBLIC-GATEWAY-POST-INCIDENT-READBACK-PLAN.md", "docs/schemas/public_gateway_preflight_inventory_v1.schema.json", "docs/security/k8s-argocd-owner-response-acceptance.snapshot.json", "docs/security/K8S-ARGOCD-OWNER-RESPONSE-ACCEPTANCE.md", @@ -9184,7 +9186,46 @@ "k8s_argocd_post_incident_readback_plan_runtime_gate_count": 0, "k8s_argocd_post_incident_readback_plan_action_button_count": 0, "k8s_argocd_post_incident_readback_plan_coverage_percent_after_readback_plan": 66, - "k8s_production_gitops_coverage_percent": 66 + "k8s_production_gitops_coverage_percent": 66, + "nginx_public_gateway_coverage_percent": 92, + "public_gateway_post_incident_readback_plan_candidate_count": 3, + "public_gateway_post_incident_readback_plan_c0_candidate_count": 2, + "public_gateway_post_incident_readback_plan_write_capable_candidate_count": 3, + "public_gateway_post_incident_readback_plan_required_readback_field_count": 30, + "public_gateway_post_incident_readback_plan_reviewer_check_count": 28, + "public_gateway_post_incident_readback_plan_outcome_lane_count": 10, + "public_gateway_post_incident_readback_plan_blocked_action_count": 41, + "public_gateway_post_incident_readback_plan_post_incident_readback_received_count": 0, + "public_gateway_post_incident_readback_plan_post_incident_readback_accepted_count": 0, + "public_gateway_post_incident_readback_plan_actor_attribution_accepted_count": 0, + "public_gateway_post_incident_readback_plan_before_after_route_state_accepted_count": 0, + "public_gateway_post_incident_readback_plan_source_live_diff_state_accepted_count": 0, + "public_gateway_post_incident_readback_plan_nginx_test_readback_accepted_count": 0, + "public_gateway_post_incident_readback_plan_nginx_reload_or_no_reload_accepted_count": 0, + "public_gateway_post_incident_readback_plan_route_smoke_readback_accepted_count": 0, + "public_gateway_post_incident_readback_plan_tls_acme_readback_accepted_count": 0, + "public_gateway_post_incident_readback_plan_websocket_readback_accepted_count": 0, + "public_gateway_post_incident_readback_plan_upstream_health_accepted_count": 0, + "public_gateway_post_incident_readback_plan_public_admin_api_route_impact_accepted_count": 0, + "public_gateway_post_incident_readback_plan_ai_provider_impact_accepted_count": 0, + "public_gateway_post_incident_readback_plan_monitoring_alert_accepted_count": 0, + "public_gateway_post_incident_readback_plan_operator_notification_accepted_count": 0, + "public_gateway_post_incident_readback_plan_cross_project_sync_accepted_count": 0, + "public_gateway_post_incident_readback_plan_rollback_validation_accepted_count": 0, + "public_gateway_post_incident_readback_plan_post_change_monitoring_accepted_count": 0, + "public_gateway_post_incident_readback_plan_postcheck_readback_accepted_count": 0, + "public_gateway_post_incident_readback_plan_recurrence_guard_accepted_count": 0, + "public_gateway_post_incident_readback_plan_no_false_green_accepted_count": 0, + "public_gateway_post_incident_readback_plan_host_live_conf_read_authorized_count": 0, + "public_gateway_post_incident_readback_plan_nginx_test_authorized_count": 0, + "public_gateway_post_incident_readback_plan_nginx_reload_authorized_count": 0, + "public_gateway_post_incident_readback_plan_public_gateway_reload_authorized_count": 0, + "public_gateway_post_incident_readback_plan_route_smoke_authorized_count": 0, + "public_gateway_post_incident_readback_plan_dns_tls_probe_authorized_count": 0, + "public_gateway_post_incident_readback_plan_certbot_renew_authorized_count": 0, + "public_gateway_post_incident_readback_plan_runtime_gate_count": 0, + "public_gateway_post_incident_readback_plan_action_button_count": 0, + "public_gateway_post_incident_readback_plan_coverage_percent_after_readback_plan": 92 }, "topology_atlas_lenses": [ { diff --git a/docs/security/public-gateway-post-incident-readback-plan.snapshot.json b/docs/security/public-gateway-post-incident-readback-plan.snapshot.json new file mode 100644 index 000000000..722a8037b --- /dev/null +++ b/docs/security/public-gateway-post-incident-readback-plan.snapshot.json @@ -0,0 +1,1049 @@ +{ + "blocked_actions": [ + "ssh_read_live_nginx_conf", + "store_raw_live_conf", + "store_full_rendered_diff_payload", + "collect_secret_value", + "collect_private_key", + "collect_certificate_body", + "collect_cookie_or_token", + "run_nginx_test", + "reload_nginx", + "restart_nginx", + "change_nginx_site_enabled", + "change_public_route", + "change_admin_route", + "change_api_route", + "change_websocket_route", + "change_acme_challenge_route", + "change_upstream", + "change_dns_record", + "tls_probe", + "dns_probe", + "certbot_renew", + "certbot_reconfigure", + "route_smoke", + "websocket_smoke", + "host_write", + "firewall_change", + "docker_restart", + "systemd_restart", + "argocd_sync", + "kubectl_apply", + "accept_route_200_as_all_green", + "accept_nginx_active_as_all_green", + "accept_dashboard_up_as_security_acceptance", + "accept_cd_success_as_security_acceptance", + "skip_cross_project_sync", + "skip_operator_notification", + "skip_rollback_validation", + "skip_post_change_monitoring", + "mark_readback_accepted_without_reviewer_record", + "open_runtime_gate", + "add_action_button" + ], + "execution_boundaries": { + "acme_challenge_change_authorized": false, + "action_buttons_allowed": false, + "admin_route_change_authorized": false, + "certbot_renew_authorized": false, + "dns_tls_probe_authorized": false, + "host_live_conf_read_authorized": false, + "nginx_reload_authorized": false, + "nginx_reload_executed": false, + "nginx_test_authorized": false, + "nginx_test_executed": false, + "not_authorization": true, + "production_write_authorized": false, + "public_gateway_reload_authorized": false, + "public_route_change_authorized": false, + "route_smoke_authorized": false, + "route_smoke_executed": false, + "runtime_gate": false, + "websocket_route_change_authorized": false + }, + "generated_at": "2026-06-15T22:10:00+08:00", + "git_commit": "a0f79315", + "mode": "metadata_only_no_live_conf_no_nginx_reload", + "operator_interpretation": [ + "此計畫只定義 Public Gateway / Nginx 事故後回讀欄位,不代表 live conf 已讀取或可讀取。", + "route 200、Nginx active、dashboard up、CD success、UI 可見都不能單獨當成 gateway 事故驗收。", + "未來若要 nginx -t、reload、route smoke、DNS / TLS probe、certbot renew 或 host write,必須另有維護窗口、rollback owner 與人工批准。" + ], + "outcome_lanes": [ + { + "lane_id": "waiting_post_incident_readback", + "meaning": "尚未收到 Public Gateway / Nginx 事故回讀包;所有 accepted / runtime count 維持 0。" + }, + { + "lane_id": "request_actor_or_time_supplement", + "meaning": "缺 actor、change time window、intent 或 break-glass reason 時要求補件。" + }, + { + "lane_id": "request_route_state_supplement", + "meaning": "缺 before / after route、upstream、WebSocket、TLS / ACME 或 route smoke readback 時要求補件。" + }, + { + "lane_id": "request_diff_test_supplement", + "meaning": "缺 source-live diff、nginx test readback、reload / no-reload 判定或 rollback validation 時要求補件。" + }, + { + "lane_id": "request_dependency_supplement", + "meaning": "缺 AI provider、monitoring、public/admin/API route 或跨專案影響時要求補件。" + }, + { + "lane_id": "quarantine_raw_payload", + "meaning": "收到 raw conf、完整 diff、secret、憑證、cookie、token 或未脫敏截圖時只能隔離。" + }, + { + "lane_id": "reject_false_green_claim", + "meaning": "把 route 200、Nginx active、dashboard up、CD success 或 UI 可見當驗收時拒收。" + }, + { + "lane_id": "ready_for_gateway_post_incident_review", + "meaning": "metadata 合格後,只能進 reviewer review。" + }, + { + "lane_id": "recurrence_guard_backfill_required", + "meaning": "需補防再發 guard、owner review、change freeze 或 automation block。" + }, + { + "lane_id": "waiting_runtime_gate", + "meaning": "即使 readback accepted,runtime gate 仍需獨立人工批准。" + } + ], + "readback_candidates": [ + { + "acme_challenge_change_authorized": false, + "action_buttons_allowed": false, + "actor_attribution_accepted": false, + "actor_attribution_ref": null, + "admin_route_change_authorized": false, + "after_route_state_ref": null, + "ai_provider_impact_accepted": false, + "ai_provider_impact_ref": null, + "before_after_route_state_accepted": false, + "before_route_state_ref": null, + "blocked_actions": [ + "ssh_read_live_nginx_conf", + "store_raw_live_conf", + "store_full_rendered_diff_payload", + "collect_secret_value", + "collect_private_key", + "collect_certificate_body", + "collect_cookie_or_token", + "run_nginx_test", + "reload_nginx", + "restart_nginx", + "change_nginx_site_enabled", + "change_public_route", + "change_admin_route", + "change_api_route", + "change_websocket_route", + "change_acme_challenge_route", + "change_upstream", + "change_dns_record", + "tls_probe", + "dns_probe", + "certbot_renew", + "certbot_reconfigure", + "route_smoke", + "websocket_smoke", + "host_write", + "firewall_change", + "docker_restart", + "systemd_restart", + "argocd_sync", + "kubectl_apply", + "accept_route_200_as_all_green", + "accept_nginx_active_as_all_green", + "accept_dashboard_up_as_security_acceptance", + "accept_cd_success_as_security_acceptance", + "skip_cross_project_sync", + "skip_operator_notification", + "skip_rollback_validation", + "skip_post_change_monitoring", + "mark_readback_accepted_without_reviewer_record", + "open_runtime_gate", + "add_action_button" + ], + "certbot_renew_authorized": false, + "change_intent_or_break_glass_ref": null, + "change_time_window_accepted": false, + "change_time_window_ref": null, + "config_id": "host188_all_sites", + "control_tier": "C0", + "cross_project_sync_accepted": false, + "cross_project_sync_ref": null, + "diff_gate_id": "public_gateway_rendered_diff_gate:host188_all_sites", + "dns_tls_probe_authorized": false, + "followup_owner": "pending_post_incident_readback", + "gateway_incident_or_change_ref": null, + "host": "192.168.0.188", + "host_live_conf_read_authorized": false, + "intent_or_break_glass_accepted": false, + "live_path": "/etc/nginx/sites-enabled/all-sites.conf", + "maintenance_window": "pending_post_incident_readback", + "monitoring_alert_accepted": false, + "monitoring_alert_ref": null, + "nginx_reload_authorized": false, + "nginx_reload_executed": false, + "nginx_reload_or_no_reload_accepted": false, + "nginx_reload_or_no_reload_ref": null, + "nginx_test_authorized": false, + "nginx_test_executed": false, + "nginx_test_readback_accepted": false, + "nginx_test_readback_ref": null, + "no_false_green_accepted": false, + "not_approval": true, + "operator_notification_accepted": false, + "operator_notification_ref": null, + "outcome_lanes": [ + "waiting_post_incident_readback", + "request_actor_or_time_supplement", + "request_route_state_supplement", + "request_diff_test_supplement", + "request_dependency_supplement", + "quarantine_raw_payload", + "reject_false_green_claim", + "ready_for_gateway_post_incident_review", + "recurrence_guard_backfill_required", + "waiting_runtime_gate" + ], + "owner_response_acceptance_id": "public_gateway_owner_response_acceptance:host188_all_sites", + "post_change_monitoring_accepted": false, + "post_change_monitoring_ref": null, + "post_incident_readback_accepted": false, + "post_incident_readback_candidate_id": "public_gateway_post_incident_readback:host188_all_sites", + "post_incident_readback_received": false, + "postcheck_readback_accepted": false, + "postcheck_readback_ref": null, + "production_write_authorized": false, + "public_admin_api_route_impact_accepted": false, + "public_admin_api_route_impact_ref": null, + "public_gateway_reload_authorized": false, + "public_route_change_authorized": false, + "readback_fields": [ + "post_incident_readback_candidate_id", + "source_diff_acceptance_id", + "owner_response_acceptance_id", + "diff_gate_id", + "config_id", + "control_tier", + "host", + "live_path", + "gateway_incident_or_change_ref", + "actor_attribution_ref", + "change_time_window_ref", + "change_intent_or_break_glass_ref", + "before_route_state_ref", + "after_route_state_ref", + "source_live_diff_state_ref", + "nginx_test_readback_ref", + "nginx_reload_or_no_reload_ref", + "route_smoke_readback_ref", + "tls_acme_readback_ref", + "websocket_readback_ref", + "upstream_health_ref", + "public_admin_api_route_impact_ref", + "ai_provider_impact_ref", + "monitoring_alert_ref", + "operator_notification_ref", + "cross_project_sync_ref", + "rollback_validation_ref", + "post_change_monitoring_ref", + "recovery_or_still_degraded_ref", + "postcheck_readback_ref", + "recurrence_guard_ref", + "maintenance_window", + "rollback_owner", + "reviewer_outcome", + "followup_owner", + "not_approval" + ], + "recovery_or_still_degraded_accepted": false, + "recovery_or_still_degraded_ref": null, + "recurrence_guard_accepted": false, + "recurrence_guard_ref": null, + "required_readback_fields": [ + "gateway_incident_or_change_ref", + "actor_attribution_ref", + "change_time_window_ref", + "change_intent_or_break_glass_ref", + "before_route_state_ref", + "after_route_state_ref", + "source_live_diff_state_ref", + "nginx_test_readback_ref", + "nginx_reload_or_no_reload_ref", + "route_smoke_readback_ref", + "tls_acme_readback_ref", + "websocket_readback_ref", + "upstream_health_ref", + "public_admin_api_route_impact_ref", + "ai_provider_impact_ref", + "monitoring_alert_ref", + "operator_notification_ref", + "cross_project_sync_ref", + "rollback_validation_ref", + "post_change_monitoring_ref", + "recovery_or_still_degraded_ref", + "postcheck_readback_ref", + "recurrence_guard_ref", + "maintenance_window", + "rollback_owner", + "followup_owner", + "redacted_evidence_refs", + "no_secret_value_attestation", + "no_raw_live_conf_or_diff_attestation", + "no_false_green_attestation" + ], + "reviewer_checks": [ + "source_diff_acceptance_current", + "incident_or_change_ref_present", + "actor_attribution_present", + "change_time_window_present", + "intent_or_break_glass_present", + "before_after_route_state_present", + "source_live_diff_state_present", + "nginx_test_readback_is_owner_provided", + "reload_or_no_reload_called_out", + "route_smoke_readback_present", + "tls_acme_readback_present", + "websocket_readback_present", + "upstream_health_present", + "public_admin_api_route_impact_present", + "ai_provider_impact_present", + "monitoring_alert_present", + "operator_notification_present", + "cross_project_sync_present", + "rollback_validation_present", + "post_change_monitoring_present", + "recovery_or_still_degraded_present", + "postcheck_independent", + "recurrence_guard_present", + "maintenance_window_present", + "no_false_green", + "raw_payload_absent", + "runtime_stays_zero", + "counts_transition_safe" + ], + "reviewer_outcome": "waiting_post_incident_readback", + "rollback_owner": "pending_post_incident_readback", + "rollback_validation_accepted": false, + "rollback_validation_ref": null, + "route_smoke_authorized": false, + "route_smoke_executed": false, + "route_smoke_readback_accepted": false, + "route_smoke_readback_ref": null, + "runtime_gate": false, + "source_diff_acceptance_id": "public_gateway_rendered_diff_acceptance:host188_all_sites", + "source_live_diff_state_accepted": false, + "source_live_diff_state_ref": null, + "status": "waiting_post_incident_readback", + "tls_acme_readback_accepted": false, + "tls_acme_readback_ref": null, + "upstream_health_accepted": false, + "upstream_health_ref": null, + "websocket_readback_accepted": false, + "websocket_readback_ref": null, + "websocket_route_change_authorized": false, + "write_capable": true + }, + { + "acme_challenge_change_authorized": false, + "action_buttons_allowed": false, + "actor_attribution_accepted": false, + "actor_attribution_ref": null, + "admin_route_change_authorized": false, + "after_route_state_ref": null, + "ai_provider_impact_accepted": false, + "ai_provider_impact_ref": null, + "before_after_route_state_accepted": false, + "before_route_state_ref": null, + "blocked_actions": [ + "ssh_read_live_nginx_conf", + "store_raw_live_conf", + "store_full_rendered_diff_payload", + "collect_secret_value", + "collect_private_key", + "collect_certificate_body", + "collect_cookie_or_token", + "run_nginx_test", + "reload_nginx", + "restart_nginx", + "change_nginx_site_enabled", + "change_public_route", + "change_admin_route", + "change_api_route", + "change_websocket_route", + "change_acme_challenge_route", + "change_upstream", + "change_dns_record", + "tls_probe", + "dns_probe", + "certbot_renew", + "certbot_reconfigure", + "route_smoke", + "websocket_smoke", + "host_write", + "firewall_change", + "docker_restart", + "systemd_restart", + "argocd_sync", + "kubectl_apply", + "accept_route_200_as_all_green", + "accept_nginx_active_as_all_green", + "accept_dashboard_up_as_security_acceptance", + "accept_cd_success_as_security_acceptance", + "skip_cross_project_sync", + "skip_operator_notification", + "skip_rollback_validation", + "skip_post_change_monitoring", + "mark_readback_accepted_without_reviewer_record", + "open_runtime_gate", + "add_action_button" + ], + "certbot_renew_authorized": false, + "change_intent_or_break_glass_ref": null, + "change_time_window_accepted": false, + "change_time_window_ref": null, + "config_id": "host188_internal_tools_https", + "control_tier": "C0", + "cross_project_sync_accepted": false, + "cross_project_sync_ref": null, + "diff_gate_id": "public_gateway_rendered_diff_gate:host188_internal_tools_https", + "dns_tls_probe_authorized": false, + "followup_owner": "pending_post_incident_readback", + "gateway_incident_or_change_ref": null, + "host": "192.168.0.188", + "host_live_conf_read_authorized": false, + "intent_or_break_glass_accepted": false, + "live_path": "owner_confirmation_required", + "maintenance_window": "pending_post_incident_readback", + "monitoring_alert_accepted": false, + "monitoring_alert_ref": null, + "nginx_reload_authorized": false, + "nginx_reload_executed": false, + "nginx_reload_or_no_reload_accepted": false, + "nginx_reload_or_no_reload_ref": null, + "nginx_test_authorized": false, + "nginx_test_executed": false, + "nginx_test_readback_accepted": false, + "nginx_test_readback_ref": null, + "no_false_green_accepted": false, + "not_approval": true, + "operator_notification_accepted": false, + "operator_notification_ref": null, + "outcome_lanes": [ + "waiting_post_incident_readback", + "request_actor_or_time_supplement", + "request_route_state_supplement", + "request_diff_test_supplement", + "request_dependency_supplement", + "quarantine_raw_payload", + "reject_false_green_claim", + "ready_for_gateway_post_incident_review", + "recurrence_guard_backfill_required", + "waiting_runtime_gate" + ], + "owner_response_acceptance_id": "public_gateway_owner_response_acceptance:host188_internal_tools_https", + "post_change_monitoring_accepted": false, + "post_change_monitoring_ref": null, + "post_incident_readback_accepted": false, + "post_incident_readback_candidate_id": "public_gateway_post_incident_readback:host188_internal_tools_https", + "post_incident_readback_received": false, + "postcheck_readback_accepted": false, + "postcheck_readback_ref": null, + "production_write_authorized": false, + "public_admin_api_route_impact_accepted": false, + "public_admin_api_route_impact_ref": null, + "public_gateway_reload_authorized": false, + "public_route_change_authorized": false, + "readback_fields": [ + "post_incident_readback_candidate_id", + "source_diff_acceptance_id", + "owner_response_acceptance_id", + "diff_gate_id", + "config_id", + "control_tier", + "host", + "live_path", + "gateway_incident_or_change_ref", + "actor_attribution_ref", + "change_time_window_ref", + "change_intent_or_break_glass_ref", + "before_route_state_ref", + "after_route_state_ref", + "source_live_diff_state_ref", + "nginx_test_readback_ref", + "nginx_reload_or_no_reload_ref", + "route_smoke_readback_ref", + "tls_acme_readback_ref", + "websocket_readback_ref", + "upstream_health_ref", + "public_admin_api_route_impact_ref", + "ai_provider_impact_ref", + "monitoring_alert_ref", + "operator_notification_ref", + "cross_project_sync_ref", + "rollback_validation_ref", + "post_change_monitoring_ref", + "recovery_or_still_degraded_ref", + "postcheck_readback_ref", + "recurrence_guard_ref", + "maintenance_window", + "rollback_owner", + "reviewer_outcome", + "followup_owner", + "not_approval" + ], + "recovery_or_still_degraded_accepted": false, + "recovery_or_still_degraded_ref": null, + "recurrence_guard_accepted": false, + "recurrence_guard_ref": null, + "required_readback_fields": [ + "gateway_incident_or_change_ref", + "actor_attribution_ref", + "change_time_window_ref", + "change_intent_or_break_glass_ref", + "before_route_state_ref", + "after_route_state_ref", + "source_live_diff_state_ref", + "nginx_test_readback_ref", + "nginx_reload_or_no_reload_ref", + "route_smoke_readback_ref", + "tls_acme_readback_ref", + "websocket_readback_ref", + "upstream_health_ref", + "public_admin_api_route_impact_ref", + "ai_provider_impact_ref", + "monitoring_alert_ref", + "operator_notification_ref", + "cross_project_sync_ref", + "rollback_validation_ref", + "post_change_monitoring_ref", + "recovery_or_still_degraded_ref", + "postcheck_readback_ref", + "recurrence_guard_ref", + "maintenance_window", + "rollback_owner", + "followup_owner", + "redacted_evidence_refs", + "no_secret_value_attestation", + "no_raw_live_conf_or_diff_attestation", + "no_false_green_attestation" + ], + "reviewer_checks": [ + "source_diff_acceptance_current", + "incident_or_change_ref_present", + "actor_attribution_present", + "change_time_window_present", + "intent_or_break_glass_present", + "before_after_route_state_present", + "source_live_diff_state_present", + "nginx_test_readback_is_owner_provided", + "reload_or_no_reload_called_out", + "route_smoke_readback_present", + "tls_acme_readback_present", + "websocket_readback_present", + "upstream_health_present", + "public_admin_api_route_impact_present", + "ai_provider_impact_present", + "monitoring_alert_present", + "operator_notification_present", + "cross_project_sync_present", + "rollback_validation_present", + "post_change_monitoring_present", + "recovery_or_still_degraded_present", + "postcheck_independent", + "recurrence_guard_present", + "maintenance_window_present", + "no_false_green", + "raw_payload_absent", + "runtime_stays_zero", + "counts_transition_safe" + ], + "reviewer_outcome": "waiting_post_incident_readback", + "rollback_owner": "pending_post_incident_readback", + "rollback_validation_accepted": false, + "rollback_validation_ref": null, + "route_smoke_authorized": false, + "route_smoke_executed": false, + "route_smoke_readback_accepted": false, + "route_smoke_readback_ref": null, + "runtime_gate": false, + "source_diff_acceptance_id": "public_gateway_rendered_diff_acceptance:host188_internal_tools_https", + "source_live_diff_state_accepted": false, + "source_live_diff_state_ref": null, + "status": "waiting_post_incident_readback", + "tls_acme_readback_accepted": false, + "tls_acme_readback_ref": null, + "upstream_health_accepted": false, + "upstream_health_ref": null, + "websocket_readback_accepted": false, + "websocket_readback_ref": null, + "websocket_route_change_authorized": false, + "write_capable": true + }, + { + "acme_challenge_change_authorized": false, + "action_buttons_allowed": false, + "actor_attribution_accepted": false, + "actor_attribution_ref": null, + "admin_route_change_authorized": false, + "after_route_state_ref": null, + "ai_provider_impact_accepted": false, + "ai_provider_impact_ref": null, + "before_after_route_state_accepted": false, + "before_route_state_ref": null, + "blocked_actions": [ + "ssh_read_live_nginx_conf", + "store_raw_live_conf", + "store_full_rendered_diff_payload", + "collect_secret_value", + "collect_private_key", + "collect_certificate_body", + "collect_cookie_or_token", + "run_nginx_test", + "reload_nginx", + "restart_nginx", + "change_nginx_site_enabled", + "change_public_route", + "change_admin_route", + "change_api_route", + "change_websocket_route", + "change_acme_challenge_route", + "change_upstream", + "change_dns_record", + "tls_probe", + "dns_probe", + "certbot_renew", + "certbot_reconfigure", + "route_smoke", + "websocket_smoke", + "host_write", + "firewall_change", + "docker_restart", + "systemd_restart", + "argocd_sync", + "kubectl_apply", + "accept_route_200_as_all_green", + "accept_nginx_active_as_all_green", + "accept_dashboard_up_as_security_acceptance", + "accept_cd_success_as_security_acceptance", + "skip_cross_project_sync", + "skip_operator_notification", + "skip_rollback_validation", + "skip_post_change_monitoring", + "mark_readback_accepted_without_reviewer_record", + "open_runtime_gate", + "add_action_button" + ], + "certbot_renew_authorized": false, + "change_intent_or_break_glass_ref": null, + "change_time_window_accepted": false, + "change_time_window_ref": null, + "config_id": "host110_ollama_proxy", + "control_tier": "C1", + "cross_project_sync_accepted": false, + "cross_project_sync_ref": null, + "diff_gate_id": "public_gateway_rendered_diff_gate:host110_ollama_proxy", + "dns_tls_probe_authorized": false, + "followup_owner": "pending_post_incident_readback", + "gateway_incident_or_change_ref": null, + "host": "192.168.0.110", + "host_live_conf_read_authorized": false, + "intent_or_break_glass_accepted": false, + "live_path": "/etc/nginx/sites-enabled/110-ollama-proxy.conf", + "maintenance_window": "pending_post_incident_readback", + "monitoring_alert_accepted": false, + "monitoring_alert_ref": null, + "nginx_reload_authorized": false, + "nginx_reload_executed": false, + "nginx_reload_or_no_reload_accepted": false, + "nginx_reload_or_no_reload_ref": null, + "nginx_test_authorized": false, + "nginx_test_executed": false, + "nginx_test_readback_accepted": false, + "nginx_test_readback_ref": null, + "no_false_green_accepted": false, + "not_approval": true, + "operator_notification_accepted": false, + "operator_notification_ref": null, + "outcome_lanes": [ + "waiting_post_incident_readback", + "request_actor_or_time_supplement", + "request_route_state_supplement", + "request_diff_test_supplement", + "request_dependency_supplement", + "quarantine_raw_payload", + "reject_false_green_claim", + "ready_for_gateway_post_incident_review", + "recurrence_guard_backfill_required", + "waiting_runtime_gate" + ], + "owner_response_acceptance_id": "public_gateway_owner_response_acceptance:host110_ollama_proxy", + "post_change_monitoring_accepted": false, + "post_change_monitoring_ref": null, + "post_incident_readback_accepted": false, + "post_incident_readback_candidate_id": "public_gateway_post_incident_readback:host110_ollama_proxy", + "post_incident_readback_received": false, + "postcheck_readback_accepted": false, + "postcheck_readback_ref": null, + "production_write_authorized": false, + "public_admin_api_route_impact_accepted": false, + "public_admin_api_route_impact_ref": null, + "public_gateway_reload_authorized": false, + "public_route_change_authorized": false, + "readback_fields": [ + "post_incident_readback_candidate_id", + "source_diff_acceptance_id", + "owner_response_acceptance_id", + "diff_gate_id", + "config_id", + "control_tier", + "host", + "live_path", + "gateway_incident_or_change_ref", + "actor_attribution_ref", + "change_time_window_ref", + "change_intent_or_break_glass_ref", + "before_route_state_ref", + "after_route_state_ref", + "source_live_diff_state_ref", + "nginx_test_readback_ref", + "nginx_reload_or_no_reload_ref", + "route_smoke_readback_ref", + "tls_acme_readback_ref", + "websocket_readback_ref", + "upstream_health_ref", + "public_admin_api_route_impact_ref", + "ai_provider_impact_ref", + "monitoring_alert_ref", + "operator_notification_ref", + "cross_project_sync_ref", + "rollback_validation_ref", + "post_change_monitoring_ref", + "recovery_or_still_degraded_ref", + "postcheck_readback_ref", + "recurrence_guard_ref", + "maintenance_window", + "rollback_owner", + "reviewer_outcome", + "followup_owner", + "not_approval" + ], + "recovery_or_still_degraded_accepted": false, + "recovery_or_still_degraded_ref": null, + "recurrence_guard_accepted": false, + "recurrence_guard_ref": null, + "required_readback_fields": [ + "gateway_incident_or_change_ref", + "actor_attribution_ref", + "change_time_window_ref", + "change_intent_or_break_glass_ref", + "before_route_state_ref", + "after_route_state_ref", + "source_live_diff_state_ref", + "nginx_test_readback_ref", + "nginx_reload_or_no_reload_ref", + "route_smoke_readback_ref", + "tls_acme_readback_ref", + "websocket_readback_ref", + "upstream_health_ref", + "public_admin_api_route_impact_ref", + "ai_provider_impact_ref", + "monitoring_alert_ref", + "operator_notification_ref", + "cross_project_sync_ref", + "rollback_validation_ref", + "post_change_monitoring_ref", + "recovery_or_still_degraded_ref", + "postcheck_readback_ref", + "recurrence_guard_ref", + "maintenance_window", + "rollback_owner", + "followup_owner", + "redacted_evidence_refs", + "no_secret_value_attestation", + "no_raw_live_conf_or_diff_attestation", + "no_false_green_attestation" + ], + "reviewer_checks": [ + "source_diff_acceptance_current", + "incident_or_change_ref_present", + "actor_attribution_present", + "change_time_window_present", + "intent_or_break_glass_present", + "before_after_route_state_present", + "source_live_diff_state_present", + "nginx_test_readback_is_owner_provided", + "reload_or_no_reload_called_out", + "route_smoke_readback_present", + "tls_acme_readback_present", + "websocket_readback_present", + "upstream_health_present", + "public_admin_api_route_impact_present", + "ai_provider_impact_present", + "monitoring_alert_present", + "operator_notification_present", + "cross_project_sync_present", + "rollback_validation_present", + "post_change_monitoring_present", + "recovery_or_still_degraded_present", + "postcheck_independent", + "recurrence_guard_present", + "maintenance_window_present", + "no_false_green", + "raw_payload_absent", + "runtime_stays_zero", + "counts_transition_safe" + ], + "reviewer_outcome": "waiting_post_incident_readback", + "rollback_owner": "pending_post_incident_readback", + "rollback_validation_accepted": false, + "rollback_validation_ref": null, + "route_smoke_authorized": false, + "route_smoke_executed": false, + "route_smoke_readback_accepted": false, + "route_smoke_readback_ref": null, + "runtime_gate": false, + "source_diff_acceptance_id": "public_gateway_rendered_diff_acceptance:host110_ollama_proxy", + "source_live_diff_state_accepted": false, + "source_live_diff_state_ref": null, + "status": "waiting_post_incident_readback", + "tls_acme_readback_accepted": false, + "tls_acme_readback_ref": null, + "upstream_health_accepted": false, + "upstream_health_ref": null, + "websocket_readback_accepted": false, + "websocket_readback_ref": null, + "websocket_route_change_authorized": false, + "write_capable": true + } + ], + "required_readback_fields": [ + "gateway_incident_or_change_ref", + "actor_attribution_ref", + "change_time_window_ref", + "change_intent_or_break_glass_ref", + "before_route_state_ref", + "after_route_state_ref", + "source_live_diff_state_ref", + "nginx_test_readback_ref", + "nginx_reload_or_no_reload_ref", + "route_smoke_readback_ref", + "tls_acme_readback_ref", + "websocket_readback_ref", + "upstream_health_ref", + "public_admin_api_route_impact_ref", + "ai_provider_impact_ref", + "monitoring_alert_ref", + "operator_notification_ref", + "cross_project_sync_ref", + "rollback_validation_ref", + "post_change_monitoring_ref", + "recovery_or_still_degraded_ref", + "postcheck_readback_ref", + "recurrence_guard_ref", + "maintenance_window", + "rollback_owner", + "followup_owner", + "redacted_evidence_refs", + "no_secret_value_attestation", + "no_raw_live_conf_or_diff_attestation", + "no_false_green_attestation" + ], + "reviewer_checks": [ + { + "check_id": "source_diff_acceptance_current", + "instruction": "來源 rendered diff acceptance snapshot 必須是目前版本。" + }, + { + "check_id": "incident_or_change_ref_present", + "instruction": "必須有 incident、change、ticket 或 maintenance ref,不能只寫路由恢復。" + }, + { + "check_id": "actor_attribution_present", + "instruction": "必須標示 actor role / team,不接受匿名 Nginx reload、shell 操作或未追蹤自動化。" + }, + { + "check_id": "change_time_window_present", + "instruction": "必須有變更 / 事故時間窗,供 route、alert 與跨專案回讀對齊。" + }, + { + "check_id": "intent_or_break_glass_present", + "instruction": "正常變更需有 change intent;緊急變更需有 break-glass reason,但 break-glass 不等於事前批准。" + }, + { + "check_id": "before_after_route_state_present", + "instruction": "必須有 before / after route state ref,不得只看現在 200。" + }, + { + "check_id": "source_live_diff_state_present", + "instruction": "必須說明 source-to-live diff 狀態;不得保存 raw live conf 或完整 diff payload。" + }, + { + "check_id": "nginx_test_readback_is_owner_provided", + "instruction": "`nginx -t` 只能是 owner-provided readback ref,本工具不得執行。" + }, + { + "check_id": "reload_or_no_reload_called_out", + "instruction": "需明確標示是否曾 reload;若未 reload 也需列原因與風險。" + }, + { + "check_id": "route_smoke_readback_present", + "instruction": "route smoke readback 必須列 affected routes、status、TLS、WebSocket、ACME 或不適用原因。" + }, + { + "check_id": "tls_acme_readback_present", + "instruction": "TLS / ACME impact 不可被 route 200 取代。" + }, + { + "check_id": "websocket_readback_present", + "instruction": "WebSocket / streaming route 需獨立回讀,不得只用首頁 200。" + }, + { + "check_id": "upstream_health_present", + "instruction": "upstream health / port / dependency 影響必須列 ref。" + }, + { + "check_id": "public_admin_api_route_impact_present", + "instruction": "public、admin、API、callback 或 webhook route 受影響時需列 impact ref。" + }, + { + "check_id": "ai_provider_impact_present", + "instruction": "Ollama、AI provider、model route 或 proxy 受影響時需列 impact ref。" + }, + { + "check_id": "monitoring_alert_present", + "instruction": "需有 monitoring / alert / incident ref,不能只靠人工看頁面。" + }, + { + "check_id": "operator_notification_present", + "instruction": "需提供已通知受影響產品、owner 或 Session 的脫敏 ref。" + }, + { + "check_id": "cross_project_sync_present", + "instruction": "若影響 AwoooP、IwoooS、agent-bounty、StockPlatform、公開網站或監控,需有跨專案同步 ref。" + }, + { + "check_id": "rollback_validation_present", + "instruction": "必須提供 rollback validation ref,包含 rollback owner 與回滾後驗證方式。" + }, + { + "check_id": "post_change_monitoring_present", + "instruction": "必須有 post-change monitoring window,觀察 route、error、alert 與 upstream。" + }, + { + "check_id": "recovery_or_still_degraded_present", + "instruction": "已恢復需提供恢復時間與證據;未恢復需提供 still-degraded ref。" + }, + { + "check_id": "postcheck_independent", + "instruction": "post-check 必須獨立於原操作人與 UI 卡片。" + }, + { + "check_id": "recurrence_guard_present", + "instruction": "必須提出防再發 guard、change freeze、owner review 或 automation block。" + }, + { + "check_id": "maintenance_window_present", + "instruction": "後續任何 nginx -t、reload、route smoke 或 DNS / TLS 動作都需維護窗口。" + }, + { + "check_id": "no_false_green", + "instruction": "不得只用 route 200、Nginx active、dashboard up、CD success 或 UI 可見當成事故已驗收。" + }, + { + "check_id": "raw_payload_absent", + "instruction": "不得保存 raw live conf、完整 diff payload、private key、憑證內容、cookie、token 或未脫敏 screenshot。" + }, + { + "check_id": "runtime_stays_zero", + "instruction": "readback plan 不得觸發 nginx -t、reload、route smoke、DNS / TLS probe、certbot renew 或 host write。" + }, + { + "check_id": "counts_transition_safe", + "instruction": "只有 reviewer record 能更新 accepted count,且不得同時開 runtime gate。" + } + ], + "schema_version": "public_gateway_post_incident_readback_plan_v1", + "source_paths": [ + "docs/security/PUBLIC-GATEWAY-RENDERED-DIFF-ACCEPTANCE.md", + "docs/security/public-gateway-rendered-diff-acceptance.snapshot.json", + "docs/security/PUBLIC-GATEWAY-OWNER-RESPONSE-ACCEPTANCE.md", + "docs/security/public-gateway-owner-response-acceptance.snapshot.json", + "docs/security/PUBLIC-GATEWAY-PREFLIGHT-INVENTORY.md", + "docs/security/public-gateway-preflight-inventory.snapshot.json" + ], + "source_schema_version": "public_gateway_rendered_diff_acceptance_v1", + "source_status": "rendered_diff_acceptance_ledger_ready_no_runtime_action", + "status": "post_incident_readback_plan_ready_no_runtime_action", + "summary": { + "acme_challenge_change_authorized_count": 0, + "action_button_count": 0, + "actor_attribution_accepted_count": 0, + "admin_route_change_authorized_count": 0, + "ai_monitoring_cross_project_review_required_candidate_count": 3, + "ai_provider_impact_accepted_count": 0, + "before_after_route_state_accepted_count": 0, + "blocked_action_count": 41, + "c0_readback_candidate_count": 2, + "c1_readback_candidate_count": 1, + "certbot_renew_authorized_count": 0, + "change_time_window_accepted_count": 0, + "coverage_percent_after_readback_plan": 92, + "cross_project_sync_accepted_count": 0, + "dns_tls_probe_authorized_count": 0, + "host_live_conf_read_authorized_count": 0, + "intent_or_break_glass_accepted_count": 0, + "monitoring_alert_accepted_count": 0, + "nginx_reload_authorized_count": 0, + "nginx_reload_executed_count": 0, + "nginx_reload_or_no_reload_accepted_count": 0, + "nginx_test_authorized_count": 0, + "nginx_test_executed_count": 0, + "nginx_test_readback_accepted_count": 0, + "no_false_green_accepted_count": 0, + "no_false_green_required_candidate_count": 3, + "operator_notification_accepted_count": 0, + "outcome_lane_count": 10, + "post_change_monitoring_accepted_count": 0, + "post_incident_readback_accepted_count": 0, + "post_incident_readback_received_count": 0, + "postcheck_readback_accepted_count": 0, + "public_admin_api_route_impact_accepted_count": 0, + "public_gateway_reload_authorized_count": 0, + "public_route_change_authorized_count": 0, + "readback_candidate_count": 3, + "readback_field_count": 36, + "recovery_or_still_degraded_accepted_count": 0, + "recurrence_guard_accepted_count": 0, + "required_readback_field_count": 30, + "reviewer_check_count": 28, + "rollback_validation_accepted_count": 0, + "route_health_review_required_candidate_count": 3, + "route_smoke_authorized_count": 0, + "route_smoke_executed_count": 0, + "route_smoke_readback_accepted_count": 0, + "runtime_gate_count": 0, + "source_blocked_action_count": 22, + "source_c0_diff_acceptance_candidate_count": 2, + "source_c1_diff_acceptance_candidate_count": 1, + "source_diff_acceptance_candidate_count": 3, + "source_live_diff_state_accepted_count": 0, + "source_nginx_test_evidence_accepted_count": 0, + "source_rendered_diff_accepted_count": 0, + "source_required_evidence_field_count": 14, + "source_reviewer_check_count": 15, + "source_route_smoke_result_accepted_count": 0, + "source_runtime_gate_count": 0, + "tls_acme_readback_accepted_count": 0, + "upstream_health_accepted_count": 0, + "upstream_websocket_tls_review_required_candidate_count": 3, + "websocket_readback_accepted_count": 0, + "websocket_route_change_authorized_count": 0, + "write_capable_readback_candidate_count": 3 + } +} diff --git a/scripts/security/high-value-config-control-coverage.py b/scripts/security/high-value-config-control-coverage.py index ce3682fa3..e98298e00 100644 --- a/scripts/security/high-value-config-control-coverage.py +++ b/scripts/security/high-value-config-control-coverage.py @@ -24,8 +24,8 @@ TAIPEI = timezone(timedelta(hours=8)) CONTROL_STATUS_BY_CATEGORY = { "nginx_public_gateway": { - "coverage_status": "emergency_change_backfill_ready_needs_owner_live_diff", - "coverage_percent": 90, + "coverage_status": "post_incident_readback_plan_ready_needs_public_gateway_owner_evidence", + "coverage_percent": 92, "evidence_refs": [ "docs/security/NGINX-CONFIG-DRIFT-DETECTOR.md", "docs/security/nginx-config-drift-repo.snapshot.json", @@ -36,10 +36,12 @@ CONTROL_STATUS_BY_CATEGORY = { "docs/security/public-gateway-owner-response-acceptance.snapshot.json", "docs/security/PUBLIC-GATEWAY-RENDERED-DIFF-ACCEPTANCE.md", "docs/security/public-gateway-rendered-diff-acceptance.snapshot.json", + "docs/security/PUBLIC-GATEWAY-POST-INCIDENT-READBACK-PLAN.md", + "docs/security/public-gateway-post-incident-readback-plan.snapshot.json", "docs/schemas/public_gateway_preflight_inventory_v1.schema.json", ], - "current_gap": "已固定 owner response acceptance、手動 / 緊急 gateway 變更回補欄位與 rendered diff evidence acceptance 只讀帳本;owner response、live conf、rendered diff evidence、nginx -t evidence、route smoke evidence、maintenance window 與 rollback owner 仍全部為 0。", - "next_owner_action": "補 public gateway owner 回覆、owner-provided live conf、source-to-live rendered diff ref、nginx -t evidence ref、route smoke evidence ref、change intent / break-glass、route health impact、rollback validation、maintenance window 與 rollback owner。", + "current_gap": "已固定 owner response acceptance、手動 / 緊急 gateway 變更回補欄位、rendered diff evidence acceptance 與 post-incident readback plan 只讀帳本;actor、時間窗、改前改後 route、source-to-live diff、nginx -t readback、reload / no-reload、route smoke、TLS / ACME、WebSocket、upstream、AI provider、monitoring、跨專案同步、防再發與 no-false-green 已納入;owner response、live conf、rendered diff evidence、nginx -t evidence、route smoke evidence、事故回讀包、maintenance window 與 rollback owner 仍全部為 0。", + "next_owner_action": "補 Public Gateway / Nginx 事故回讀包:owner 回覆、owner-provided live conf、source-to-live rendered diff ref、nginx -t evidence ref、route smoke evidence ref、change intent / break-glass、actor role / team、變更時間窗、改前改後 route state、upstream / WebSocket / TLS / ACME 影響、AI provider / monitoring 影響、跨專案同步、route health impact、rollback validation、post-change monitoring、recurrence guard、maintenance window 與 rollback owner。", }, "dns_tls_certbot": { "coverage_status": "owner_response_acceptance_ledger_ready_needs_certificate_owner_evidence", @@ -382,6 +384,7 @@ def build_report(root: Path, generated_at: str | None) -> dict[str, Any]: "owner_response_acceptance_ledger_ready_needs_owner_live_diff", "rendered_diff_acceptance_ledger_ready_needs_owner_live_diff", "emergency_change_backfill_ready_needs_owner_live_diff", + "post_incident_readback_plan_ready_needs_public_gateway_owner_evidence", "owner_response_acceptance_ledger_ready_needs_runtime_evidence", "change_evidence_acceptance_ready_needs_gitops_owner_evidence", "post_incident_readback_plan_ready_needs_gitops_owner_evidence", diff --git a/scripts/security/iwooos-config-control-guard.py b/scripts/security/iwooos-config-control-guard.py index 034a63d26..5a9ae79f8 100644 --- a/scripts/security/iwooos-config-control-guard.py +++ b/scripts/security/iwooos-config-control-guard.py @@ -17,7 +17,7 @@ from typing import Any EXPECTED_CATEGORIES = { - "nginx_public_gateway": 90, + "nginx_public_gateway": 92, "dns_tls_certbot": 78, "k8s_production_gitops": 66, "secret_metadata": 68, @@ -50,6 +50,7 @@ REQUIRED_CONTROL_DOCS = [ "docs/security/PUBLIC-GATEWAY-PREFLIGHT-INVENTORY.md", "docs/security/PUBLIC-GATEWAY-OWNER-RESPONSE-ACCEPTANCE.md", "docs/security/PUBLIC-GATEWAY-RENDERED-DIFF-ACCEPTANCE.md", + "docs/security/PUBLIC-GATEWAY-POST-INCIDENT-READBACK-PLAN.md", "docs/security/DOMAIN-TLS-CERTBOT-OWNER-RESPONSE-ACCEPTANCE.md", "docs/security/HOST-SERVICE-OWNER-RESPONSE-ACCEPTANCE.md", "docs/security/HOST-SERVICE-CHANGE-EVIDENCE-ACCEPTANCE.md", @@ -149,6 +150,85 @@ ARTIFACT_SPECS = [ "runtime_gate_count": 0, }, }, + { + "label": "public gateway post incident readback plan", + "path": "docs/security/public-gateway-post-incident-readback-plan.snapshot.json", + "schema": "public_gateway_post_incident_readback_plan_v1", + "status": "post_incident_readback_plan_ready_no_runtime_action", + "list_counts": { + "readback_candidates": 3, + "blocked_actions": 41, + "reviewer_checks": 28, + "outcome_lanes": 10, + "required_readback_fields": 30, + }, + "summary_counts": { + "source_diff_acceptance_candidate_count": 3, + "source_c0_diff_acceptance_candidate_count": 2, + "source_c1_diff_acceptance_candidate_count": 1, + "source_required_evidence_field_count": 14, + "source_reviewer_check_count": 15, + "source_blocked_action_count": 22, + "source_rendered_diff_accepted_count": 0, + "source_nginx_test_evidence_accepted_count": 0, + "source_route_smoke_result_accepted_count": 0, + "source_runtime_gate_count": 0, + "readback_candidate_count": 3, + "c0_readback_candidate_count": 2, + "c1_readback_candidate_count": 1, + "write_capable_readback_candidate_count": 3, + "route_health_review_required_candidate_count": 3, + "upstream_websocket_tls_review_required_candidate_count": 3, + "ai_monitoring_cross_project_review_required_candidate_count": 3, + "no_false_green_required_candidate_count": 3, + "readback_field_count": 36, + "required_readback_field_count": 30, + "reviewer_check_count": 28, + "outcome_lane_count": 10, + "blocked_action_count": 41, + "post_incident_readback_received_count": 0, + "post_incident_readback_accepted_count": 0, + "actor_attribution_accepted_count": 0, + "change_time_window_accepted_count": 0, + "intent_or_break_glass_accepted_count": 0, + "before_after_route_state_accepted_count": 0, + "source_live_diff_state_accepted_count": 0, + "nginx_test_readback_accepted_count": 0, + "nginx_reload_or_no_reload_accepted_count": 0, + "route_smoke_readback_accepted_count": 0, + "tls_acme_readback_accepted_count": 0, + "websocket_readback_accepted_count": 0, + "upstream_health_accepted_count": 0, + "public_admin_api_route_impact_accepted_count": 0, + "ai_provider_impact_accepted_count": 0, + "monitoring_alert_accepted_count": 0, + "operator_notification_accepted_count": 0, + "cross_project_sync_accepted_count": 0, + "rollback_validation_accepted_count": 0, + "post_change_monitoring_accepted_count": 0, + "recovery_or_still_degraded_accepted_count": 0, + "postcheck_readback_accepted_count": 0, + "recurrence_guard_accepted_count": 0, + "no_false_green_accepted_count": 0, + "host_live_conf_read_authorized_count": 0, + "nginx_test_authorized_count": 0, + "nginx_test_executed_count": 0, + "nginx_reload_authorized_count": 0, + "nginx_reload_executed_count": 0, + "public_gateway_reload_authorized_count": 0, + "route_smoke_authorized_count": 0, + "route_smoke_executed_count": 0, + "dns_tls_probe_authorized_count": 0, + "certbot_renew_authorized_count": 0, + "public_route_change_authorized_count": 0, + "admin_route_change_authorized_count": 0, + "websocket_route_change_authorized_count": 0, + "acme_challenge_change_authorized_count": 0, + "runtime_gate_count": 0, + "action_button_count": 0, + "coverage_percent_after_readback_plan": 92, + }, + }, { "label": "domain tls certbot owner response acceptance", "path": "docs/security/domain-tls-certbot-owner-response-acceptance.snapshot.json", diff --git a/scripts/security/public-gateway-post-incident-readback-plan.py b/scripts/security/public-gateway-post-incident-readback-plan.py new file mode 100644 index 000000000..9ee19bbe2 --- /dev/null +++ b/scripts/security/public-gateway-post-incident-readback-plan.py @@ -0,0 +1,463 @@ +#!/usr/bin/env python3 +""" +IwoooS Public Gateway / Nginx post-incident readback 只讀計畫產生器。 + +本工具讀取 Public Gateway rendered diff acceptance snapshot,建立事故後回讀 +計畫:誰改了 Nginx / public gateway、變更或事故時間窗、route / upstream / +WebSocket / ACME / AI provider / monitoring 影響、回滾與防再發。它不讀 live +conf、不執行 nginx -t、不 reload、不做 route smoke、不連 DNS / TLS、不 +renew cert、不 SSH、不保存 raw conf / full diff / secret value。 +""" + +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)) + +READBACK_FIELDS = [ + "post_incident_readback_candidate_id", + "source_diff_acceptance_id", + "owner_response_acceptance_id", + "diff_gate_id", + "config_id", + "control_tier", + "host", + "live_path", + "gateway_incident_or_change_ref", + "actor_attribution_ref", + "change_time_window_ref", + "change_intent_or_break_glass_ref", + "before_route_state_ref", + "after_route_state_ref", + "source_live_diff_state_ref", + "nginx_test_readback_ref", + "nginx_reload_or_no_reload_ref", + "route_smoke_readback_ref", + "tls_acme_readback_ref", + "websocket_readback_ref", + "upstream_health_ref", + "public_admin_api_route_impact_ref", + "ai_provider_impact_ref", + "monitoring_alert_ref", + "operator_notification_ref", + "cross_project_sync_ref", + "rollback_validation_ref", + "post_change_monitoring_ref", + "recovery_or_still_degraded_ref", + "postcheck_readback_ref", + "recurrence_guard_ref", + "maintenance_window", + "rollback_owner", + "reviewer_outcome", + "followup_owner", + "not_approval", +] + +REQUIRED_READBACK_FIELDS = [ + "gateway_incident_or_change_ref", + "actor_attribution_ref", + "change_time_window_ref", + "change_intent_or_break_glass_ref", + "before_route_state_ref", + "after_route_state_ref", + "source_live_diff_state_ref", + "nginx_test_readback_ref", + "nginx_reload_or_no_reload_ref", + "route_smoke_readback_ref", + "tls_acme_readback_ref", + "websocket_readback_ref", + "upstream_health_ref", + "public_admin_api_route_impact_ref", + "ai_provider_impact_ref", + "monitoring_alert_ref", + "operator_notification_ref", + "cross_project_sync_ref", + "rollback_validation_ref", + "post_change_monitoring_ref", + "recovery_or_still_degraded_ref", + "postcheck_readback_ref", + "recurrence_guard_ref", + "maintenance_window", + "rollback_owner", + "followup_owner", + "redacted_evidence_refs", + "no_secret_value_attestation", + "no_raw_live_conf_or_diff_attestation", + "no_false_green_attestation", +] + +REVIEWER_CHECKS = [ + {"check_id": "source_diff_acceptance_current", "instruction": "來源 rendered diff acceptance snapshot 必須是目前版本。"}, + {"check_id": "incident_or_change_ref_present", "instruction": "必須有 incident、change、ticket 或 maintenance ref,不能只寫路由恢復。"}, + {"check_id": "actor_attribution_present", "instruction": "必須標示 actor role / team,不接受匿名 Nginx reload、shell 操作或未追蹤自動化。"}, + {"check_id": "change_time_window_present", "instruction": "必須有變更 / 事故時間窗,供 route、alert 與跨專案回讀對齊。"}, + {"check_id": "intent_or_break_glass_present", "instruction": "正常變更需有 change intent;緊急變更需有 break-glass reason,但 break-glass 不等於事前批准。"}, + {"check_id": "before_after_route_state_present", "instruction": "必須有 before / after route state ref,不得只看現在 200。"}, + {"check_id": "source_live_diff_state_present", "instruction": "必須說明 source-to-live diff 狀態;不得保存 raw live conf 或完整 diff payload。"}, + {"check_id": "nginx_test_readback_is_owner_provided", "instruction": "`nginx -t` 只能是 owner-provided readback ref,本工具不得執行。"}, + {"check_id": "reload_or_no_reload_called_out", "instruction": "需明確標示是否曾 reload;若未 reload 也需列原因與風險。"}, + {"check_id": "route_smoke_readback_present", "instruction": "route smoke readback 必須列 affected routes、status、TLS、WebSocket、ACME 或不適用原因。"}, + {"check_id": "tls_acme_readback_present", "instruction": "TLS / ACME impact 不可被 route 200 取代。"}, + {"check_id": "websocket_readback_present", "instruction": "WebSocket / streaming route 需獨立回讀,不得只用首頁 200。"}, + {"check_id": "upstream_health_present", "instruction": "upstream health / port / dependency 影響必須列 ref。"}, + {"check_id": "public_admin_api_route_impact_present", "instruction": "public、admin、API、callback 或 webhook route 受影響時需列 impact ref。"}, + {"check_id": "ai_provider_impact_present", "instruction": "Ollama、AI provider、model route 或 proxy 受影響時需列 impact ref。"}, + {"check_id": "monitoring_alert_present", "instruction": "需有 monitoring / alert / incident ref,不能只靠人工看頁面。"}, + {"check_id": "operator_notification_present", "instruction": "需提供已通知受影響產品、owner 或 Session 的脫敏 ref。"}, + {"check_id": "cross_project_sync_present", "instruction": "若影響 AwoooP、IwoooS、agent-bounty、StockPlatform、公開網站或監控,需有跨專案同步 ref。"}, + {"check_id": "rollback_validation_present", "instruction": "必須提供 rollback validation ref,包含 rollback owner 與回滾後驗證方式。"}, + {"check_id": "post_change_monitoring_present", "instruction": "必須有 post-change monitoring window,觀察 route、error、alert 與 upstream。"}, + {"check_id": "recovery_or_still_degraded_present", "instruction": "已恢復需提供恢復時間與證據;未恢復需提供 still-degraded ref。"}, + {"check_id": "postcheck_independent", "instruction": "post-check 必須獨立於原操作人與 UI 卡片。"}, + {"check_id": "recurrence_guard_present", "instruction": "必須提出防再發 guard、change freeze、owner review 或 automation block。"}, + {"check_id": "maintenance_window_present", "instruction": "後續任何 nginx -t、reload、route smoke 或 DNS / TLS 動作都需維護窗口。"}, + {"check_id": "no_false_green", "instruction": "不得只用 route 200、Nginx active、dashboard up、CD success 或 UI 可見當成事故已驗收。"}, + {"check_id": "raw_payload_absent", "instruction": "不得保存 raw live conf、完整 diff payload、private key、憑證內容、cookie、token 或未脫敏 screenshot。"}, + {"check_id": "runtime_stays_zero", "instruction": "readback plan 不得觸發 nginx -t、reload、route smoke、DNS / TLS probe、certbot renew 或 host write。"}, + {"check_id": "counts_transition_safe", "instruction": "只有 reviewer record 能更新 accepted count,且不得同時開 runtime gate。"}, +] + +OUTCOME_LANES = [ + {"lane_id": "waiting_post_incident_readback", "meaning": "尚未收到 Public Gateway / Nginx 事故回讀包;所有 accepted / runtime count 維持 0。"}, + {"lane_id": "request_actor_or_time_supplement", "meaning": "缺 actor、change time window、intent 或 break-glass reason 時要求補件。"}, + {"lane_id": "request_route_state_supplement", "meaning": "缺 before / after route、upstream、WebSocket、TLS / ACME 或 route smoke readback 時要求補件。"}, + {"lane_id": "request_diff_test_supplement", "meaning": "缺 source-live diff、nginx test readback、reload / no-reload 判定或 rollback validation 時要求補件。"}, + {"lane_id": "request_dependency_supplement", "meaning": "缺 AI provider、monitoring、public/admin/API route 或跨專案影響時要求補件。"}, + {"lane_id": "quarantine_raw_payload", "meaning": "收到 raw conf、完整 diff、secret、憑證、cookie、token 或未脫敏截圖時只能隔離。"}, + {"lane_id": "reject_false_green_claim", "meaning": "把 route 200、Nginx active、dashboard up、CD success 或 UI 可見當驗收時拒收。"}, + {"lane_id": "ready_for_gateway_post_incident_review", "meaning": "metadata 合格後,只能進 reviewer review。"}, + {"lane_id": "recurrence_guard_backfill_required", "meaning": "需補防再發 guard、owner review、change freeze 或 automation block。"}, + {"lane_id": "waiting_runtime_gate", "meaning": "即使 readback accepted,runtime gate 仍需獨立人工批准。"}, +] + +BLOCKED_ACTIONS = [ + "ssh_read_live_nginx_conf", + "store_raw_live_conf", + "store_full_rendered_diff_payload", + "collect_secret_value", + "collect_private_key", + "collect_certificate_body", + "collect_cookie_or_token", + "run_nginx_test", + "reload_nginx", + "restart_nginx", + "change_nginx_site_enabled", + "change_public_route", + "change_admin_route", + "change_api_route", + "change_websocket_route", + "change_acme_challenge_route", + "change_upstream", + "change_dns_record", + "tls_probe", + "dns_probe", + "certbot_renew", + "certbot_reconfigure", + "route_smoke", + "websocket_smoke", + "host_write", + "firewall_change", + "docker_restart", + "systemd_restart", + "argocd_sync", + "kubectl_apply", + "accept_route_200_as_all_green", + "accept_nginx_active_as_all_green", + "accept_dashboard_up_as_security_acceptance", + "accept_cd_success_as_security_acceptance", + "skip_cross_project_sync", + "skip_operator_notification", + "skip_rollback_validation", + "skip_post_change_monitoring", + "mark_readback_accepted_without_reviewer_record", + "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(source: dict[str, Any]) -> dict[str, Any]: + config_id = source["config_id"] + return { + "post_incident_readback_candidate_id": f"public_gateway_post_incident_readback:{config_id}", + "status": "waiting_post_incident_readback", + "source_diff_acceptance_id": source["diff_acceptance_id"], + "owner_response_acceptance_id": source["owner_response_acceptance_id"], + "diff_gate_id": source["diff_gate_id"], + "config_id": config_id, + "control_tier": source["control_tier"], + "host": source["host"], + "live_path": source["live_path"], + "write_capable": True, + "gateway_incident_or_change_ref": None, + "actor_attribution_ref": None, + "change_time_window_ref": None, + "change_intent_or_break_glass_ref": None, + "before_route_state_ref": None, + "after_route_state_ref": None, + "source_live_diff_state_ref": None, + "nginx_test_readback_ref": None, + "nginx_reload_or_no_reload_ref": None, + "route_smoke_readback_ref": None, + "tls_acme_readback_ref": None, + "websocket_readback_ref": None, + "upstream_health_ref": None, + "public_admin_api_route_impact_ref": None, + "ai_provider_impact_ref": None, + "monitoring_alert_ref": None, + "operator_notification_ref": None, + "cross_project_sync_ref": None, + "rollback_validation_ref": None, + "post_change_monitoring_ref": None, + "recovery_or_still_degraded_ref": None, + "postcheck_readback_ref": None, + "recurrence_guard_ref": None, + "maintenance_window": "pending_post_incident_readback", + "rollback_owner": "pending_post_incident_readback", + "reviewer_outcome": "waiting_post_incident_readback", + "followup_owner": "pending_post_incident_readback", + "readback_fields": READBACK_FIELDS, + "required_readback_fields": REQUIRED_READBACK_FIELDS, + "reviewer_checks": [item["check_id"] for item in REVIEWER_CHECKS], + "outcome_lanes": [item["lane_id"] for item in OUTCOME_LANES], + "blocked_actions": BLOCKED_ACTIONS, + "not_approval": True, + "post_incident_readback_received": False, + "post_incident_readback_accepted": False, + "actor_attribution_accepted": False, + "change_time_window_accepted": False, + "intent_or_break_glass_accepted": False, + "before_after_route_state_accepted": False, + "source_live_diff_state_accepted": False, + "nginx_test_readback_accepted": False, + "nginx_reload_or_no_reload_accepted": False, + "route_smoke_readback_accepted": False, + "tls_acme_readback_accepted": False, + "websocket_readback_accepted": False, + "upstream_health_accepted": False, + "public_admin_api_route_impact_accepted": False, + "ai_provider_impact_accepted": False, + "monitoring_alert_accepted": False, + "operator_notification_accepted": False, + "cross_project_sync_accepted": False, + "rollback_validation_accepted": False, + "post_change_monitoring_accepted": False, + "recovery_or_still_degraded_accepted": False, + "postcheck_readback_accepted": False, + "recurrence_guard_accepted": False, + "no_false_green_accepted": False, + "host_live_conf_read_authorized": False, + "nginx_test_authorized": False, + "nginx_test_executed": False, + "nginx_reload_authorized": False, + "nginx_reload_executed": False, + "public_gateway_reload_authorized": False, + "route_smoke_authorized": False, + "route_smoke_executed": False, + "dns_tls_probe_authorized": False, + "certbot_renew_authorized": False, + "public_route_change_authorized": False, + "admin_route_change_authorized": False, + "websocket_route_change_authorized": False, + "acme_challenge_change_authorized": False, + "production_write_authorized": False, + "runtime_gate": False, + "action_buttons_allowed": False, + } + + +def build_report(root: Path, source_report: dict[str, Any], generated_at: str | None) -> dict[str, Any]: + report_time = generated_at or datetime.now(TAIPEI).isoformat(timespec="seconds") + candidates = [build_candidate(item) for item in source_report.get("diff_acceptance_candidates", [])] + c0_candidates = [item for item in candidates if item["control_tier"] == "C0"] + c1_candidates = [item for item in candidates if item["control_tier"] == "C1"] + source_summary = source_report.get("summary", {}) + + return { + "schema_version": "public_gateway_post_incident_readback_plan_v1", + "generated_at": report_time, + "git_commit": git_short_sha(root), + "source_schema_version": source_report.get("schema_version"), + "source_status": source_report.get("status"), + "status": "post_incident_readback_plan_ready_no_runtime_action", + "summary": { + "source_diff_acceptance_candidate_count": source_summary.get("diff_acceptance_candidate_count", 0), + "source_c0_diff_acceptance_candidate_count": source_summary.get( + "c0_diff_acceptance_candidate_count", 0 + ), + "source_c1_diff_acceptance_candidate_count": source_summary.get( + "c1_diff_acceptance_candidate_count", 0 + ), + "source_required_evidence_field_count": source_summary.get("required_evidence_field_count", 0), + "source_reviewer_check_count": source_summary.get("reviewer_check_count", 0), + "source_blocked_action_count": source_summary.get("blocked_action_count", 0), + "source_rendered_diff_accepted_count": source_summary.get("rendered_diff_accepted_count", 0), + "source_nginx_test_evidence_accepted_count": source_summary.get( + "nginx_test_evidence_accepted_count", 0 + ), + "source_route_smoke_result_accepted_count": source_summary.get( + "route_smoke_result_accepted_count", 0 + ), + "source_runtime_gate_count": source_summary.get("runtime_gate_count", 0), + "readback_candidate_count": len(candidates), + "c0_readback_candidate_count": len(c0_candidates), + "c1_readback_candidate_count": len(c1_candidates), + "write_capable_readback_candidate_count": len(candidates), + "route_health_review_required_candidate_count": len(candidates), + "upstream_websocket_tls_review_required_candidate_count": len(candidates), + "ai_monitoring_cross_project_review_required_candidate_count": len(candidates), + "no_false_green_required_candidate_count": len(candidates), + "readback_field_count": len(READBACK_FIELDS), + "required_readback_field_count": len(REQUIRED_READBACK_FIELDS), + "reviewer_check_count": len(REVIEWER_CHECKS), + "outcome_lane_count": len(OUTCOME_LANES), + "blocked_action_count": len(BLOCKED_ACTIONS), + "post_incident_readback_received_count": 0, + "post_incident_readback_accepted_count": 0, + "actor_attribution_accepted_count": 0, + "change_time_window_accepted_count": 0, + "intent_or_break_glass_accepted_count": 0, + "before_after_route_state_accepted_count": 0, + "source_live_diff_state_accepted_count": 0, + "nginx_test_readback_accepted_count": 0, + "nginx_reload_or_no_reload_accepted_count": 0, + "route_smoke_readback_accepted_count": 0, + "tls_acme_readback_accepted_count": 0, + "websocket_readback_accepted_count": 0, + "upstream_health_accepted_count": 0, + "public_admin_api_route_impact_accepted_count": 0, + "ai_provider_impact_accepted_count": 0, + "monitoring_alert_accepted_count": 0, + "operator_notification_accepted_count": 0, + "cross_project_sync_accepted_count": 0, + "rollback_validation_accepted_count": 0, + "post_change_monitoring_accepted_count": 0, + "recovery_or_still_degraded_accepted_count": 0, + "postcheck_readback_accepted_count": 0, + "recurrence_guard_accepted_count": 0, + "no_false_green_accepted_count": 0, + "host_live_conf_read_authorized_count": 0, + "nginx_test_authorized_count": 0, + "nginx_test_executed_count": 0, + "nginx_reload_authorized_count": 0, + "nginx_reload_executed_count": 0, + "public_gateway_reload_authorized_count": 0, + "route_smoke_authorized_count": 0, + "route_smoke_executed_count": 0, + "dns_tls_probe_authorized_count": 0, + "certbot_renew_authorized_count": 0, + "public_route_change_authorized_count": 0, + "admin_route_change_authorized_count": 0, + "websocket_route_change_authorized_count": 0, + "acme_challenge_change_authorized_count": 0, + "runtime_gate_count": 0, + "action_button_count": 0, + "coverage_percent_after_readback_plan": 92, + }, + "readback_candidates": candidates, + "required_readback_fields": REQUIRED_READBACK_FIELDS, + "reviewer_checks": REVIEWER_CHECKS, + "outcome_lanes": OUTCOME_LANES, + "blocked_actions": BLOCKED_ACTIONS, + "execution_boundaries": { + "not_authorization": True, + "host_live_conf_read_authorized": False, + "nginx_test_authorized": False, + "nginx_test_executed": False, + "nginx_reload_authorized": False, + "nginx_reload_executed": False, + "public_gateway_reload_authorized": False, + "route_smoke_authorized": False, + "route_smoke_executed": False, + "dns_tls_probe_authorized": False, + "certbot_renew_authorized": False, + "public_route_change_authorized": False, + "admin_route_change_authorized": False, + "websocket_route_change_authorized": False, + "acme_challenge_change_authorized": False, + "production_write_authorized": False, + "runtime_gate": False, + "action_buttons_allowed": False, + }, + "source_paths": [ + "docs/security/PUBLIC-GATEWAY-RENDERED-DIFF-ACCEPTANCE.md", + "docs/security/public-gateway-rendered-diff-acceptance.snapshot.json", + "docs/security/PUBLIC-GATEWAY-OWNER-RESPONSE-ACCEPTANCE.md", + "docs/security/public-gateway-owner-response-acceptance.snapshot.json", + "docs/security/PUBLIC-GATEWAY-PREFLIGHT-INVENTORY.md", + "docs/security/public-gateway-preflight-inventory.snapshot.json", + ], + "mode": "metadata_only_no_live_conf_no_nginx_reload", + "operator_interpretation": [ + "此計畫只定義 Public Gateway / Nginx 事故後回讀欄位,不代表 live conf 已讀取或可讀取。", + "route 200、Nginx active、dashboard up、CD success、UI 可見都不能單獨當成 gateway 事故驗收。", + "未來若要 nginx -t、reload、route smoke、DNS / TLS probe、certbot renew 或 host write,必須另有維護窗口、rollback owner 與人工批准。", + ], + } + + +def main() -> int: + parser = argparse.ArgumentParser(description="IwoooS Public Gateway / Nginx 事故後回讀只讀計畫產生器") + parser.add_argument("--root", default=".", help="repo root") + parser.add_argument( + "--source-report", + default="docs/security/public-gateway-rendered-diff-acceptance.snapshot.json", + help="public-gateway-rendered-diff-acceptance.py 輸出的 JSON", + ) + parser.add_argument("--output", help="寫出 JSON 報告") + parser.add_argument("--generated-at", help="固定報告時間,供 committed snapshot 使用") + args = parser.parse_args() + + root = Path(args.root).resolve() + source_report = load_json(root / args.source_report) + report = build_report(root, source_report, args.generated_at) + payload = json.dumps(report, ensure_ascii=False, indent=2, sort_keys=True) + + if args.output: + output = Path(args.output) + output.parent.mkdir(parents=True, exist_ok=True) + output.write_text(payload + "\n", encoding="utf-8") + else: + print(payload) + + summary = report["summary"] + print( + "PUBLIC_GATEWAY_POST_INCIDENT_READBACK_PLAN_OK " + f"candidates={summary['readback_candidate_count']} " + f"c0={summary['c0_readback_candidate_count']} " + f"checks={summary['reviewer_check_count']} " + f"lanes={summary['outcome_lane_count']} " + f"accepted={summary['post_incident_readback_accepted_count']} " + f"runtime_gate={summary['runtime_gate_count']}", + file=sys.stderr, + ) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/security/security-mirror-progress-guard.py b/scripts/security/security-mirror-progress-guard.py index 8b796039b..3a505f901 100755 --- a/scripts/security/security-mirror-progress-guard.py +++ b/scripts/security/security-mirror-progress-guard.py @@ -234,6 +234,9 @@ def validate(root: Path) -> None: public_gateway_rendered_diff_acceptance = load_json( security_dir / "public-gateway-rendered-diff-acceptance.snapshot.json" ) + public_gateway_post_incident_readback_plan = load_json( + security_dir / "public-gateway-post-incident-readback-plan.snapshot.json" + ) s49_request_draft = load_json(security_dir / "gitea-inventory-owner-attestation-request-draft.snapshot.json") kali_status = load_json(security_dir / "kali-integration-status.snapshot.json") iwooos_projection_page = ( @@ -3036,12 +3039,12 @@ def validate(root: Path) -> None: assert_equal( "high_value_config_coverage.coverage_categories.nginx.coverage_percent", nginx_gateway_category["coverage_percent"], - 90, + 92, ) assert_equal( "high_value_config_coverage.coverage_categories.nginx.coverage_status", nginx_gateway_category["coverage_status"], - "emergency_change_backfill_ready_needs_owner_live_diff", + "post_incident_readback_plan_ready_needs_public_gateway_owner_evidence", ) for evidence_ref in [ "docs/security/PUBLIC-GATEWAY-PREFLIGHT-INVENTORY.md", @@ -3050,6 +3053,8 @@ def validate(root: Path) -> None: "docs/security/public-gateway-owner-response-acceptance.snapshot.json", "docs/security/PUBLIC-GATEWAY-RENDERED-DIFF-ACCEPTANCE.md", "docs/security/public-gateway-rendered-diff-acceptance.snapshot.json", + "docs/security/PUBLIC-GATEWAY-POST-INCIDENT-READBACK-PLAN.md", + "docs/security/public-gateway-post-incident-readback-plan.snapshot.json", "docs/schemas/public_gateway_preflight_inventory_v1.schema.json", ]: assert_contains( @@ -7120,6 +7125,288 @@ def validate(root: Path) -> None: f"public_gateway_rendered_diff_acceptance.{item['diff_acceptance_id']}.{false_key}", item[false_key], ) + assert_equal( + "public_gateway_post_incident_readback_plan.schema", + public_gateway_post_incident_readback_plan["schema_version"], + "public_gateway_post_incident_readback_plan_v1", + ) + assert_equal( + "public_gateway_post_incident_readback_plan.source_schema_version", + public_gateway_post_incident_readback_plan["source_schema_version"], + "public_gateway_rendered_diff_acceptance_v1", + ) + assert_equal( + "public_gateway_post_incident_readback_plan.status", + public_gateway_post_incident_readback_plan["status"], + "post_incident_readback_plan_ready_no_runtime_action", + ) + expected_public_gateway_post_incident_readback_summary = { + "source_diff_acceptance_candidate_count": 3, + "source_c0_diff_acceptance_candidate_count": 2, + "source_c1_diff_acceptance_candidate_count": 1, + "source_required_evidence_field_count": 14, + "source_reviewer_check_count": 15, + "source_blocked_action_count": 22, + "source_rendered_diff_accepted_count": 0, + "source_nginx_test_evidence_accepted_count": 0, + "source_route_smoke_result_accepted_count": 0, + "source_runtime_gate_count": 0, + "readback_candidate_count": 3, + "c0_readback_candidate_count": 2, + "c1_readback_candidate_count": 1, + "write_capable_readback_candidate_count": 3, + "route_health_review_required_candidate_count": 3, + "upstream_websocket_tls_review_required_candidate_count": 3, + "ai_monitoring_cross_project_review_required_candidate_count": 3, + "no_false_green_required_candidate_count": 3, + "readback_field_count": 36, + "required_readback_field_count": 30, + "reviewer_check_count": 28, + "outcome_lane_count": 10, + "blocked_action_count": 41, + "post_incident_readback_received_count": 0, + "post_incident_readback_accepted_count": 0, + "actor_attribution_accepted_count": 0, + "change_time_window_accepted_count": 0, + "intent_or_break_glass_accepted_count": 0, + "before_after_route_state_accepted_count": 0, + "source_live_diff_state_accepted_count": 0, + "nginx_test_readback_accepted_count": 0, + "nginx_reload_or_no_reload_accepted_count": 0, + "route_smoke_readback_accepted_count": 0, + "tls_acme_readback_accepted_count": 0, + "websocket_readback_accepted_count": 0, + "upstream_health_accepted_count": 0, + "public_admin_api_route_impact_accepted_count": 0, + "ai_provider_impact_accepted_count": 0, + "monitoring_alert_accepted_count": 0, + "operator_notification_accepted_count": 0, + "cross_project_sync_accepted_count": 0, + "rollback_validation_accepted_count": 0, + "post_change_monitoring_accepted_count": 0, + "recovery_or_still_degraded_accepted_count": 0, + "postcheck_readback_accepted_count": 0, + "recurrence_guard_accepted_count": 0, + "no_false_green_accepted_count": 0, + "host_live_conf_read_authorized_count": 0, + "nginx_test_authorized_count": 0, + "nginx_test_executed_count": 0, + "nginx_reload_authorized_count": 0, + "nginx_reload_executed_count": 0, + "public_gateway_reload_authorized_count": 0, + "route_smoke_authorized_count": 0, + "route_smoke_executed_count": 0, + "dns_tls_probe_authorized_count": 0, + "certbot_renew_authorized_count": 0, + "public_route_change_authorized_count": 0, + "admin_route_change_authorized_count": 0, + "websocket_route_change_authorized_count": 0, + "acme_challenge_change_authorized_count": 0, + "runtime_gate_count": 0, + "action_button_count": 0, + "coverage_percent_after_readback_plan": 92, + } + for key, expected in expected_public_gateway_post_incident_readback_summary.items(): + assert_equal( + f"public_gateway_post_incident_readback_plan.summary.{key}", + public_gateway_post_incident_readback_plan["summary"][key], + expected, + ) + for key, value in public_gateway_post_incident_readback_plan["execution_boundaries"].items(): + if key == "not_authorization": + assert_true(f"public_gateway_post_incident_readback_plan.execution_boundaries.{key}", value) + else: + assert_false(f"public_gateway_post_incident_readback_plan.execution_boundaries.{key}", value) + expected_public_gateway_post_incident_readback_ids = [ + "public_gateway_post_incident_readback:host188_all_sites", + "public_gateway_post_incident_readback:host188_internal_tools_https", + "public_gateway_post_incident_readback:host110_ollama_proxy", + ] + assert_equal( + "public_gateway_post_incident_readback_plan.readback_candidate_ids", + [ + item["post_incident_readback_candidate_id"] + for item in public_gateway_post_incident_readback_plan["readback_candidates"] + ], + expected_public_gateway_post_incident_readback_ids, + ) + expected_public_gateway_post_incident_reviewer_checks = [ + "source_diff_acceptance_current", + "incident_or_change_ref_present", + "actor_attribution_present", + "change_time_window_present", + "intent_or_break_glass_present", + "before_after_route_state_present", + "source_live_diff_state_present", + "nginx_test_readback_is_owner_provided", + "reload_or_no_reload_called_out", + "route_smoke_readback_present", + "tls_acme_readback_present", + "websocket_readback_present", + "upstream_health_present", + "public_admin_api_route_impact_present", + "ai_provider_impact_present", + "monitoring_alert_present", + "operator_notification_present", + "cross_project_sync_present", + "rollback_validation_present", + "post_change_monitoring_present", + "recovery_or_still_degraded_present", + "postcheck_independent", + "recurrence_guard_present", + "maintenance_window_present", + "no_false_green", + "raw_payload_absent", + "runtime_stays_zero", + "counts_transition_safe", + ] + assert_equal( + "public_gateway_post_incident_readback_plan.reviewer_check_ids", + [item["check_id"] for item in public_gateway_post_incident_readback_plan["reviewer_checks"]], + expected_public_gateway_post_incident_reviewer_checks, + ) + expected_public_gateway_post_incident_outcome_lanes = [ + "waiting_post_incident_readback", + "request_actor_or_time_supplement", + "request_route_state_supplement", + "request_diff_test_supplement", + "request_dependency_supplement", + "quarantine_raw_payload", + "reject_false_green_claim", + "ready_for_gateway_post_incident_review", + "recurrence_guard_backfill_required", + "waiting_runtime_gate", + ] + assert_equal( + "public_gateway_post_incident_readback_plan.outcome_lane_ids", + [item["lane_id"] for item in public_gateway_post_incident_readback_plan["outcome_lanes"]], + expected_public_gateway_post_incident_outcome_lanes, + ) + expected_public_gateway_post_incident_blocked_actions = [ + "ssh_read_live_nginx_conf", + "store_raw_live_conf", + "store_full_rendered_diff_payload", + "collect_secret_value", + "collect_private_key", + "collect_certificate_body", + "collect_cookie_or_token", + "run_nginx_test", + "reload_nginx", + "restart_nginx", + "change_nginx_site_enabled", + "change_public_route", + "change_admin_route", + "change_api_route", + "change_websocket_route", + "change_acme_challenge_route", + "change_upstream", + "change_dns_record", + "tls_probe", + "dns_probe", + "certbot_renew", + "certbot_reconfigure", + "route_smoke", + "websocket_smoke", + "host_write", + "firewall_change", + "docker_restart", + "systemd_restart", + "argocd_sync", + "kubectl_apply", + "accept_route_200_as_all_green", + "accept_nginx_active_as_all_green", + "accept_dashboard_up_as_security_acceptance", + "accept_cd_success_as_security_acceptance", + "skip_cross_project_sync", + "skip_operator_notification", + "skip_rollback_validation", + "skip_post_change_monitoring", + "mark_readback_accepted_without_reviewer_record", + "open_runtime_gate", + "add_action_button", + ] + assert_equal( + "public_gateway_post_incident_readback_plan.blocked_actions", + public_gateway_post_incident_readback_plan["blocked_actions"], + expected_public_gateway_post_incident_blocked_actions, + ) + for item in public_gateway_post_incident_readback_plan["readback_candidates"]: + assert_equal( + f"public_gateway_post_incident_readback_plan.{item['post_incident_readback_candidate_id']}.field_count", + len(item["readback_fields"]), + 36, + ) + assert_equal( + f"public_gateway_post_incident_readback_plan.{item['post_incident_readback_candidate_id']}.required_readback_fields", + len(item["required_readback_fields"]), + 30, + ) + assert_equal( + f"public_gateway_post_incident_readback_plan.{item['post_incident_readback_candidate_id']}.reviewer_checks", + len(item["reviewer_checks"]), + 28, + ) + assert_equal( + f"public_gateway_post_incident_readback_plan.{item['post_incident_readback_candidate_id']}.outcome_lanes", + len(item["outcome_lanes"]), + 10, + ) + assert_equal( + f"public_gateway_post_incident_readback_plan.{item['post_incident_readback_candidate_id']}.blocked_actions", + len(item["blocked_actions"]), + 41, + ) + assert_true( + f"public_gateway_post_incident_readback_plan.{item['post_incident_readback_candidate_id']}.not_approval", + item["not_approval"], + ) + for false_key in [ + "post_incident_readback_received", + "post_incident_readback_accepted", + "actor_attribution_accepted", + "change_time_window_accepted", + "intent_or_break_glass_accepted", + "before_after_route_state_accepted", + "source_live_diff_state_accepted", + "nginx_test_readback_accepted", + "nginx_reload_or_no_reload_accepted", + "route_smoke_readback_accepted", + "tls_acme_readback_accepted", + "websocket_readback_accepted", + "upstream_health_accepted", + "public_admin_api_route_impact_accepted", + "ai_provider_impact_accepted", + "monitoring_alert_accepted", + "operator_notification_accepted", + "cross_project_sync_accepted", + "rollback_validation_accepted", + "post_change_monitoring_accepted", + "recovery_or_still_degraded_accepted", + "postcheck_readback_accepted", + "recurrence_guard_accepted", + "no_false_green_accepted", + "host_live_conf_read_authorized", + "nginx_test_authorized", + "nginx_test_executed", + "nginx_reload_authorized", + "nginx_reload_executed", + "public_gateway_reload_authorized", + "route_smoke_authorized", + "route_smoke_executed", + "dns_tls_probe_authorized", + "certbot_renew_authorized", + "public_route_change_authorized", + "admin_route_change_authorized", + "websocket_route_change_authorized", + "acme_challenge_change_authorized", + "production_write_authorized", + "runtime_gate", + "action_buttons_allowed", + ]: + assert_false( + f"public_gateway_post_incident_readback_plan.{item['post_incident_readback_candidate_id']}.{false_key}", + item[false_key], + ) for source_path in [ "docs/security/public-gateway-preflight-inventory.snapshot.json", "docs/security/PUBLIC-GATEWAY-PREFLIGHT-INVENTORY.md", @@ -7127,6 +7414,8 @@ def validate(root: Path) -> None: "docs/security/PUBLIC-GATEWAY-OWNER-RESPONSE-ACCEPTANCE.md", "docs/security/public-gateway-rendered-diff-acceptance.snapshot.json", "docs/security/PUBLIC-GATEWAY-RENDERED-DIFF-ACCEPTANCE.md", + "docs/security/public-gateway-post-incident-readback-plan.snapshot.json", + "docs/security/PUBLIC-GATEWAY-POST-INCIDENT-READBACK-PLAN.md", "docs/schemas/public_gateway_preflight_inventory_v1.schema.json", "docs/security/k8s-argocd-owner-response-acceptance.snapshot.json", "docs/security/K8S-ARGOCD-OWNER-RESPONSE-ACCEPTANCE.md", @@ -7677,6 +7966,53 @@ def validate(root: Path) -> None: iwooos_projection["summary"][key], expected, ) + expected_public_gateway_post_incident_projection_summary = { + "nginx_public_gateway_coverage_percent": 92, + "public_gateway_post_incident_readback_plan_candidate_count": 3, + "public_gateway_post_incident_readback_plan_c0_candidate_count": 2, + "public_gateway_post_incident_readback_plan_write_capable_candidate_count": 3, + "public_gateway_post_incident_readback_plan_required_readback_field_count": 30, + "public_gateway_post_incident_readback_plan_reviewer_check_count": 28, + "public_gateway_post_incident_readback_plan_outcome_lane_count": 10, + "public_gateway_post_incident_readback_plan_blocked_action_count": 41, + "public_gateway_post_incident_readback_plan_post_incident_readback_received_count": 0, + "public_gateway_post_incident_readback_plan_post_incident_readback_accepted_count": 0, + "public_gateway_post_incident_readback_plan_actor_attribution_accepted_count": 0, + "public_gateway_post_incident_readback_plan_before_after_route_state_accepted_count": 0, + "public_gateway_post_incident_readback_plan_source_live_diff_state_accepted_count": 0, + "public_gateway_post_incident_readback_plan_nginx_test_readback_accepted_count": 0, + "public_gateway_post_incident_readback_plan_nginx_reload_or_no_reload_accepted_count": 0, + "public_gateway_post_incident_readback_plan_route_smoke_readback_accepted_count": 0, + "public_gateway_post_incident_readback_plan_tls_acme_readback_accepted_count": 0, + "public_gateway_post_incident_readback_plan_websocket_readback_accepted_count": 0, + "public_gateway_post_incident_readback_plan_upstream_health_accepted_count": 0, + "public_gateway_post_incident_readback_plan_public_admin_api_route_impact_accepted_count": 0, + "public_gateway_post_incident_readback_plan_ai_provider_impact_accepted_count": 0, + "public_gateway_post_incident_readback_plan_monitoring_alert_accepted_count": 0, + "public_gateway_post_incident_readback_plan_operator_notification_accepted_count": 0, + "public_gateway_post_incident_readback_plan_cross_project_sync_accepted_count": 0, + "public_gateway_post_incident_readback_plan_rollback_validation_accepted_count": 0, + "public_gateway_post_incident_readback_plan_post_change_monitoring_accepted_count": 0, + "public_gateway_post_incident_readback_plan_postcheck_readback_accepted_count": 0, + "public_gateway_post_incident_readback_plan_recurrence_guard_accepted_count": 0, + "public_gateway_post_incident_readback_plan_no_false_green_accepted_count": 0, + "public_gateway_post_incident_readback_plan_host_live_conf_read_authorized_count": 0, + "public_gateway_post_incident_readback_plan_nginx_test_authorized_count": 0, + "public_gateway_post_incident_readback_plan_nginx_reload_authorized_count": 0, + "public_gateway_post_incident_readback_plan_public_gateway_reload_authorized_count": 0, + "public_gateway_post_incident_readback_plan_route_smoke_authorized_count": 0, + "public_gateway_post_incident_readback_plan_dns_tls_probe_authorized_count": 0, + "public_gateway_post_incident_readback_plan_certbot_renew_authorized_count": 0, + "public_gateway_post_incident_readback_plan_runtime_gate_count": 0, + "public_gateway_post_incident_readback_plan_action_button_count": 0, + "public_gateway_post_incident_readback_plan_coverage_percent_after_readback_plan": 92, + } + for key, expected in expected_public_gateway_post_incident_projection_summary.items(): + assert_equal( + f"iwooos_projection.summary.{key}", + iwooos_projection["summary"][key], + expected, + ) assert_true( "iwooos_projection.summary.high_value_config_owner_packet_first_layer", iwooos_projection["summary"]["high_value_config_owner_packet_first_layer"], @@ -17298,7 +17634,7 @@ def validate(root: Path) -> None: "high_value_config_control_coverage_owner_response_accepted_count=0", "high_value_config_control_coverage_runtime_gate_count=0", "high_value_config_control_coverage_action_button_count=0", - "nginx_public_gateway_coverage_percent=90", + "nginx_public_gateway_coverage_percent=92", "public_gateway_owner_response_acceptance_required_owner_response_field_count=22", "public_gateway_owner_response_acceptance_reviewer_check_count=22", "public_gateway_owner_response_acceptance_outcome_lane_count=8", @@ -17411,6 +17747,18 @@ def validate(root: Path) -> None: "public_gateway_rendered_diff_acceptance_candidate_count=3", "public_gateway_rendered_diff_acceptance_reviewer_check_count=15", "public_gateway_rendered_diff_acceptance_runtime_gate_count=0", + "public_gateway_post_incident_readback_plan_candidate_count=3", + "public_gateway_post_incident_readback_plan_c0_candidate_count=2", + "public_gateway_post_incident_readback_plan_required_readback_field_count=30", + "public_gateway_post_incident_readback_plan_reviewer_check_count=28", + "public_gateway_post_incident_readback_plan_outcome_lane_count=10", + "public_gateway_post_incident_readback_plan_blocked_action_count=41", + "public_gateway_post_incident_readback_plan_post_incident_readback_received_count=0", + "public_gateway_post_incident_readback_plan_post_incident_readback_accepted_count=0", + "public_gateway_post_incident_readback_plan_nginx_test_authorized_count=0", + "public_gateway_post_incident_readback_plan_nginx_reload_authorized_count=0", + "public_gateway_post_incident_readback_plan_route_smoke_authorized_count=0", + "public_gateway_post_incident_readback_plan_runtime_gate_count=0", "public_gateway_preflight_inventory_source_config_count=3", "public_gateway_preflight_inventory_route_impact_count=14", "public_gateway_preflight_inventory_preflight_gate_count=12", @@ -17654,7 +18002,7 @@ def validate(root: Path) -> None: assert_text_contains( "iwooos_page.critical_config_priority_nginx_percent", iwooos_projection_page, - "{ key: 'nginxGateway', rank: 'P0-1', state: '90% / live 0'", + "{ key: 'nginxGateway', rank: 'P0-1', state: '92% / readback 0'", ) assert_text_contains( "iwooos_page.critical_config_priority_dns_percent", @@ -17689,6 +18037,9 @@ def validate(root: Path) -> None: "nginx_public_gateway_live_conf_evidence_received=false", "nginx_public_gateway_rendered_diff_accepted=false", "nginx_public_gateway_rendered_diff_acceptance_candidate_count=3", + "nginx_public_gateway_post_incident_readback_plan_candidate_count=3", + "nginx_public_gateway_post_incident_readback_plan_blocked_action_count=41", + "nginx_public_gateway_post_incident_readback_plan_runtime_gate_count=0", "nginx_public_gateway_nginx_test_evidence_count=0", "secret_metadata_coverage_percent=68", "gitea_workflow_runner_source_control_coverage_percent=72",