feat(web): promote IwoooS unlock path
All checks were successful
CD Pipeline / tests (push) Successful in 1m33s
Code Review / ai-code-review (push) Successful in 13s
CD Pipeline / build-and-deploy (push) Successful in 3m45s
CD Pipeline / post-deploy-checks (push) Successful in 2m15s

This commit is contained in:
Your Name
2026-05-31 23:15:28 +08:00
parent 4f053d97f8
commit dc2679ea75
7 changed files with 443 additions and 94 deletions

View File

@@ -10962,8 +10962,9 @@
}
},
"firstProgressUnlockPath": {
"eyebrow": "61% 下一步",
"title": "第一個進度解鎖路徑",
"subtitle": "S2.114 把 61% 下一個真正能往前的路徑收斂到 S4.9 負責人回覆:先收到可追溯回覆,再補齊脫敏證據參照,通過收件預檢與審查接受後,才可能成為 重點 審查 候選。",
"subtitle": "把 61% 下一個真正能往前的路徑放到首頁首層:先收 S4.9 負責人可追溯回覆,再補齊脫敏證據參照,通過收件預檢與審查接受後,才可能成為進度審查候選。",
"stepLabel": "步驟",
"boundaryTitle": "第一解鎖路徑邊界",
"boundaryIntro": "以下鍵值固定:這是 S4.9 第一解鎖路徑的只讀收斂,不是送件完成、回覆已收到、批准、掃描、修復、部署或執行期入口。",

View File

@@ -10962,8 +10962,9 @@
}
},
"firstProgressUnlockPath": {
"eyebrow": "61% 下一步",
"title": "第一個進度解鎖路徑",
"subtitle": "S2.114 把 61% 下一個真正能往前的路徑收斂到 S4.9 負責人回覆:先收到可追溯回覆,再補齊脫敏證據參照,通過收件預檢與審查接受後,才可能成為 重點 審查 候選。",
"subtitle": "把 61% 下一個真正能往前的路徑放到首頁首層:先收 S4.9 負責人可追溯回覆,再補齊脫敏證據參照,通過收件預檢與審查接受後,才可能成為進度審查候選。",
"stepLabel": "步驟",
"boundaryTitle": "第一解鎖路徑邊界",
"boundaryIntro": "以下鍵值固定:這是 S4.9 第一解鎖路徑的只讀收斂,不是送件完成、回覆已收到、批准、掃描、修復、部署或執行期入口。",

View File

@@ -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 />

View File

@@ -1,3 +1,85 @@
## 2026-05-31IwoooS 第一解鎖路徑首層化
**背景**
- 使用者指出 `/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-31Alerts 告警入口接上 Incident 真相鏈
**背景**

View File

@@ -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",

View File

@@ -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": [

View File

@@ -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()),