diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 425051408..4d6613a06 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -1543,6 +1543,49 @@ "required": "follow-up gate pointer; active runtime gates=0" } } + }, + "hostEvidenceReviewerChecklist": { + "title": "Host Evidence Reviewer Checklist", + "subtitle": "After reading handoff packets, reviewers can only use this read-only checklist to decide whether the case can move to the next human decision. The checklist does not mark passed, received / accepted, approval, or runtime gates.", + "checkLabel": "Review check", + "verifyLabel": "Verify", + "items": { + "scopeBoundaryMatch": { + "title": "Scope boundary match", + "body": "Confirm the handoff scope matches host coverage, network, service, and exclusions without expanding scan boundary.", + "verify": "compare redacted pointer only; no scan starts" + }, + "ownerDecisionScopeExpiry": { + "title": "Owner decision scope / expiry", + "body": "Confirm the owner decision record has reviewer, scope, constraints, expiry, and is still valid.", + "verify": "read decision pointer only; no approval record created" + }, + "credentialHandlingMetadataOnly": { + "title": "Credential handling metadata only", + "body": "Confirm the reviewer sees only handling method and accountable owner, without plaintext authentication material.", + "verify": "secret value collection=false" + }, + "redactionAttestationPass": { + "title": "Redaction attestation pass", + "body": "Confirm raw logs, host dumps, unredacted screenshots, private URL credentials, and sensitive payloads are excluded.", + "verify": "raw payload allowed=false" + }, + "maintenanceRollbackComplete": { + "title": "Maintenance / rollback complete", + "body": "If evidence implies later change, confirm maintenance window, rollback owner, and recovery validation metrics exist.", + "verify": "display future-change conditions only; no change execution" + }, + "validationMetricsLinked": { + "title": "Validation metrics linked", + "body": "Confirm post-check metrics, baseline, logs, or follow-up evidence link to readable redacted pointers.", + "verify": "display validation pointer only; runtime gate stays closed" + }, + "runtimeGateSeparated": { + "title": "Runtime gate separated", + "body": "Confirm any reviewer checklist result cannot directly become runtime action and must return to a separate runtime gate.", + "verify": "active runtime gates=0; action buttons=false" + } + } } }, "tickets": { diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index 49823cf5a..7d423bc72 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -1544,6 +1544,49 @@ "required": "follow-up gate pointer;active runtime gates=0" } } + }, + "hostEvidenceReviewerChecklist": { + "title": "主機 Evidence Reviewer Checklist", + "subtitle": "Reviewer 在看 handoff packets 後,只能依這些只讀檢查判斷是否可進下一個人工決策。檢查清單不會標示 passed、received / accepted、approval 或 runtime gate。", + "checkLabel": "審查檢查", + "verifyLabel": "確認項目", + "items": { + "scopeBoundaryMatch": { + "title": "Scope boundary match", + "body": "確認 handoff scope 與 host coverage、網段、服務、排除範圍一致,沒有自行擴大掃描邊界。", + "verify": "只核對 redacted pointer;不啟動 scan" + }, + "ownerDecisionScopeExpiry": { + "title": "Owner decision scope / expiry", + "body": "確認 owner decision record 有批准人、範圍、限制條件與到期時間,且仍在有效期內。", + "verify": "只判讀 decision pointer;不建立 approval record" + }, + "credentialHandlingMetadataOnly": { + "title": "Credential handling metadata only", + "body": "確認 reviewer 只看到處理方式與責任人,不接觸任何明文驗證素材。", + "verify": "secret value collection=false" + }, + "redactionAttestationPass": { + "title": "Redaction attestation pass", + "body": "確認 raw log、host dump、未脫敏截圖、private URL credential 與敏感 payload 都已排除。", + "verify": "raw payload allowed=false" + }, + "maintenanceRollbackComplete": { + "title": "Maintenance / rollback complete", + "body": "若 evidence 暗示後續變更,確認已存在維護窗口、rollback owner 與復原驗證指標。", + "verify": "只顯示未來變更條件;不執行 change" + }, + "validationMetricsLinked": { + "title": "Validation metrics linked", + "body": "確認 post-check metrics、baseline、logs 或 follow-up evidence 已連到可讀的脫敏 pointer。", + "verify": "只顯示 validation pointer;不開 runtime gate" + }, + "runtimeGateSeparated": { + "title": "Runtime gate separated", + "body": "確認 reviewer checklist 的任何結果都不能直接變成 runtime action,必須回到獨立 runtime gate。", + "verify": "active runtime gates=0;action buttons=false" + } + } } }, "tickets": { diff --git a/apps/web/src/app/[locale]/iwooos/page.tsx b/apps/web/src/app/[locale]/iwooos/page.tsx index 1de5af097..7d3c10d7b 100644 --- a/apps/web/src/app/[locale]/iwooos/page.tsx +++ b/apps/web/src/app/[locale]/iwooos/page.tsx @@ -123,6 +123,13 @@ type HostEvidenceReviewHandoffPacket = { tone: 'steady' | 'warn' | 'locked' } +type HostEvidenceReviewerChecklistItem = { + key: string + check: string + icon: typeof ShieldCheck + tone: 'steady' | 'warn' | 'locked' +} + const postureMetrics: PostureMetric[] = [ { key: 'overall', value: '58%', tone: 'warn' }, { key: 'framework', value: '80-85%', tone: 'steady' }, @@ -284,6 +291,16 @@ const hostEvidenceReviewHandoffPackets: HostEvidenceReviewHandoffPacket[] = [ { key: 'runtimeGatePacket', packet: 'H7', icon: FileWarning, tone: 'locked' }, ] +const hostEvidenceReviewerChecklistItems: HostEvidenceReviewerChecklistItem[] = [ + { key: 'scopeBoundaryMatch', check: 'C1', icon: Radar, tone: 'warn' }, + { key: 'ownerDecisionScopeExpiry', check: 'C2', icon: ClipboardCheck, tone: 'warn' }, + { key: 'credentialHandlingMetadataOnly', check: 'C3', icon: Lock, tone: 'locked' }, + { key: 'redactionAttestationPass', check: 'C4', icon: ShieldCheck, tone: 'locked' }, + { key: 'maintenanceRollbackComplete', check: 'C5', icon: Clock3, tone: 'warn' }, + { key: 'validationMetricsLinked', check: 'C6', icon: CheckCircle2, tone: 'warn' }, + { key: 'runtimeGateSeparated', check: 'C7', icon: FileWarning, tone: 'locked' }, +] + const evidenceItems = [ 'iwooos-posture-projection.snapshot.json', 'security-rollout-policy.snapshot.json', @@ -726,6 +743,34 @@ function HostEvidenceReviewHandoffCard({ item }: { item: HostEvidenceReviewHando ) } +function HostEvidenceReviewerChecklistCard({ item }: { item: HostEvidenceReviewerChecklistItem }) { + const t = useTranslations('iwooos.hostEvidenceReviewerChecklist') + const Icon = item.icon + return ( +
+
+
+ + {t('checkLabel')} +
+ {item.check} +
+

+ {t(`items.${item.key}.title` as never)} +

+

+ {t(`items.${item.key}.body` as never)} +

+
+
{t('verifyLabel')}
+
+ {t(`items.${item.key}.verify` as never)} +
+
+
+ ) +} + export default function IwoooSPage({ params }: { params: { locale: string } }) { const t = useTranslations('iwooos') @@ -938,6 +983,26 @@ export default function IwoooSPage({ params }: { params: { locale: string } }) { +
+
+

{t('hostEvidenceReviewerChecklist.title')}

+

+ {t('hostEvidenceReviewerChecklist.subtitle')} +

+
+
+ {hostEvidenceReviewerChecklistItems.map(item => ( + + ))} +
+
+
None: "s2_18_iwooos_host_evidence_intake_preflight", "s2_19_iwooos_host_evidence_review_outcome_lanes", "s2_20_iwooos_host_evidence_review_handoff_packets", + "s2_21_iwooos_host_evidence_reviewer_checklist", ] assert_equal( "progress_delta_ledger.delta_ids", @@ -398,6 +399,15 @@ def validate(root: Path) -> None: "host_redaction_attestation_handoff_packet", "host_runtime_gate_pointer_handoff_packet", ] + expected_iwooos_host_evidence_reviewer_checklist_item_ids = [ + "host_scope_boundary_match_check", + "host_owner_decision_scope_expiry_check", + "host_credential_handling_metadata_only_check", + "host_redaction_attestation_pass_check", + "host_maintenance_rollback_complete_check", + "host_validation_metrics_linked_check", + "host_runtime_gate_separated_check", + ] assert_equal( "iwooos_projection.summary.frontend_surface_coverage_group_count", iwooos_projection["summary"]["frontend_surface_coverage_group_count"], @@ -453,6 +463,11 @@ def validate(root: Path) -> None: iwooos_projection["summary"]["host_evidence_review_handoff_packet_count"], len(expected_iwooos_host_evidence_review_handoff_packet_ids), ) + assert_equal( + "iwooos_projection.summary.host_evidence_reviewer_checklist_item_count", + iwooos_projection["summary"]["host_evidence_reviewer_checklist_item_count"], + len(expected_iwooos_host_evidence_reviewer_checklist_item_ids), + ) iwooos_progress = iwooos_projection["progress"] assert_equal("iwooos_projection.progress.overall_percent", iwooos_progress["overall_percent"], progress["overall_percent"]) assert_equal( @@ -1042,6 +1057,75 @@ def validate(root: Path) -> None: f"iwooos_projection.host_evidence_review_handoff_packets.{item['packet_id']}.not_authorization", item["not_authorization"], ) + iwooos_host_evidence_reviewer_checklist = iwooos_projection["host_evidence_reviewer_checklist_items"] + assert_equal( + "iwooos_projection.host_evidence_reviewer_checklist_items.ids", + [item["check_id"] for item in iwooos_host_evidence_reviewer_checklist], + expected_iwooos_host_evidence_reviewer_checklist_item_ids, + ) + assert_equal( + "iwooos_projection.host_evidence_reviewer_checklist_items.display_order", + [item["display_order"] for item in iwooos_host_evidence_reviewer_checklist], + list(range(1, len(expected_iwooos_host_evidence_reviewer_checklist_item_ids) + 1)), + ) + expected_iwooos_host_evidence_reviewer_checklist_pass_conditions = [ + "redacted_scope_pointer_only_no_scan_started", + "decision_pointer_only_no_approval_record_created", + "secret_value_collection_false", + "raw_payload_allowed_false", + "future_change_conditions_only_no_change_execution", + "validation_pointer_only_runtime_gate_closed", + "active_runtime_gates_zero_action_buttons_false", + ] + assert_equal( + "iwooos_projection.host_evidence_reviewer_checklist_items.pass_conditions", + [item["pass_condition"] for item in iwooos_host_evidence_reviewer_checklist], + expected_iwooos_host_evidence_reviewer_checklist_pass_conditions, + ) + for item in iwooos_host_evidence_reviewer_checklist: + assert_equal( + f"iwooos_projection.host_evidence_reviewer_checklist_items.{item['check_id']}.display_mode", + item["display_mode"], + "reviewer_checklist_only", + ) + assert_equal( + f"iwooos_projection.host_evidence_reviewer_checklist_items.{item['check_id']}.received_count", + item["received_count"], + 0, + ) + assert_equal( + f"iwooos_projection.host_evidence_reviewer_checklist_items.{item['check_id']}.accepted_count", + item["accepted_count"], + 0, + ) + assert_false( + f"iwooos_projection.host_evidence_reviewer_checklist_items.{item['check_id']}.approval_record_created", + item["approval_record_created"], + ) + assert_false( + f"iwooos_projection.host_evidence_reviewer_checklist_items.{item['check_id']}.runtime_gate_opened", + item["runtime_gate_opened"], + ) + assert_false( + f"iwooos_projection.host_evidence_reviewer_checklist_items.{item['check_id']}.raw_payload_allowed", + item["raw_payload_allowed"], + ) + assert_false( + f"iwooos_projection.host_evidence_reviewer_checklist_items.{item['check_id']}.secret_value_collection_allowed", + item["secret_value_collection_allowed"], + ) + assert_false( + f"iwooos_projection.host_evidence_reviewer_checklist_items.{item['check_id']}.runtime_execution_authorized", + item["runtime_execution_authorized"], + ) + assert_false( + f"iwooos_projection.host_evidence_reviewer_checklist_items.{item['check_id']}.action_buttons_allowed", + item["action_buttons_allowed"], + ) + assert_true( + f"iwooos_projection.host_evidence_reviewer_checklist_items.{item['check_id']}.not_authorization", + item["not_authorization"], + ) assert_equal( "iwooos_projection.non_blocking_lane_ids", iwooos_projection["non_blocking_lane_ids"], @@ -1071,6 +1155,7 @@ def validate(root: Path) -> None: "display_host_evidence_intake_preflight_checks", "display_host_evidence_review_outcome_lanes", "display_host_evidence_review_handoff_packets", + "display_host_evidence_reviewer_checklist", "display_evidence_refs", "display_forbidden_actions", ]: @@ -1106,6 +1191,9 @@ def validate(root: Path) -> None: "treat_host_handoff_packet_as_approval", "mark_host_handoff_packet_received", "store_host_handoff_sensitive_payload", + "treat_host_reviewer_check_as_approval", + "mark_host_reviewer_check_passed_from_frontend", + "open_runtime_gate_from_reviewer_check", "apply_runtime_blocking_control", "switch_github_primary", "production_deploy",