feat(web): promote IwoooS unlock path
This commit is contained in:
@@ -10962,8 +10962,9 @@
|
||||
}
|
||||
},
|
||||
"firstProgressUnlockPath": {
|
||||
"eyebrow": "61% 下一步",
|
||||
"title": "第一個進度解鎖路徑",
|
||||
"subtitle": "S2.114 把 61% 下一個真正能往前的路徑收斂到 S4.9 負責人回覆:先收到可追溯回覆,再補齊脫敏證據參照,通過收件預檢與審查接受後,才可能成為 重點 審查 候選。",
|
||||
"subtitle": "把 61% 下一個真正能往前的路徑放到首頁首層:先收 S4.9 負責人可追溯回覆,再補齊脫敏證據參照,通過收件預檢與審查接受後,才可能成為進度審查候選。",
|
||||
"stepLabel": "步驟",
|
||||
"boundaryTitle": "第一解鎖路徑邊界",
|
||||
"boundaryIntro": "以下鍵值固定:這是 S4.9 第一解鎖路徑的只讀收斂,不是送件完成、回覆已收到、批准、掃描、修復、部署或執行期入口。",
|
||||
|
||||
@@ -10962,8 +10962,9 @@
|
||||
}
|
||||
},
|
||||
"firstProgressUnlockPath": {
|
||||
"eyebrow": "61% 下一步",
|
||||
"title": "第一個進度解鎖路徑",
|
||||
"subtitle": "S2.114 把 61% 下一個真正能往前的路徑收斂到 S4.9 負責人回覆:先收到可追溯回覆,再補齊脫敏證據參照,通過收件預檢與審查接受後,才可能成為 重點 審查 候選。",
|
||||
"subtitle": "把 61% 下一個真正能往前的路徑放到首頁首層:先收 S4.9 負責人可追溯回覆,再補齊脫敏證據參照,通過收件預檢與審查接受後,才可能成為進度審查候選。",
|
||||
"stepLabel": "步驟",
|
||||
"boundaryTitle": "第一解鎖路徑邊界",
|
||||
"boundaryIntro": "以下鍵值固定:這是 S4.9 第一解鎖路徑的只讀收斂,不是送件完成、回覆已收到、批准、掃描、修復、部署或執行期入口。",
|
||||
|
||||
@@ -8868,99 +8868,133 @@ function IwoooSFirstProgressUnlockPathBoard() {
|
||||
const textWrap = { overflowWrap: 'anywhere' as const, wordBreak: 'break-word' as const }
|
||||
|
||||
return (
|
||||
<section style={{ marginBottom: 14 }} data-testid="iwooos-first-progress-unlock-path-board">
|
||||
<div style={{ ...band, padding: 16, background: '#f8fbff' }}>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 300px), 1fr))', gap: 16 }}>
|
||||
<div>
|
||||
<div style={{ marginBottom: 14 }}>
|
||||
<h2 style={{ fontSize: 17, margin: 0 }}>{t('title')}</h2>
|
||||
<p style={{ fontSize: 12, color: '#5b6572', margin: '6px 0 0', lineHeight: 1.55, ...textWrap }}>
|
||||
{t('subtitle')}
|
||||
</p>
|
||||
<section style={{ marginBottom: 14, maxWidth: '100%', overflow: 'hidden' }} data-testid="iwooos-first-progress-unlock-path-board">
|
||||
<div style={{ ...band, padding: 16, background: '#f7fbfa', borderColor: '#cfe2d8' }}>
|
||||
<div
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 320px), 1fr))',
|
||||
gap: 14,
|
||||
alignItems: 'start',
|
||||
}}
|
||||
>
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, color: '#405f4f', fontSize: 12, fontWeight: 700 }}>
|
||||
<ListChecks size={17} color="#1f7a4d" />
|
||||
{t('eyebrow')}
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(126px, 1fr))', gap: 8, marginBottom: 14 }}>
|
||||
{summaryItems.map(item => (
|
||||
<div key={item.key} style={{ border: '0.5px solid #dce6f0', borderRadius: 8, padding: 12, background: '#fff' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
|
||||
<span style={{ fontSize: 11, color: '#758192' }}>{t(`summary.${item.key}.label` as never)}</span>
|
||||
<ToneDot tone={item.tone} />
|
||||
</div>
|
||||
<div style={{ fontSize: 20, fontWeight: 700, lineHeight: 1.1, marginTop: 8, color: '#141413' }}>
|
||||
{item.value}
|
||||
</div>
|
||||
<p style={{ fontSize: 11, color: '#5b6572', lineHeight: 1.45, margin: '8px 0 0', ...textWrap }}>
|
||||
{t(`summary.${item.key}.detail` as never)}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(164px, 1fr))', gap: 10 }}>
|
||||
{iwooosFirstProgressUnlockPathSteps.map(item => {
|
||||
const Icon = item.icon
|
||||
return (
|
||||
<div
|
||||
key={item.key}
|
||||
style={{
|
||||
display: 'grid',
|
||||
gap: 8,
|
||||
minHeight: 154,
|
||||
border: '0.5px solid #d7e4f1',
|
||||
borderRadius: 8,
|
||||
background: '#fff',
|
||||
padding: 13,
|
||||
color: '#141413',
|
||||
...textWrap,
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
|
||||
<span style={{ color: toneColors[item.tone], fontSize: 12, fontWeight: 700 }}>
|
||||
{t('stepLabel')} {item.step}
|
||||
</span>
|
||||
<Icon size={18} color={toneColors[item.tone]} />
|
||||
</div>
|
||||
<div style={{ fontSize: 15, fontWeight: 700, color: toneColors[item.tone], lineHeight: 1.2 }}>
|
||||
{item.state}
|
||||
</div>
|
||||
<h3 style={{ fontSize: 14, margin: 0, color: '#141413', lineHeight: 1.3 }}>
|
||||
{t(`items.${item.key}.title` as never)}
|
||||
</h3>
|
||||
<p style={{ fontSize: 11, color: '#5b6572', lineHeight: 1.5, margin: 0, ...textWrap }}>
|
||||
{t(`items.${item.key}.body` as never)}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ ...band, padding: 14, background: '#edf5fc', alignSelf: 'stretch' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 10 }}>
|
||||
<ListChecks size={16} color={toneColors.warn} />
|
||||
<h3 style={{ fontSize: 14, margin: 0 }}>{t('boundaryTitle')}</h3>
|
||||
</div>
|
||||
<p style={{ fontSize: 12, color: '#5b6572', lineHeight: 1.55, margin: '0 0 10px', ...textWrap }}>
|
||||
{t('boundaryIntro')}
|
||||
<h2 style={{ fontSize: 17, margin: '8px 0 0', color: '#141413' }}>{t('title')}</h2>
|
||||
<p style={{ fontSize: 12, color: '#405f4f', margin: '6px 0 0', lineHeight: 1.55, ...textWrap }}>
|
||||
{t('subtitle')}
|
||||
</p>
|
||||
<div style={{ display: 'grid', gap: 7 }}>
|
||||
{iwooosFirstProgressUnlockPathBoundaries.map(item => (
|
||||
<span
|
||||
key={item}
|
||||
style={{
|
||||
border: '0.5px solid #d7e4f1',
|
||||
borderRadius: 8,
|
||||
padding: '7px 9px',
|
||||
color: '#455261',
|
||||
fontSize: 11,
|
||||
lineHeight: 1.4,
|
||||
background: '#fff',
|
||||
overflowWrap: 'anywhere',
|
||||
}}
|
||||
>
|
||||
{item}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(118px, 1fr))', gap: 8 }}>
|
||||
{summaryItems.map(item => (
|
||||
<div key={item.key} style={{ border: '0.5px solid #cfe2d8', borderRadius: 8, padding: 11, background: '#fff' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
|
||||
<span style={{ fontSize: 11, color: '#607466' }}>{t(`summary.${item.key}.label` as never)}</span>
|
||||
<ToneDot tone={item.tone} />
|
||||
</div>
|
||||
<div style={{ fontSize: 20, fontWeight: 700, lineHeight: 1.1, marginTop: 8, color: toneColors[item.tone] }}>
|
||||
{item.value}
|
||||
</div>
|
||||
<p style={{ fontSize: 11, color: '#405f4f', lineHeight: 1.45, margin: '7px 0 0', ...textWrap }}>
|
||||
{t(`summary.${item.key}.detail` as never)}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div aria-hidden style={{ display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', gap: 6, margin: '14px 0 10px' }}>
|
||||
{iwooosFirstProgressUnlockPathSteps.map(item => (
|
||||
<span
|
||||
key={item.key}
|
||||
style={{
|
||||
height: 5,
|
||||
borderRadius: 999,
|
||||
background: toneColors[item.tone],
|
||||
opacity: item.tone === 'locked' ? 0.38 : 0.86,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 154px), 1fr))', gap: 9 }}>
|
||||
{iwooosFirstProgressUnlockPathSteps.map(item => {
|
||||
const Icon = item.icon
|
||||
return (
|
||||
<div
|
||||
key={item.key}
|
||||
style={{
|
||||
display: 'grid',
|
||||
gap: 7,
|
||||
minHeight: 136,
|
||||
border: `0.5px solid ${item.tone === 'steady' ? '#cfe2d8' : item.tone === 'warn' ? '#e6c8b8' : '#dad7ce'}`,
|
||||
borderRadius: 8,
|
||||
background: '#fff',
|
||||
padding: 12,
|
||||
color: '#141413',
|
||||
...textWrap,
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
|
||||
<span style={{ color: toneColors[item.tone], fontSize: 12, fontWeight: 700 }}>
|
||||
{t('stepLabel')} {item.step}
|
||||
</span>
|
||||
<Icon size={17} color={toneColors[item.tone]} />
|
||||
</div>
|
||||
<div style={{ fontSize: 14, fontWeight: 700, color: toneColors[item.tone], lineHeight: 1.2 }}>
|
||||
{item.state}
|
||||
</div>
|
||||
<h3 style={{ fontSize: 13, margin: 0, color: '#141413', lineHeight: 1.3 }}>
|
||||
{t(`items.${item.key}.title` as never)}
|
||||
</h3>
|
||||
<p style={{ fontSize: 11, color: '#405f4f', lineHeight: 1.45, margin: 0, ...textWrap }}>
|
||||
{t(`items.${item.key}.body` as never)}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
<details
|
||||
data-testid="iwooos-first-progress-unlock-path-boundaries"
|
||||
style={{
|
||||
marginTop: 12,
|
||||
border: '0.5px solid #cfe2d8',
|
||||
borderRadius: 8,
|
||||
background: '#fff',
|
||||
padding: '8px 10px',
|
||||
}}
|
||||
>
|
||||
<summary style={{ cursor: 'pointer', fontSize: 12, fontWeight: 700, color: '#405f4f' }}>
|
||||
{t('boundaryTitle')}
|
||||
</summary>
|
||||
<p style={{ fontSize: 11, color: '#405f4f', lineHeight: 1.5, margin: '8px 0', ...textWrap }}>
|
||||
{t('boundaryIntro')}
|
||||
</p>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(230px, 1fr))', gap: 6 }}>
|
||||
{iwooosFirstProgressUnlockPathBoundaries.map(item => (
|
||||
<code
|
||||
key={item}
|
||||
style={{
|
||||
border: '0.5px solid #d9eadf',
|
||||
borderRadius: 8,
|
||||
padding: '6px 8px',
|
||||
color: '#405f4f',
|
||||
fontSize: 11,
|
||||
lineHeight: 1.4,
|
||||
background: '#f7fbfa',
|
||||
overflowWrap: 'anywhere',
|
||||
}}
|
||||
>
|
||||
{item}
|
||||
</code>
|
||||
))}
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
@@ -13685,6 +13719,7 @@ export default function IwoooSPage({ params }: { params: { locale: string } }) {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<IwoooSFirstProgressUnlockPathBoard />
|
||||
<IwoooSVisualCommandDashboard />
|
||||
<IwoooSProfessionalSecurityExperience />
|
||||
<IwoooSConcreteWorkSnapshot />
|
||||
@@ -13956,8 +13991,6 @@ export default function IwoooSPage({ params }: { params: { locale: string } }) {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<IwoooSFirstProgressUnlockPathBoard />
|
||||
|
||||
<IwoooSFirstUnlockEvidencePacketBoard />
|
||||
|
||||
<IwoooSFirstUnlockEvidencePacketPreflightOutcomeBoard />
|
||||
|
||||
@@ -1,3 +1,85 @@
|
||||
## 2026-05-31|IwoooS 第一解鎖路徑首層化
|
||||
|
||||
**背景**:
|
||||
|
||||
- 使用者指出 `/zh-TW/iwooos` 仍像長文字頁,難以第一眼理解目前資安工作到底完成什麼、下一步為什麼卡在 61%、Kali 112 與所有主機 / 產品 / 工具是否真的納入。
|
||||
- 本輪不新增執行權限、不提高管控強度;只把真正能讓 61% 往前的第一個 evidence 路徑放到首層,讓使用者先看懂下一步,而不是翻到深層收合區。
|
||||
|
||||
**本次調整**:
|
||||
|
||||
- `apps/web/src/app/[locale]/iwooos/page.tsx`:
|
||||
- 將 `IwoooSFirstProgressUnlockPathBoard` 從深層「下一步與阻塞解除」區移到標題區正下方,並放在視覺指揮板之前。
|
||||
- 看板改成「指標卡 + 五段進度軌 + compact step cards」;詳細 boundary 鍵值改為預設收合,降低首頁文字壓力。
|
||||
- 保留只讀邊界:S4.9 負責人回覆已收到=0、已接受=0、headline review 未開、active runtime gate=0。
|
||||
- `apps/web/messages/zh-TW.json` / `apps/web/messages/en.json`:
|
||||
- 補上「61% 下一步」首層文案;`en.json` 維持繁中鏡像。
|
||||
- `docs/security/iwooos-posture-projection.snapshot.json`:
|
||||
- 新增 `first_progress_unlock_path_steps` 五步驟與首層可見 / boundary 收合 / runtime gate 0 的只讀投影。
|
||||
- `docs/security/security-mirror-status-rollup.snapshot.json`:
|
||||
- 新增 `s2_142_iwooos_first_unlock_path_first_layer`,明確記錄這是 framework_detail 增量,headline 不提升、runtime 不開閘。
|
||||
- `scripts/security/security-mirror-progress-guard.py`:
|
||||
- 新增 guard:第一解鎖路徑只能渲染一次,必須出現在視覺指揮板與深層 section 之前,且 S4.9 / runtime / action buttons 仍不可被前端提升成授權。
|
||||
|
||||
**驗證**:
|
||||
|
||||
```text
|
||||
python3 -m json.tool apps/web/messages/zh-TW.json / en.json -> pass
|
||||
python3 -m json.tool docs/security/iwooos-posture-projection.snapshot.json -> pass
|
||||
python3 -m json.tool docs/security/security-mirror-status-rollup.snapshot.json -> pass
|
||||
cmp -s apps/web/messages/zh-TW.json apps/web/messages/en.json -> pass
|
||||
python3 scripts/security/security-mirror-progress-guard.py -> SECURITY_MIRROR_PROGRESS_GUARD_OK
|
||||
python3 scripts/security/source-control-owner-response-guard.py -> SOURCE_CONTROL_OWNER_RESPONSE_GUARD_OK
|
||||
git diff --check -> pass
|
||||
pnpm --dir apps/web exec tsc --noEmit --tsBuildInfoFile /tmp/awoooi-iwooos-unlock-path-20260531-r2.tsbuildinfo -> pass
|
||||
NEXT_PUBLIC_API_URL=https://awoooi.wooo.work NEXT_PRIVATE_BUILD_WORKER_COUNT=1 pnpm --dir apps/web run build -> pass
|
||||
```
|
||||
|
||||
**本機瀏覽器檢查**:
|
||||
|
||||
```text
|
||||
http://localhost:3021/zh-TW/iwooos?_v=unlock-path-local-r2
|
||||
|
||||
in-app browser desktop:
|
||||
hasBoard=true
|
||||
boardTop=245
|
||||
boardBeforeVisual=true
|
||||
boardBeforeHost=true
|
||||
boundaryOpen=false
|
||||
hasS49=true
|
||||
hasFirstUnlock=true
|
||||
hasNoScanText=true
|
||||
horizontalOverflow=false
|
||||
|
||||
Playwright viewport checks:
|
||||
desktop 1440x1000:
|
||||
boardTop=245
|
||||
visualTop=626
|
||||
horizontalOverflow=false
|
||||
screenshot=/tmp/iwooos-unlock-path-r2-desktop.png
|
||||
mobile 390x844:
|
||||
boardTop=449
|
||||
visualTop=1699
|
||||
horizontalOverflow=false
|
||||
screenshot=/tmp/iwooos-unlock-path-r2-mobile.png
|
||||
```
|
||||
|
||||
**進度邊界**:
|
||||
|
||||
```text
|
||||
overall_percent=61
|
||||
framework_detail += s2_142_iwooos_first_unlock_path_first_layer
|
||||
owner_response_received_count=0
|
||||
owner_response_accepted_count=0
|
||||
runtime_execution_authorized=false
|
||||
active_runtime_gate_count=0
|
||||
repo_creation_authorized=false
|
||||
refs_sync_authorized=false
|
||||
workflow_modification_authorized=false
|
||||
github_primary_switch_authorized=false
|
||||
kali_execution_authorized=false
|
||||
host_mutation_authorized=false
|
||||
```
|
||||
|
||||
## 2026-05-31|Alerts 告警入口接上 Incident 真相鏈
|
||||
|
||||
**背景**:
|
||||
|
||||
@@ -41,6 +41,14 @@
|
||||
"professional_security_experience_response_flow_step_count": 5,
|
||||
"concrete_work_snapshot_first_layer": true,
|
||||
"concrete_work_snapshot_workstream_count": 6,
|
||||
"first_progress_unlock_path_first_layer": true,
|
||||
"first_progress_unlock_path_default_visible": true,
|
||||
"first_progress_unlock_path_above_visual_dashboard": true,
|
||||
"first_progress_unlock_path_step_count": 5,
|
||||
"first_progress_unlock_path_boundary_details_collapsed": true,
|
||||
"first_progress_unlock_path_owner_response_received_count": 0,
|
||||
"first_progress_unlock_path_owner_response_accepted_count": 0,
|
||||
"first_progress_unlock_path_runtime_gate_count": 0,
|
||||
"long_form_sections_default_collapsed": true,
|
||||
"owner_response_validation_received_count": 0,
|
||||
"owner_response_validation_accepted_count": 0,
|
||||
@@ -120,6 +128,88 @@
|
||||
"headline_status": "reviewed_after_awooop_read_only_production_landing_evidence",
|
||||
"not_authorization": true
|
||||
},
|
||||
"first_progress_unlock_path_steps": [
|
||||
{
|
||||
"step_id": "owner_response_scope",
|
||||
"display_order": 1,
|
||||
"source_focus": "s4_9_owner_response",
|
||||
"display_state": "waiting_owner_response",
|
||||
"display_mode": "first_layer_progress_unlock_path",
|
||||
"owner_response_received_count": 0,
|
||||
"owner_response_accepted_count": 0,
|
||||
"redacted_evidence_pointer_count": 0,
|
||||
"preflight_passed_count": 0,
|
||||
"headline_review_authorized": false,
|
||||
"runtime_gate_opened": false,
|
||||
"runtime_execution_authorized": false,
|
||||
"action_buttons_allowed": false,
|
||||
"not_authorization": true
|
||||
},
|
||||
{
|
||||
"step_id": "redacted_evidence_pointer",
|
||||
"display_order": 2,
|
||||
"source_focus": "s4_9_owner_response",
|
||||
"display_state": "waiting_redacted_evidence_pointer",
|
||||
"display_mode": "first_layer_progress_unlock_path",
|
||||
"owner_response_received_count": 0,
|
||||
"owner_response_accepted_count": 0,
|
||||
"redacted_evidence_pointer_count": 0,
|
||||
"preflight_passed_count": 0,
|
||||
"headline_review_authorized": false,
|
||||
"runtime_gate_opened": false,
|
||||
"runtime_execution_authorized": false,
|
||||
"action_buttons_allowed": false,
|
||||
"not_authorization": true
|
||||
},
|
||||
{
|
||||
"step_id": "intake_preflight",
|
||||
"display_order": 3,
|
||||
"source_focus": "s4_9_owner_response",
|
||||
"display_state": "waiting_intake_preflight",
|
||||
"display_mode": "first_layer_progress_unlock_path",
|
||||
"owner_response_received_count": 0,
|
||||
"owner_response_accepted_count": 0,
|
||||
"redacted_evidence_pointer_count": 0,
|
||||
"preflight_passed_count": 0,
|
||||
"headline_review_authorized": false,
|
||||
"runtime_gate_opened": false,
|
||||
"runtime_execution_authorized": false,
|
||||
"action_buttons_allowed": false,
|
||||
"not_authorization": true
|
||||
},
|
||||
{
|
||||
"step_id": "review_acceptance",
|
||||
"display_order": 4,
|
||||
"source_focus": "s4_9_owner_response",
|
||||
"display_state": "waiting_reviewer_acceptance",
|
||||
"display_mode": "first_layer_progress_unlock_path",
|
||||
"owner_response_received_count": 0,
|
||||
"owner_response_accepted_count": 0,
|
||||
"redacted_evidence_pointer_count": 0,
|
||||
"preflight_passed_count": 0,
|
||||
"headline_review_authorized": false,
|
||||
"runtime_gate_opened": false,
|
||||
"runtime_execution_authorized": false,
|
||||
"action_buttons_allowed": false,
|
||||
"not_authorization": true
|
||||
},
|
||||
{
|
||||
"step_id": "headline_review_candidate",
|
||||
"display_order": 5,
|
||||
"source_focus": "s4_9_owner_response",
|
||||
"display_state": "not_opened",
|
||||
"display_mode": "first_layer_progress_unlock_path",
|
||||
"owner_response_received_count": 0,
|
||||
"owner_response_accepted_count": 0,
|
||||
"redacted_evidence_pointer_count": 0,
|
||||
"preflight_passed_count": 0,
|
||||
"headline_review_authorized": false,
|
||||
"runtime_gate_opened": false,
|
||||
"runtime_execution_authorized": false,
|
||||
"action_buttons_allowed": false,
|
||||
"not_authorization": true
|
||||
}
|
||||
],
|
||||
"posture_pillars": [
|
||||
{
|
||||
"pillar_id": "exposure_posture",
|
||||
|
||||
@@ -2173,6 +2173,18 @@
|
||||
"runtime_delta": false,
|
||||
"execution_authorized": false,
|
||||
"not_authorization": true
|
||||
},
|
||||
{
|
||||
"delta_id": "s2_142_iwooos_first_unlock_path_first_layer",
|
||||
"display_order": 171,
|
||||
"completed_stage": "S2.142 IwoooS 第一解鎖路徑首層化",
|
||||
"progress_axis": "framework_detail",
|
||||
"headline_percent_delta": 0,
|
||||
"framework_delta_visible": true,
|
||||
"why_headline_unchanged": "IwoooS 只把第一個可讓 61% 真正往前的 S4.9 負責人回覆路徑提升到首頁首層,並放在視覺指揮板之前,同時把詳細 boundary 鍵值預設收合;iwooos_first_unlock_path_step_count=5、iwooos_first_unlock_path_current_focus=s4_9_owner_response、iwooos_first_unlock_path_owner_response_received_count=0、iwooos_first_unlock_path_owner_response_accepted_count=0、iwooos_first_unlock_path_above_visual_dashboard=true、iwooos_first_unlock_path_boundary_details_collapsed=true、runtime_execution_authorized=false、active_runtime_gate_count=0,不把首層顯示當 owner response 已收到、已接受、headline review、runtime gate、Kali 執行、主機變更、repo/refs/workflow/secret 操作、GitHub primary 切換、Gitea 停用或正式部署。",
|
||||
"runtime_delta": false,
|
||||
"execution_authorized": false,
|
||||
"not_authorization": true
|
||||
}
|
||||
],
|
||||
"next_safe_actions": [
|
||||
|
||||
@@ -45,6 +45,17 @@ def assert_text_not_contains(label: str, text: str, forbidden: str) -> None:
|
||||
raise SystemExit(f"BLOCKED {label}: forbidden {forbidden!r}")
|
||||
|
||||
|
||||
def assert_text_before(label: str, text: str, first: str, second: str) -> None:
|
||||
first_index = text.find(first)
|
||||
second_index = text.find(second)
|
||||
if first_index == -1:
|
||||
raise SystemExit(f"BLOCKED {label}: missing first marker {first!r}")
|
||||
if second_index == -1:
|
||||
raise SystemExit(f"BLOCKED {label}: missing second marker {second!r}")
|
||||
if first_index >= second_index:
|
||||
raise SystemExit(f"BLOCKED {label}: expected {first!r} before {second!r}")
|
||||
|
||||
|
||||
def collect_string_values(value: Any) -> list[str]:
|
||||
if isinstance(value, str):
|
||||
return [value]
|
||||
@@ -587,6 +598,7 @@ def validate(root: Path) -> None:
|
||||
"s2_139_iwooos_product_evidence_wiring_preflight_retry_review_candidate_preflight_recovery_ledger",
|
||||
"s2_140_iwooos_product_evidence_wiring_preflight_retry_review_candidate_preflight_recovery_retry_gates",
|
||||
"s2_141_iwooos_all_product_coverage_snapshot",
|
||||
"s2_142_iwooos_first_unlock_path_first_layer",
|
||||
]
|
||||
assert_equal(
|
||||
"progress_delta_ledger.delta_ids",
|
||||
@@ -1411,6 +1423,42 @@ def validate(root: Path) -> None:
|
||||
iwooos_projection["summary"]["all_product_coverage_snapshot_default_summary_mode"],
|
||||
"compact_first",
|
||||
)
|
||||
assert_true(
|
||||
"iwooos_projection.summary.first_progress_unlock_path_first_layer",
|
||||
iwooos_projection["summary"]["first_progress_unlock_path_first_layer"],
|
||||
)
|
||||
assert_true(
|
||||
"iwooos_projection.summary.first_progress_unlock_path_default_visible",
|
||||
iwooos_projection["summary"]["first_progress_unlock_path_default_visible"],
|
||||
)
|
||||
assert_true(
|
||||
"iwooos_projection.summary.first_progress_unlock_path_above_visual_dashboard",
|
||||
iwooos_projection["summary"]["first_progress_unlock_path_above_visual_dashboard"],
|
||||
)
|
||||
assert_equal(
|
||||
"iwooos_projection.summary.first_progress_unlock_path_step_count",
|
||||
iwooos_projection["summary"]["first_progress_unlock_path_step_count"],
|
||||
5,
|
||||
)
|
||||
assert_true(
|
||||
"iwooos_projection.summary.first_progress_unlock_path_boundary_details_collapsed",
|
||||
iwooos_projection["summary"]["first_progress_unlock_path_boundary_details_collapsed"],
|
||||
)
|
||||
assert_equal(
|
||||
"iwooos_projection.summary.first_progress_unlock_path_owner_response_received_count",
|
||||
iwooos_projection["summary"]["first_progress_unlock_path_owner_response_received_count"],
|
||||
0,
|
||||
)
|
||||
assert_equal(
|
||||
"iwooos_projection.summary.first_progress_unlock_path_owner_response_accepted_count",
|
||||
iwooos_projection["summary"]["first_progress_unlock_path_owner_response_accepted_count"],
|
||||
0,
|
||||
)
|
||||
assert_equal(
|
||||
"iwooos_projection.summary.first_progress_unlock_path_runtime_gate_count",
|
||||
iwooos_projection["summary"]["first_progress_unlock_path_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"],
|
||||
@@ -11602,6 +11650,88 @@ def validate(root: Path) -> None:
|
||||
iwooos_projection_page,
|
||||
"IwoooSFirstProgressUnlockPathBoard",
|
||||
)
|
||||
assert_equal(
|
||||
"iwooos_page.first_progress_unlock_path_render_count",
|
||||
iwooos_projection_page.count("<IwoooSFirstProgressUnlockPathBoard />"),
|
||||
1,
|
||||
)
|
||||
assert_text_before(
|
||||
"iwooos_page.first_progress_unlock_path_before_visual_dashboard",
|
||||
iwooos_projection_page,
|
||||
"<IwoooSFirstProgressUnlockPathBoard />",
|
||||
"<IwoooSVisualCommandDashboard />",
|
||||
)
|
||||
assert_text_before(
|
||||
"iwooos_page.first_progress_unlock_path_first_layer_order",
|
||||
iwooos_projection_page,
|
||||
"<IwoooSFirstProgressUnlockPathBoard />",
|
||||
"<IwoooSProfessionalSecurityExperience />",
|
||||
)
|
||||
assert_text_before(
|
||||
"iwooos_page.first_progress_unlock_path_before_deep_sections",
|
||||
iwooos_projection_page,
|
||||
"<IwoooSFirstProgressUnlockPathBoard />",
|
||||
"<IwoooSSectionGroup",
|
||||
)
|
||||
assert_text_contains(
|
||||
"iwooos_page.first_progress_unlock_path_boundary_details",
|
||||
iwooos_projection_page,
|
||||
'data-testid="iwooos-first-progress-unlock-path-boundaries"',
|
||||
)
|
||||
expected_first_progress_unlock_path_step_ids = [
|
||||
"owner_response_scope",
|
||||
"redacted_evidence_pointer",
|
||||
"intake_preflight",
|
||||
"review_acceptance",
|
||||
"headline_review_candidate",
|
||||
]
|
||||
first_progress_unlock_path_steps = iwooos_projection["first_progress_unlock_path_steps"]
|
||||
assert_equal(
|
||||
"iwooos_projection.first_progress_unlock_path_steps.ids",
|
||||
[item["step_id"] for item in first_progress_unlock_path_steps],
|
||||
expected_first_progress_unlock_path_step_ids,
|
||||
)
|
||||
assert_equal(
|
||||
"iwooos_projection.first_progress_unlock_path_steps.display_order",
|
||||
[item["display_order"] for item in first_progress_unlock_path_steps],
|
||||
list(range(1, len(expected_first_progress_unlock_path_step_ids) + 1)),
|
||||
)
|
||||
for item in first_progress_unlock_path_steps:
|
||||
assert_equal(
|
||||
f"iwooos_projection.first_progress_unlock_path_steps.{item['step_id']}.source_focus",
|
||||
item["source_focus"],
|
||||
"s4_9_owner_response",
|
||||
)
|
||||
assert_equal(
|
||||
f"iwooos_projection.first_progress_unlock_path_steps.{item['step_id']}.display_mode",
|
||||
item["display_mode"],
|
||||
"first_layer_progress_unlock_path",
|
||||
)
|
||||
for count_key in [
|
||||
"owner_response_received_count",
|
||||
"owner_response_accepted_count",
|
||||
"redacted_evidence_pointer_count",
|
||||
"preflight_passed_count",
|
||||
]:
|
||||
assert_equal(
|
||||
f"iwooos_projection.first_progress_unlock_path_steps.{item['step_id']}.{count_key}",
|
||||
item[count_key],
|
||||
0,
|
||||
)
|
||||
for flag in [
|
||||
"headline_review_authorized",
|
||||
"runtime_gate_opened",
|
||||
"runtime_execution_authorized",
|
||||
"action_buttons_allowed",
|
||||
]:
|
||||
assert_false(
|
||||
f"iwooos_projection.first_progress_unlock_path_steps.{item['step_id']}.{flag}",
|
||||
item[flag],
|
||||
)
|
||||
assert_true(
|
||||
f"iwooos_projection.first_progress_unlock_path_steps.{item['step_id']}.not_authorization",
|
||||
item["not_authorization"],
|
||||
)
|
||||
for text in [
|
||||
"iwooos_first_unlock_path_step_count=5",
|
||||
"iwooos_first_unlock_path_current_focus=s4_9_owner_response",
|
||||
@@ -11637,7 +11767,7 @@ def validate(root: Path) -> None:
|
||||
list(web_messages_en["iwooos"].keys()),
|
||||
"firstProgressUnlockPath",
|
||||
)
|
||||
for key in ["title", "subtitle", "summary", "items", "stepLabel", "boundaryTitle", "boundaryIntro"]:
|
||||
for key in ["eyebrow", "title", "subtitle", "summary", "items", "stepLabel", "boundaryTitle", "boundaryIntro"]:
|
||||
assert_contains(
|
||||
"web_messages.zh-TW.iwooos.firstProgressUnlockPath.keys",
|
||||
list(web_messages_zh["iwooos"]["firstProgressUnlockPath"].keys()),
|
||||
|
||||
Reference in New Issue
Block a user