diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 8797e3ba..c1e6e6cc 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -1218,6 +1218,48 @@ } } }, + "evidenceReadiness": { + "title": "Owner Evidence Readiness", + "subtitle": "Shows the evidence that can actually move headline progress. Every item is waiting for collection or human decision and does not trigger execution from the frontend.", + "unlockLabel": "Unlock condition", + "items": { + "giteaOwnerAttestation": { + "title": "Gitea owner attestation", + "body": "The recommended first collection item is S4.9, covering Gitea inventory coverage and owner disposition.", + "unlock": "redacted owner response received and accepted" + }, + "githubTargetOwner": { + "title": "GitHub target owner", + "body": "Confirms GitHub targets, visibility, canonical owner, and whether repos can enter primary readiness.", + "unlock": "S4.10 owner response accepted" + }, + "refsTruthOwner": { + "title": "Refs truth owner", + "body": "Confirms truth for main/dev, deprecated drift, release tags, and GitHub-only refs.", + "unlock": "S4.11 refs truth response accepted" + }, + "workflowSecretOwner": { + "title": "Workflow / secret name owner", + "body": "Confirms workflow, webhook, runner, deploy key, branch protection, and secret name parity.", + "unlock": "S4.12 workflow / secret response accepted" + }, + "redactedFindingIngestion": { + "title": "Redacted finding ingestion", + "body": "Kali findings and security findings must enter mirror as redacted payloads before any runtime path.", + "unlock": "human-approved redacted finding ingestion" + }, + "kaliScanScope": { + "title": "Kali scan scope", + "body": "Kali 112, 111, and 168 remain observe-only; active scan and /execute require separate approval.", + "unlock": "scan scope approval plus follow-up gate" + }, + "followupRuntimeGate": { + "title": "Follow-up runtime gate", + "body": "Real execution waits for a human decision record and a separate follow-up runtime gate.", + "unlock": "decision record accepted; active gates remain 0" + } + } + }, "nextGate": { "title": "Next High-level Gate", "body": "S4.9 Gitea owner attestation response is the recommended next owner evidence. Headline progress should only increase after owner responses, redacted payload ingestion, active runtime gates, or GitHub primary readiness actually change." diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index f01c6e0b..6418e030 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -1219,6 +1219,48 @@ } } }, + "evidenceReadiness": { + "title": "Owner Evidence Readiness", + "subtitle": "這裡顯示 headline 進度下一步真正需要的 evidence。每一項都是等待收件或人工決策,不會從前端直接觸發任何執行。", + "unlockLabel": "解除條件", + "items": { + "giteaOwnerAttestation": { + "title": "Gitea owner attestation", + "body": "目前建議先收 S4.9,補齊 Gitea 清冊覆蓋與 owner 判定。", + "unlock": "收到並接受脫敏 owner response" + }, + "githubTargetOwner": { + "title": "GitHub target owner", + "body": "確認 GitHub target、visibility、canonical owner 與 repo 是否可進 primary readiness。", + "unlock": "S4.10 owner response accepted" + }, + "refsTruthOwner": { + "title": "Refs truth owner", + "body": "確認 main/dev truth、deprecated drift、release tags 與 GitHub-only refs 的真相來源。", + "unlock": "S4.11 refs truth response accepted" + }, + "workflowSecretOwner": { + "title": "Workflow / secret name owner", + "body": "確認 workflow、webhook、runner、deploy key、branch protection、secret name parity。", + "unlock": "S4.12 workflow / secret response accepted" + }, + "redactedFindingIngestion": { + "title": "Redacted finding ingestion", + "body": "Kali finding 與安全發現需要先以脫敏 payload 進入 mirror,不能直接進 runtime。", + "unlock": "人工批准後接收脫敏 finding" + }, + "kaliScanScope": { + "title": "Kali scan scope", + "body": "Kali 112、111、168 目前仍是 observe-only;active scan 與 /execute 仍需獨立批准。", + "unlock": "scan scope approval + follow-up gate" + }, + "followupRuntimeGate": { + "title": "Follow-up runtime gate", + "body": "所有實際執行都要等人工 decision record 後,另開 follow-up runtime gate。", + "unlock": "decision record accepted,active gate 仍為 0" + } + } + }, "nextGate": { "title": "下一個高層 Gate", "body": "S4.9 Gitea owner attestation response 是目前建議先收的 owner evidence。任何 headline 提升都要等 owner response、redacted payload ingestion、active runtime gate 或 GitHub primary readiness 有真實變化。" diff --git a/apps/web/src/app/[locale]/iwooos/page.tsx b/apps/web/src/app/[locale]/iwooos/page.tsx index a1aa1b0a..4fa2407d 100644 --- a/apps/web/src/app/[locale]/iwooos/page.tsx +++ b/apps/web/src/app/[locale]/iwooos/page.tsx @@ -67,6 +67,13 @@ type JourneyStep = { tone: 'steady' | 'warn' | 'locked' } +type EvidenceReadinessItem = { + key: string + lane: string + icon: typeof ShieldCheck + tone: 'steady' | 'warn' | 'locked' +} + const postureMetrics: PostureMetric[] = [ { key: 'overall', value: '58%', tone: 'warn' }, { key: 'framework', value: '80-85%', tone: 'steady' }, @@ -153,6 +160,16 @@ const journeySteps: JourneyStep[] = [ { key: 'runtimeGate', icon: ShieldCheck, tone: 'locked' }, ] +const evidenceReadinessItems: EvidenceReadinessItem[] = [ + { key: 'giteaOwnerAttestation', lane: 'S4.9', icon: GitBranch, tone: 'warn' }, + { key: 'githubTargetOwner', lane: 'S4.10', icon: GitBranch, tone: 'warn' }, + { key: 'refsTruthOwner', lane: 'S4.11', icon: GitBranch, tone: 'warn' }, + { key: 'workflowSecretOwner', lane: 'S4.12', icon: Lock, tone: 'warn' }, + { key: 'redactedFindingIngestion', lane: 'S1.6', icon: FileWarning, tone: 'warn' }, + { key: 'kaliScanScope', lane: 'S1.6', icon: Activity, tone: 'locked' }, + { key: 'followupRuntimeGate', lane: 'S3.4', icon: ShieldCheck, tone: 'locked' }, +] + const evidenceItems = [ 'iwooos-posture-projection.snapshot.json', 'security-rollout-policy.snapshot.json', @@ -371,6 +388,34 @@ function JourneyStepCard({ item, index }: { item: JourneyStep; index: number }) ) } +function EvidenceReadinessCard({ item, index }: { item: EvidenceReadinessItem; index: number }) { + const t = useTranslations('iwooos.evidenceReadiness') + const Icon = item.icon + return ( +
+
+
+ + {item.lane} +
+ {String(index + 1).padStart(2, '0')} +
+

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

+

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

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

{t('evidenceReadiness.title')}

+

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

+
+
+ {evidenceReadinessItems.map((item, index) => ( + + ))} +
+
+
None: "s2_10_iwooos_existing_frontend_surface_integration", "s2_11_iwooos_surface_coverage_boundary_matrix", "s2_12_iwooos_operator_journey_projection", + "s2_13_iwooos_owner_evidence_readiness_board", ] assert_equal( "progress_delta_ledger.delta_ids", @@ -323,6 +324,15 @@ def validate(root: Path) -> None: "wait_for_human_decision", "prepare_followup_runtime_gate", ] + expected_iwooos_evidence_readiness_item_ids = [ + "s4_9_gitea_owner_attestation_response", + "s4_10_github_target_owner_response", + "s4_11_refs_truth_owner_response", + "s4_12_workflow_secret_owner_response", + "s1_6_redacted_finding_ingestion", + "s1_6_kali_scan_scope_approval", + "s3_4_followup_runtime_gate", + ] assert_equal( "iwooos_projection.summary.frontend_surface_coverage_group_count", iwooos_projection["summary"]["frontend_surface_coverage_group_count"], @@ -338,6 +348,11 @@ def validate(root: Path) -> None: iwooos_projection["summary"]["operator_journey_step_count"], len(expected_iwooos_journey_step_ids), ) + assert_equal( + "iwooos_projection.summary.owner_evidence_readiness_item_count", + iwooos_projection["summary"]["owner_evidence_readiness_item_count"], + len(expected_iwooos_evidence_readiness_item_ids), + ) iwooos_progress = iwooos_projection["progress"] assert_equal("iwooos_projection.progress.overall_percent", iwooos_progress["overall_percent"], progress["overall_percent"]) assert_equal( @@ -501,6 +516,45 @@ def validate(root: Path) -> None: f"iwooos_projection.operator_journey_steps.{item['step_id']}.not_authorization", item["not_authorization"], ) + iwooos_evidence_readiness = iwooos_projection["owner_evidence_readiness_items"] + assert_equal( + "iwooos_projection.owner_evidence_readiness_items.ids", + [item["item_id"] for item in iwooos_evidence_readiness], + expected_iwooos_evidence_readiness_item_ids, + ) + assert_equal( + "iwooos_projection.owner_evidence_readiness_items.display_order", + [item["display_order"] for item in iwooos_evidence_readiness], + list(range(1, len(expected_iwooos_evidence_readiness_item_ids) + 1)), + ) + for item in iwooos_evidence_readiness: + assert_equal( + f"iwooos_projection.owner_evidence_readiness_items.{item['item_id']}.display_mode", + item["display_mode"], + "readiness_only", + ) + assert_equal( + f"iwooos_projection.owner_evidence_readiness_items.{item['item_id']}.received_count", + item["received_count"], + 0, + ) + assert_equal( + f"iwooos_projection.owner_evidence_readiness_items.{item['item_id']}.accepted_count", + item["accepted_count"], + 0, + ) + assert_false( + f"iwooos_projection.owner_evidence_readiness_items.{item['item_id']}.runtime_execution_authorized", + item["runtime_execution_authorized"], + ) + assert_false( + f"iwooos_projection.owner_evidence_readiness_items.{item['item_id']}.action_buttons_allowed", + item["action_buttons_allowed"], + ) + assert_true( + f"iwooos_projection.owner_evidence_readiness_items.{item['item_id']}.not_authorization", + item["not_authorization"], + ) assert_equal( "iwooos_projection.non_blocking_lane_ids", iwooos_projection["non_blocking_lane_ids"], @@ -522,6 +576,7 @@ def validate(root: Path) -> None: "display_frontend_surface_coverage_matrix", "display_frontend_surface_conflict_controls", "display_operator_journey_steps", + "display_owner_evidence_readiness_board", "display_evidence_refs", "display_forbidden_actions", ]: