feat(web): add IwoooS host review outcomes

This commit is contained in:
Your Name
2026-05-19 22:12:26 +08:00
parent 394a74908c
commit b4c2c3d7e8
11 changed files with 481 additions and 6 deletions

View File

@@ -1447,6 +1447,49 @@
}
}
},
"hostEvidenceReviewOutcomes": {
"title": "Host Evidence Review Outcome Lanes",
"subtitle": "After preflight, evidence can only move into these read-only lanes. This shows possible human review outcomes and does not create approval records, runtime gates, or host actions.",
"laneLabel": "Outcome lane",
"nextLabel": "Next step",
"items": {
"readyForHumanReview": {
"title": "Ready for human review",
"body": "Evidence becomes a human review candidate only when metadata pointer, dependency order, scope, and owner pointer are readable.",
"next": "display candidate only; received=0, accepted=0"
},
"needsScopeEvidence": {
"title": "Needs scope evidence",
"body": "Scan or finding evidence that cannot map to scope boundary returns to the scope lane.",
"next": "collect scope, no scan"
},
"needsOwnerDecision": {
"title": "Needs owner decision",
"body": "Host change, update, SSH, or blocking evidence without decision pointer returns to owner decision lane.",
"next": "collect decision record, no host action"
},
"quarantineDependencySkip": {
"title": "Quarantine dependency skip",
"body": "Evidence that skips the S2.17 order or has incomplete prerequisites is quarantined for human interpretation.",
"next": "show quarantine reason, do not advance counters"
},
"rejectRawPayload": {
"title": "Reject raw payload",
"body": "Full scan output, unredacted findings, host dumps, or log bundles do not enter IwoooS.",
"next": "request redacted summary instead"
},
"rejectCredentialPlaintext": {
"title": "Reject credential plaintext",
"body": "Passwords, tokens, private keys, sessions, or plaintext credentials are rejected and quarantined.",
"next": "do not store, forward, or display plaintext"
},
"waitingRuntimeGate": {
"title": "Waiting runtime gate",
"body": "Even after human review allows action, the work waits for a later runtime gate and is not executed by this lane.",
"next": "active runtime gates remain 0"
}
}
},
"nextGate": {
"title": "Next High-level Gate",
"body": "S4.9 Gitea owner attestation response is the recommended next owner evidence. Headline progress should only increase after owner responses, redacted payload ingestion, active runtime gates, or GitHub primary readiness actually change."

View File

@@ -1448,6 +1448,49 @@
}
}
},
"hostEvidenceReviewOutcomes": {
"title": "主機 Evidence Review Outcome Lanes",
"subtitle": "預檢後的 evidence 只能進這些只讀分流。這裡顯示人工審查可能結果,不會建立 approval record、runtime gate 或主機動作。",
"laneLabel": "結果分流",
"nextLabel": "下一步",
"items": {
"readyForHumanReview": {
"title": "Ready for human review",
"body": "metadata pointer、依賴順序、scope 與 owner pointer 都可讀時,才可標示為人工審查候選。",
"next": "只顯示候選received=0、accepted=0"
},
"needsScopeEvidence": {
"title": "Needs scope evidence",
"body": "scan 或 finding evidence 無法對到 scope boundary 時,回到補 scope lane。",
"next": "補 scope不進 scan"
},
"needsOwnerDecision": {
"title": "Needs owner decision",
"body": "主機變更、更新、SSH 或 blocking 類 evidence 缺 decision pointer 時,回到 owner decision lane。",
"next": "補 decision record不啟動主機動作"
},
"quarantineDependencySkip": {
"title": "Quarantine dependency skip",
"body": "跳過 S2.17 收件順序或前置依賴不完整時,先隔離再人工判讀。",
"next": "隔離原因可見,不推 counter"
},
"rejectRawPayload": {
"title": "Reject raw payload",
"body": "完整掃描輸出、未脫敏 finding、host dump 或 log bundle 不進 IwoooS。",
"next": "要求改交脫敏摘要"
},
"rejectCredentialPlaintext": {
"title": "Reject credential plaintext",
"body": "任何帳密、token、private key、session 或憑證明文都直接拒收並隔離。",
"next": "不保存、不轉送、不顯示明文"
},
"waitingRuntimeGate": {
"title": "Waiting runtime gate",
"body": "即使人工審查後可採取行動,也只能等待後續 runtime gate不由 outcome lane 執行。",
"next": "active runtime gates 仍為 0"
}
}
},
"nextGate": {
"title": "下一個高層 Gate",
"body": "S4.9 Gitea owner attestation response 是目前建議先收的 owner evidence。任何 headline 提升都要等 owner response、redacted payload ingestion、active runtime gate 或 GitHub primary readiness 有真實變化。"

View File

@@ -109,6 +109,13 @@ type HostEvidenceIntakeCheck = {
tone: 'steady' | 'warn' | 'locked'
}
type HostEvidenceReviewOutcomeLane = {
key: string
lane: string
icon: typeof ShieldCheck
tone: 'steady' | 'warn' | 'locked'
}
const postureMetrics: PostureMetric[] = [
{ key: 'overall', value: '58%', tone: 'warn' },
{ key: 'framework', value: '80-85%', tone: 'steady' },
@@ -250,6 +257,16 @@ const hostEvidenceIntakeChecks: HostEvidenceIntakeCheck[] = [
{ key: 'counterFreeze', check: 'P7', icon: ShieldCheck, tone: 'locked' },
]
const hostEvidenceReviewOutcomeLanes: HostEvidenceReviewOutcomeLane[] = [
{ key: 'readyForHumanReview', lane: 'R1', icon: CheckCircle2, tone: 'steady' },
{ key: 'needsScopeEvidence', lane: 'R2', icon: Radar, tone: 'warn' },
{ key: 'needsOwnerDecision', lane: 'R3', icon: ClipboardCheck, tone: 'warn' },
{ key: 'quarantineDependencySkip', lane: 'R4', icon: GitBranch, tone: 'warn' },
{ key: 'rejectRawPayload', lane: 'R5', icon: FileWarning, tone: 'locked' },
{ key: 'rejectCredentialPlaintext', lane: 'R6', icon: Lock, tone: 'locked' },
{ key: 'waitingRuntimeGate', lane: 'R7', icon: Clock3, tone: 'locked' },
]
const evidenceItems = [
'iwooos-posture-projection.snapshot.json',
'security-rollout-policy.snapshot.json',
@@ -636,6 +653,34 @@ function HostEvidenceIntakeCard({ item }: { item: HostEvidenceIntakeCheck }) {
)
}
function HostEvidenceReviewOutcomeCard({ item }: { item: HostEvidenceReviewOutcomeLane }) {
const t = useTranslations('iwooos.hostEvidenceReviewOutcomes')
const Icon = item.icon
return (
<div style={{ ...band, minHeight: 190, padding: 16 }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 9 }}>
<Icon size={18} color={toneColors[item.tone]} />
<span style={{ fontSize: 11, color: '#87867f' }}>{t('laneLabel')}</span>
</div>
<span style={{ fontSize: 11, color: '#9b978b' }}>{item.lane}</span>
</div>
<h2 style={{ fontSize: 14, margin: '12px 0 6px', color: '#141413' }}>
{t(`items.${item.key}.title` as never)}
</h2>
<p style={{ fontSize: 12, lineHeight: 1.55, color: '#6f6d66', margin: 0 }}>
{t(`items.${item.key}.body` as never)}
</p>
<div style={{ marginTop: 10, display: 'grid', gap: 5 }}>
<div style={{ fontSize: 11, color: '#87867f' }}>{t('nextLabel')}</div>
<div style={{ fontSize: 11, color: toneColors[item.tone], lineHeight: 1.45 }}>
{t(`items.${item.key}.next` as never)}
</div>
</div>
</div>
)
}
export default function IwoooSPage({ params }: { params: { locale: string } }) {
const t = useTranslations('iwooos')
@@ -808,6 +853,26 @@ export default function IwoooSPage({ params }: { params: { locale: string } }) {
</div>
</section>
<section style={{ marginBottom: 14 }}>
<div style={{ marginBottom: 14 }}>
<h2 style={{ fontSize: 16, margin: 0 }}>{t('hostEvidenceReviewOutcomes.title')}</h2>
<p style={{ fontSize: 12, color: '#6f6d66', margin: '6px 0 0', lineHeight: 1.55 }}>
{t('hostEvidenceReviewOutcomes.subtitle')}
</p>
</div>
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(210px, 1fr))',
gap: 12,
}}
>
{hostEvidenceReviewOutcomeLanes.map(item => (
<HostEvidenceReviewOutcomeCard key={item.key} item={item} />
))}
</div>
</section>
<section
style={{
display: 'grid',