From d84ccb630a8b33b5240d850f27dec9b436725e50 Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 1 Jun 2026 09:22:57 +0800 Subject: [PATCH] feat(web): add IwoooS gate radar --- apps/web/messages/en.json | 59 ++++ apps/web/messages/zh-TW.json | 59 ++++ apps/web/src/app/[locale]/iwooos/page.tsx | 263 ++++++++++++++++++ docs/LOGBOOK.md | 26 ++ .../iwooos-posture-projection.snapshot.json | 60 ++++ ...ecurity-mirror-status-rollup.snapshot.json | 12 + .../security-mirror-progress-guard.py | 234 ++++++++++++++++ 7 files changed, 713 insertions(+) diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index ed62458f0..4631fd315 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -5447,6 +5447,65 @@ "boundaryTitle": "視覺模型邊界", "boundaryIntro": "這張圖是首屏理解模型,不是操作面板;所有高風險動作仍需人工批准與後續 runtime gate。" }, + "gateRadar": { + "eyebrow": "執行閘雷達", + "title": "一眼看懂哪裡可前進、哪裡不能碰", + "subtitle": "把可見成果、目前卡點、人工審查、禁止動作拆成四條雷達線;這裡只切換視角,不執行任何動作。", + "tabsLabel": "執行閘雷達視角", + "activeLabel": "目前視角", + "summary": { + "visibleScope": { + "label": "可見範圍" + }, + "currentBlocker": { + "label": "主要卡點" + }, + "runtimeGate": { + "label": "執行閘" + } + }, + "panelLabels": { + "evidence": "已呈現", + "next": "下一步", + "locked": "仍鎖住" + }, + "lanes": { + "visible": { + "title": "已可見成果", + "state": "前台可理解", + "detail": "使用者已能在第一屏看見產品、主機、版本來源、監控工具、AwoooP 與 Gate 0 的整體關係。", + "evidence": "8 類資產與 3 台主機都進入只讀視覺範圍。", + "next": "持續把下方長證據轉成可掃描的圖表與狀態卡。", + "locked": "可見成果不等於可執行授權。" + }, + "blocker": { + "title": "目前主要卡點", + "state": "S4.9 等待回覆", + "detail": "真正會推動整體百分比的卡點仍是 Gitea / GitHub 版本來源負責人回覆與脫敏證據。", + "evidence": "owner response received=0、accepted=0,GitHub primary ready=0。", + "next": "先補 S4.9 負責人回覆與可驗證 evidence pointer。", + "locked": "不能把視覺化、文件或導覽當成 accepted decision。" + }, + "review": { + "title": "人工審查區", + "state": "等待 reviewer", + "detail": "Kali 維護、主機更新、GitHub primary、runtime gate 都需要人工決策紀錄與回復窗口。", + "evidence": "目前只準備審查材料,沒有建立正式執行紀錄。", + "next": "把審查材料收斂成 owner 可回覆的最小包。", + "locked": "沒有 reviewer acceptance 前,不進入修復或主機變更。" + }, + "locked": { + "title": "禁止動作", + "state": "Gate 0", + "detail": "掃描、修復、部署、主機變更、Kali 更新、repo / refs / workflow 變更全部仍維持關閉。", + "evidence": "active_runtime_gate_count=0;runtime_execution_authorized=false。", + "next": "只有明確批准、脫敏證據與後續 runtime gate 才能往下一段。", + "locked": "不得把這個雷達面板做成操作按鈕。" + } + }, + "boundaryTitle": "雷達邊界", + "boundaryIntro": "這個雷達只是決策視角切換;它不會觸發 Kali、SSH、掃描、修復、部署或版本來源變更。" + }, "visualCommandDashboard": { "eyebrow": "視覺化資安指揮板", "title": "先看圖,再展開證據", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index ed62458f0..4631fd315 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -5447,6 +5447,65 @@ "boundaryTitle": "視覺模型邊界", "boundaryIntro": "這張圖是首屏理解模型,不是操作面板;所有高風險動作仍需人工批准與後續 runtime gate。" }, + "gateRadar": { + "eyebrow": "執行閘雷達", + "title": "一眼看懂哪裡可前進、哪裡不能碰", + "subtitle": "把可見成果、目前卡點、人工審查、禁止動作拆成四條雷達線;這裡只切換視角,不執行任何動作。", + "tabsLabel": "執行閘雷達視角", + "activeLabel": "目前視角", + "summary": { + "visibleScope": { + "label": "可見範圍" + }, + "currentBlocker": { + "label": "主要卡點" + }, + "runtimeGate": { + "label": "執行閘" + } + }, + "panelLabels": { + "evidence": "已呈現", + "next": "下一步", + "locked": "仍鎖住" + }, + "lanes": { + "visible": { + "title": "已可見成果", + "state": "前台可理解", + "detail": "使用者已能在第一屏看見產品、主機、版本來源、監控工具、AwoooP 與 Gate 0 的整體關係。", + "evidence": "8 類資產與 3 台主機都進入只讀視覺範圍。", + "next": "持續把下方長證據轉成可掃描的圖表與狀態卡。", + "locked": "可見成果不等於可執行授權。" + }, + "blocker": { + "title": "目前主要卡點", + "state": "S4.9 等待回覆", + "detail": "真正會推動整體百分比的卡點仍是 Gitea / GitHub 版本來源負責人回覆與脫敏證據。", + "evidence": "owner response received=0、accepted=0,GitHub primary ready=0。", + "next": "先補 S4.9 負責人回覆與可驗證 evidence pointer。", + "locked": "不能把視覺化、文件或導覽當成 accepted decision。" + }, + "review": { + "title": "人工審查區", + "state": "等待 reviewer", + "detail": "Kali 維護、主機更新、GitHub primary、runtime gate 都需要人工決策紀錄與回復窗口。", + "evidence": "目前只準備審查材料,沒有建立正式執行紀錄。", + "next": "把審查材料收斂成 owner 可回覆的最小包。", + "locked": "沒有 reviewer acceptance 前,不進入修復或主機變更。" + }, + "locked": { + "title": "禁止動作", + "state": "Gate 0", + "detail": "掃描、修復、部署、主機變更、Kali 更新、repo / refs / workflow 變更全部仍維持關閉。", + "evidence": "active_runtime_gate_count=0;runtime_execution_authorized=false。", + "next": "只有明確批准、脫敏證據與後續 runtime gate 才能往下一段。", + "locked": "不得把這個雷達面板做成操作按鈕。" + } + }, + "boundaryTitle": "雷達邊界", + "boundaryIntro": "這個雷達只是決策視角切換;它不會觸發 Kali、SSH、掃描、修復、部署或版本來源變更。" + }, "visualCommandDashboard": { "eyebrow": "視覺化資安指揮板", "title": "先看圖,再展開證據", diff --git a/apps/web/src/app/[locale]/iwooos/page.tsx b/apps/web/src/app/[locale]/iwooos/page.tsx index 03a78d00f..8941a217c 100644 --- a/apps/web/src/app/[locale]/iwooos/page.tsx +++ b/apps/web/src/app/[locale]/iwooos/page.tsx @@ -188,6 +188,16 @@ type IwoooSImmediateVisualMeshNode = { tone: 'steady' | 'warn' | 'locked' } +type IwoooSGateRadarMode = 'visible' | 'blocker' | 'review' | 'locked' + +type IwoooSGateRadarLane = { + key: IwoooSGateRadarMode + metric: string + score: number + icon: typeof ShieldCheck + tone: 'steady' | 'warn' | 'locked' +} + type AwoooPReadOnlyLandingReadinessItem = { key: string item: string @@ -2353,6 +2363,36 @@ const iwooosImmediateVisualMeshBoundaries = [ 'not_authorization=true', ] as const +const iwooosGateRadarLanes: IwoooSGateRadarLane[] = [ + { key: 'visible', metric: '8/8', score: 86, icon: ShieldCheck, tone: 'steady' }, + { key: 'blocker', metric: 'S4.9', score: 61, icon: FileWarning, tone: 'warn' }, + { key: 'review', metric: '0/5', score: 40, icon: ClipboardCheck, tone: 'warn' }, + { key: 'locked', metric: 'Gate 0', score: 0, icon: Lock, tone: 'locked' }, +] + +const iwooosGateRadarSummary = [ + { key: 'visibleScope', value: '8/8', icon: ShieldCheck, tone: 'steady' }, + { key: 'currentBlocker', value: 'S4.9', icon: FileWarning, tone: 'warn' }, + { key: 'runtimeGate', value: '0', icon: Lock, tone: 'locked' }, +] as const + +const iwooosGateRadarBoundaries = [ + 'iwooos_gate_radar_first_layer=true', + 'iwooos_gate_radar_lane_count=4', + 'iwooos_gate_radar_default_lane=visible', + 'iwooos_gate_radar_above_command_map=true', + 'iwooos_gate_radar_interactive_lens_allowed=true', + 'iwooos_gate_radar_execution_action_buttons_allowed=false', + 'iwooos_gate_radar_runtime_gate_count=0', + 'iwooos_gate_radar_scan_authorized=false', + 'iwooos_gate_radar_host_change_authorized=false', + 'iwooos_gate_radar_source_control_mutation_authorized=false', + 'runtime_execution_authorized=false', + 'active_runtime_gate_count=0', + 'action_buttons_allowed=false', + 'not_authorization=true', +] as const + const iwooosFirstUnlockEvidencePacketSlots = [ { key: 'ownerDecisionMetadata', slot: '01', state: '待補', icon: ClipboardCheck, tone: 'warn' }, { key: 'scopeEvidenceRefs', slot: '02', state: '待補', icon: GitBranch, tone: 'warn' }, @@ -9491,6 +9531,228 @@ function IwoooSImmediateVisualMeshBoard() { ) } +function IwoooSGateRadarBoard() { + const t = useTranslations('iwooos.gateRadar') + const [activeKey, setActiveKey] = useState('visible') + const activeLane = iwooosGateRadarLanes.find(item => item.key === activeKey) ?? iwooosGateRadarLanes[0] + const ActiveIcon = activeLane.icon + const textWrap = { overflowWrap: 'anywhere' as const, wordBreak: 'break-word' as const } + + return ( +
+
+
+
+
+ + {t('eyebrow')} +
+

{t('title')}

+

+ {t('subtitle')} +

+
+ +
+ {iwooosGateRadarSummary.map(item => { + const Icon = item.icon + return ( +
+
+ {t(`summary.${item.key}.label` as never)} + +
+
+ {item.value} +
+
+ ) + })} +
+
+ +
+
+
+
+ + {activeLane.metric} +
+
+
+
{t('activeLabel')}
+

+ {t(`lanes.${activeLane.key}.title` as never)} +

+

+ {t(`lanes.${activeLane.key}.detail` as never)} +

+
+
+ +
+
+ {iwooosGateRadarLanes.map(item => { + const Icon = item.icon + const selected = activeKey === item.key + return ( + + ) + })} +
+ +
+ {['evidence', 'next', 'locked'].map(key => ( +
+
{t(`panelLabels.${key}` as never)}
+
+ {t(`lanes.${activeLane.key}.${key}` as never)} +
+
+ ))} +
+
+
+ +
+ + {t('boundaryTitle')} + +

+ {t('boundaryIntro')} +

+
+ {iwooosGateRadarBoundaries.map(item => ( + + {item} + + ))} +
+
+
+
+ ) +} + function IwoooSCommandMapBoard() { const t = useTranslations('iwooos.commandMap') const [activeKey, setActiveKey] = useState('unlock') @@ -14393,6 +14655,7 @@ export default function IwoooSPage({ params }: { params: { locale: string } }) { + diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index 9595b88bc..980f71712 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -1,3 +1,29 @@ +## 2026-06-01|IwoooS 執行閘雷達 + +**背景**: + +- 使用者批准繼續推進,且前一輪已把 IwoooS 首屏從長文字推進到資安網視覺模型。 +- 本輪接著解決「仍然不好一眼理解哪些工作已完成、哪些卡住、哪些禁止」的問題,但不新增任何執行能力。 + +**本次調整**: + +- `apps/web/src/app/[locale]/iwooos/page.tsx`: + - 新增 `IwoooSGateRadarBoard`,放在首屏資安網視覺模型後、命令地圖前。 + - 四條雷達視角:已可見成果、目前主要卡點、人工審查區、禁止動作。 + - 使用 tab / radar ring / 三格狀態面板呈現,使用者可以切換理解視角,但沒有任何掃描、修復、部署或主機操作按鈕。 +- `apps/web/messages/zh-TW.json` / `apps/web/messages/en.json`: + - 新增 `iwooos.gateRadar` 繁中文案;`en.json` 維持繁中鏡像。 +- `docs/security/iwooos-posture-projection.snapshot.json` / `security-mirror-status-rollup.snapshot.json`: + - 補上 `gate_radar_first_layer=true`、`gate_radar_lane_count=4`、`gate_radar_default_lane=visible`、`gate_radar_runtime_gate_count=0`。 + - 新增 `S2.147` 進度 ledger;headline 不增加,因為這是決策可理解度,不是 runtime gate。 +- `scripts/security/security-mirror-progress-guard.py`: + - 新增 guard,鎖住雷達位置、四條 lane、繁中文案、snapshot 欄位與 runtime false 邊界。 + +**進度邊界**: + +- 整體維持 `61%`。 +- 本輪沒有連入 Kali 主機執行更新,沒有掃描、修復、部署按鈕、主機變更、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 4a53f388c..25eff0436 100644 --- a/docs/security/iwooos-posture-projection.snapshot.json +++ b/docs/security/iwooos-posture-projection.snapshot.json @@ -71,6 +71,12 @@ "immediate_visual_mesh_link_count": 6, "immediate_visual_mesh_above_command_map": true, "immediate_visual_mesh_runtime_gate_count": 0, + "gate_radar_first_layer": true, + "gate_radar_lane_count": 4, + "gate_radar_default_lane": "visible", + "gate_radar_above_command_map": true, + "gate_radar_interactive_lens_allowed": true, + "gate_radar_runtime_gate_count": 0, "long_form_sections_default_collapsed": true, "owner_response_validation_received_count": 0, "owner_response_validation_accepted_count": 0, @@ -437,6 +443,60 @@ "not_authorization": true } ], + "gate_radar_lanes": [ + { + "lane_id": "visible", + "display_order": 1, + "display_mode": "first_screen_gate_radar", + "score": 86, + "interactive_lens_allowed": true, + "execution_action_button_allowed": false, + "runtime_gate_opened": false, + "runtime_execution_authorized": false, + "not_authorization": true + }, + { + "lane_id": "blocker", + "display_order": 2, + "display_mode": "first_screen_gate_radar", + "score": 61, + "interactive_lens_allowed": true, + "execution_action_button_allowed": false, + "owner_response_received_count": 0, + "owner_response_accepted_count": 0, + "github_primary_ready_count": 0, + "runtime_gate_opened": false, + "runtime_execution_authorized": false, + "not_authorization": true + }, + { + "lane_id": "review", + "display_order": 3, + "display_mode": "first_screen_gate_radar", + "score": 40, + "interactive_lens_allowed": true, + "execution_action_button_allowed": false, + "human_review_required": true, + "formal_decision_record_created": false, + "runtime_gate_opened": false, + "runtime_execution_authorized": false, + "not_authorization": true + }, + { + "lane_id": "locked", + "display_order": 4, + "display_mode": "first_screen_gate_radar", + "score": 0, + "interactive_lens_allowed": true, + "execution_action_button_allowed": false, + "scan_authorized": false, + "host_change_authorized": false, + "source_control_mutation_authorized": false, + "runtime_gate_opened": false, + "runtime_execution_authorized": false, + "not_authorization": true + } + ], "posture_pillars": [ { "pillar_id": "exposure_posture", diff --git a/docs/security/security-mirror-status-rollup.snapshot.json b/docs/security/security-mirror-status-rollup.snapshot.json index 015df12f5..2d0fc77b8 100644 --- a/docs/security/security-mirror-status-rollup.snapshot.json +++ b/docs/security/security-mirror-status-rollup.snapshot.json @@ -2233,6 +2233,18 @@ "runtime_delta": false, "execution_authorized": false, "not_authorization": true + }, + { + "delta_id": "s2_147_iwooos_gate_radar_first_layer", + "display_order": 176, + "completed_stage": "S2.147 IwoooS 執行閘雷達", + "progress_axis": "framework_detail", + "headline_percent_delta": 0, + "framework_delta_visible": true, + "why_headline_unchanged": "IwoooS 只新增首屏執行閘雷達,把已可見成果、目前卡點、人工審查與禁止動作拆成四條互動式視角;iwooos_gate_radar_lane_count=4、iwooos_gate_radar_default_lane=visible、iwooos_gate_radar_above_command_map=true、iwooos_gate_radar_interactive_lens_allowed=true、iwooos_gate_radar_execution_action_buttons_allowed=false、iwooos_gate_radar_runtime_gate_count=0、runtime_execution_authorized=false、active_runtime_gate_count=0,不把視角切換當掃描、修復、部署、主機更新、source-control mutation、GitHub primary 切換、Gitea 停用或 runtime gate。", + "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 2897b7648..6cba07601 100755 --- a/scripts/security/security-mirror-progress-guard.py +++ b/scripts/security/security-mirror-progress-guard.py @@ -611,6 +611,7 @@ def validate(root: Path) -> None: "s2_144_iwooos_command_palette_security_entry_unified", "s2_145_iwooos_first_layer_focus_deck", "s2_146_iwooos_immediate_visual_mesh", + "s2_147_iwooos_gate_radar_first_layer", ] assert_equal( "progress_delta_ledger.delta_ids", @@ -1564,6 +1565,33 @@ def validate(root: Path) -> None: iwooos_projection["summary"]["immediate_visual_mesh_runtime_gate_count"], 0, ) + assert_true( + "iwooos_projection.summary.gate_radar_first_layer", + iwooos_projection["summary"]["gate_radar_first_layer"], + ) + assert_equal( + "iwooos_projection.summary.gate_radar_lane_count", + iwooos_projection["summary"]["gate_radar_lane_count"], + 4, + ) + assert_equal( + "iwooos_projection.summary.gate_radar_default_lane", + iwooos_projection["summary"]["gate_radar_default_lane"], + "visible", + ) + assert_true( + "iwooos_projection.summary.gate_radar_above_command_map", + iwooos_projection["summary"]["gate_radar_above_command_map"], + ) + assert_true( + "iwooos_projection.summary.gate_radar_interactive_lens_allowed", + iwooos_projection["summary"]["gate_radar_interactive_lens_allowed"], + ) + assert_equal( + "iwooos_projection.summary.gate_radar_runtime_gate_count", + iwooos_projection["summary"]["gate_radar_runtime_gate_count"], + 0, + ) assert_true( "iwooos_projection.summary.all_product_coverage_snapshot_detail_ledger_collapsed", iwooos_projection["summary"]["all_product_coverage_snapshot_detail_ledger_collapsed"], @@ -11929,6 +11957,56 @@ def validate(root: Path) -> None: iwooos_projection_page, text, ) + for text in [ + 'data-testid="iwooos-gate-radar-board"', + 'data-testid="iwooos-gate-radar-canvas"', + 'data-testid="iwooos-gate-radar-active-panel"', + 'data-testid="iwooos-gate-radar-boundaries"', + "IwoooSGateRadarBoard", + "IwoooSGateRadarMode", + "iwooosGateRadarLanes", + "iwooosGateRadarSummary", + "iwooosGateRadarBoundaries", + "conic-gradient", + ]: + assert_text_contains( + "iwooos_page.gate_radar", + iwooos_projection_page, + text, + ) + assert_text_before( + "iwooos_page.immediate_visual_mesh_before_gate_radar", + iwooos_projection_page, + "", + "", + ) + assert_text_before( + "iwooos_page.gate_radar_before_command_map", + iwooos_projection_page, + "", + "", + ) + for text in [ + "iwooos_gate_radar_first_layer=true", + "iwooos_gate_radar_lane_count=4", + "iwooos_gate_radar_default_lane=visible", + "iwooos_gate_radar_above_command_map=true", + "iwooos_gate_radar_interactive_lens_allowed=true", + "iwooos_gate_radar_execution_action_buttons_allowed=false", + "iwooos_gate_radar_runtime_gate_count=0", + "iwooos_gate_radar_scan_authorized=false", + "iwooos_gate_radar_host_change_authorized=false", + "iwooos_gate_radar_source_control_mutation_authorized=false", + "runtime_execution_authorized=false", + "active_runtime_gate_count=0", + "action_buttons_allowed=false", + "not_authorization=true", + ]: + assert_text_contains( + "iwooos_page.gate_radar_boundary", + iwooos_projection_page, + text, + ) assert_text_contains( "iwooos_page.command_map_testid", iwooos_projection_page, @@ -12150,6 +12228,76 @@ def validate(root: Path) -> None: "iwooos_projection.immediate_visual_mesh_nodes.runtimeGate.scan_authorized", runtime_gate_mesh_node["scan_authorized"], ) + expected_gate_radar_lane_ids = [ + "visible", + "blocker", + "review", + "locked", + ] + gate_radar_lanes = iwooos_projection["gate_radar_lanes"] + assert_equal( + "iwooos_projection.gate_radar_lanes.ids", + [item["lane_id"] for item in gate_radar_lanes], + expected_gate_radar_lane_ids, + ) + assert_equal( + "iwooos_projection.gate_radar_lanes.display_order", + [item["display_order"] for item in gate_radar_lanes], + list(range(1, len(expected_gate_radar_lane_ids) + 1)), + ) + assert_equal( + "iwooos_projection.gate_radar_lanes.scores", + [item["score"] for item in gate_radar_lanes], + [86, 61, 40, 0], + ) + for item in gate_radar_lanes: + assert_equal( + f"iwooos_projection.gate_radar_lanes.{item['lane_id']}.display_mode", + item["display_mode"], + "first_screen_gate_radar", + ) + assert_true( + f"iwooos_projection.gate_radar_lanes.{item['lane_id']}.interactive_lens_allowed", + item["interactive_lens_allowed"], + ) + assert_false( + f"iwooos_projection.gate_radar_lanes.{item['lane_id']}.execution_action_button_allowed", + item["execution_action_button_allowed"], + ) + assert_false( + f"iwooos_projection.gate_radar_lanes.{item['lane_id']}.runtime_gate_opened", + item["runtime_gate_opened"], + ) + assert_false( + f"iwooos_projection.gate_radar_lanes.{item['lane_id']}.runtime_execution_authorized", + item["runtime_execution_authorized"], + ) + assert_true( + f"iwooos_projection.gate_radar_lanes.{item['lane_id']}.not_authorization", + item["not_authorization"], + ) + blocker_gate_radar_lane = next(item for item in gate_radar_lanes if item["lane_id"] == "blocker") + for count_key in ["owner_response_received_count", "owner_response_accepted_count", "github_primary_ready_count"]: + assert_equal( + f"iwooos_projection.gate_radar_lanes.blocker.{count_key}", + blocker_gate_radar_lane[count_key], + 0, + ) + review_gate_radar_lane = next(item for item in gate_radar_lanes if item["lane_id"] == "review") + assert_true( + "iwooos_projection.gate_radar_lanes.review.human_review_required", + review_gate_radar_lane["human_review_required"], + ) + assert_false( + "iwooos_projection.gate_radar_lanes.review.formal_decision_record_created", + review_gate_radar_lane["formal_decision_record_created"], + ) + locked_gate_radar_lane = next(item for item in gate_radar_lanes if item["lane_id"] == "locked") + for flag in ["scan_authorized", "host_change_authorized", "source_control_mutation_authorized"]: + assert_false( + f"iwooos_projection.gate_radar_lanes.locked.{flag}", + locked_gate_radar_lane[flag], + ) for text in [ "iwooos_first_unlock_path_step_count=5", "iwooos_first_unlock_path_current_focus=s4_9_owner_response", @@ -12396,6 +12544,92 @@ def validate(root: Path) -> None: list(web_messages_en["iwooos"]["immediateVisualMesh"]["nodes"][key].keys()), field, ) + assert_contains( + "web_messages.zh-TW.iwooos.gateRadar", + list(web_messages_zh["iwooos"].keys()), + "gateRadar", + ) + assert_contains( + "web_messages.en.iwooos.gateRadar", + list(web_messages_en["iwooos"].keys()), + "gateRadar", + ) + for key in [ + "eyebrow", + "title", + "subtitle", + "tabsLabel", + "activeLabel", + "summary", + "panelLabels", + "lanes", + "boundaryTitle", + "boundaryIntro", + ]: + assert_contains( + "web_messages.zh-TW.iwooos.gateRadar.keys", + list(web_messages_zh["iwooos"]["gateRadar"].keys()), + key, + ) + assert_contains( + "web_messages.en.iwooos.gateRadar.keys", + list(web_messages_en["iwooos"]["gateRadar"].keys()), + key, + ) + for key in ["visibleScope", "currentBlocker", "runtimeGate"]: + assert_contains( + "web_messages.zh-TW.iwooos.gateRadar.summary", + list(web_messages_zh["iwooos"]["gateRadar"]["summary"].keys()), + key, + ) + assert_contains( + "web_messages.en.iwooos.gateRadar.summary", + list(web_messages_en["iwooos"]["gateRadar"]["summary"].keys()), + key, + ) + assert_contains( + f"web_messages.zh-TW.iwooos.gateRadar.summary.{key}", + list(web_messages_zh["iwooos"]["gateRadar"]["summary"][key].keys()), + "label", + ) + assert_contains( + f"web_messages.en.iwooos.gateRadar.summary.{key}", + list(web_messages_en["iwooos"]["gateRadar"]["summary"][key].keys()), + "label", + ) + for key in ["evidence", "next", "locked"]: + assert_contains( + "web_messages.zh-TW.iwooos.gateRadar.panelLabels", + list(web_messages_zh["iwooos"]["gateRadar"]["panelLabels"].keys()), + key, + ) + assert_contains( + "web_messages.en.iwooos.gateRadar.panelLabels", + list(web_messages_en["iwooos"]["gateRadar"]["panelLabels"].keys()), + key, + ) + for key in ["visible", "blocker", "review", "locked"]: + assert_contains( + "web_messages.zh-TW.iwooos.gateRadar.lanes", + list(web_messages_zh["iwooos"]["gateRadar"]["lanes"].keys()), + key, + ) + assert_contains( + "web_messages.en.iwooos.gateRadar.lanes", + list(web_messages_en["iwooos"]["gateRadar"]["lanes"].keys()), + key, + ) + for field in ["title", "state", "detail", "evidence", "next", "locked"]: + assert_contains( + f"web_messages.zh-TW.iwooos.gateRadar.lanes.{key}", + list(web_messages_zh["iwooos"]["gateRadar"]["lanes"][key].keys()), + field, + ) + assert_contains( + f"web_messages.en.iwooos.gateRadar.lanes.{key}", + list(web_messages_en["iwooos"]["gateRadar"]["lanes"][key].keys()), + field, + ) assert_text_contains( "iwooos_page.first_unlock_evidence_packet_testid", iwooos_projection_page,