diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 237ba6c8c..425051408 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -1500,6 +1500,49 @@ "blocked": { "title": "Blocked Actions", "body": "This page does not provide scan, execute, repo, refs, workflow, secret, runner, primary switch, or deploy action buttons." + }, + "hostEvidenceReviewHandoff": { + "title": "Host Evidence Review Handoff Packets", + "subtitle": "Human reviewers can interpret evidence only through these redacted handoff packets. This shows required review material and does not mark received / accepted, create approval records, or open runtime gates.", + "packetLabel": "Handoff packet", + "requiredLabel": "Required material", + "items": { + "scopeSummaryPacket": { + "title": "Scope summary packet", + "body": "Describes host, service, network, scan boundary, and exclusions with indicators and summaries only, without storing raw scan output.", + "required": "redacted scope pointer; no raw payload" + }, + "ownerDecisionPacket": { + "title": "Owner decision packet", + "body": "Shows who approved review, scope, constraints, and expiry so the reviewer cannot expand authority.", + "required": "owner decision record pointer; not host-action approval" + }, + "credentialHandlingPacket": { + "title": "Credential handling packet", + "body": "Shows credential handling and custody responsibility only, without exposing plaintext authentication material.", + "required": "metadata-only handling statement; secret value=blocked" + }, + "maintenanceRollbackPacket": { + "title": "Maintenance / rollback packet", + "body": "If later change is needed, it first shows maintenance window, blast radius, rollback owner, and recovery validation method.", + "required": "maintenance window + rollback pointer; no change execution" + }, + "validationMetricsPacket": { + "title": "Validation metrics packet", + "body": "Defines which metrics, logs, baselines, or follow-up evidence the reviewer should inspect after review.", + "required": "post-check metrics pointer; runtime gate not opened" + }, + "redactionAttestationPacket": { + "title": "Redaction attestation packet", + "body": "Confirms evidence removed raw logs, host dumps, credentials, private URL credentials, and unredacted screenshots.", + "required": "redaction attestation only; sensitive payload not stored" + }, + "runtimeGatePacket": { + "title": "Runtime gate pointer packet", + "body": "Routes any possible later action back to a separate runtime gate so review outcome lanes cannot execute work.", + "required": "follow-up gate pointer; active runtime gates=0" + } + } } }, "tickets": { @@ -2168,38 +2211,38 @@ "runsDetail": "Run state is the single view into async work", "approvals": "Pending Approvals", "approvalsDetail": "Every high-risk action must stop at the human gate", - "contracts": "Contracts", - "contractsDetail": "Project / Agent / Policy contract publish state" + "contracts": "Contracts", + "contractsDetail": "Project / Agent / Policy contract publish state" + }, + "disposition": { + "title": "Disposition Semantics", + "diagnosis": { + "title": "Read-only Diagnosis", + "signal": "AI collected evidence", + "owner": "Owner: AI summarizes, SRE judges", + "route": "Route: Run monitor / incident detail" }, - "disposition": { - "title": "Disposition Semantics", - "diagnosis": { - "title": "Read-only Diagnosis", - "signal": "AI collected evidence", - "owner": "Owner: AI summarizes, SRE judges", - "route": "Route: Run monitor / incident detail" - }, - "approval": { - "title": "Human Gate", - "signal": "High-risk approval pending", - "owner": "Owner: SRE approve / reject", - "route": "Route: Approval queue" - }, - "execute": { - "title": "Auto Execution", - "signal": "Low-risk closure path", - "owner": "Owner: MCP Gateway executes and audits", - "route": "Route: Run State / Audit" - }, - "manual": { - "title": "Manual Escalation", - "signal": "AI cannot safely repair", - "owner": "Owner: war room takes over", - "route": "Route: AwoooI SRE war room" - } + "approval": { + "title": "Human Gate", + "signal": "High-risk approval pending", + "owner": "Owner: SRE approve / reject", + "route": "Route: Approval queue" }, - "lanes": { - "title": "Flywheel Lanes", + "execute": { + "title": "Auto Execution", + "signal": "Low-risk closure path", + "owner": "Owner: MCP Gateway executes and audits", + "route": "Route: Run State / Audit" + }, + "manual": { + "title": "Manual Escalation", + "signal": "AI cannot safely repair", + "owner": "Owner: war room takes over", + "route": "Route: AwoooI SRE war room" + } + }, + "lanes": { + "title": "Flywheel Lanes", "live": "Live", "mirror": "Mirror", "providerName": "Provider Order", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index 4acdcfe06..49823cf5a 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -1501,6 +1501,49 @@ "blocked": { "title": "禁止動作", "body": "此頁不提供 scan、execute、repo、refs、workflow、secret、runner、primary switch 或 deploy action button。" + }, + "hostEvidenceReviewHandoff": { + "title": "主機 Evidence 人工審查交接包", + "subtitle": "人工 reviewer 只能依這些脫敏交接包判讀 evidence。這裡顯示送審必備資料,不會標示 received / accepted、建立 approval record 或啟動 runtime gate。", + "packetLabel": "交接包", + "requiredLabel": "必備內容", + "items": { + "scopeSummaryPacket": { + "title": "Scope 摘要包", + "body": "描述主機、服務、網段、掃描邊界與排除範圍,只允許指標與摘要,不保存原始掃描輸出。", + "required": "redacted scope pointer;不含 raw payload" + }, + "ownerDecisionPacket": { + "title": "Owner Decision 包", + "body": "提供誰批准審查、批准範圍、限制條件與到期時間,避免 reviewer 自行擴權。", + "required": "owner decision record pointer;不等於主機動作批准" + }, + "credentialHandlingPacket": { + "title": "Credential Handling 包", + "body": "只顯示憑證處理方式與保管責任,不顯示帳密、token、private key 或 session value。", + "required": "metadata-only handling statement;secret value=blocked" + }, + "maintenanceRollbackPacket": { + "title": "Maintenance / Rollback 包", + "body": "若後續需要變更,先提供維護窗口、影響範圍、rollback owner 與復原驗證方法。", + "required": "maintenance window + rollback pointer;不啟動變更" + }, + "validationMetricsPacket": { + "title": "Validation Metrics 包", + "body": "定義 reviewer 檢查後要看哪些 metrics、logs、baseline 或 follow-up evidence。", + "required": "post-check metrics pointer;不代表 runtime gate opened" + }, + "redactionAttestationPacket": { + "title": "Redaction Attestation 包", + "body": "確認 evidence 已移除 raw log、host dump、credential、private URL credential 與未脫敏截圖。", + "required": "redaction attestation only;不保存敏感 payload" + }, + "runtimeGatePacket": { + "title": "Runtime Gate Pointer 包", + "body": "把可能的後續行動導回獨立 runtime gate,避免 reviewer outcome lane 直接變成執行。", + "required": "follow-up gate pointer;active runtime gates=0" + } + } } }, "tickets": { @@ -2169,38 +2212,38 @@ "runsDetail": "Run state 是非同步任務的唯一觀測入口", "approvals": "待審批", "approvalsDetail": "所有高風險動作都必須停在人工閘門", - "contracts": "合約", - "contractsDetail": "Project / Agent / Policy contract 發布狀態" + "contracts": "合約", + "contractsDetail": "Project / Agent / Policy contract 發布狀態" + }, + "disposition": { + "title": "處置語義", + "diagnosis": { + "title": "只讀診斷", + "signal": "AI 已收集證據", + "owner": "負責:AI 先整理,SRE 判讀", + "route": "流向:Run 監控 / 事件詳情" }, - "disposition": { - "title": "處置語義", - "diagnosis": { - "title": "只讀診斷", - "signal": "AI 已收集證據", - "owner": "負責:AI 先整理,SRE 判讀", - "route": "流向:Run 監控 / 事件詳情" - }, - "approval": { - "title": "人工閘門", - "signal": "高風險待批准", - "owner": "負責:SRE approve / reject", - "route": "流向:審批佇列" - }, - "execute": { - "title": "自動執行", - "signal": "低風險可閉環", - "owner": "負責:MCP Gateway 執行並稽核", - "route": "流向:Run State / Audit" - }, - "manual": { - "title": "人工升級", - "signal": "AI 無法安全修復", - "owner": "負責:戰情室接手", - "route": "流向:AwoooI SRE 戰情室" - } + "approval": { + "title": "人工閘門", + "signal": "高風險待批准", + "owner": "負責:SRE approve / reject", + "route": "流向:審批佇列" }, - "lanes": { - "title": "飛輪鏈路", + "execute": { + "title": "自動執行", + "signal": "低風險可閉環", + "owner": "負責:MCP Gateway 執行並稽核", + "route": "流向:Run State / Audit" + }, + "manual": { + "title": "人工升級", + "signal": "AI 無法安全修復", + "owner": "負責:戰情室接手", + "route": "流向:AwoooI SRE 戰情室" + } + }, + "lanes": { + "title": "飛輪鏈路", "live": "已接線", "mirror": "Mirror", "providerName": "Provider 順序", diff --git a/apps/web/src/app/[locale]/iwooos/page.tsx b/apps/web/src/app/[locale]/iwooos/page.tsx index bfafabd40..1de5af097 100644 --- a/apps/web/src/app/[locale]/iwooos/page.tsx +++ b/apps/web/src/app/[locale]/iwooos/page.tsx @@ -116,6 +116,13 @@ type HostEvidenceReviewOutcomeLane = { tone: 'steady' | 'warn' | 'locked' } +type HostEvidenceReviewHandoffPacket = { + key: string + packet: string + icon: typeof ShieldCheck + tone: 'steady' | 'warn' | 'locked' +} + const postureMetrics: PostureMetric[] = [ { key: 'overall', value: '58%', tone: 'warn' }, { key: 'framework', value: '80-85%', tone: 'steady' }, @@ -267,6 +274,16 @@ const hostEvidenceReviewOutcomeLanes: HostEvidenceReviewOutcomeLane[] = [ { key: 'waitingRuntimeGate', lane: 'R7', icon: Clock3, tone: 'locked' }, ] +const hostEvidenceReviewHandoffPackets: HostEvidenceReviewHandoffPacket[] = [ + { key: 'scopeSummaryPacket', packet: 'H1', icon: Radar, tone: 'warn' }, + { key: 'ownerDecisionPacket', packet: 'H2', icon: ClipboardCheck, tone: 'warn' }, + { key: 'credentialHandlingPacket', packet: 'H3', icon: Lock, tone: 'locked' }, + { key: 'maintenanceRollbackPacket', packet: 'H4', icon: Clock3, tone: 'warn' }, + { key: 'validationMetricsPacket', packet: 'H5', icon: CheckCircle2, tone: 'warn' }, + { key: 'redactionAttestationPacket', packet: 'H6', icon: ShieldCheck, tone: 'locked' }, + { key: 'runtimeGatePacket', packet: 'H7', icon: FileWarning, tone: 'locked' }, +] + const evidenceItems = [ 'iwooos-posture-projection.snapshot.json', 'security-rollout-policy.snapshot.json', @@ -681,6 +698,34 @@ function HostEvidenceReviewOutcomeCard({ item }: { item: HostEvidenceReviewOutco ) } +function HostEvidenceReviewHandoffCard({ item }: { item: HostEvidenceReviewHandoffPacket }) { + const t = useTranslations('iwooos.hostEvidenceReviewHandoff') + const Icon = item.icon + return ( +
+
+
+ + {t('packetLabel')} +
+ {item.packet} +
+

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

+

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

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

{t('hostEvidenceReviewHandoff.title')}

+

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

+
+
+ {hostEvidenceReviewHandoffPackets.map(item => ( + + ))} +
+
+
None: "s2_17_iwooos_host_evidence_collection_order", "s2_18_iwooos_host_evidence_intake_preflight", "s2_19_iwooos_host_evidence_review_outcome_lanes", + "s2_20_iwooos_host_evidence_review_handoff_packets", ] assert_equal( "progress_delta_ledger.delta_ids", @@ -388,6 +389,15 @@ def validate(root: Path) -> None: "host_reject_credential_plaintext_lane", "host_waiting_runtime_gate_lane", ] + expected_iwooos_host_evidence_review_handoff_packet_ids = [ + "host_scope_summary_handoff_packet", + "host_owner_decision_handoff_packet", + "host_credential_handling_handoff_packet", + "host_maintenance_rollback_handoff_packet", + "host_validation_metrics_handoff_packet", + "host_redaction_attestation_handoff_packet", + "host_runtime_gate_pointer_handoff_packet", + ] assert_equal( "iwooos_projection.summary.frontend_surface_coverage_group_count", iwooos_projection["summary"]["frontend_surface_coverage_group_count"], @@ -438,6 +448,11 @@ def validate(root: Path) -> None: iwooos_projection["summary"]["host_evidence_review_outcome_lane_count"], len(expected_iwooos_host_evidence_review_outcome_lane_ids), ) + assert_equal( + "iwooos_projection.summary.host_evidence_review_handoff_packet_count", + iwooos_projection["summary"]["host_evidence_review_handoff_packet_count"], + len(expected_iwooos_host_evidence_review_handoff_packet_ids), + ) iwooos_progress = iwooos_projection["progress"] assert_equal("iwooos_projection.progress.overall_percent", iwooos_progress["overall_percent"], progress["overall_percent"]) assert_equal( @@ -962,6 +977,71 @@ def validate(root: Path) -> None: f"iwooos_projection.host_evidence_review_outcome_lanes.{item['lane_id']}.not_authorization", item["not_authorization"], ) + iwooos_host_evidence_review_handoff_packets = iwooos_projection["host_evidence_review_handoff_packets"] + assert_equal( + "iwooos_projection.host_evidence_review_handoff_packets.ids", + [item["packet_id"] for item in iwooos_host_evidence_review_handoff_packets], + expected_iwooos_host_evidence_review_handoff_packet_ids, + ) + assert_equal( + "iwooos_projection.host_evidence_review_handoff_packets.display_order", + [item["display_order"] for item in iwooos_host_evidence_review_handoff_packets], + list(range(1, len(expected_iwooos_host_evidence_review_handoff_packet_ids) + 1)), + ) + expected_iwooos_host_evidence_review_handoff_requirements = [ + "redacted_scope_boundary_summary", + "owner_decision_record_pointer", + "credential_handling_metadata_only_statement", + "maintenance_window_and_rollback_pointer", + "post_review_validation_metrics_pointer", + "redaction_attestation_metadata_only", + "followup_runtime_gate_pointer_only", + ] + assert_equal( + "iwooos_projection.host_evidence_review_handoff_packets.packet_requirements", + [item["packet_requirement"] for item in iwooos_host_evidence_review_handoff_packets], + expected_iwooos_host_evidence_review_handoff_requirements, + ) + for item in iwooos_host_evidence_review_handoff_packets: + assert_equal( + f"iwooos_projection.host_evidence_review_handoff_packets.{item['packet_id']}.display_mode", + item["display_mode"], + "review_handoff_only", + ) + assert_equal( + f"iwooos_projection.host_evidence_review_handoff_packets.{item['packet_id']}.received_count", + item["received_count"], + 0, + ) + assert_equal( + f"iwooos_projection.host_evidence_review_handoff_packets.{item['packet_id']}.accepted_count", + item["accepted_count"], + 0, + ) + assert_false( + f"iwooos_projection.host_evidence_review_handoff_packets.{item['packet_id']}.approval_record_created", + item["approval_record_created"], + ) + assert_false( + f"iwooos_projection.host_evidence_review_handoff_packets.{item['packet_id']}.raw_payload_allowed", + item["raw_payload_allowed"], + ) + assert_false( + f"iwooos_projection.host_evidence_review_handoff_packets.{item['packet_id']}.secret_value_collection_allowed", + item["secret_value_collection_allowed"], + ) + assert_false( + f"iwooos_projection.host_evidence_review_handoff_packets.{item['packet_id']}.runtime_execution_authorized", + item["runtime_execution_authorized"], + ) + assert_false( + f"iwooos_projection.host_evidence_review_handoff_packets.{item['packet_id']}.action_buttons_allowed", + item["action_buttons_allowed"], + ) + assert_true( + f"iwooos_projection.host_evidence_review_handoff_packets.{item['packet_id']}.not_authorization", + item["not_authorization"], + ) assert_equal( "iwooos_projection.non_blocking_lane_ids", iwooos_projection["non_blocking_lane_ids"], @@ -990,6 +1070,7 @@ def validate(root: Path) -> None: "display_host_evidence_collection_order", "display_host_evidence_intake_preflight_checks", "display_host_evidence_review_outcome_lanes", + "display_host_evidence_review_handoff_packets", "display_evidence_refs", "display_forbidden_actions", ]: @@ -1022,6 +1103,9 @@ def validate(root: Path) -> None: "create_host_approval_from_review_lane", "treat_host_review_lane_as_runtime_gate", "mark_host_review_outcome_accepted", + "treat_host_handoff_packet_as_approval", + "mark_host_handoff_packet_received", + "store_host_handoff_sensitive_payload", "apply_runtime_blocking_control", "switch_github_primary", "production_deploy",