feat(iwooos): 強化 public gateway 緊急變更回補
This commit is contained in:
@@ -24,8 +24,8 @@ TAIPEI = timezone(timedelta(hours=8))
|
||||
|
||||
CONTROL_STATUS_BY_CATEGORY = {
|
||||
"nginx_public_gateway": {
|
||||
"coverage_status": "rendered_diff_acceptance_ledger_ready_needs_owner_live_diff",
|
||||
"coverage_percent": 88,
|
||||
"coverage_status": "emergency_change_backfill_ready_needs_owner_live_diff",
|
||||
"coverage_percent": 90,
|
||||
"evidence_refs": [
|
||||
"docs/security/NGINX-CONFIG-DRIFT-DETECTOR.md",
|
||||
"docs/security/nginx-config-drift-repo.snapshot.json",
|
||||
@@ -38,8 +38,8 @@ CONTROL_STATUS_BY_CATEGORY = {
|
||||
"docs/security/public-gateway-rendered-diff-acceptance.snapshot.json",
|
||||
"docs/schemas/public_gateway_preflight_inventory_v1.schema.json",
|
||||
],
|
||||
"current_gap": "已固定 owner response acceptance 與 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、maintenance window 與 rollback owner。",
|
||||
"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。",
|
||||
},
|
||||
"dns_tls_certbot": {
|
||||
"coverage_status": "owner_response_acceptance_ledger_ready_needs_certificate_owner_evidence",
|
||||
@@ -370,6 +370,7 @@ def build_report(root: Path, generated_at: str | None) -> dict[str, Any]:
|
||||
"repo_only_preflight_contract_ready_needs_owner_live_diff",
|
||||
"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",
|
||||
"owner_response_acceptance_ledger_ready_needs_runtime_evidence",
|
||||
"change_evidence_acceptance_ready_needs_gitops_owner_evidence",
|
||||
"secret_injection_change_evidence_acceptance_ready_needs_owner_evidence",
|
||||
|
||||
@@ -17,7 +17,7 @@ from typing import Any
|
||||
|
||||
|
||||
EXPECTED_CATEGORIES = {
|
||||
"nginx_public_gateway": 88,
|
||||
"nginx_public_gateway": 90,
|
||||
"dns_tls_certbot": 78,
|
||||
"k8s_production_gitops": 64,
|
||||
"secret_metadata": 68,
|
||||
@@ -112,10 +112,10 @@ ARTIFACT_SPECS = [
|
||||
"status": "owner_response_acceptance_ledger_ready_no_runtime_action",
|
||||
"list_counts": {
|
||||
"acceptance_candidates": 3,
|
||||
"blocked_actions": 22,
|
||||
"reviewer_checks": 16,
|
||||
"outcome_lanes": 7,
|
||||
"required_owner_response_fields": 16,
|
||||
"blocked_actions": 28,
|
||||
"reviewer_checks": 22,
|
||||
"outcome_lanes": 8,
|
||||
"required_owner_response_fields": 22,
|
||||
},
|
||||
"summary_counts": {
|
||||
"acceptance_candidate_count": 3,
|
||||
|
||||
@@ -46,6 +46,12 @@ ACCEPTANCE_FIELDS = [
|
||||
"change_time_window",
|
||||
"cross_project_impact_ref",
|
||||
"communication_sync_ref",
|
||||
"change_intent_or_ticket_ref",
|
||||
"pre_change_approval_ref",
|
||||
"break_glass_reason_ref",
|
||||
"route_health_impact_ref",
|
||||
"rollback_validation_ref",
|
||||
"post_change_monitoring_window",
|
||||
"reviewer_outcome",
|
||||
"followup_owner",
|
||||
"not_approval",
|
||||
@@ -67,6 +73,12 @@ REQUIRED_OWNER_RESPONSE_FIELDS = [
|
||||
"change_time_window",
|
||||
"cross_project_impact_ref",
|
||||
"communication_sync_ref",
|
||||
"change_intent_or_ticket_ref",
|
||||
"pre_change_approval_ref",
|
||||
"break_glass_reason_ref",
|
||||
"route_health_impact_ref",
|
||||
"rollback_validation_ref",
|
||||
"post_change_monitoring_window",
|
||||
"followup_owner",
|
||||
]
|
||||
|
||||
@@ -131,6 +143,30 @@ REVIEWER_CHECKS = [
|
||||
"check_id": "communication_sync_ref_present",
|
||||
"instruction": "必須提供通知或協調 ref,證明相關專案 owner 已收到影響與回復狀態。",
|
||||
},
|
||||
{
|
||||
"check_id": "change_intent_or_ticket_ref_present",
|
||||
"instruction": "每次 Public Gateway / Nginx 變更都必須有 change intent、ticket、incident 或 maintenance ref,不接受口頭或聊天截圖當唯一依據。",
|
||||
},
|
||||
{
|
||||
"check_id": "pre_change_approval_or_break_glass_present",
|
||||
"instruction": "正常變更需有 pre-change approval ref;緊急變更需有 break-glass reason ref,且不得把 break-glass 當成事前批准。",
|
||||
},
|
||||
{
|
||||
"check_id": "route_health_impact_present",
|
||||
"instruction": "必須提供 route health / service health impact ref,確認受影響 domain、upstream、WebSocket、ACME 與 API 是否恢復。",
|
||||
},
|
||||
{
|
||||
"check_id": "rollback_validation_present",
|
||||
"instruction": "必須提供 rollback validation ref,證明回滾路徑、回滾 owner 與回滾後驗證方式可追溯。",
|
||||
},
|
||||
{
|
||||
"check_id": "post_change_monitoring_window_present",
|
||||
"instruction": "必須提供 post-change monitoring window,讓告警、route smoke 與跨專案回復狀態能對齊。",
|
||||
},
|
||||
{
|
||||
"check_id": "manual_change_not_silent",
|
||||
"instruction": "任何手動或緊急 gateway 變更不得靜默收件;若缺 actor、意圖、影響、通知或回滾驗證,必須走補件或拒收。",
|
||||
},
|
||||
{
|
||||
"check_id": "counts_transition_safe",
|
||||
"instruction": "只有 reviewer record 可更新 received / accepted / rejected;不得同時開 runtime gate。",
|
||||
@@ -162,6 +198,10 @@ OUTCOME_LANES = [
|
||||
"lane_id": "owner_review_only_update",
|
||||
"meaning": "只允許更新只讀 owner review ledger,不得修改 Nginx、DNS、TLS 或 workflow。",
|
||||
},
|
||||
{
|
||||
"lane_id": "emergency_change_backfill_required",
|
||||
"meaning": "手動或緊急 gateway 變更只能進事後補件,不得因 break-glass 而自動接受或開 runtime gate。",
|
||||
},
|
||||
{
|
||||
"lane_id": "waiting_runtime_gate",
|
||||
"meaning": "即使 owner response accepted,runtime gate 仍等待獨立人工批准。",
|
||||
@@ -189,6 +229,12 @@ BLOCKED_ACTIONS = [
|
||||
"accept_missing_change_time_window",
|
||||
"skip_cross_project_impact_review",
|
||||
"skip_incident_communication_sync",
|
||||
"accept_silent_manual_nginx_change",
|
||||
"treat_break_glass_as_approval",
|
||||
"mark_gateway_change_resolved_without_route_health",
|
||||
"skip_rollback_validation",
|
||||
"skip_post_change_monitoring",
|
||||
"hide_cross_project_route_impact",
|
||||
"open_runtime_gate",
|
||||
"add_action_button",
|
||||
]
|
||||
@@ -240,6 +286,12 @@ def acceptance_candidate(diff_gate: dict[str, Any]) -> dict[str, Any]:
|
||||
"change_time_window": "pending_owner_response",
|
||||
"cross_project_impact_ref": None,
|
||||
"communication_sync_ref": None,
|
||||
"change_intent_or_ticket_ref": None,
|
||||
"pre_change_approval_ref": None,
|
||||
"break_glass_reason_ref": None,
|
||||
"route_health_impact_ref": None,
|
||||
"rollback_validation_ref": None,
|
||||
"post_change_monitoring_window": "pending_owner_response",
|
||||
"reviewer_outcome": "waiting_owner_response",
|
||||
"followup_owner": "pending_owner_response",
|
||||
"acceptance_fields": ACCEPTANCE_FIELDS,
|
||||
@@ -336,6 +388,12 @@ def build_report(
|
||||
"change_time_window_accepted_count": 0,
|
||||
"cross_project_impact_accepted_count": 0,
|
||||
"communication_sync_accepted_count": 0,
|
||||
"change_intent_accepted_count": 0,
|
||||
"pre_change_approval_accepted_count": 0,
|
||||
"break_glass_reason_accepted_count": 0,
|
||||
"route_health_impact_accepted_count": 0,
|
||||
"rollback_validation_accepted_count": 0,
|
||||
"post_change_monitoring_window_accepted_count": 0,
|
||||
"runtime_gate_count": 0,
|
||||
"action_button_count": 0,
|
||||
},
|
||||
@@ -369,7 +427,7 @@ def build_report(
|
||||
"等待 owner response;未收到前不得更新 accepted count。",
|
||||
"收到回覆後先走 raw payload / secret / scope / evidence ref 檢查,不合格即隔離、拒收或補件。",
|
||||
"metadata 合格也只能進 rendered diff reviewer review;`nginx -t`、reload、route smoke 與 production write 仍需獨立人工批准。",
|
||||
"若涉及手動或緊急 gateway 變更,必須先補 change actor/source、time window、cross-project impact 與 communication sync refs。",
|
||||
"若涉及手動或緊急 gateway 變更,必須先補 change actor/source、time window、cross-project impact、communication sync、change intent、break-glass reason、route health impact、rollback validation 與 post-change monitoring refs。",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@@ -2988,12 +2988,12 @@ def validate(root: Path) -> None:
|
||||
assert_equal(
|
||||
"high_value_config_coverage.coverage_categories.nginx.coverage_percent",
|
||||
nginx_gateway_category["coverage_percent"],
|
||||
88,
|
||||
90,
|
||||
)
|
||||
assert_equal(
|
||||
"high_value_config_coverage.coverage_categories.nginx.coverage_status",
|
||||
nginx_gateway_category["coverage_status"],
|
||||
"rendered_diff_acceptance_ledger_ready_needs_owner_live_diff",
|
||||
"emergency_change_backfill_ready_needs_owner_live_diff",
|
||||
)
|
||||
for evidence_ref in [
|
||||
"docs/security/PUBLIC-GATEWAY-PREFLIGHT-INVENTORY.md",
|
||||
@@ -5862,11 +5862,11 @@ def validate(root: Path) -> None:
|
||||
"acceptance_candidate_count": 3,
|
||||
"c0_acceptance_candidate_count": 2,
|
||||
"c1_acceptance_candidate_count": 1,
|
||||
"acceptance_field_count": 27,
|
||||
"required_owner_response_field_count": 16,
|
||||
"reviewer_check_count": 16,
|
||||
"outcome_lane_count": 7,
|
||||
"blocked_action_count": 22,
|
||||
"acceptance_field_count": 33,
|
||||
"required_owner_response_field_count": 22,
|
||||
"reviewer_check_count": 22,
|
||||
"outcome_lane_count": 8,
|
||||
"blocked_action_count": 28,
|
||||
"request_sent_count": 0,
|
||||
"recipient_confirmed_count": 0,
|
||||
"owner_response_received_count": 0,
|
||||
@@ -5892,6 +5892,12 @@ def validate(root: Path) -> None:
|
||||
"change_time_window_accepted_count": 0,
|
||||
"cross_project_impact_accepted_count": 0,
|
||||
"communication_sync_accepted_count": 0,
|
||||
"change_intent_accepted_count": 0,
|
||||
"pre_change_approval_accepted_count": 0,
|
||||
"break_glass_reason_accepted_count": 0,
|
||||
"route_health_impact_accepted_count": 0,
|
||||
"rollback_validation_accepted_count": 0,
|
||||
"post_change_monitoring_window_accepted_count": 0,
|
||||
"runtime_gate_count": 0,
|
||||
"action_button_count": 0,
|
||||
}
|
||||
@@ -5935,6 +5941,12 @@ def validate(root: Path) -> None:
|
||||
"change_time_window_present",
|
||||
"cross_project_impact_review_present",
|
||||
"communication_sync_ref_present",
|
||||
"change_intent_or_ticket_ref_present",
|
||||
"pre_change_approval_or_break_glass_present",
|
||||
"route_health_impact_present",
|
||||
"rollback_validation_present",
|
||||
"post_change_monitoring_window_present",
|
||||
"manual_change_not_silent",
|
||||
"counts_transition_safe",
|
||||
]
|
||||
assert_equal(
|
||||
@@ -5949,6 +5961,7 @@ def validate(root: Path) -> None:
|
||||
"request_supplement",
|
||||
"ready_for_rendered_diff_review",
|
||||
"owner_review_only_update",
|
||||
"emergency_change_backfill_required",
|
||||
"waiting_runtime_gate",
|
||||
]
|
||||
assert_equal(
|
||||
@@ -5977,6 +5990,12 @@ def validate(root: Path) -> None:
|
||||
"accept_missing_change_time_window",
|
||||
"skip_cross_project_impact_review",
|
||||
"skip_incident_communication_sync",
|
||||
"accept_silent_manual_nginx_change",
|
||||
"treat_break_glass_as_approval",
|
||||
"mark_gateway_change_resolved_without_route_health",
|
||||
"skip_rollback_validation",
|
||||
"skip_post_change_monitoring",
|
||||
"hide_cross_project_route_impact",
|
||||
"open_runtime_gate",
|
||||
"add_action_button",
|
||||
]
|
||||
@@ -5989,27 +6008,27 @@ def validate(root: Path) -> None:
|
||||
assert_equal(
|
||||
f"public_gateway_owner_response_acceptance.{item['acceptance_candidate_id']}.field_count",
|
||||
len(item["acceptance_fields"]),
|
||||
27,
|
||||
33,
|
||||
)
|
||||
assert_equal(
|
||||
f"public_gateway_owner_response_acceptance.{item['acceptance_candidate_id']}.required_owner_response_fields",
|
||||
len(item["required_owner_response_fields"]),
|
||||
16,
|
||||
22,
|
||||
)
|
||||
assert_equal(
|
||||
f"public_gateway_owner_response_acceptance.{item['acceptance_candidate_id']}.reviewer_checks",
|
||||
len(item["reviewer_checks"]),
|
||||
16,
|
||||
22,
|
||||
)
|
||||
assert_equal(
|
||||
f"public_gateway_owner_response_acceptance.{item['acceptance_candidate_id']}.outcome_lanes",
|
||||
len(item["outcome_lanes"]),
|
||||
7,
|
||||
8,
|
||||
)
|
||||
assert_equal(
|
||||
f"public_gateway_owner_response_acceptance.{item['acceptance_candidate_id']}.blocked_actions",
|
||||
len(item["blocked_actions"]),
|
||||
22,
|
||||
28,
|
||||
)
|
||||
assert_true(
|
||||
f"public_gateway_owner_response_acceptance.{item['acceptance_candidate_id']}.not_approval",
|
||||
@@ -16222,14 +16241,18 @@ def validate(root: Path) -> None:
|
||||
"high_value_config_control_coverage_category_count=14",
|
||||
"high_value_config_control_coverage_c0_category_count=8",
|
||||
"high_value_config_control_coverage_c1_category_count=4",
|
||||
"high_value_config_control_coverage_average_percent=68",
|
||||
"high_value_config_control_coverage_average_percent=69",
|
||||
"high_value_config_control_coverage_needs_live_evidence_count=9",
|
||||
"high_value_config_control_coverage_owner_response_required_count=14",
|
||||
"high_value_config_control_coverage_owner_response_received_count=0",
|
||||
"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=88",
|
||||
"nginx_public_gateway_coverage_percent=90",
|
||||
"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",
|
||||
"public_gateway_owner_response_acceptance_blocked_action_count=28",
|
||||
"k8s_production_gitops_coverage_percent=64",
|
||||
"secret_metadata_coverage_percent=68",
|
||||
"gitea_workflow_runner_source_control_coverage_percent=72",
|
||||
@@ -16461,7 +16484,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: '88% / live 0'",
|
||||
"{ key: 'nginxGateway', rank: 'P0-1', state: '90% / live 0'",
|
||||
)
|
||||
assert_text_contains(
|
||||
"iwooos_page.critical_config_priority_dns_percent",
|
||||
|
||||
Reference in New Issue
Block a user