diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 83ceccd0f..89202e970 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -5545,6 +5545,34 @@ } } }, + "progressEvidenceRail": { + "eyebrow": "進度證據流速儀", + "title": "61% 要前進,還缺哪些實證", + "subtitle": "用短指標把卡點固定在首屏:負責人回覆、脫敏證據、審查接受、GitHub 主來源與執行期閘門都還沒有達到可推進條件。", + "boundaryTitle": "流速儀邊界", + "items": { + "ownerResponses": { + "title": "負責人回覆", + "body": "四包必要回覆仍待收齊與驗收。" + }, + "redactedEvidence": { + "title": "脫敏證據", + "body": "尚未匯入可驗收 payload。" + }, + "reviewAcceptance": { + "title": "審查接受", + "body": "尚無可讓進度跳點的接受結果。" + }, + "githubPrimary": { + "title": "GitHub 主來源", + "body": "八個候選庫仍在 readiness gate。" + }, + "runtimeGate": { + "title": "執行期閘門", + "body": "掃描、SSH、更新、修復仍維持關閉。" + } + } + }, "immediateVisualMesh": { "eyebrow": "首屏資安網視覺模型", "title": "資安關聯視覺模型", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index 83ceccd0f..89202e970 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -5545,6 +5545,34 @@ } } }, + "progressEvidenceRail": { + "eyebrow": "進度證據流速儀", + "title": "61% 要前進,還缺哪些實證", + "subtitle": "用短指標把卡點固定在首屏:負責人回覆、脫敏證據、審查接受、GitHub 主來源與執行期閘門都還沒有達到可推進條件。", + "boundaryTitle": "流速儀邊界", + "items": { + "ownerResponses": { + "title": "負責人回覆", + "body": "四包必要回覆仍待收齊與驗收。" + }, + "redactedEvidence": { + "title": "脫敏證據", + "body": "尚未匯入可驗收 payload。" + }, + "reviewAcceptance": { + "title": "審查接受", + "body": "尚無可讓進度跳點的接受結果。" + }, + "githubPrimary": { + "title": "GitHub 主來源", + "body": "八個候選庫仍在 readiness gate。" + }, + "runtimeGate": { + "title": "執行期閘門", + "body": "掃描、SSH、更新、修復仍維持關閉。" + } + } + }, "immediateVisualMesh": { "eyebrow": "首屏資安網視覺模型", "title": "資安關聯視覺模型", diff --git a/apps/web/src/app/[locale]/iwooos/page.tsx b/apps/web/src/app/[locale]/iwooos/page.tsx index 82ff5b342..f38de0f5b 100644 --- a/apps/web/src/app/[locale]/iwooos/page.tsx +++ b/apps/web/src/app/[locale]/iwooos/page.tsx @@ -48,6 +48,13 @@ type FirstScreenDepthLayer = { href?: string } +type ProgressEvidenceRailItem = { + key: string + value: string + icon: typeof ShieldCheck + tone: 'steady' | 'warn' | 'locked' +} + type FastProgressItem = { key: string value: string @@ -908,6 +915,27 @@ const firstScreenDepthBoundaries = [ 'not_authorization=true', ] +const progressEvidenceRailItems: ProgressEvidenceRailItem[] = [ + { key: 'ownerResponses', value: '0/4', icon: ClipboardCheck, tone: 'warn' }, + { key: 'redactedEvidence', value: '0', icon: FileText, tone: 'warn' }, + { key: 'reviewAcceptance', value: '0', icon: CheckCircle2, tone: 'warn' }, + { key: 'githubPrimary', value: '0/8', icon: GitBranch, tone: 'locked' }, + { key: 'runtimeGate', value: '0', icon: Lock, tone: 'locked' }, +] + +const progressEvidenceRailBoundaries = [ + 'iwooos_progress_evidence_rail_owner_response_received_count=0', + 'iwooos_progress_evidence_rail_owner_response_accepted_count=0', + 'iwooos_progress_evidence_rail_redacted_evidence_count=0', + 'iwooos_progress_evidence_rail_review_acceptance_count=0', + 'iwooos_progress_evidence_rail_github_primary_ready_count=0', + 'iwooos_progress_evidence_rail_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' }, @@ -10093,6 +10121,128 @@ function IwoooSFirstScreenDepthMapBoard() { ) } +function IwoooSProgressEvidenceRailBoard() { + const t = useTranslations('iwooos.progressEvidenceRail') + const textWrap = { overflowWrap: 'anywhere' as const, wordBreak: 'break-word' as const } + + return ( +
+
+
+
+
+ + {t('eyebrow')} +
+

{t('title')}

+

{t('subtitle')}

+
+ +
+ {progressEvidenceRailItems.map((item, index) => { + const Icon = item.icon + return ( +
+
+ + {item.value} +
+
+ {String(index + 1).padStart(2, '0')} +
+

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

+

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

+
+ ) + })} +
+
+ +
+ + {t('boundaryTitle')} + +
+ {progressEvidenceRailBoundaries.map(item => ( + + {item} + + ))} +
+
+
+
+ ) +} + function IwoooSImmediateVisualMeshBoard() { const t = useTranslations('iwooos.immediateVisualMesh') const textWrap = { overflowWrap: 'anywhere' as const, wordBreak: 'break-word' as const } @@ -16570,6 +16720,7 @@ export default function IwoooSPage({ params }: { params: { locale: string } }) { + diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index 6e27ca31a..aa09da595 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`:新增 `IwoooSProgressEvidenceRailBoard`,在首屏以五個短卡顯示負責人回覆、脫敏證據、審查接受、GitHub 主來源與執行期閘門狀態。 +- `apps/web/messages/zh-TW.json` / `en.json`:新增進度證據流速儀繁中文案;英文語系維持繁中鏡像。 +- `docs/security/iwooos-posture-projection.snapshot.json`:新增 `progress_evidence_rail_items` 與 summary 計數,固定目前 `0/4`、`0`、`0/8`、閘門 `0` 的可驗證語義。 +- `scripts/security/security-mirror-progress-guard.py`:新增 S2.157 guard,鎖住流速儀 testid、首屏順序、五項短指標與 runtime gate 0 邊界。 +- `docs/security/security-mirror-status-rollup.snapshot.json`:新增 `s2_157_iwooos_progress_evidence_rail` 進度台帳。 + +**進度邊界**: + +- 整體維持 `61%`;本輪是 UI/UX、證據語義與防退化,不是 Kali SSH、掃描、主機更新、修復、部署、GitHub primary 切換或 Gitea 停用。 + ## 2026-06-01|StockWoooWorkDown 防止誤配 K3s node PlayBook **背景**: diff --git a/docs/security/iwooos-posture-projection.snapshot.json b/docs/security/iwooos-posture-projection.snapshot.json index fcbf7edee..b1df757c1 100644 --- a/docs/security/iwooos-posture-projection.snapshot.json +++ b/docs/security/iwooos-posture-projection.snapshot.json @@ -44,6 +44,14 @@ "first_screen_depth_map_runtime_gate_count": 0, "first_screen_depth_map_advanced_default_visible": false, "first_screen_depth_map_scope_evidence_default_visible": false, + "progress_evidence_rail_first_layer": true, + "progress_evidence_rail_item_count": 5, + "progress_evidence_rail_owner_response_received_count": 0, + "progress_evidence_rail_owner_response_accepted_count": 0, + "progress_evidence_rail_redacted_evidence_count": 0, + "progress_evidence_rail_review_acceptance_count": 0, + "progress_evidence_rail_github_primary_ready_count": 0, + "progress_evidence_rail_runtime_gate_count": 0, "visual_command_dashboard_widget_count": 14, "visual_command_dashboard_first_layer": true, "visual_command_dashboard_default_visible": false, @@ -346,6 +354,68 @@ "not_authorization": true } ], + "progress_evidence_rail_items": [ + { + "item_id": "ownerResponses", + "display_order": 1, + "display_mode": "first_screen_progress_evidence_rail", + "value": "0/4", + "owner_response_received_count": 0, + "owner_response_accepted_count": 0, + "runtime_gate_count": 0, + "runtime_execution_authorized": false, + "action_buttons_allowed": false, + "not_authorization": true + }, + { + "item_id": "redactedEvidence", + "display_order": 2, + "display_mode": "first_screen_progress_evidence_rail", + "value": "0", + "redacted_evidence_count": 0, + "runtime_gate_count": 0, + "runtime_execution_authorized": false, + "action_buttons_allowed": false, + "not_authorization": true + }, + { + "item_id": "reviewAcceptance", + "display_order": 3, + "display_mode": "first_screen_progress_evidence_rail", + "value": "0", + "review_acceptance_count": 0, + "runtime_gate_count": 0, + "runtime_execution_authorized": false, + "action_buttons_allowed": false, + "not_authorization": true + }, + { + "item_id": "githubPrimary", + "display_order": 4, + "display_mode": "first_screen_progress_evidence_rail", + "value": "0/8", + "candidate_repo_count": 8, + "github_primary_ready_count": 0, + "source_control_mutation_authorized": false, + "runtime_gate_count": 0, + "runtime_execution_authorized": false, + "action_buttons_allowed": false, + "not_authorization": true + }, + { + "item_id": "runtimeGate", + "display_order": 5, + "display_mode": "first_screen_progress_evidence_rail", + "value": "0", + "runtime_gate_count": 0, + "scan_authorized": false, + "host_change_authorized": false, + "source_control_mutation_authorized": false, + "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 ae3a58c6b..47b0f6cd6 100644 --- a/docs/security/security-mirror-status-rollup.snapshot.json +++ b/docs/security/security-mirror-status-rollup.snapshot.json @@ -2353,6 +2353,18 @@ "runtime_delta": false, "execution_authorized": false, "not_authorization": true + }, + { + "delta_id": "s2_157_iwooos_progress_evidence_rail", + "display_order": 186, + "completed_stage": "S2.157 IwoooS 進度證據流速儀", + "progress_axis": "framework_detail", + "headline_percent_delta": 0, + "framework_delta_visible": true, + "why_headline_unchanged": "IwoooS 只新增首屏進度證據流速儀,把 61% 無法前進的條件壓成五個短指標:負責人回覆 0/4、脫敏證據 0、審查接受 0、GitHub 主來源 0/8、執行期閘門 0;iwooos_progress_evidence_rail_owner_response_received_count=0、iwooos_progress_evidence_rail_owner_response_accepted_count=0、iwooos_progress_evidence_rail_redacted_evidence_count=0、iwooos_progress_evidence_rail_review_acceptance_count=0、iwooos_progress_evidence_rail_github_primary_ready_count=0、iwooos_progress_evidence_rail_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 58f5c77ef..491f2a27f 100755 --- a/scripts/security/security-mirror-progress-guard.py +++ b/scripts/security/security-mirror-progress-guard.py @@ -745,6 +745,7 @@ def validate(root: Path) -> None: "s2_154_iwooos_first_screen_language_polish", "s2_155_iwooos_first_layer_progressive_disclosure", "s2_156_iwooos_first_screen_depth_map", + "s2_157_iwooos_progress_evidence_rail", ] assert_equal( "progress_delta_ledger.delta_ids", @@ -1651,6 +1652,45 @@ def validate(root: Path) -> None: "iwooos_projection.summary.first_screen_depth_map_scope_evidence_default_visible", iwooos_projection["summary"]["first_screen_depth_map_scope_evidence_default_visible"], ) + assert_true( + "iwooos_projection.summary.progress_evidence_rail_first_layer", + iwooos_projection["summary"]["progress_evidence_rail_first_layer"], + ) + assert_equal( + "iwooos_projection.summary.progress_evidence_rail_item_count", + iwooos_projection["summary"]["progress_evidence_rail_item_count"], + 5, + ) + assert_equal( + "iwooos_projection.summary.progress_evidence_rail_owner_response_received_count", + iwooos_projection["summary"]["progress_evidence_rail_owner_response_received_count"], + 0, + ) + assert_equal( + "iwooos_projection.summary.progress_evidence_rail_owner_response_accepted_count", + iwooos_projection["summary"]["progress_evidence_rail_owner_response_accepted_count"], + 0, + ) + assert_equal( + "iwooos_projection.summary.progress_evidence_rail_redacted_evidence_count", + iwooos_projection["summary"]["progress_evidence_rail_redacted_evidence_count"], + 0, + ) + assert_equal( + "iwooos_projection.summary.progress_evidence_rail_review_acceptance_count", + iwooos_projection["summary"]["progress_evidence_rail_review_acceptance_count"], + 0, + ) + assert_equal( + "iwooos_projection.summary.progress_evidence_rail_github_primary_ready_count", + iwooos_projection["summary"]["progress_evidence_rail_github_primary_ready_count"], + 0, + ) + assert_equal( + "iwooos_projection.summary.progress_evidence_rail_runtime_gate_count", + iwooos_projection["summary"]["progress_evidence_rail_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"], @@ -12462,6 +12502,99 @@ def validate(root: Path) -> None: f"iwooos_projection.first_screen_depth_map_layers.runtime.{flag}", runtime_depth_layer[flag], ) + expected_progress_evidence_rail_item_ids = [ + "ownerResponses", + "redactedEvidence", + "reviewAcceptance", + "githubPrimary", + "runtimeGate", + ] + progress_evidence_rail_items = iwooos_projection["progress_evidence_rail_items"] + assert_equal( + "iwooos_projection.progress_evidence_rail_items.ids", + [item["item_id"] for item in progress_evidence_rail_items], + expected_progress_evidence_rail_item_ids, + ) + assert_equal( + "iwooos_projection.progress_evidence_rail_items.display_order", + [item["display_order"] for item in progress_evidence_rail_items], + list(range(1, len(expected_progress_evidence_rail_item_ids) + 1)), + ) + progress_evidence_rail_values = {item["item_id"]: item["value"] for item in progress_evidence_rail_items} + assert_equal("iwooos_projection.progress_evidence_rail_items.ownerResponses.value", progress_evidence_rail_values["ownerResponses"], "0/4") + assert_equal("iwooos_projection.progress_evidence_rail_items.redactedEvidence.value", progress_evidence_rail_values["redactedEvidence"], "0") + assert_equal("iwooos_projection.progress_evidence_rail_items.reviewAcceptance.value", progress_evidence_rail_values["reviewAcceptance"], "0") + assert_equal("iwooos_projection.progress_evidence_rail_items.githubPrimary.value", progress_evidence_rail_values["githubPrimary"], "0/8") + assert_equal("iwooos_projection.progress_evidence_rail_items.runtimeGate.value", progress_evidence_rail_values["runtimeGate"], "0") + for item in progress_evidence_rail_items: + item_id = item["item_id"] + assert_equal( + f"iwooos_projection.progress_evidence_rail_items.{item_id}.display_mode", + item["display_mode"], + "first_screen_progress_evidence_rail", + ) + assert_equal( + f"iwooos_projection.progress_evidence_rail_items.{item_id}.runtime_gate_count", + item["runtime_gate_count"], + 0, + ) + assert_false( + f"iwooos_projection.progress_evidence_rail_items.{item_id}.runtime_execution_authorized", + item["runtime_execution_authorized"], + ) + assert_false( + f"iwooos_projection.progress_evidence_rail_items.{item_id}.action_buttons_allowed", + item["action_buttons_allowed"], + ) + assert_true( + f"iwooos_projection.progress_evidence_rail_items.{item_id}.not_authorization", + item["not_authorization"], + ) + owner_response_rail_item = next(item for item in progress_evidence_rail_items if item["item_id"] == "ownerResponses") + assert_equal( + "iwooos_projection.progress_evidence_rail_items.ownerResponses.owner_response_received_count", + owner_response_rail_item["owner_response_received_count"], + 0, + ) + assert_equal( + "iwooos_projection.progress_evidence_rail_items.ownerResponses.owner_response_accepted_count", + owner_response_rail_item["owner_response_accepted_count"], + 0, + ) + redacted_evidence_rail_item = next(item for item in progress_evidence_rail_items if item["item_id"] == "redactedEvidence") + assert_equal( + "iwooos_projection.progress_evidence_rail_items.redactedEvidence.redacted_evidence_count", + redacted_evidence_rail_item["redacted_evidence_count"], + 0, + ) + review_acceptance_rail_item = next(item for item in progress_evidence_rail_items if item["item_id"] == "reviewAcceptance") + assert_equal( + "iwooos_projection.progress_evidence_rail_items.reviewAcceptance.review_acceptance_count", + review_acceptance_rail_item["review_acceptance_count"], + 0, + ) + github_primary_rail_item = next(item for item in progress_evidence_rail_items if item["item_id"] == "githubPrimary") + assert_equal( + "iwooos_projection.progress_evidence_rail_items.githubPrimary.candidate_repo_count", + github_primary_rail_item["candidate_repo_count"], + 8, + ) + assert_equal( + "iwooos_projection.progress_evidence_rail_items.githubPrimary.github_primary_ready_count", + github_primary_rail_item["github_primary_ready_count"], + 0, + ) + for flag in ["source_control_mutation_authorized"]: + assert_false( + f"iwooos_projection.progress_evidence_rail_items.githubPrimary.{flag}", + github_primary_rail_item[flag], + ) + runtime_gate_rail_item = next(item for item in progress_evidence_rail_items if item["item_id"] == "runtimeGate") + for flag in ["scan_authorized", "host_change_authorized", "source_control_mutation_authorized"]: + assert_false( + f"iwooos_projection.progress_evidence_rail_items.runtimeGate.{flag}", + runtime_gate_rail_item[flag], + ) for text in [ 'data-testid="iwooos-first-screen-depth-map-board"', 'data-testid="iwooos-first-screen-depth-map-layers"', @@ -12507,6 +12640,48 @@ def validate(root: Path) -> None: iwooos_projection_page, text, ) + for text in [ + 'data-testid="iwooos-progress-evidence-rail-board"', + 'data-testid="iwooos-progress-evidence-rail-items"', + 'data-testid="iwooos-progress-evidence-rail-boundaries"', + "IwoooSProgressEvidenceRailBoard", + "progressEvidenceRailItems", + "progressEvidenceRailBoundaries", + ]: + assert_text_contains( + "iwooos_page.progress_evidence_rail", + iwooos_projection_page, + text, + ) + assert_text_before( + "iwooos_page.first_screen_depth_map_before_progress_evidence_rail", + iwooos_projection_page, + "", + "", + ) + assert_text_before( + "iwooos_page.progress_evidence_rail_before_immediate_visual_mesh", + iwooos_projection_page, + "", + "", + ) + for text in [ + "iwooos_progress_evidence_rail_owner_response_received_count=0", + "iwooos_progress_evidence_rail_owner_response_accepted_count=0", + "iwooos_progress_evidence_rail_redacted_evidence_count=0", + "iwooos_progress_evidence_rail_review_acceptance_count=0", + "iwooos_progress_evidence_rail_github_primary_ready_count=0", + "iwooos_progress_evidence_rail_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.progress_evidence_rail_boundary", + iwooos_projection_page, + text, + ) assert_text_contains( "iwooos_page.focus_deck_testid", iwooos_projection_page,