From 5a56162a75460943cb9b279c66886a8b5fc10edd Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 1 Jun 2026 13:18:52 +0800 Subject: [PATCH] feat(web): add IwoooS decision runway --- apps/web/messages/en.json | 112 +++++- apps/web/messages/zh-TW.json | 96 +++++ apps/web/src/app/[locale]/iwooos/page.tsx | 377 ++++++++++++++++++ docs/LOGBOOK.md | 28 ++ .../iwooos-posture-projection.snapshot.json | 179 ++++++++- ...ecurity-mirror-status-rollup.snapshot.json | 12 + .../security-mirror-progress-guard.py | 351 ++++++++++++++++ 7 files changed, 1146 insertions(+), 9 deletions(-) diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 0e017cf6e..dcdec0799 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -306,16 +306,16 @@ }, "homeCommandMap": { "eyebrow": "AI Automation Command Map", - "title": "Flow first, lists second", - "subtitle": "The first screen now answers where an alert is in the pipeline, what gate is blocking it, and which evidence source backs the status.", - "selectedStage": "Current stage", - "evidenceTitle": "Evidence sources", - "openRuns": "Open Runs", + "title": "先看流程,再看清單", + "subtitle": "第一屏直接用流程圖、瓶頸和證據來源回答:告警跑到哪一段、卡在哪一關、下一步由誰處理。", + "selectedStage": "目前階段", + "evidenceTitle": "證據來源", + "openRuns": "查看 Runs", "metrics": { - "claim": "Auto-repair claim", - "quality": "Quality gate", + "claim": "自動修復宣稱", + "quality": "品質閘門", "provider": "AI Provider", - "km": "KM health" + "km": "KM 健康" } }, "automationDelivery": { @@ -5754,6 +5754,102 @@ "boundaryTitle": "圖譜邊界", "boundaryIntro": "這張圖是專業可視化與理解層,不是掃描拓樸、不是自動修復、不是 GitHub / Gitea 切換授權。" }, + "decisionRunway": { + "eyebrow": "決策跑道", + "title": "把下一步卡點、責任邊界與禁止動作放在同一張圖", + "subtitle": "這一層回答使用者最關心的問題:到底做了什麼、下一個真正能推進百分比的是哪個 Gate、哪些主機與產品已納管、哪些仍不能自動執行。", + "railLabel": "下一步推進路線", + "selectorLabel": "切換決策跑道節點", + "fields": { + "evidence": "已具備", + "next": "下一步", + "blocked": "仍卡住" + }, + "boundarySignals": { + "scan": { + "label": "掃描授權" + }, + "hostChange": { + "label": "主機變更" + }, + "sourceMutation": { + "label": "版本異動" + }, + "runtimeExecution": { + "label": "執行期" + } + }, + "steps": { + "ownerEvidence": { + "short": "負責人證據", + "title": "S4.9 負責人回覆與脫敏證據", + "body": "目前最能讓整體進度往前的不是再加文字,而是取得版本來源負責人回覆、脫敏 evidence pointer 與可追蹤決策紀錄。", + "evidence": "IwoooS、AwoooP、拓樸圖與 progress guard 已把卡點顯示到前台。", + "next": "補 owner response received 與 redacted evidence pointer。", + "blocked": "received=0、accepted=0,所以 headline 不再硬往上推。" + }, + "reviewerAcceptance": { + "short": "審查接受", + "title": "Reviewer acceptance 才能解除下一段", + "body": "文件、schema 與前台呈現只能讓工作可理解;真正解除 gate 仍需要 reviewer acceptance 與審查紀錄。", + "evidence": "審查清單、回覆模板、handoff packet 已有只讀材料。", + "next": "把 S4.9 最小審查包交給負責人與 reviewer。", + "blocked": "accepted=0;不能把 UI 可見度當批准。" + }, + "hostWindow": { + "short": "主機窗口", + "title": "Kali 112 與開發主機 111 / 168 的維護窗口", + "body": "三台主機已進入資安架構圖,但目前是只讀納管,不代表已允許更新、調校、掃描或 SSH 操作。", + "evidence": "112 / 111 / 168 在拓樸圖、路徑探索器與主機證據鏈中可見。", + "next": "等待維護窗口、範圍、負責人與回復路徑確認。", + "blocked": "host_change_authorized=false;scan_authorized=false。" + }, + "githubPrimary": { + "short": "GitHub 主來源", + "title": "GitHub primary / Gitea 遷移仍在決策 Gate", + "body": "長期策略可朝 GitHub 主來源前進,但必須完整保留 Gitea 目前所有專案版本與可回滾證據。", + "evidence": "版本來源、AwoooP 同步、Gitea / GitHub 決策缺口已被納入 IwoooS。", + "next": "完成 S4.9 owner evidence 後,再做遷移驗收包。", + "blocked": "source_control_mutation_authorized=false;GitHub primary switch 未授權。" + }, + "runtimeGate": { + "short": "執行閘 0", + "title": "初期框架完成前不提高資安限制", + "body": "目前策略是先把資安網、可視化、證據流、責任邊界建好,再分階段收斂;不一開始把限制拉到影響產品速度。", + "evidence": "所有可見區塊都標記 action_buttons_allowed=false 與 runtime gate 0。", + "next": "等人工決策完成後,才開下一個受控 runtime gate。", + "blocked": "runtime_execution_authorized=false;active_runtime_gate_count=0。" + } + }, + "dependencies": { + "iwooos": { + "title": "IwoooS 前台入口", + "body": "統一呈現資安態勢、進度與邊界。" + }, + "awooop": { + "title": "AwoooP 真相鏈", + "body": "同步跨 Session 工作狀態與人工 Gate。" + }, + "vibework": { + "title": "VibeWork 納管", + "body": "新專案已放入全產品資安面。" + }, + "kali": { + "title": "Kali 192.168.0.112", + "body": "列入資安主機,但目前只讀顯示。" + }, + "devHosts": { + "title": "開發主機 111 / 168", + "body": "等待維護窗口與負責人決策。" + }, + "monitoring": { + "title": "監控與工具鏈", + "body": "先做證據鏡像與狀態理解。" + } + }, + "boundaryTitle": "決策跑道邊界", + "boundaryIntro": "這個區塊只負責讓進度、卡點與責任更清楚;它不是 Kali 掃描、不是主機更新、不是版本來源切換,也不是自動修復按鈕。" + }, "gateRadar": { "eyebrow": "執行閘雷達", "title": "一眼看懂哪裡可前進、哪裡不能碰", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index 9559ba748..dcdec0799 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -5754,6 +5754,102 @@ "boundaryTitle": "圖譜邊界", "boundaryIntro": "這張圖是專業可視化與理解層,不是掃描拓樸、不是自動修復、不是 GitHub / Gitea 切換授權。" }, + "decisionRunway": { + "eyebrow": "決策跑道", + "title": "把下一步卡點、責任邊界與禁止動作放在同一張圖", + "subtitle": "這一層回答使用者最關心的問題:到底做了什麼、下一個真正能推進百分比的是哪個 Gate、哪些主機與產品已納管、哪些仍不能自動執行。", + "railLabel": "下一步推進路線", + "selectorLabel": "切換決策跑道節點", + "fields": { + "evidence": "已具備", + "next": "下一步", + "blocked": "仍卡住" + }, + "boundarySignals": { + "scan": { + "label": "掃描授權" + }, + "hostChange": { + "label": "主機變更" + }, + "sourceMutation": { + "label": "版本異動" + }, + "runtimeExecution": { + "label": "執行期" + } + }, + "steps": { + "ownerEvidence": { + "short": "負責人證據", + "title": "S4.9 負責人回覆與脫敏證據", + "body": "目前最能讓整體進度往前的不是再加文字,而是取得版本來源負責人回覆、脫敏 evidence pointer 與可追蹤決策紀錄。", + "evidence": "IwoooS、AwoooP、拓樸圖與 progress guard 已把卡點顯示到前台。", + "next": "補 owner response received 與 redacted evidence pointer。", + "blocked": "received=0、accepted=0,所以 headline 不再硬往上推。" + }, + "reviewerAcceptance": { + "short": "審查接受", + "title": "Reviewer acceptance 才能解除下一段", + "body": "文件、schema 與前台呈現只能讓工作可理解;真正解除 gate 仍需要 reviewer acceptance 與審查紀錄。", + "evidence": "審查清單、回覆模板、handoff packet 已有只讀材料。", + "next": "把 S4.9 最小審查包交給負責人與 reviewer。", + "blocked": "accepted=0;不能把 UI 可見度當批准。" + }, + "hostWindow": { + "short": "主機窗口", + "title": "Kali 112 與開發主機 111 / 168 的維護窗口", + "body": "三台主機已進入資安架構圖,但目前是只讀納管,不代表已允許更新、調校、掃描或 SSH 操作。", + "evidence": "112 / 111 / 168 在拓樸圖、路徑探索器與主機證據鏈中可見。", + "next": "等待維護窗口、範圍、負責人與回復路徑確認。", + "blocked": "host_change_authorized=false;scan_authorized=false。" + }, + "githubPrimary": { + "short": "GitHub 主來源", + "title": "GitHub primary / Gitea 遷移仍在決策 Gate", + "body": "長期策略可朝 GitHub 主來源前進,但必須完整保留 Gitea 目前所有專案版本與可回滾證據。", + "evidence": "版本來源、AwoooP 同步、Gitea / GitHub 決策缺口已被納入 IwoooS。", + "next": "完成 S4.9 owner evidence 後,再做遷移驗收包。", + "blocked": "source_control_mutation_authorized=false;GitHub primary switch 未授權。" + }, + "runtimeGate": { + "short": "執行閘 0", + "title": "初期框架完成前不提高資安限制", + "body": "目前策略是先把資安網、可視化、證據流、責任邊界建好,再分階段收斂;不一開始把限制拉到影響產品速度。", + "evidence": "所有可見區塊都標記 action_buttons_allowed=false 與 runtime gate 0。", + "next": "等人工決策完成後,才開下一個受控 runtime gate。", + "blocked": "runtime_execution_authorized=false;active_runtime_gate_count=0。" + } + }, + "dependencies": { + "iwooos": { + "title": "IwoooS 前台入口", + "body": "統一呈現資安態勢、進度與邊界。" + }, + "awooop": { + "title": "AwoooP 真相鏈", + "body": "同步跨 Session 工作狀態與人工 Gate。" + }, + "vibework": { + "title": "VibeWork 納管", + "body": "新專案已放入全產品資安面。" + }, + "kali": { + "title": "Kali 192.168.0.112", + "body": "列入資安主機,但目前只讀顯示。" + }, + "devHosts": { + "title": "開發主機 111 / 168", + "body": "等待維護窗口與負責人決策。" + }, + "monitoring": { + "title": "監控與工具鏈", + "body": "先做證據鏡像與狀態理解。" + } + }, + "boundaryTitle": "決策跑道邊界", + "boundaryIntro": "這個區塊只負責讓進度、卡點與責任更清楚;它不是 Kali 掃描、不是主機更新、不是版本來源切換,也不是自動修復按鈕。" + }, "gateRadar": { "eyebrow": "執行閘雷達", "title": "一眼看懂哪裡可前進、哪裡不能碰", diff --git a/apps/web/src/app/[locale]/iwooos/page.tsx b/apps/web/src/app/[locale]/iwooos/page.tsx index d921bba1f..aa51adb2e 100644 --- a/apps/web/src/app/[locale]/iwooos/page.tsx +++ b/apps/web/src/app/[locale]/iwooos/page.tsx @@ -268,6 +268,35 @@ type IwoooSTopologyInsightItem = { tone: 'steady' | 'warn' | 'locked' } +type IwoooSDecisionRunwayStepKey = + | 'ownerEvidence' + | 'reviewerAcceptance' + | 'hostWindow' + | 'githubPrimary' + | 'runtimeGate' + +type IwoooSDecisionRunwayStep = { + key: IwoooSDecisionRunwayStepKey + metric: string + progress: number + icon: typeof ShieldCheck + tone: 'steady' | 'warn' | 'locked' +} + +type IwoooSDecisionRunwayDependency = { + key: string + value: string + icon: typeof ShieldCheck + tone: 'steady' | 'warn' | 'locked' +} + +type IwoooSDecisionRunwayBoundarySignal = { + key: string + value: string + icon: typeof ShieldCheck + tone: 'steady' | 'warn' | 'locked' +} + type IwoooSGateRadarMode = 'visible' | 'blocker' | 'review' | 'locked' type IwoooSGateRadarLane = { @@ -2676,6 +2705,51 @@ const iwooosTopologyAtlasBoundaries = [ 'not_authorization=true', ] as const +const iwooosDecisionRunwaySteps: IwoooSDecisionRunwayStep[] = [ + { key: 'ownerEvidence', metric: '0/4', progress: 18, icon: FileWarning, tone: 'warn' }, + { key: 'reviewerAcceptance', metric: '0/5', progress: 12, icon: ClipboardCheck, tone: 'warn' }, + { key: 'hostWindow', metric: '112 / 111 / 168', progress: 35, icon: Server, tone: 'warn' }, + { key: 'githubPrimary', metric: 'S4.9', progress: 22, icon: GitBranch, tone: 'warn' }, + { key: 'runtimeGate', metric: 'Gate 0', progress: 0, icon: Lock, tone: 'locked' }, +] + +const iwooosDecisionRunwayDependencies: IwoooSDecisionRunwayDependency[] = [ + { key: 'iwooos', value: '前台', icon: ShieldCheck, tone: 'steady' }, + { key: 'awooop', value: '真相鏈', icon: Workflow, tone: 'steady' }, + { key: 'vibework', value: '納管', icon: Boxes, tone: 'steady' }, + { key: 'kali', value: '只讀', icon: Server, tone: 'warn' }, + { key: 'devHosts', value: '窗口', icon: Network, tone: 'warn' }, + { key: 'monitoring', value: '證據', icon: Radar, tone: 'steady' }, +] + +const iwooosDecisionRunwayBoundarySignals: IwoooSDecisionRunwayBoundarySignal[] = [ + { key: 'scan', value: 'false', icon: SearchCheck, tone: 'locked' }, + { key: 'hostChange', value: 'false', icon: Server, tone: 'locked' }, + { key: 'sourceMutation', value: 'false', icon: GitBranch, tone: 'locked' }, + { key: 'runtimeExecution', value: 'false', icon: Lock, tone: 'locked' }, +] + +const iwooosDecisionRunwayBoundaries = [ + 'iwooos_decision_runway_first_layer=true', + 'iwooos_decision_runway_step_count=5', + 'iwooos_decision_runway_default_step=ownerEvidence', + 'iwooos_decision_runway_dependency_count=6', + 'iwooos_decision_runway_boundary_signal_count=4', + 'iwooos_decision_runway_above_gate_radar=true', + 'iwooos_decision_runway_interactive_step_allowed=true', + 'iwooos_decision_runway_execution_action_buttons_allowed=false', + 'iwooos_decision_runway_runtime_gate_count=0', + 'iwooos_decision_runway_owner_response_received_count=0', + 'iwooos_decision_runway_owner_response_accepted_count=0', + 'iwooos_decision_runway_scan_authorized=false', + 'iwooos_decision_runway_host_change_authorized=false', + 'iwooos_decision_runway_source_control_mutation_authorized=false', + 'runtime_execution_authorized=false', + 'active_runtime_gate_count=0', + 'action_buttons_allowed=false', + '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' }, @@ -10680,6 +10754,308 @@ function IwoooSTopologyAtlasBoard() { ) } +function IwoooSDecisionRunwayBoard() { + const t = useTranslations('iwooos.decisionRunway') + const [activeKey, setActiveKey] = useState('ownerEvidence') + const activeStep = iwooosDecisionRunwaySteps.find(item => item.key === activeKey) ?? iwooosDecisionRunwaySteps[0] + const ActiveIcon = activeStep.icon + const textWrap = { overflowWrap: 'anywhere' as const, wordBreak: 'break-word' as const } + const railPoints = iwooosDecisionRunwaySteps + .map((item, index) => { + const x = 10 + index * 20 + const y = 54 - Math.max(item.progress, 2) * 0.42 + return `${x},${y}` + }) + .join(' ') + + return ( +
+
+
+
+
+ + {t('eyebrow')} +
+

{t('title')}

+

+ {t('subtitle')} +

+
+ +
+ {iwooosDecisionRunwayBoundarySignals.map(item => { + const Icon = item.icon + return ( +
+
+ + {item.value} +
+
+ {t(`boundarySignals.${item.key}.label` as never)} +
+
+ ) + })} +
+
+ +
+
+
+ + + + + + + + + + + + + + + {iwooosDecisionRunwaySteps.map((item, index) => { + const x = 10 + index * 20 + const y = 54 - Math.max(item.progress, 2) * 0.42 + const selected = item.key === activeKey + return ( + + + + + ) + })} + + +
+
+
+
{t('railLabel')}
+
+ {t(`steps.${activeStep.key}.title` as never)} +
+
+
+ {activeStep.metric} +
+
+ +
+ {iwooosDecisionRunwaySteps.map(item => { + const Icon = item.icon + const selected = item.key === activeKey + return ( + + ) + })} +
+
+
+ +
+ {iwooosDecisionRunwayDependencies.map(item => { + const Icon = item.icon + return ( +
+
+ + {item.value} +
+
+ {t(`dependencies.${item.key}.title` as never)} +
+
+ {t(`dependencies.${item.key}.body` as never)} +
+
+ ) + })} +
+
+ +
+
+ + {activeStep.metric} +
+

+ {t(`steps.${activeStep.key}.title` as never)} +

+

+ {t(`steps.${activeStep.key}.body` as never)} +

+
+
+
+
+ {(['evidence', 'next', 'blocked'] as const).map(key => ( +
+
{t(`fields.${key}`)}
+
+ {t(`steps.${activeStep.key}.${key}` as never)} +
+
+ ))} +
+ +
+ + {t('boundaryTitle')} + +

+ {t('boundaryIntro')} +

+
+ {iwooosDecisionRunwayBoundaries.map(item => ( + + {item} + + ))} +
+
+
+
+
+
+ ) +} + function IwoooSGateRadarBoard() { const t = useTranslations('iwooos.gateRadar') const [activeKey, setActiveKey] = useState('visible') @@ -15805,6 +16181,7 @@ export default function IwoooSPage({ params }: { params: { locale: string } }) { + diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index 0f3891a71..c2702ced8 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -1,3 +1,31 @@ +## 2026-06-01|IwoooS 決策跑道 + +**背景**: + +- 使用者指出 IwoooS 已有拓樸圖與情報面板,但仍需要更快讓人理解「到底完成哪些工作、下一步卡在哪、112 Kali 與 111 / 168 開發主機是否納入、哪些動作仍未授權」。 +- 本輪不啟動掃描、不更新主機、不提高初期資安限制;先把主流資安平台常見的 decision runway / gate visualization 放進前台。 + +**本次調整**: + +- `apps/web/src/app/[locale]/iwooos/page.tsx`: + - 新增 `IwoooSDecisionRunwayBoard`,放在專業拓樸圖之後、Gate 雷達之前。 + - 以可切換跑道呈現 5 個真正會推進工作的 Gate:S4.9 owner evidence、reviewer acceptance、112 / 111 / 168 主機窗口、GitHub primary / Gitea 遷移、runtime Gate 0。 + - 新增依賴格:IwoooS、AwoooP、VibeWork、Kali、開發主機、監控工具,讓所有產品 / 主機 / 工具納管關係更容易被看懂。 + - 新增四個邊界 signal:scan=false、host change=false、source mutation=false、runtime execution=false。 +- `apps/web/messages/zh-TW.json` / `apps/web/messages/en.json`: + - 新增 `iwooos.decisionRunway` 全繁中文案;`en.json` 維持繁中鏡像。 +- `docs/security/iwooos-posture-projection.snapshot.json` / `security-mirror-status-rollup.snapshot.json`: + - 新增 `decision_runway_*` summary、steps、dependencies、boundary signals。 + - 新增 `S2.152` 進度 ledger;headline 不增加,因為這是 framework / UX / 可理解度提升,不是 runtime gate。 +- `scripts/security/security-mirror-progress-guard.py`: + - 新增決策跑道的 component、testid、i18n、snapshot 與 runtime false 邊界 guard。 + +**進度邊界**: + +- 整體維持 `61%`;這輪提升的是「可理解度、責任邊界、下一步 Gate」。 +- `active_runtime_gate_count=0`、`runtime_execution_authorized=false`。 +- 未執行 Kali 掃描、未 SSH 變更 112 / 111 / 168、未開啟自動修復、未做 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 4851404ef..500374f26 100644 --- a/docs/security/iwooos-posture-projection.snapshot.json +++ b/docs/security/iwooos-posture-projection.snapshot.json @@ -165,7 +165,17 @@ "topology_atlas_runtime_gate_count": 0, "topology_drilldown_runtime_gate_count": 0, "topology_path_explorer_runtime_gate_count": 0, - "topology_intelligence_runtime_gate_count": 0 + "topology_intelligence_runtime_gate_count": 0, + "decision_runway_first_layer": true, + "decision_runway_step_count": 5, + "decision_runway_default_step": "ownerEvidence", + "decision_runway_dependency_count": 6, + "decision_runway_boundary_signal_count": 4, + "decision_runway_above_gate_radar": true, + "decision_runway_interactive_step_allowed": true, + "decision_runway_runtime_gate_count": 0, + "decision_runway_owner_response_received_count": 0, + "decision_runway_owner_response_accepted_count": 0 }, "progress": { "overall_percent": 61, @@ -7843,6 +7853,173 @@ "not_authorization": true } ], + "decision_runway_steps": [ + { + "step_id": "ownerEvidence", + "display_order": 1, + "display_mode": "first_screen_decision_runway", + "metric": "0/4", + "progress": 18, + "source_focus": "s4_9_owner_response", + "blocking_state": "waiting_owner_response", + "interactive_step_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 + }, + { + "step_id": "reviewerAcceptance", + "display_order": 2, + "display_mode": "first_screen_decision_runway", + "metric": "0/5", + "progress": 12, + "source_focus": "reviewer_acceptance", + "blocking_state": "waiting_reviewer_acceptance", + "interactive_step_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 + }, + { + "step_id": "hostWindow", + "display_order": 3, + "display_mode": "first_screen_decision_runway", + "metric": "112 / 111 / 168", + "progress": 35, + "source_focus": "host_window", + "blocking_state": "waiting_maintenance_window", + "interactive_step_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 + }, + { + "step_id": "githubPrimary", + "display_order": 4, + "display_mode": "first_screen_decision_runway", + "metric": "S4.9", + "progress": 22, + "source_focus": "github_primary_readiness", + "blocking_state": "waiting_source_control_decision", + "interactive_step_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 + }, + { + "step_id": "runtimeGate", + "display_order": 5, + "display_mode": "first_screen_decision_runway", + "metric": "Gate 0", + "progress": 0, + "source_focus": "runtime_gate", + "blocking_state": "runtime_closed", + "interactive_step_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 + } + ], + "decision_runway_dependencies": [ + { + "dependency_id": "iwooos", + "display_order": 1, + "display_mode": "first_screen_decision_runway_dependency", + "status": "frontend_entry_visible", + "runtime_delta": false, + "not_authorization": true + }, + { + "dependency_id": "awooop", + "display_order": 2, + "display_mode": "first_screen_decision_runway_dependency", + "status": "truth_chain_read_only", + "runtime_delta": false, + "not_authorization": true + }, + { + "dependency_id": "vibework", + "display_order": 3, + "display_mode": "first_screen_decision_runway_dependency", + "status": "product_scope_included", + "runtime_delta": false, + "not_authorization": true + }, + { + "dependency_id": "kali", + "display_order": 4, + "display_mode": "first_screen_decision_runway_dependency", + "status": "read_only_host_node", + "runtime_delta": false, + "not_authorization": true + }, + { + "dependency_id": "devHosts", + "display_order": 5, + "display_mode": "first_screen_decision_runway_dependency", + "status": "waiting_owner_window", + "runtime_delta": false, + "not_authorization": true + }, + { + "dependency_id": "monitoring", + "display_order": 6, + "display_mode": "first_screen_decision_runway_dependency", + "status": "evidence_lane_visible", + "runtime_delta": false, + "not_authorization": true + } + ], + "decision_runway_boundary_signals": [ + { + "signal_id": "scan", + "display_order": 1, + "authorized": false, + "runtime_gate_opened": false, + "not_authorization": true + }, + { + "signal_id": "hostChange", + "display_order": 2, + "authorized": false, + "runtime_gate_opened": false, + "not_authorization": true + }, + { + "signal_id": "sourceMutation", + "display_order": 3, + "authorized": false, + "runtime_gate_opened": false, + "not_authorization": true + }, + { + "signal_id": "runtimeExecution", + "display_order": 4, + "authorized": false, + "runtime_gate_opened": false, + "not_authorization": true + } + ], "topology_atlas_technical_charts": [ { "chart_id": "contextDepth", diff --git a/docs/security/security-mirror-status-rollup.snapshot.json b/docs/security/security-mirror-status-rollup.snapshot.json index 9b362cf72..3e6e1fe15 100644 --- a/docs/security/security-mirror-status-rollup.snapshot.json +++ b/docs/security/security-mirror-status-rollup.snapshot.json @@ -2293,6 +2293,18 @@ "runtime_delta": false, "execution_authorized": false, "not_authorization": true + }, + { + "delta_id": "s2_152_iwooos_decision_runway", + "display_order": 181, + "completed_stage": "S2.152 IwoooS 決策跑道", + "progress_axis": "framework_detail", + "headline_percent_delta": 0, + "framework_delta_visible": true, + "why_headline_unchanged": "IwoooS 只新增第一屏決策跑道,把 S4.9 owner evidence、reviewer acceptance、Kali 112 / 開發主機 111 / 168 維護窗口、GitHub primary / Gitea 遷移與 Gate 0 放進同一張可切換圖;iwooos_decision_runway_step_count=5、iwooos_decision_runway_default_step=ownerEvidence、iwooos_decision_runway_dependency_count=6、iwooos_decision_runway_interactive_step_allowed=true、iwooos_decision_runway_execution_action_buttons_allowed=false、iwooos_decision_runway_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 8c6ec113a..f62eef3bf 100755 --- a/scripts/security/security-mirror-progress-guard.py +++ b/scripts/security/security-mirror-progress-guard.py @@ -616,6 +616,7 @@ def validate(root: Path) -> None: "s2_149_iwooos_topology_node_drilldown", "s2_150_iwooos_topology_path_explorer", "s2_151_iwooos_topology_intelligence_deck", + "s2_152_iwooos_decision_runway", ] assert_equal( "progress_delta_ledger.delta_ids", @@ -1663,6 +1664,53 @@ def validate(root: Path) -> None: iwooos_projection["summary"]["topology_intelligence_runtime_gate_count"], 0, ) + assert_true( + "iwooos_projection.summary.decision_runway_first_layer", + iwooos_projection["summary"]["decision_runway_first_layer"], + ) + assert_equal( + "iwooos_projection.summary.decision_runway_step_count", + iwooos_projection["summary"]["decision_runway_step_count"], + 5, + ) + assert_equal( + "iwooos_projection.summary.decision_runway_default_step", + iwooos_projection["summary"]["decision_runway_default_step"], + "ownerEvidence", + ) + assert_equal( + "iwooos_projection.summary.decision_runway_dependency_count", + iwooos_projection["summary"]["decision_runway_dependency_count"], + 6, + ) + assert_equal( + "iwooos_projection.summary.decision_runway_boundary_signal_count", + iwooos_projection["summary"]["decision_runway_boundary_signal_count"], + 4, + ) + assert_true( + "iwooos_projection.summary.decision_runway_above_gate_radar", + iwooos_projection["summary"]["decision_runway_above_gate_radar"], + ) + assert_true( + "iwooos_projection.summary.decision_runway_interactive_step_allowed", + iwooos_projection["summary"]["decision_runway_interactive_step_allowed"], + ) + assert_equal( + "iwooos_projection.summary.decision_runway_runtime_gate_count", + iwooos_projection["summary"]["decision_runway_runtime_gate_count"], + 0, + ) + assert_equal( + "iwooos_projection.summary.decision_runway_owner_response_received_count", + iwooos_projection["summary"]["decision_runway_owner_response_received_count"], + 0, + ) + assert_equal( + "iwooos_projection.summary.decision_runway_owner_response_accepted_count", + iwooos_projection["summary"]["decision_runway_owner_response_accepted_count"], + 0, + ) assert_true( "iwooos_projection.summary.gate_radar_first_layer", iwooos_projection["summary"]["gate_radar_first_layer"], @@ -12159,6 +12207,73 @@ def validate(root: Path) -> None: iwooos_projection_page, text, ) + for text in [ + 'data-testid="iwooos-decision-runway-board"', + 'data-testid="iwooos-decision-runway-progress-rail"', + 'data-testid="iwooos-decision-runway-selector"', + 'data-testid="iwooos-decision-runway-active-panel"', + 'data-testid="iwooos-decision-runway-dependency-grid"', + 'data-testid="iwooos-decision-runway-boundary-strip"', + 'data-testid="iwooos-decision-runway-boundaries"', + "IwoooSDecisionRunwayBoard", + "IwoooSDecisionRunwayStepKey", + "IwoooSDecisionRunwayStep", + "IwoooSDecisionRunwayDependency", + "IwoooSDecisionRunwayBoundarySignal", + "iwooosDecisionRunwaySteps", + "iwooosDecisionRunwayDependencies", + "iwooosDecisionRunwayBoundarySignals", + "iwooosDecisionRunwayBoundaries", + "iwooos-decision-runway-grid", + ]: + assert_text_contains( + "iwooos_page.decision_runway", + iwooos_projection_page, + text, + ) + assert_text_before( + "iwooos_page.topology_atlas_before_decision_runway", + iwooos_projection_page, + "", + "", + ) + assert_text_before( + "iwooos_page.decision_runway_before_gate_radar", + iwooos_projection_page, + "", + "", + ) + assert_text_before( + "iwooos_page.decision_runway_before_command_map", + iwooos_projection_page, + "", + "", + ) + for text in [ + "iwooos_decision_runway_first_layer=true", + "iwooos_decision_runway_step_count=5", + "iwooos_decision_runway_default_step=ownerEvidence", + "iwooos_decision_runway_dependency_count=6", + "iwooos_decision_runway_boundary_signal_count=4", + "iwooos_decision_runway_above_gate_radar=true", + "iwooos_decision_runway_interactive_step_allowed=true", + "iwooos_decision_runway_execution_action_buttons_allowed=false", + "iwooos_decision_runway_runtime_gate_count=0", + "iwooos_decision_runway_owner_response_received_count=0", + "iwooos_decision_runway_owner_response_accepted_count=0", + "iwooos_decision_runway_scan_authorized=false", + "iwooos_decision_runway_host_change_authorized=false", + "iwooos_decision_runway_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.decision_runway_boundary", + iwooos_projection_page, + text, + ) for text in [ 'data-testid="iwooos-gate-radar-board"', 'data-testid="iwooos-gate-radar-canvas"', @@ -12761,6 +12876,133 @@ def validate(root: Path) -> None: f"iwooos_projection.topology_intelligence_deck.{insight_id}.not_authorization", item["not_authorization"], ) + expected_decision_runway_step_ids = [ + "ownerEvidence", + "reviewerAcceptance", + "hostWindow", + "githubPrimary", + "runtimeGate", + ] + decision_runway_steps = iwooos_projection["decision_runway_steps"] + assert_equal( + "iwooos_projection.decision_runway_steps.ids", + [item["step_id"] for item in decision_runway_steps], + expected_decision_runway_step_ids, + ) + assert_equal( + "iwooos_projection.decision_runway_steps.display_order", + [item["display_order"] for item in decision_runway_steps], + list(range(1, len(expected_decision_runway_step_ids) + 1)), + ) + assert_equal( + "iwooos_projection.decision_runway_steps.progress", + [item["progress"] for item in decision_runway_steps], + [18, 12, 35, 22, 0], + ) + for item in decision_runway_steps: + step_id = item["step_id"] + assert_equal( + f"iwooos_projection.decision_runway_steps.{step_id}.display_mode", + item["display_mode"], + "first_screen_decision_runway", + ) + assert_true( + f"iwooos_projection.decision_runway_steps.{step_id}.interactive_step_allowed", + item["interactive_step_allowed"], + ) + assert_false( + f"iwooos_projection.decision_runway_steps.{step_id}.execution_action_button_allowed", + item["execution_action_button_allowed"], + ) + assert_false( + f"iwooos_projection.decision_runway_steps.{step_id}.scan_authorized", + item["scan_authorized"], + ) + assert_false( + f"iwooos_projection.decision_runway_steps.{step_id}.host_change_authorized", + item["host_change_authorized"], + ) + assert_false( + f"iwooos_projection.decision_runway_steps.{step_id}.source_control_mutation_authorized", + item["source_control_mutation_authorized"], + ) + assert_false( + f"iwooos_projection.decision_runway_steps.{step_id}.runtime_gate_opened", + item["runtime_gate_opened"], + ) + assert_false( + f"iwooos_projection.decision_runway_steps.{step_id}.runtime_execution_authorized", + item["runtime_execution_authorized"], + ) + assert_true( + f"iwooos_projection.decision_runway_steps.{step_id}.not_authorization", + item["not_authorization"], + ) + expected_decision_runway_dependency_ids = [ + "iwooos", + "awooop", + "vibework", + "kali", + "devHosts", + "monitoring", + ] + decision_runway_dependencies = iwooos_projection["decision_runway_dependencies"] + assert_equal( + "iwooos_projection.decision_runway_dependencies.ids", + [item["dependency_id"] for item in decision_runway_dependencies], + expected_decision_runway_dependency_ids, + ) + assert_equal( + "iwooos_projection.decision_runway_dependencies.display_order", + [item["display_order"] for item in decision_runway_dependencies], + list(range(1, len(expected_decision_runway_dependency_ids) + 1)), + ) + for item in decision_runway_dependencies: + dependency_id = item["dependency_id"] + assert_equal( + f"iwooos_projection.decision_runway_dependencies.{dependency_id}.display_mode", + item["display_mode"], + "first_screen_decision_runway_dependency", + ) + assert_false( + f"iwooos_projection.decision_runway_dependencies.{dependency_id}.runtime_delta", + item["runtime_delta"], + ) + assert_true( + f"iwooos_projection.decision_runway_dependencies.{dependency_id}.not_authorization", + item["not_authorization"], + ) + expected_decision_runway_boundary_signal_ids = [ + "scan", + "hostChange", + "sourceMutation", + "runtimeExecution", + ] + decision_runway_boundary_signals = iwooos_projection["decision_runway_boundary_signals"] + assert_equal( + "iwooos_projection.decision_runway_boundary_signals.ids", + [item["signal_id"] for item in decision_runway_boundary_signals], + expected_decision_runway_boundary_signal_ids, + ) + assert_equal( + "iwooos_projection.decision_runway_boundary_signals.display_order", + [item["display_order"] for item in decision_runway_boundary_signals], + list(range(1, len(expected_decision_runway_boundary_signal_ids) + 1)), + ) + for item in decision_runway_boundary_signals: + signal_id = item["signal_id"] + assert_false( + f"iwooos_projection.decision_runway_boundary_signals.{signal_id}.authorized", + item["authorized"], + ) + assert_false( + f"iwooos_projection.decision_runway_boundary_signals.{signal_id}.runtime_gate_opened", + item["runtime_gate_opened"], + ) + assert_true( + f"iwooos_projection.decision_runway_boundary_signals.{signal_id}.not_authorization", + item["not_authorization"], + ) expected_gate_radar_lane_ids = [ "visible", "blocker", @@ -13342,6 +13584,115 @@ def validate(root: Path) -> None: list(web_messages_en["iwooos"]["topologyAtlas"]["charts"][key].keys()), field, ) + assert_contains( + "web_messages.zh-TW.iwooos.decisionRunway", + list(web_messages_zh["iwooos"].keys()), + "decisionRunway", + ) + assert_contains( + "web_messages.en.iwooos.decisionRunway", + list(web_messages_en["iwooos"].keys()), + "decisionRunway", + ) + for key in [ + "eyebrow", + "title", + "subtitle", + "railLabel", + "selectorLabel", + "fields", + "boundarySignals", + "steps", + "dependencies", + "boundaryTitle", + "boundaryIntro", + ]: + assert_contains( + "web_messages.zh-TW.iwooos.decisionRunway.keys", + list(web_messages_zh["iwooos"]["decisionRunway"].keys()), + key, + ) + assert_contains( + "web_messages.en.iwooos.decisionRunway.keys", + list(web_messages_en["iwooos"]["decisionRunway"].keys()), + key, + ) + for key in ["evidence", "next", "blocked"]: + assert_contains( + "web_messages.zh-TW.iwooos.decisionRunway.fields", + list(web_messages_zh["iwooos"]["decisionRunway"]["fields"].keys()), + key, + ) + assert_contains( + "web_messages.en.iwooos.decisionRunway.fields", + list(web_messages_en["iwooos"]["decisionRunway"]["fields"].keys()), + key, + ) + for key in expected_decision_runway_boundary_signal_ids: + assert_contains( + "web_messages.zh-TW.iwooos.decisionRunway.boundarySignals", + list(web_messages_zh["iwooos"]["decisionRunway"]["boundarySignals"].keys()), + key, + ) + assert_contains( + "web_messages.en.iwooos.decisionRunway.boundarySignals", + list(web_messages_en["iwooos"]["decisionRunway"]["boundarySignals"].keys()), + key, + ) + assert_contains( + f"web_messages.zh-TW.iwooos.decisionRunway.boundarySignals.{key}", + list(web_messages_zh["iwooos"]["decisionRunway"]["boundarySignals"][key].keys()), + "label", + ) + assert_contains( + f"web_messages.en.iwooos.decisionRunway.boundarySignals.{key}", + list(web_messages_en["iwooos"]["decisionRunway"]["boundarySignals"][key].keys()), + "label", + ) + for key in expected_decision_runway_step_ids: + assert_contains( + "web_messages.zh-TW.iwooos.decisionRunway.steps", + list(web_messages_zh["iwooos"]["decisionRunway"]["steps"].keys()), + key, + ) + assert_contains( + "web_messages.en.iwooos.decisionRunway.steps", + list(web_messages_en["iwooos"]["decisionRunway"]["steps"].keys()), + key, + ) + for field in ["short", "title", "body", "evidence", "next", "blocked"]: + assert_contains( + f"web_messages.zh-TW.iwooos.decisionRunway.steps.{key}", + list(web_messages_zh["iwooos"]["decisionRunway"]["steps"][key].keys()), + field, + ) + assert_contains( + f"web_messages.en.iwooos.decisionRunway.steps.{key}", + list(web_messages_en["iwooos"]["decisionRunway"]["steps"][key].keys()), + field, + ) + for key in expected_decision_runway_dependency_ids: + assert_contains( + "web_messages.zh-TW.iwooos.decisionRunway.dependencies", + list(web_messages_zh["iwooos"]["decisionRunway"]["dependencies"].keys()), + key, + ) + assert_contains( + "web_messages.en.iwooos.decisionRunway.dependencies", + list(web_messages_en["iwooos"]["decisionRunway"]["dependencies"].keys()), + key, + ) + for field in ["title", "body"]: + assert_contains( + f"web_messages.zh-TW.iwooos.decisionRunway.dependencies.{key}", + list(web_messages_zh["iwooos"]["decisionRunway"]["dependencies"][key].keys()), + field, + ) + assert_contains( + f"web_messages.en.iwooos.decisionRunway.dependencies.{key}", + list(web_messages_en["iwooos"]["decisionRunway"]["dependencies"][key].keys()), + field, + ) assert_contains( "web_messages.zh-TW.iwooos.gateRadar", list(web_messages_zh["iwooos"].keys()),