Merge remote-tracking branch 'origin/main' into codex/agent99-alert-control-20260710
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 2m36s
CD Pipeline / build-and-deploy (push) Successful in 4m58s
CD Pipeline / post-deploy-checks (push) Has been cancelled

This commit is contained in:
ogt
2026-07-10 16:06:09 +08:00
5 changed files with 226 additions and 29 deletions

View File

@@ -21829,6 +21829,7 @@
"domainStatusLabel": "狀態",
"p0Title": "P0 優先順序",
"p0Intro": "以下是下一階段真正能降低即時風險的順序;每一項都需要脫敏證據與 owner acceptance不能靠 UI 可見或一般批准跳過。",
"p0More": "Show remaining {count} P0 items and evidence requirements",
"requiredEvidenceLabel": "需要證據",
"boundaryTitle": "不可假綠燈規則",
"status": {
@@ -21876,6 +21877,16 @@
},
"review": {
"title": "為什麼還沒有完成資安管理閉環",
"p0Queue": "Next P0 queue",
"toolRail": "External security tools",
"aiLoop": "AI automation loop",
"rootCauseDetails": "Show root causes and closure gates",
"cockpit": {
"findingCount": "Findings",
"p0Count": "P0",
"toolCount": "Tools",
"aiStageCount": "AI stages"
},
"diagnosis": {
"controlPlaneRuntimeGap": "控制面已建立runtime 防護閉環未完成",
"unknown": "需要重新讀回診斷狀態"

View File

@@ -21829,6 +21829,7 @@
"domainStatusLabel": "狀態",
"p0Title": "P0 優先順序",
"p0Intro": "以下是下一階段真正能降低即時風險的順序;每一項都需要脫敏證據與 owner acceptance不能靠 UI 可見或一般批准跳過。",
"p0More": "展開其餘 {count} 個 P0 與證據要求",
"requiredEvidenceLabel": "需要證據",
"boundaryTitle": "不可假綠燈規則",
"status": {
@@ -21876,6 +21877,16 @@
},
"review": {
"title": "為什麼還沒有完成資安管理閉環",
"p0Queue": "下一步 P0 queue",
"toolRail": "外部資安工具軌",
"aiLoop": "AI 自動化閉環",
"rootCauseDetails": "展開根因與 closure 條件",
"cockpit": {
"findingCount": "根因",
"p0Count": "P0",
"toolCount": "工具",
"aiStageCount": "AI 階段"
},
"diagnosis": {
"controlPlaneRuntimeGap": "控制面已建立runtime 防護閉環未完成",
"unknown": "需要重新讀回診斷狀態"

View File

@@ -8770,6 +8770,37 @@ function IwoooSSecurityControlCoverageBoard() {
]
const domains = data?.domains ?? []
const p0Actions = data?.p0_next_actions ?? []
const orderedP0Actions = review?.priority_work_items ?? p0Actions
const p0Preview = orderedP0Actions.slice(0, 4)
const p0Remainder = orderedP0Actions.slice(4)
const toolTracks = review?.security_tool_integration_matrix ?? []
const aiLoopStages = review?.ai_automation_closure_loop ?? []
const cockpitStats = review ? [
{
key: 'findingCount',
value: String(review.root_cause_summary.length),
icon: FileWarning,
tone: 'warn' as const,
},
{
key: 'p0Count',
value: String(review.priority_work_items.length),
icon: ListChecks,
tone: 'warn' as const,
},
{
key: 'toolCount',
value: String(toolTracks.length),
icon: SearchCheck,
tone: 'steady' as const,
},
{
key: 'aiStageCount',
value: String(aiLoopStages.length),
icon: Workflow,
tone: 'steady' as const,
},
] : []
return (
<section
@@ -8835,48 +8866,167 @@ function IwoooSSecurityControlCoverageBoard() {
data-testid="iwooos-security-professional-review-strip"
style={{
marginTop: 14,
border: '0.5px solid #e0d8bf',
border: '0.5px solid #1f3d35',
borderRadius: 8,
background: '#fff',
background: '#101816',
padding: 12,
display: 'grid',
gap: 10,
gap: 12,
minWidth: 0,
}}
>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10, flexWrap: 'wrap' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, minWidth: 0 }}>
<AlertTriangle size={16} color="#8a5b1f" />
<span style={{ fontSize: 12, color: '#6a5730', fontWeight: 800, ...textWrap }}>
<ShieldAlert size={17} color="#f0b35a" />
<span style={{ fontSize: 12, color: '#f7f3e8', fontWeight: 800, ...textWrap }}>
{t('review.title')}
</span>
</div>
<span style={{ fontSize: 11, color: '#8a5b1f', fontWeight: 700, ...textWrap }}>
<span style={{ fontSize: 11, color: '#f0b35a', fontWeight: 800, ...textWrap }}>
{t(`review.diagnosis.${securityReviewDiagnosisKey(review.completion_diagnosis.diagnosis)}` as never)}
</span>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 230px), 1fr))', gap: 8 }}>
{reviewFindings.map(finding => (
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 116px), 1fr))', gap: 8 }}>
{cockpitStats.map(item => {
const Icon = item.icon
return (
<div
key={item.key}
style={{
border: '0.5px solid rgba(232, 238, 225, 0.18)',
borderRadius: 8,
background: 'rgba(255, 255, 255, 0.06)',
padding: 9,
minWidth: 0,
}}
>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 6 }}>
<span style={{ fontSize: 10, color: '#cbd8cc', fontWeight: 700, ...textWrap }}>
{t(`review.cockpit.${item.key}` as never)}
</span>
<Icon size={15} color={toneColors[item.tone]} />
</div>
<div style={{ marginTop: 7, fontSize: 22, lineHeight: 1, color: '#f7f3e8', fontWeight: 900 }}>
{item.value}
</div>
</div>
)
})}
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 260px), 1fr))', gap: 10 }}>
<div style={{ border: '0.5px solid rgba(232, 238, 225, 0.18)', borderRadius: 8, padding: 10, minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 7, color: '#cbd8cc', fontSize: 11, fontWeight: 800 }}>
<ListChecks size={15} color="#f0b35a" />
{t('review.p0Queue')}
</div>
<div style={{ marginTop: 8, display: 'grid', gap: 6 }}>
{p0Preview.map(action => (
<div
key={action.priority}
style={{
display: 'grid',
gridTemplateColumns: '46px minmax(0, 1fr)',
alignItems: 'center',
gap: 8,
border: '0.5px solid rgba(240, 179, 90, 0.25)',
borderRadius: 8,
background: 'rgba(240, 179, 90, 0.08)',
padding: '7px 8px',
minWidth: 0,
}}
>
<span style={{ color: '#f0b35a', fontSize: 11, fontWeight: 900 }}>{action.priority}</span>
<span style={{ color: '#f7f3e8', fontSize: 12, fontWeight: 800, ...textWrap }}>{action.title}</span>
</div>
))}
</div>
</div>
<div style={{ display: 'grid', gap: 8, minWidth: 0 }}>
<div style={{ border: '0.5px solid rgba(232, 238, 225, 0.18)', borderRadius: 8, padding: 10, minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 7, color: '#cbd8cc', fontSize: 11, fontWeight: 800 }}>
<SearchCheck size={15} color="#86c89a" />
{t('review.toolRail')}
</div>
<div style={{ marginTop: 8, display: 'flex', gap: 6, flexWrap: 'wrap' }}>
{toolTracks.slice(0, 8).map(tool => (
<span
key={tool.tool_id}
style={{
border: '0.5px solid rgba(134, 200, 154, 0.35)',
borderRadius: 999,
color: '#dff5e4',
background: 'rgba(134, 200, 154, 0.12)',
padding: '4px 7px',
fontSize: 10,
fontWeight: 800,
...textWrap,
}}
>
{tool.tool_id}
</span>
))}
</div>
</div>
<div style={{ border: '0.5px solid rgba(232, 238, 225, 0.18)', borderRadius: 8, padding: 10, minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 7, color: '#cbd8cc', fontSize: 11, fontWeight: 800 }}>
<Workflow size={15} color="#8db7f2" />
{t('review.aiLoop')}
</div>
<div style={{ marginTop: 8, display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(62px, 1fr))', gap: 6 }}>
{aiLoopStages.map(stage => (
<div
key={stage.stage_id}
style={{
border: '0.5px solid rgba(141, 183, 242, 0.28)',
borderRadius: 8,
color: '#e6eefc',
background: 'rgba(141, 183, 242, 0.1)',
padding: '6px 7px',
fontSize: 10,
fontWeight: 800,
textAlign: 'center',
...textWrap,
}}
>
{stage.stage_id}
</div>
))}
</div>
</div>
</div>
</div>
<details style={{ border: '0.5px solid rgba(232, 238, 225, 0.18)', borderRadius: 8, padding: '8px 10px' }}>
<summary style={{ cursor: 'pointer', fontSize: 11, color: '#cbd8cc', fontWeight: 800 }}>
{t('review.rootCauseDetails')}
</summary>
<div style={{ marginTop: 8, display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 230px), 1fr))', gap: 8 }}>
{reviewFindings.map(finding => (
<div
key={finding.finding_id}
style={{
border: '0.5px solid #e7debf',
border: '0.5px solid rgba(240, 179, 90, 0.26)',
borderRadius: 8,
background: '#fffaf1',
background: 'rgba(240, 179, 90, 0.08)',
padding: 10,
minWidth: 0,
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: 7, minWidth: 0 }}>
<span style={{ fontSize: 11, color: '#8a5b1f', fontWeight: 800 }}>{finding.severity}</span>
<span style={{ fontSize: 12, color: '#141413', fontWeight: 800, ...textWrap }}>{finding.title}</span>
<span style={{ fontSize: 11, color: '#f0b35a', fontWeight: 800 }}>{finding.severity}</span>
<span style={{ fontSize: 12, color: '#f7f3e8', fontWeight: 800, ...textWrap }}>{finding.title}</span>
</div>
<p style={{ fontSize: 11, color: '#5d5036', lineHeight: 1.45, margin: '7px 0 0', ...textWrap }}>
<p style={{ fontSize: 11, color: '#cbd8cc', lineHeight: 1.45, margin: '7px 0 0', ...textWrap }}>
{finding.required_closure}
</p>
</div>
))}
</div>
))}
</div>
</details>
</div>
) : null}
@@ -8952,18 +9102,43 @@ function IwoooSSecurityControlCoverageBoard() {
{t('p0Intro')}
</p>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 240px), 1fr))', gap: 8 }}>
{p0Actions.map(action => (
<div key={action.priority} style={{ border: '0.5px solid #e0d8bf', borderRadius: 8, background: '#fff', padding: 11, minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
<span style={{ fontSize: 11, color: '#8a5b1f', fontWeight: 800 }}>{action.priority}</span>
<span style={{ fontSize: 12, color: '#141413', fontWeight: 700, ...textWrap }}>{action.title}</span>
{p0Preview.map(action => (
<div key={action.priority} style={{ border: '0.5px solid #e0d8bf', borderRadius: 8, background: '#fff', padding: 11, minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
<span style={{ fontSize: 11, color: '#8a5b1f', fontWeight: 800 }}>{action.priority}</span>
<span style={{ fontSize: 12, color: '#141413', fontWeight: 700, ...textWrap }}>{action.title}</span>
</div>
</div>
<p style={{ fontSize: 11, color: '#5d5036', lineHeight: 1.45, margin: '7px 0 0', ...textWrap }}>
{t('requiredEvidenceLabel')}{action.required_evidence}
</p>
</div>
))}
))}
</div>
{p0Remainder.length > 0 ? (
<details
style={{
marginTop: 8,
border: '0.5px solid #e0d8bf',
borderRadius: 8,
background: '#fff',
padding: '8px 10px',
}}
>
<summary style={{ cursor: 'pointer', fontSize: 12, color: '#6a5730', fontWeight: 800 }}>
{t('p0More', { count: p0Remainder.length })}
</summary>
<div style={{ marginTop: 8, display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 240px), 1fr))', gap: 8 }}>
{p0Remainder.map(action => (
<div key={action.priority} style={{ border: '0.5px solid #e0d8bf', borderRadius: 8, background: '#fffaf1', padding: 10, minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
<span style={{ fontSize: 11, color: '#8a5b1f', fontWeight: 800 }}>{action.priority}</span>
<span style={{ fontSize: 12, color: '#141413', fontWeight: 700, ...textWrap }}>{action.title}</span>
</div>
<p style={{ fontSize: 11, color: '#5d5036', lineHeight: 1.45, margin: '7px 0 0', ...textWrap }}>
{t('requiredEvidenceLabel')}{action.required_evidence}
</p>
</div>
))}
</div>
</details>
) : null}
</div>
<details

View File

@@ -84,12 +84,12 @@ spec:
- name: AWOOOI_BUILD_COMMIT_SHA
# 2026-06-29 Codex: CD rewrites this to the deployed image tag so
# production deploy readback does not rely on a stale static snapshot.
value: "3b1c659f14c82eb6128cb234e3aab2744c5034fa"
value: "a049c909e270a225300b6780f079472c47ec149d"
- name: AWOOOI_DESIRED_API_IMAGE_TAG
# 2026-06-30 Codex: CD rewrites this alongside AWOOOI_BUILD_COMMIT_SHA.
# Production readback compares runtime image truth against this
# GitOps desired tag instead of doing a slow Gitea raw fetch.
value: "3b1c659f14c82eb6128cb234e3aab2744c5034fa"
value: "a049c909e270a225300b6780f079472c47ec149d"
- name: DATABASE_POOL_SIZE
# 2026-07-01 Codex: production role `awoooi` currently has a low
# connection limit. Keep API pool conservative until DB role

View File

@@ -41,7 +41,7 @@ resources:
images:
- name: 192.168.0.110:5000/library/api:IMAGE_TAG_PLACEHOLDER
newName: 192.168.0.110:5000/awoooi/api
newTag: 3b1c659f14c82eb6128cb234e3aab2744c5034fa
newTag: a049c909e270a225300b6780f079472c47ec149d
- name: 192.168.0.110:5000/library/web:IMAGE_TAG_PLACEHOLDER
newName: 192.168.0.110:5000/awoooi/web
newTag: 3b1c659f14c82eb6128cb234e3aab2744c5034fa
newTag: a049c909e270a225300b6780f079472c47ec149d