feat(web): add IwoooS S4.9 preflight

This commit is contained in:
Your Name
2026-05-20 13:19:11 +08:00
parent 5979e3d22d
commit 52e3137f48
11 changed files with 685 additions and 8 deletions

View File

@@ -2786,6 +2786,51 @@
"guard": "No secret value collection, workflow mutation, runner enablement, or write token use."
}
}
},
"s49OwnerResponsePreflight": {
"title": "S4.9 Owner Response Intake Preflight",
"subtitle": "S2.48 surfaces the 6 S4.9 intake preflight checks in IwoooS: known attestation item, complete fields, allowed decision, redacted evidence, no execution request, and no accepted state until all five items are covered. This is preflight display only: no request send, received marking, or audit event creation.",
"checkLabel": "Preflight",
"failureLabel": "If it fails",
"guardLabel": "Still forbidden",
"items": {
"knownAttestationItem": {
"title": "Match a known S4.7 item",
"body": "The owner response must map to public-only / local gap, org/user endpoint, 110 adjacent source, canonical owner, or legacy disposition.",
"failure": "Unclear mapping can only request owner correction.",
"guard": "Do not treat vague text as coverage attestation or auto-map it to an item."
},
"requiredOwnerFields": {
"title": "Required owner fields complete",
"body": "Each response needs owner role/team, decision, decision reason, affected scope, evidence refs, and followup owner.",
"failure": "Missing fields can only request more evidence.",
"guard": "No verbal OK and no approval record from incomplete responses."
},
"allowedDecision": {
"title": "Decision is allowed",
"body": "The decision must fit the acceptable decisions for the matching template so free text is not misread as authorization.",
"failure": "Invalid decisions request owner correction.",
"guard": "Do not upgrade OK / looks fine language into migration or primary approval."
},
"redactedEvidenceOnly": {
"title": "Redacted evidence refs only",
"body": "Evidence may only point to repo docs, snapshots, or redacted metadata pointers; no tokens, secrets, cookies, sessions, private keys, or private URL credentials.",
"failure": "Sensitive payloads go to quarantine.",
"guard": "No raw secret storage, DB dump import, git object pack, or repo archive collection."
},
"noExecutionRequest": {
"title": "No execution request",
"body": "Responses must not request Gitea/GitHub writes, repo creation, visibility changes, refs sync/delete/force-push, workflow/secret/runner changes, scans, or runtime actions.",
"failure": "Embedded execution asks are rejected.",
"guard": "No Gitea writes, GitHub repo creation, refs sync, or runtime gate opening."
},
"allFiveItemsBeforeAccepted": {
"title": "All five items before Accepted",
"body": "S4.9 cannot be accepted until all five response templates have acceptable owner responses.",
"failure": "Partial responses remain waiting or request more evidence.",
"guard": "Visible preflight is not request sent, received, accepted, or audit emitted."
}
}
}
},
"tickets": {

View File

@@ -2787,6 +2787,51 @@
"guard": "不收 secret value、不改 workflow、不啟用 runner、不使用 write token。"
}
}
},
"s49OwnerResponsePreflight": {
"title": "S4.9 Owner Response 收件前 Preflight",
"subtitle": "S2.48 把 S4.9 的 6 個收件前檢查拉到 IwoooS先確認回覆是否對應已知 attestation item、欄位完整、decision 合法、evidence 脫敏、沒有執行要求,且五個 items 到齊前不得 accepted。這裡只顯示 preflight不寄送 request、不標記 received、不建立 audit event。",
"checkLabel": "Preflight",
"failureLabel": "不通過時",
"guardLabel": "仍禁止",
"items": {
"knownAttestationItem": {
"title": "對應 S4.7 已知 item",
"body": "owner response 必須明確對應 public-only / local gap、org/user endpoint、110 adjacent source、canonical owner 或 legacy disposition 其中之一。",
"failure": "不明確時只能 request owner correction不能進 accepted。",
"guard": "不把模糊回覆當作 coverage attestation也不自動補成某個 item。"
},
"requiredOwnerFields": {
"title": "必填欄位完整",
"body": "每筆回覆都要有 owner role/team、decision、decision reason、受影響 scope、evidence refs 與 followup owner。",
"failure": "欄位不足只能 request more evidence。",
"guard": "不接受口頭 OK、不用缺欄位回覆建立 approval record。"
},
"allowedDecision": {
"title": "Decision 在允許值內",
"body": "decision 必須落在該 template 允許的 acceptable decisions避免自由文字被誤讀成授權。",
"failure": "decision 不合規時 request owner correction。",
"guard": "不把同意、可進行、看起來沒問題升級成 migration 或 primary approval。"
},
"redactedEvidenceOnly": {
"title": "只接受脫敏 evidence refs",
"body": "evidence 只能指向 repo 內文件、snapshot 或脫敏 metadata pointer不貼 token、secret、cookie、session、private key 或私有 URL 憑證。",
"failure": "出現敏感 payload 只能 quarantine sensitive payload。",
"guard": "不保存 raw secret、不匯入 DB dump、不收 git object pack 或 repo archive。"
},
"noExecutionRequest": {
"title": "不得夾帶執行要求",
"body": "回覆不得要求 Gitea/GitHub 寫入、repo 建立、visibility 修改、refs sync/delete/force-push、workflow/secret/runner 變更、scan 或 runtime action。",
"failure": "夾帶執行要求時 reject execution request。",
"guard": "不寫 Gitea、不建 GitHub repo、不同步 refs、不開 runtime gate。"
},
"allFiveItemsBeforeAccepted": {
"title": "五個 items 到齊前不得 Accepted",
"body": "S4.9 要被標示 accepted 前,五個 response templates 都必須收到可驗收的 owner response。",
"failure": "部分回覆只能維持 waiting 或 request more evidence。",
"guard": "preflight 可見不代表 request sent、received、accepted 或 audit emitted。"
}
}
}
},
"tickets": {

View File

@@ -45,6 +45,13 @@ type OwnerResponseNextActionFocusItem = {
tone: 'steady' | 'warn' | 'locked'
}
type S49OwnerResponsePreflightCheck = {
key: string
check: string
icon: typeof ShieldCheck
tone: 'steady' | 'warn' | 'locked'
}
type Pillar = {
key: string
icon: typeof ShieldCheck
@@ -337,6 +344,15 @@ const ownerResponseNextActionFocusItems: OwnerResponseNextActionFocusItem[] = [
{ key: 'workflowSecretOwnerResponse', focus: 'S4.12', icon: Lock, tone: 'locked' },
]
const s49OwnerResponsePreflightChecks: S49OwnerResponsePreflightCheck[] = [
{ key: 'knownAttestationItem', check: 'P1', icon: SearchCheck, tone: 'warn' },
{ key: 'requiredOwnerFields', check: 'P2', icon: ListChecks, tone: 'warn' },
{ key: 'allowedDecision', check: 'P3', icon: CheckCircle2, tone: 'warn' },
{ key: 'redactedEvidenceOnly', check: 'P4', icon: Lock, tone: 'locked' },
{ key: 'noExecutionRequest', check: 'P5', icon: AlertTriangle, tone: 'locked' },
{ key: 'allFiveItemsBeforeAccepted', check: 'P6', icon: ShieldCheck, tone: 'locked' },
]
const posturePillars: Pillar[] = [
{ key: 'exposure', icon: Radar, tone: 'warn' },
{ key: 'sourceControl', icon: GitBranch, tone: 'warn' },
@@ -878,6 +894,38 @@ function OwnerResponseNextActionFocusCard({ item }: { item: OwnerResponseNextAct
)
}
function S49OwnerResponsePreflightCard({ item }: { item: S49OwnerResponsePreflightCheck }) {
const t = useTranslations('iwooos.s49OwnerResponsePreflight')
const Icon = item.icon
return (
<div style={{ ...band, minHeight: 178, 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('checkLabel')}</span>
</div>
<span style={{ fontSize: 11, color: toneColors[item.tone] }}>{item.check}</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('failureLabel')}</div>
<div style={{ fontSize: 11, color: toneColors[item.tone], lineHeight: 1.45 }}>
{t(`items.${item.key}.failure` as never)}
</div>
<div style={{ fontSize: 11, color: '#87867f', marginTop: 4 }}>{t('guardLabel')}</div>
<div style={{ fontSize: 11, color: '#6f6d66', lineHeight: 1.45 }}>
{t(`items.${item.key}.guard` as never)}
</div>
</div>
</div>
)
}
function PillarCard({ item }: { item: Pillar }) {
const t = useTranslations('iwooos.pillars')
const Icon = item.icon
@@ -2105,6 +2153,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('s49OwnerResponsePreflight.title')}</h2>
<p style={{ fontSize: 12, color: '#6f6d66', margin: '6px 0 0', lineHeight: 1.55 }}>
{t('s49OwnerResponsePreflight.subtitle')}
</p>
</div>
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))',
gap: 12,
}}
>
{s49OwnerResponsePreflightChecks.map(item => (
<S49OwnerResponsePreflightCard key={item.key} item={item} />
))}
</div>
</section>
<section style={{ marginBottom: 14 }}>
<div style={{ marginBottom: 14 }}>
<h2 style={{ fontSize: 16, margin: 0 }}>{t('journey.title')}</h2>