diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 89202e970..9a4b1b1af 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -5573,6 +5573,30 @@ } } }, + "evidenceUnlockQueue": { + "eyebrow": "證據解鎖工作佇列", + "title": "下一批能讓進度前進的工作包", + "subtitle": "把 S4.9-S4.12 四包待補實證放在同一條佇列;目前只顯示收件、預檢、審查與卡點,不送出請求、不建立審批、不打開執行期閘門。", + "boundaryTitle": "工作佇列邊界", + "items": { + "giteaOwner": { + "title": "Gitea 負責人覆核", + "body": "確認專案清冊、可見性、負責人與覆蓋範圍。" + }, + "githubTarget": { + "title": "GitHub 目標來源確認", + "body": "八個候選庫仍需 readiness 與回滾證據。" + }, + "refTruth": { + "title": "分支與標籤事實", + "body": "補齊主要分支、標籤、保護規則與同步邊界。" + }, + "workflowSecrets": { + "title": "工作流程與機密名稱", + "body": "只收名稱與用途中繼資料,不收機密明文。" + } + } + }, "immediateVisualMesh": { "eyebrow": "首屏資安網視覺模型", "title": "資安關聯視覺模型", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index 89202e970..9a4b1b1af 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -5573,6 +5573,30 @@ } } }, + "evidenceUnlockQueue": { + "eyebrow": "證據解鎖工作佇列", + "title": "下一批能讓進度前進的工作包", + "subtitle": "把 S4.9-S4.12 四包待補實證放在同一條佇列;目前只顯示收件、預檢、審查與卡點,不送出請求、不建立審批、不打開執行期閘門。", + "boundaryTitle": "工作佇列邊界", + "items": { + "giteaOwner": { + "title": "Gitea 負責人覆核", + "body": "確認專案清冊、可見性、負責人與覆蓋範圍。" + }, + "githubTarget": { + "title": "GitHub 目標來源確認", + "body": "八個候選庫仍需 readiness 與回滾證據。" + }, + "refTruth": { + "title": "分支與標籤事實", + "body": "補齊主要分支、標籤、保護規則與同步邊界。" + }, + "workflowSecrets": { + "title": "工作流程與機密名稱", + "body": "只收名稱與用途中繼資料,不收機密明文。" + } + } + }, "immediateVisualMesh": { "eyebrow": "首屏資安網視覺模型", "title": "資安關聯視覺模型", diff --git a/apps/web/src/app/[locale]/iwooos/page.tsx b/apps/web/src/app/[locale]/iwooos/page.tsx index f38de0f5b..593459825 100644 --- a/apps/web/src/app/[locale]/iwooos/page.tsx +++ b/apps/web/src/app/[locale]/iwooos/page.tsx @@ -55,6 +55,14 @@ type ProgressEvidenceRailItem = { tone: 'steady' | 'warn' | 'locked' } +type EvidenceUnlockQueueItem = { + key: string + gate: string + value: string + icon: typeof ShieldCheck + tone: 'steady' | 'warn' | 'locked' +} + type FastProgressItem = { key: string value: string @@ -936,6 +944,26 @@ const progressEvidenceRailBoundaries = [ 'not_authorization=true', ] +const evidenceUnlockQueueItems: EvidenceUnlockQueueItem[] = [ + { key: 'giteaOwner', gate: 'S4.9', value: '0/5', icon: ClipboardCheck, tone: 'warn' }, + { key: 'githubTarget', gate: 'S4.10', value: '0/8', icon: GitBranch, tone: 'locked' }, + { key: 'refTruth', gate: 'S4.11', value: '0/5', icon: Route, tone: 'warn' }, + { key: 'workflowSecrets', gate: 'S4.12', value: '0/5', icon: Workflow, tone: 'locked' }, +] + +const evidenceUnlockQueueBoundaries = [ + 'iwooos_evidence_unlock_queue_item_count=4', + 'iwooos_evidence_unlock_queue_request_sent_count=0', + 'iwooos_evidence_unlock_queue_received_count=0', + 'iwooos_evidence_unlock_queue_accepted_count=0', + 'iwooos_evidence_unlock_queue_github_primary_ready_count=0', + 'iwooos_evidence_unlock_queue_runtime_gate_count=0', + 'runtime_execution_authorized=false', + 'active_runtime_gate_count=0', + 'action_buttons_allowed=false', + 'not_authorization=true', +] + const fastProgressItems: FastProgressItem[] = [ { key: 'done', value: '7 頁', icon: CheckCircle2, tone: 'steady' }, { key: 'kali112', value: '只讀通過', icon: ShieldCheck, tone: 'warn' }, @@ -10243,6 +10271,125 @@ function IwoooSProgressEvidenceRailBoard() { ) } +function IwoooSEvidenceUnlockQueueBoard() { + const t = useTranslations('iwooos.evidenceUnlockQueue') + const textWrap = { overflowWrap: 'anywhere' as const, wordBreak: 'break-word' as const } + + return ( +
+
+
+
+
+ + {t('eyebrow')} +
+

{t('title')}

+

{t('subtitle')}

+
+ +
+ {evidenceUnlockQueueItems.map(item => { + const Icon = item.icon + return ( +
+
+ {item.gate} + +
+
{item.value}
+

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

+

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

+
+ ) + })} +
+
+ +
+ + {t('boundaryTitle')} + +
+ {evidenceUnlockQueueBoundaries.map(item => ( + + {item} + + ))} +
+
+
+
+ ) +} + function IwoooSImmediateVisualMeshBoard() { const t = useTranslations('iwooos.immediateVisualMesh') const textWrap = { overflowWrap: 'anywhere' as const, wordBreak: 'break-word' as const } @@ -16721,6 +16868,7 @@ export default function IwoooSPage({ params }: { params: { locale: string } }) { + diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index e712affd1..5b5b7376d 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -1,3 +1,22 @@ +## 2026-06-01|IwoooS 證據解鎖工作佇列 + +**背景**: + +- IwoooS 首屏已補上進度證據流速儀,但仍需要把「下一批可以讓 61% 往前的具體工作包」轉成更像資安營運產品的可掃描佇列。 +- 本輪維持低摩擦策略:只做可視化、只讀證據語義與防退化,不新增執行按鈕或任何主機、版本來源變更。 + +**本次調整**: + +- `apps/web/src/app/[locale]/iwooos/page.tsx`:新增 `IwoooSEvidenceUnlockQueueBoard`,在首屏列出 S4.9-S4.12 四個實證工作包:Gitea 負責人覆核、GitHub 目標來源確認、分支與標籤事實、工作流程與機密名稱。 +- `apps/web/messages/zh-TW.json` / `en.json`:新增證據解鎖工作佇列繁中文案;英文語系維持繁中鏡像。 +- `docs/security/iwooos-posture-projection.snapshot.json`:新增 `evidence_unlock_queue_items` 與 summary 計數,固定 request sent / received / accepted / runtime gate 仍為 `0`。 +- `scripts/security/security-mirror-progress-guard.py`:新增 S2.158 guard,鎖住工作佇列 testid、首屏順序、四包 gate 與 `action_buttons_allowed=false` 邊界。 +- `docs/security/security-mirror-status-rollup.snapshot.json`:新增 `s2_158_iwooos_evidence_unlock_queue` 進度台帳。 + +**進度邊界**: + +- 整體維持 `61%`;本輪不是請求送出、審批建立、Kali SSH、掃描、主機更新、修復、部署、GitHub primary 切換或 Gitea 停用。 + ## 2026-06-01|IwoooS 進度證據流速儀 **背景**: diff --git a/docs/security/iwooos-posture-projection.snapshot.json b/docs/security/iwooos-posture-projection.snapshot.json index b1df757c1..27273beb5 100644 --- a/docs/security/iwooos-posture-projection.snapshot.json +++ b/docs/security/iwooos-posture-projection.snapshot.json @@ -52,6 +52,13 @@ "progress_evidence_rail_review_acceptance_count": 0, "progress_evidence_rail_github_primary_ready_count": 0, "progress_evidence_rail_runtime_gate_count": 0, + "evidence_unlock_queue_first_layer": true, + "evidence_unlock_queue_item_count": 4, + "evidence_unlock_queue_request_sent_count": 0, + "evidence_unlock_queue_received_count": 0, + "evidence_unlock_queue_accepted_count": 0, + "evidence_unlock_queue_github_primary_ready_count": 0, + "evidence_unlock_queue_runtime_gate_count": 0, "visual_command_dashboard_widget_count": 14, "visual_command_dashboard_first_layer": true, "visual_command_dashboard_default_visible": false, @@ -416,6 +423,69 @@ "not_authorization": true } ], + "evidence_unlock_queue_items": [ + { + "item_id": "giteaOwner", + "display_order": 1, + "gate": "S4.9", + "display_mode": "first_screen_evidence_unlock_queue", + "value": "0/5", + "request_sent_count": 0, + "received_count": 0, + "accepted_count": 0, + "runtime_gate_count": 0, + "runtime_execution_authorized": false, + "action_buttons_allowed": false, + "not_authorization": true + }, + { + "item_id": "githubTarget", + "display_order": 2, + "gate": "S4.10", + "display_mode": "first_screen_evidence_unlock_queue", + "value": "0/8", + "candidate_repo_count": 8, + "github_primary_ready_count": 0, + "request_sent_count": 0, + "received_count": 0, + "accepted_count": 0, + "source_control_mutation_authorized": false, + "runtime_gate_count": 0, + "runtime_execution_authorized": false, + "action_buttons_allowed": false, + "not_authorization": true + }, + { + "item_id": "refTruth", + "display_order": 3, + "gate": "S4.11", + "display_mode": "first_screen_evidence_unlock_queue", + "value": "0/5", + "request_sent_count": 0, + "received_count": 0, + "accepted_count": 0, + "refs_sync_authorized": false, + "runtime_gate_count": 0, + "runtime_execution_authorized": false, + "action_buttons_allowed": false, + "not_authorization": true + }, + { + "item_id": "workflowSecrets", + "display_order": 4, + "gate": "S4.12", + "display_mode": "first_screen_evidence_unlock_queue", + "value": "0/5", + "request_sent_count": 0, + "received_count": 0, + "accepted_count": 0, + "secret_plaintext_collection_allowed": false, + "runtime_gate_count": 0, + "runtime_execution_authorized": false, + "action_buttons_allowed": false, + "not_authorization": true + } + ], "first_progress_unlock_path_steps": [ { "step_id": "owner_response_scope", diff --git a/docs/security/security-mirror-status-rollup.snapshot.json b/docs/security/security-mirror-status-rollup.snapshot.json index 47b0f6cd6..04b74ab4c 100644 --- a/docs/security/security-mirror-status-rollup.snapshot.json +++ b/docs/security/security-mirror-status-rollup.snapshot.json @@ -2365,6 +2365,18 @@ "runtime_delta": false, "execution_authorized": false, "not_authorization": true + }, + { + "delta_id": "s2_158_iwooos_evidence_unlock_queue", + "display_order": 187, + "completed_stage": "S2.158 IwoooS 證據解鎖工作佇列", + "progress_axis": "framework_detail", + "headline_percent_delta": 0, + "framework_delta_visible": true, + "why_headline_unchanged": "IwoooS 只新增首屏證據解鎖工作佇列,把 S4.9-S4.12 四包待補實證拆成 Gitea 負責人覆核 0/5、GitHub 目標來源確認 0/8、分支與標籤事實 0/5、工作流程與機密名稱 0/5;iwooos_evidence_unlock_queue_request_sent_count=0、iwooos_evidence_unlock_queue_received_count=0、iwooos_evidence_unlock_queue_accepted_count=0、iwooos_evidence_unlock_queue_github_primary_ready_count=0、iwooos_evidence_unlock_queue_runtime_gate_count=0、runtime_execution_authorized=false、active_runtime_gate_count=0、action_buttons_allowed=false,不把工作佇列當請求送出、審批建立、掃描、修復、部署、主機更新、版本來源切換、GitHub primary 切換或 Gitea 停用。", + "runtime_delta": false, + "execution_authorized": false, + "not_authorization": true } ], "next_safe_actions": [ diff --git a/scripts/security/security-mirror-progress-guard.py b/scripts/security/security-mirror-progress-guard.py index 491f2a27f..2b2c10da9 100755 --- a/scripts/security/security-mirror-progress-guard.py +++ b/scripts/security/security-mirror-progress-guard.py @@ -746,6 +746,7 @@ def validate(root: Path) -> None: "s2_155_iwooos_first_layer_progressive_disclosure", "s2_156_iwooos_first_screen_depth_map", "s2_157_iwooos_progress_evidence_rail", + "s2_158_iwooos_evidence_unlock_queue", ] assert_equal( "progress_delta_ledger.delta_ids", @@ -1691,6 +1692,40 @@ def validate(root: Path) -> None: iwooos_projection["summary"]["progress_evidence_rail_runtime_gate_count"], 0, ) + assert_true( + "iwooos_projection.summary.evidence_unlock_queue_first_layer", + iwooos_projection["summary"]["evidence_unlock_queue_first_layer"], + ) + assert_equal( + "iwooos_projection.summary.evidence_unlock_queue_item_count", + iwooos_projection["summary"]["evidence_unlock_queue_item_count"], + 4, + ) + assert_equal( + "iwooos_projection.summary.evidence_unlock_queue_request_sent_count", + iwooos_projection["summary"]["evidence_unlock_queue_request_sent_count"], + 0, + ) + assert_equal( + "iwooos_projection.summary.evidence_unlock_queue_received_count", + iwooos_projection["summary"]["evidence_unlock_queue_received_count"], + 0, + ) + assert_equal( + "iwooos_projection.summary.evidence_unlock_queue_accepted_count", + iwooos_projection["summary"]["evidence_unlock_queue_accepted_count"], + 0, + ) + assert_equal( + "iwooos_projection.summary.evidence_unlock_queue_github_primary_ready_count", + iwooos_projection["summary"]["evidence_unlock_queue_github_primary_ready_count"], + 0, + ) + assert_equal( + "iwooos_projection.summary.evidence_unlock_queue_runtime_gate_count", + iwooos_projection["summary"]["evidence_unlock_queue_runtime_gate_count"], + 0, + ) assert_equal( "iwooos_projection.summary.all_product_coverage_snapshot_default_summary_mode", iwooos_projection["summary"]["all_product_coverage_snapshot_default_summary_mode"], @@ -12595,6 +12630,83 @@ def validate(root: Path) -> None: f"iwooos_projection.progress_evidence_rail_items.runtimeGate.{flag}", runtime_gate_rail_item[flag], ) + expected_evidence_unlock_queue_item_ids = [ + "giteaOwner", + "githubTarget", + "refTruth", + "workflowSecrets", + ] + evidence_unlock_queue_items = iwooos_projection["evidence_unlock_queue_items"] + assert_equal( + "iwooos_projection.evidence_unlock_queue_items.ids", + [item["item_id"] for item in evidence_unlock_queue_items], + expected_evidence_unlock_queue_item_ids, + ) + assert_equal( + "iwooos_projection.evidence_unlock_queue_items.display_order", + [item["display_order"] for item in evidence_unlock_queue_items], + list(range(1, len(expected_evidence_unlock_queue_item_ids) + 1)), + ) + evidence_unlock_queue_gates = {item["item_id"]: item["gate"] for item in evidence_unlock_queue_items} + assert_equal("iwooos_projection.evidence_unlock_queue_items.giteaOwner.gate", evidence_unlock_queue_gates["giteaOwner"], "S4.9") + assert_equal("iwooos_projection.evidence_unlock_queue_items.githubTarget.gate", evidence_unlock_queue_gates["githubTarget"], "S4.10") + assert_equal("iwooos_projection.evidence_unlock_queue_items.refTruth.gate", evidence_unlock_queue_gates["refTruth"], "S4.11") + assert_equal("iwooos_projection.evidence_unlock_queue_items.workflowSecrets.gate", evidence_unlock_queue_gates["workflowSecrets"], "S4.12") + evidence_unlock_queue_values = {item["item_id"]: item["value"] for item in evidence_unlock_queue_items} + assert_equal("iwooos_projection.evidence_unlock_queue_items.giteaOwner.value", evidence_unlock_queue_values["giteaOwner"], "0/5") + assert_equal("iwooos_projection.evidence_unlock_queue_items.githubTarget.value", evidence_unlock_queue_values["githubTarget"], "0/8") + assert_equal("iwooos_projection.evidence_unlock_queue_items.refTruth.value", evidence_unlock_queue_values["refTruth"], "0/5") + assert_equal("iwooos_projection.evidence_unlock_queue_items.workflowSecrets.value", evidence_unlock_queue_values["workflowSecrets"], "0/5") + for item in evidence_unlock_queue_items: + item_id = item["item_id"] + assert_equal( + f"iwooos_projection.evidence_unlock_queue_items.{item_id}.display_mode", + item["display_mode"], + "first_screen_evidence_unlock_queue", + ) + for count_key in ["request_sent_count", "received_count", "accepted_count", "runtime_gate_count"]: + assert_equal( + f"iwooos_projection.evidence_unlock_queue_items.{item_id}.{count_key}", + item[count_key], + 0, + ) + assert_false( + f"iwooos_projection.evidence_unlock_queue_items.{item_id}.runtime_execution_authorized", + item["runtime_execution_authorized"], + ) + assert_false( + f"iwooos_projection.evidence_unlock_queue_items.{item_id}.action_buttons_allowed", + item["action_buttons_allowed"], + ) + assert_true( + f"iwooos_projection.evidence_unlock_queue_items.{item_id}.not_authorization", + item["not_authorization"], + ) + github_target_queue_item = next(item for item in evidence_unlock_queue_items if item["item_id"] == "githubTarget") + assert_equal( + "iwooos_projection.evidence_unlock_queue_items.githubTarget.candidate_repo_count", + github_target_queue_item["candidate_repo_count"], + 8, + ) + assert_equal( + "iwooos_projection.evidence_unlock_queue_items.githubTarget.github_primary_ready_count", + github_target_queue_item["github_primary_ready_count"], + 0, + ) + assert_false( + "iwooos_projection.evidence_unlock_queue_items.githubTarget.source_control_mutation_authorized", + github_target_queue_item["source_control_mutation_authorized"], + ) + ref_truth_queue_item = next(item for item in evidence_unlock_queue_items if item["item_id"] == "refTruth") + assert_false( + "iwooos_projection.evidence_unlock_queue_items.refTruth.refs_sync_authorized", + ref_truth_queue_item["refs_sync_authorized"], + ) + workflow_secrets_queue_item = next(item for item in evidence_unlock_queue_items if item["item_id"] == "workflowSecrets") + assert_false( + "iwooos_projection.evidence_unlock_queue_items.workflowSecrets.secret_plaintext_collection_allowed", + workflow_secrets_queue_item["secret_plaintext_collection_allowed"], + ) for text in [ 'data-testid="iwooos-first-screen-depth-map-board"', 'data-testid="iwooos-first-screen-depth-map-layers"', @@ -12682,6 +12794,48 @@ def validate(root: Path) -> None: iwooos_projection_page, text, ) + for text in [ + 'data-testid="iwooos-evidence-unlock-queue-board"', + 'data-testid="iwooos-evidence-unlock-queue-items"', + 'data-testid="iwooos-evidence-unlock-queue-boundaries"', + "IwoooSEvidenceUnlockQueueBoard", + "evidenceUnlockQueueItems", + "evidenceUnlockQueueBoundaries", + ]: + assert_text_contains( + "iwooos_page.evidence_unlock_queue", + iwooos_projection_page, + text, + ) + assert_text_before( + "iwooos_page.progress_evidence_rail_before_evidence_unlock_queue", + iwooos_projection_page, + "", + "", + ) + assert_text_before( + "iwooos_page.evidence_unlock_queue_before_immediate_visual_mesh", + iwooos_projection_page, + "", + "", + ) + for text in [ + "iwooos_evidence_unlock_queue_item_count=4", + "iwooos_evidence_unlock_queue_request_sent_count=0", + "iwooos_evidence_unlock_queue_received_count=0", + "iwooos_evidence_unlock_queue_accepted_count=0", + "iwooos_evidence_unlock_queue_github_primary_ready_count=0", + "iwooos_evidence_unlock_queue_runtime_gate_count=0", + "runtime_execution_authorized=false", + "active_runtime_gate_count=0", + "action_buttons_allowed=false", + "not_authorization=true", + ]: + assert_text_contains( + "iwooos_page.evidence_unlock_queue_boundary", + iwooos_projection_page, + text, + ) assert_text_contains( "iwooos_page.focus_deck_testid", iwooos_projection_page,