feat(web): 顯示 Backup DR 治理證據
Some checks failed
CD Pipeline / build-and-deploy (push) Has been cancelled
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / tests (push) Has been cancelled
Code Review / ai-code-review (push) Has been cancelled

This commit is contained in:
Your Name
2026-06-05 00:30:01 +08:00
parent 5dd274253d
commit b54477fdb6
8 changed files with 487 additions and 29 deletions

View File

@@ -18,11 +18,11 @@ def test_ai_agent_automation_backlog_snapshot_endpoint_returns_committed_snapsho
assert data["schema_version"] == "ai_agent_automation_backlog_v1"
assert data["program_status"]["overall_completion_percent"] == 100
assert data["program_status"]["read_only_mode"] is True
assert data["program_status"]["current_task_id"] == "P1-103"
assert data["program_status"]["next_task_id"] == "P1-104"
assert data["rollups"]["total_items"] == len(data["backlog_items"]) == 18
assert data["rollups"]["by_priority"]["P1"] == 16
assert data["rollups"]["by_status"]["done"] == 11
assert data["program_status"]["current_task_id"] == "P1-104"
assert data["program_status"]["next_task_id"] == "P1-105"
assert data["rollups"]["total_items"] == len(data["backlog_items"]) == 19
assert data["rollups"]["by_priority"]["P1"] == 17
assert data["rollups"]["by_status"]["done"] == 12
assert data["approval_boundaries"]["sdk_installation_allowed"] is False
assert data["approval_boundaries"]["paid_api_call_allowed"] is False
assert data["approval_boundaries"]["production_routing_allowed"] is False
@@ -30,4 +30,5 @@ def test_ai_agent_automation_backlog_snapshot_endpoint_returns_committed_snapsho
assert any(item["item_id"] == "AUTO-P1-205" for item in data["backlog_items"])
assert any(item["item_id"] == "AUTO-P1-206" for item in data["backlog_items"])
assert any(item["item_id"] == "AUTO-P1-103" for item in data["backlog_items"])
assert any(item["item_id"] == "AUTO-P1-104" for item in data["backlog_items"])
assert any(item["item_id"] == "AUTO-P3-001" for item in data["backlog_items"])

View File

@@ -18,8 +18,8 @@ def test_ai_agent_automation_inventory_snapshot_endpoint_returns_committed_snaps
assert data["schema_version"] == "ai_agent_automation_inventory_snapshot_v1"
assert data["program_status"]["overall_completion_percent"] == 100
assert data["program_status"]["read_only_mode"] is True
assert data["program_status"]["current_task_id"] == "P1-103"
assert data["program_status"]["next_task_id"] == "P1-104"
assert data["program_status"]["current_task_id"] == "P1-104"
assert data["program_status"]["next_task_id"] == "P1-105"
assert data["approval_boundaries"]["sdk_installation_allowed"] is False
assert data["approval_boundaries"]["paid_api_call_allowed"] is False
assert data["approval_boundaries"]["production_routing_allowed"] is False
@@ -28,6 +28,7 @@ def test_ai_agent_automation_inventory_snapshot_endpoint_returns_committed_snaps
assert any(task["task_id"] == "P1-205" for task in data["tasks"])
assert any(task["task_id"] == "P1-206" for task in data["tasks"])
assert any(task["task_id"] == "P1-103" for task in data["tasks"])
assert any(task["task_id"] == "P1-104" for task in data["tasks"])
assert any(evidence["evidence_id"] == "dependency_risk_policy_api" for evidence in data["evidence"])
assert any(evidence["evidence_id"] == "dependency_drift_check_plan_api" for evidence in data["evidence"])
assert any(
@@ -35,3 +36,4 @@ def test_ai_agent_automation_inventory_snapshot_endpoint_returns_committed_snaps
for evidence in data["evidence"]
)
assert any(evidence["evidence_id"] == "backup_notification_policy_api" for evidence in data["evidence"])
assert any(evidence["evidence_id"] == "backup_dr_evidence_ui" for evidence in data["evidence"])

View File

@@ -9,7 +9,20 @@
*/
import { useEffect, useMemo, useState, type ReactNode } from 'react'
import { AlertTriangle, Boxes, Database, Lock, PackageCheck, RefreshCw, Server, ShieldCheck } from 'lucide-react'
import {
AlertTriangle,
Archive,
BellOff,
BellRing,
Boxes,
Database,
HardDrive,
Lock,
PackageCheck,
RefreshCw,
Server,
ShieldCheck,
} from 'lucide-react'
import { useTranslations } from 'next-intl'
import { GlassCard } from '@/components/ui/glass-card'
import { StatusOrb } from '@/components/ui/status-orb'
@@ -17,6 +30,9 @@ import {
apiClient,
type AiAgentAutomationBacklogSnapshot,
type AiAgentAutomationInventorySnapshot,
type BackupDrReadinessMatrixSnapshot,
type BackupDrTargetInventorySnapshot,
type BackupNotificationPolicySnapshot,
} from '@/lib/api-client'
function formatDateTime(value: string): string {
@@ -75,6 +91,13 @@ function Chip({ value, muted = false }: { value: string; muted?: boolean }) {
)
}
function evidenceTone(value: string): 'ok' | 'warn' | 'danger' | 'neutral' {
if (value === 'ready' || value === 'verified' || value === 'active') return 'ok'
if (value === 'action_required' || value === 'approval_required' || value === 'needs_metric_binding') return 'warn'
if (value === 'blocked' || value === 'failed') return 'danger'
return 'neutral'
}
function MetricCard({
label,
value,
@@ -177,6 +200,9 @@ export function AutomationInventoryTab() {
const t = useTranslations('governance.automationInventory')
const [snapshot, setSnapshot] = useState<AiAgentAutomationInventorySnapshot | null>(null)
const [backlog, setBacklog] = useState<AiAgentAutomationBacklogSnapshot | null>(null)
const [backupTargets, setBackupTargets] = useState<BackupDrTargetInventorySnapshot | null>(null)
const [backupReadiness, setBackupReadiness] = useState<BackupDrReadinessMatrixSnapshot | null>(null)
const [backupPolicy, setBackupPolicy] = useState<BackupNotificationPolicySnapshot | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(false)
@@ -185,10 +211,16 @@ export function AutomationInventoryTab() {
Promise.all([
apiClient.getAiAgentAutomationInventorySnapshot(),
apiClient.getAiAgentAutomationBacklogSnapshot(),
apiClient.getBackupDrTargetInventory(),
apiClient.getBackupDrReadinessMatrix(),
apiClient.getBackupNotificationPolicy(),
])
.then(([inventoryData, backlogData]) => {
.then(([inventoryData, backlogData, targetData, readinessData, policyData]) => {
setSnapshot(inventoryData)
setBacklog(backlogData)
setBackupTargets(targetData)
setBackupReadiness(readinessData)
setBackupPolicy(policyData)
setError(false)
})
.catch(() => setError(true))
@@ -224,6 +256,32 @@ export function AutomationInventoryTab() {
.filter(group => group.items.length > 0)
}, [backlog])
const visibleReadinessRows = useMemo(() => {
if (!backupReadiness) return []
const priority = { blocked: 0, action_required: 1, deferred: 2, ready: 3 } as Record<string, number>
return [...backupReadiness.readiness_rows]
.sort((a, b) => {
const left = priority[a.overall_readiness] ?? 4
const right = priority[b.overall_readiness] ?? 4
if (left !== right) return left - right
return a.target_id.localeCompare(b.target_id)
})
.slice(0, 8)
}, [backupReadiness])
const visibleBackupTargets = useMemo(() => {
if (!backupTargets) return []
const priority = { critical: 0, high: 1, medium: 2, low: 3 } as Record<string, number>
return [...backupTargets.backup_targets]
.sort((a, b) => {
const left = priority[a.risk_level] ?? 4
const right = priority[b.risk_level] ?? 4
if (left !== right) return left - right
return a.target_id.localeCompare(b.target_id)
})
.slice(0, 6)
}, [backupTargets])
if (loading) {
return (
<div style={{ padding: 20, display: 'grid', gridTemplateColumns: 'repeat(4, minmax(0, 1fr))', gap: 12 }} className="automation-inventory-kpi-grid">
@@ -237,7 +295,7 @@ export function AutomationInventoryTab() {
)
}
if (error || !snapshot || !backlog) {
if (error || !snapshot || !backlog || !backupTargets || !backupReadiness || !backupPolicy) {
return (
<div style={{ padding: 20 }}>
<GlassCard variant="subtle" padding="lg">
@@ -275,10 +333,23 @@ export function AutomationInventoryTab() {
const criticalAssets = snapshot.assets.filter(asset => asset.risk_level === 'critical').length
const completedTasks = snapshot.tasks.filter(task => task.status === 'done').length
const p1BacklogCount = backlog.rollups.by_priority.P1 ?? 0
const readyBackupRows = backupReadiness.rollups.by_overall_readiness.ready ?? 0
const actionRequiredBackupRows = backupReadiness.rollups.by_overall_readiness.action_required ?? 0
const blockedBackupRows = backupReadiness.rollups.by_overall_readiness.blocked ?? 0
const suppressedSuccessRules = backupPolicy.rollups.by_decision.suppress_immediate_success ?? 0
const immediateEscalationRules = backupPolicy.rollups.by_decision.escalate_immediate ?? 0
const blockedApprovals = Object.entries(snapshot.approval_boundaries)
.filter(([, allowed]) => allowed === false)
.map(([key]) => key)
const statusLabel = (value: string) => {
try {
return t(`backupEvidence.statuses.${value}` as never)
} catch {
return value
}
}
return (
<div style={{ padding: 20, display: 'flex', flexDirection: 'column', gap: 16 }}>
<GlassCard variant="subtle" padding="md">
@@ -450,6 +521,125 @@ export function AutomationInventoryTab() {
</div>
</GlassCard>
<GlassCard variant="subtle" padding="md">
<div style={{ display: 'flex', flexDirection: 'column', gap: 13, minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, flexWrap: 'wrap' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 7, minWidth: 0 }}>
<Archive size={14} style={{ color: '#d97757' }} />
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 13, fontWeight: 700, color: '#141413' }}>
{t('backupEvidence.title')}
</span>
</div>
<div style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#87867f' }}>
{t('backupEvidence.source', {
targets: formatDateTime(backupTargets.generated_at),
readiness: formatDateTime(backupReadiness.generated_at),
policy: formatDateTime(backupPolicy.generated_at),
})}
</div>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(6, minmax(0, 1fr))', gap: 12 }} className="automation-inventory-backup-kpi-grid">
<MetricCard label={t('backupEvidence.metrics.targets')} value={backupTargets.rollups.total_targets} icon={<HardDrive size={16} />} />
<MetricCard label={t('backupEvidence.metrics.ready')} value={readyBackupRows} tone="ok" icon={<ShieldCheck size={16} />} />
<MetricCard label={t('backupEvidence.metrics.actionRequired')} value={actionRequiredBackupRows} tone="warn" icon={<AlertTriangle size={16} />} />
<MetricCard label={t('backupEvidence.metrics.blocked')} value={blockedBackupRows} tone={blockedBackupRows > 0 ? 'danger' : 'ok'} icon={<Lock size={16} />} />
<MetricCard label={t('backupEvidence.metrics.successSuppressed')} value={suppressedSuccessRules} tone="ok" icon={<BellOff size={16} />} />
<MetricCard label={t('backupEvidence.metrics.immediateEscalations')} value={immediateEscalationRules} tone="warn" icon={<BellRing size={16} />} />
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1.35fr) minmax(0, 0.65fr)', gap: 12 }} className="automation-inventory-backup-evidence-grid">
<div style={{ padding: 12, border: '0.5px solid #e0ddd4', borderRadius: 7, background: '#fff', display: 'flex', flexDirection: 'column', gap: 11, minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 13, fontWeight: 700, color: '#141413' }}>
{t('backupEvidence.readinessTitle')}
</span>
<Chip value={backupReadiness.program_status.current_task_id} muted />
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 10 }} className="automation-inventory-backup-readiness-grid">
{visibleReadinessRows.map(row => (
<div key={row.target_id} style={{ padding: 11, border: '0.5px solid #e0ddd4', borderRadius: 7, background: '#faf9f3', minWidth: 0 }}>
<div style={{ display: 'flex', flexDirection: 'column', gap: 8, minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8, minWidth: 0 }}>
<span style={{
fontFamily: 'Syne, sans-serif',
fontSize: 12,
fontWeight: 700,
color: '#141413',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}}>
{row.display_name}
</span>
<Chip value={statusLabel(row.overall_readiness)} muted={row.overall_readiness === 'ready'} />
</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, minWidth: 0 }}>
<Chip value={`${t('backupEvidence.labels.freshness')}: ${statusLabel(row.freshness_status)}`} muted={evidenceTone(row.freshness_status) === 'ok'} />
<Chip value={`${t('backupEvidence.labels.integrity')}: ${statusLabel(row.integrity_status)}`} muted={evidenceTone(row.integrity_status) === 'ok'} />
<Chip value={`${t('backupEvidence.labels.restore')}: ${statusLabel(row.restore_drill_status)}`} muted={evidenceTone(row.restore_drill_status) === 'ok'} />
<Chip value={`${t('backupEvidence.labels.offsite')}: ${statusLabel(row.offsite_status)}`} muted={evidenceTone(row.offsite_status) === 'ok'} />
</div>
<div style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#87867f', lineHeight: 1.45, overflowWrap: 'anywhere' }}>
{row.blocker_summary || t('backupEvidence.noBlocker')}
</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, minWidth: 0 }}>
<Chip value={row.gate_status ? statusLabel(row.gate_status) : '--'} muted />
<Chip value={row.evidence_refs[0] ?? t('backupEvidence.noEvidence')} muted />
</div>
</div>
</div>
))}
</div>
</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 12, minWidth: 0 }}>
<div style={{ padding: 12, border: '0.5px solid #e0ddd4', borderRadius: 7, background: '#fff', display: 'flex', flexDirection: 'column', gap: 10, minWidth: 0 }}>
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 13, fontWeight: 700, color: '#141413' }}>
{t('backupEvidence.policyTitle')}
</span>
{backupPolicy.policy_rules.slice(0, 5).map(rule => (
<div key={rule.rule_id} style={{ display: 'flex', flexDirection: 'column', gap: 6, minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
<span style={{ fontFamily: "'DM Mono', monospace", fontSize: 11, fontWeight: 700, color: '#141413', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
{rule.rule_id}
</span>
<Chip value={statusLabel(rule.decision)} muted={rule.decision === 'suppress_immediate_success'} />
</div>
<div style={{ fontFamily: "'DM Mono', monospace", fontSize: 10, color: '#87867f', lineHeight: 1.45, overflowWrap: 'anywhere' }}>
{rule.message_contract}
</div>
</div>
))}
</div>
<div style={{ padding: 12, border: '0.5px solid #e0ddd4', borderRadius: 7, background: '#fff', display: 'flex', flexDirection: 'column', gap: 10, minWidth: 0 }}>
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 13, fontWeight: 700, color: '#141413' }}>
{t('backupEvidence.targetsTitle')}
</span>
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
{visibleBackupTargets.map(target => (
<div key={target.target_id} style={{ display: 'flex', flexDirection: 'column', gap: 6, minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
<span style={{ fontFamily: 'Syne, sans-serif', fontSize: 12, fontWeight: 700, color: '#141413', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
{target.display_name}
</span>
<Chip value={target.rpo} muted />
</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
<Chip value={target.risk_level} />
<Chip value={target.owner_host} muted />
<Chip value={target.primary_script} muted />
</div>
</div>
))}
</div>
</div>
</div>
</div>
</div>
</GlassCard>
<div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1.2fr) minmax(0, 0.8fr)', gap: 12 }} className="automation-inventory-bottom-grid">
<GlassCard variant="subtle" padding="md">
<div style={{ display: 'flex', flexDirection: 'column', gap: 13, minWidth: 0 }}>
@@ -511,6 +701,9 @@ export function AutomationInventoryTab() {
.automation-inventory-workstream-grid,
.automation-inventory-domain-grid,
.automation-inventory-backlog-grid,
.automation-inventory-backup-kpi-grid,
.automation-inventory-backup-evidence-grid,
.automation-inventory-backup-readiness-grid,
.automation-inventory-bottom-grid,
.automation-inventory-task-grid {
grid-template-columns: 1fr !important;

View File

@@ -261,6 +261,21 @@ export const apiClient = {
const res = await fetch(`${API_BASE_URL}/agents/automation-backlog-snapshot`)
return handleResponse<AiAgentAutomationBacklogSnapshot>(res)
},
async getBackupDrTargetInventory() {
const res = await fetch(`${API_BASE_URL}/agents/backup-dr-target-inventory`)
return handleResponse<BackupDrTargetInventorySnapshot>(res)
},
async getBackupDrReadinessMatrix() {
const res = await fetch(`${API_BASE_URL}/agents/backup-dr-readiness-matrix`)
return handleResponse<BackupDrReadinessMatrixSnapshot>(res)
},
async getBackupNotificationPolicy() {
const res = await fetch(`${API_BASE_URL}/agents/backup-notification-policy`)
return handleResponse<BackupNotificationPolicySnapshot>(res)
},
}
// =========================================================================
@@ -721,3 +736,126 @@ export interface AiAgentAutomationBacklogSnapshot {
false
>
}
export interface BackupDrTargetInventorySnapshot {
schema_version: 'backup_dr_target_inventory_v1'
generated_at: string
source_refs: string[]
program_status: {
overall_completion_percent: number
current_priority: 'P0' | 'P1' | 'P2' | 'P3'
current_task_id: string
next_task_id: string
read_only_mode: true
}
rollups: {
total_targets: number
by_status: Record<string, number>
by_target_type: Record<string, number>
by_gate_status: Record<string, number>
blocked_target_ids: string[]
}
backup_targets: Array<{
target_id: string
display_name: string
target_type: string
status: string
risk_level: 'low' | 'medium' | 'high' | 'critical'
owner_host: string
primary_script: string
schedule: string
rpo: string
storage_class: string
storage_ref: string
offsite_policy: string
automation_gate_status: string
restore_gate_status: string
secret_policy: string
evidence_refs: string[]
next_action: string
}>
approval_boundaries: Record<string, false>
operation_boundaries: Record<string, boolean>
}
export interface BackupDrReadinessMatrixSnapshot {
schema_version: 'backup_dr_readiness_matrix_v1'
generated_at: string
source_target_inventory_ref: string
source_refs: string[]
program_status: {
overall_completion_percent: number
current_priority: 'P0' | 'P1' | 'P2' | 'P3'
current_task_id: string
next_task_id: string
read_only_mode: true
}
rollups: {
total_rows: number
by_overall_readiness: Record<string, number>
by_restore_drill_status: Record<string, number>
by_offsite_status: Record<string, number>
blocked_row_ids: string[]
action_required_row_ids: string[]
}
readiness_rows: Array<{
target_id: string
display_name: string
overall_readiness: string
freshness_status: string
integrity_status: string
restore_drill_status: string
offsite_status: string
notification_policy: string
gate_status: string
evidence_level: string
evidence_refs: string[]
blocker_summary: string
next_action: string
}>
approval_boundaries: Record<string, false>
operation_boundaries: Record<string, boolean>
}
export interface BackupNotificationPolicySnapshot {
schema_version: 'backup_notification_policy_v1'
generated_at: string
source_readiness_matrix_ref: string
source_refs: string[]
program_status: {
overall_completion_percent: number
current_priority: 'P0' | 'P1' | 'P2' | 'P3'
current_task_id: string
next_task_id: string
read_only_mode: true
}
rollups: {
total_rules: number
by_decision: Record<string, number>
immediate_escalation_rule_ids: string[]
suppressed_success_rule_ids: string[]
}
notification_channels: Array<{
channel_id: string
purpose: string
immediate_allowed: boolean
success_immediate_allowed: boolean
requires_operator_action: boolean
}>
policy_rules: Array<{
rule_id: string
event_kind: string
backup_state: string
severity: string
decision: string
channels: string[]
owner_agent: string
requires_incident: boolean
requires_approval_record: boolean
message_contract: string
evidence_refs: string[]
}>
daily_summary_contract: Record<string, unknown>
approval_boundaries: Record<string, false>
operation_boundaries: Record<string, boolean>
}

View File

@@ -686,6 +686,52 @@ API
- Backup notification policy service + API tests `9 passed`
- `py_compile` 通過。
### P1-104 Backup / DR 證據 UI 摘要
正式 UI
- `/zh-TW/governance?tab=automation-inventory`
接入只讀 API
- `GET /api/v1/agents/backup-dr-target-inventory`
- `GET /api/v1/agents/backup-dr-readiness-matrix`
- `GET /api/v1/agents/backup-notification-policy`
顯示內容:
- Backup / DR 目標:`17`
- Ready`12`
- 需處置:`2`
- 阻擋:`2`
- 成功即時抑制:`2`
- failure / warning / core blocker 立即升級:`4`
- 通知規則:`8`
核心裁決:
- UI 只顯示備份目標、readiness matrix、通知政策、關鍵 blocker 與 evidence ref。
- `configs_capture``credential_escrow_markers` 仍為 blocked不得宣稱 full DR green。
- `signoz` 顯示 disruptive backup guardAgent 不得任意觸發會短暫停止 collector 的備份。
- 成功備份仍不即時送 Telegram / AwoooP成功狀態由每日摘要與查詢承載。
- warning、failed、action-required、core blocker 才能進即時升級。
實作邊界:
- 不執行 backup。
- 不執行 restore。
- 不執行 offsite sync。
- 不寫 credential marker。
- 不改排程、不寫 workflow。
- 不發 Telegram 測試訊息。
驗證:
- `pnpm --filter @awoooi/web exec tsc --noEmit --tsBuildInfoFile /tmp/awoooi-p1-104-backup-evidence.tsbuildinfo` 通過。
- 本地 desktop `/zh-TW/governance?tab=automation-inventory&_v=p1-104-backup-evidence-local`Backup / DR 證據、準備度矩陣、通知政策、成功抑制、即時升級、Gitea 與 SignOz disruptive guard 可見;無載入錯誤;`horizontalOverflow=-6`
- 本地 390px mobile `/zh-TW/governance?tab=automation-inventory&_v=p1-104-backup-evidence-local`Backup / DR 證據、準備度矩陣、通知政策、成功抑制、即時升級、Gitea 與 SignOz disruptive guard 可見;無載入錯誤;`horizontalOverflow=-6`
- 截圖:`/tmp/awoooi-p1-104-backup-evidence-local-desktop.png``/tmp/awoooi-p1-104-backup-evidence-local-mobile.png`
### P0 - 治理與 Inventory 基礎
| ID | 狀態 | % | 負責 Agent | 任務 | 產出 | 關卡 |
@@ -718,7 +764,7 @@ API
| P1-101 | 完成 | 100 | Hermes | 把備份 runbook / 腳本轉成機器可讀目標盤點 | `docs/evaluations/backup_dr_target_inventory_2026-06-04.json` | 只讀 |
| P1-102 | 完成 | 100 | OpenClaw | 顯示備份新鮮度、完整性、復原演練狀態 | `docs/evaluations/backup_dr_readiness_matrix_2026-06-04.json` | 不執行 restore |
| P1-103 | 完成 | 100 | Hermes | 對齊備份通知政策 | `docs/evaluations/backup_notification_policy_2026-06-04.json` | 不發成功洗版 |
| P1-104 | 待辦 | 0 | OpenClaw | 在 AwoooP / governance UI 加備份證據 | 備份卡片 | 瀏覽器驗證 |
| P1-104 | 完成 | 100 | OpenClaw | 在 AwoooP / governance UI 加備份證據 | `/zh-TW/governance?tab=automation-inventory` | 只讀 + 瀏覽器驗證 |
| P1-105 | 待辦 | 0 | OpenClaw | 定義復原演練批准包 | 復原計畫範本 | 人工批准 |
| P1-106 | 待辦 | 0 | Hermes | 顯示異地 / escrow 準備度狀態 | DR 準備度區塊 | 不暴露 credential |
@@ -860,13 +906,24 @@ API
任何完成宣告前,必須同步更新本文件或後續生成的 JSON 快照。
本次同步:
```text
進度100%。
目前優先級P1。
目前任務P1-104 在 AwoooP / governance UI 加備份證據。
狀態變更:待辦 -> 完成。
證據typecheck 通過;本地 desktop 與 390px mobile governance automation-inventory tab 驗證 Backup / DR 證據、準備度矩陣、通知政策、成功抑制、即時升級、Gitea 與 SignOz disruptive guard 可見無載入錯誤horizontalOverflow <= 0。
阻擋backup、restore、offsite sync、credential marker、排程、workflow、Telegram 測試通知仍未批准。
下一步P1-105 定義復原演練批准包。
```
## 13. 立即執行順序
1. P1-104在 AwoooP / governance UI 加備份證據
2. P1-105定義復原演練批准包
3. P1-106顯示異地 / escrow 準備度狀態
4. P1-305 / P1-306補每個任務的批准邊界與進度彙總細節
5. P2 / P3 必須等 P1 可見且關卡穩定後再做。
1. P1-105定義復原演練批准包
2. P1-106顯示異地 / escrow 準備度狀態
3. P1-305 / P1-306補每個任務的批准邊界與進度彙總細節
4. P2 / P3 必須等 P1 可見且關卡穩定後再做
## 14. 目前風險

View File

@@ -5,30 +5,30 @@
"program_status": {
"overall_completion_percent": 100,
"current_priority": "P1",
"current_task_id": "P1-103",
"next_task_id": "P1-104",
"current_task_id": "P1-104",
"next_task_id": "P1-105",
"read_only_mode": true
},
"rollups": {
"total_items": 18,
"total_items": 19,
"by_priority": {
"P1": 16,
"P1": 17,
"P2": 1,
"P3": 1
},
"by_status": {
"planned": 7,
"done": 11
"done": 12
},
"by_gate_status": {
"read_only_allowed": 15,
"read_only_allowed": 16,
"production_change_blocked": 1,
"cost_approval_required": 1,
"blocked_by_evidence": 1
},
"by_owner_agent": {
"hermes": 10,
"openclaw": 7,
"openclaw": 8,
"nemotron": 1
}
},
@@ -280,6 +280,33 @@
],
"next_review": "P1-103"
},
{
"item_id": "AUTO-P1-104",
"priority": "P1",
"status": "done",
"workstream_id": "WS4",
"source_asset_id": "backup_dr_readiness_matrix",
"source_signal_kind": "ui_visibility_gap",
"title": "在 AwoooP / governance UI 加備份證據",
"owner_agent": "openclaw",
"recommended_action": "在 automation inventory tab 顯示 Backup / DR 目標、readiness matrix、通知政策、blocked / action-required 與 success-noise suppression 證據。",
"action_class": "execute_read_only",
"gate_status": "read_only_allowed",
"risk_level": "high",
"evidence_refs": [
"apps/web/src/app/[locale]/governance/tabs/automation-inventory-tab.tsx",
"GET /api/v1/agents/backup-dr-target-inventory",
"GET /api/v1/agents/backup-dr-readiness-matrix",
"GET /api/v1/agents/backup-notification-policy"
],
"acceptance_criteria": [
"顯示 Backup / DR 證據但不提供 backup、restore、offsite sync、credential marker、schedule 或 workflow 操作",
"顯示 ready、action-required、blocked、success suppressed 與 immediate escalation rollup",
"desktop 與 390px mobile 無橫向溢出",
"成功備份仍不得即時送 Telegram / AwoooP 洗版"
],
"next_review": "P1-104"
},
{
"item_id": "AUTO-P1-201",
"priority": "P1",

View File

@@ -4,8 +4,8 @@
"program_status": {
"overall_completion_percent": 100,
"current_priority": "P1",
"current_task_id": "P1-103",
"next_task_id": "P1-104",
"current_task_id": "P1-104",
"next_task_id": "P1-105",
"read_only_mode": true
},
"status_taxonomy": {
@@ -425,7 +425,7 @@
"display_name": "備份與 DR 自動化",
"completion_percent": 67,
"status": "in_progress",
"next_task_id": "P1-104"
"next_task_id": "P1-105"
},
{
"workstream_id": "WS5",
@@ -451,9 +451,9 @@
{
"workstream_id": "WS8",
"display_name": "產品 UI",
"completion_percent": 75,
"completion_percent": 82,
"status": "in_progress",
"next_task_id": "P1-104"
"next_task_id": "P1-305"
}
],
"tasks": [
@@ -620,7 +620,18 @@
"title": "對齊備份通知政策",
"output": "docs/evaluations/backup_notification_policy_2026-06-04.json",
"gate_status": "read_only_allowed",
"next_action": "完成P1-104 在 AwoooP / governance UI 加備份證據。"
"next_action": "完成P1-104 Backup / DR 證據 UI 已推進。"
},
{
"task_id": "P1-104",
"priority": "P1",
"status": "done",
"completion_percent": 100,
"owner_agent": "openclaw",
"title": "在 AwoooP / governance UI 加備份證據",
"output": "/zh-TW/governance?tab=automation-inventory",
"gate_status": "read_only_allowed",
"next_action": "完成P1-105 定義復原演練批准包。"
},
{
"task_id": "P1-201",
@@ -810,6 +821,12 @@
"ref": "GET /api/v1/agents/backup-notification-policy",
"result": "備份通知政策只讀 API 已新增,不送通知、不執行備份/restore/offsite sync、不寫 credential marker、不改排程或 workflow。"
},
{
"evidence_id": "backup_dr_evidence_ui",
"kind": "browser",
"ref": "/zh-TW/governance?tab=automation-inventory",
"result": "P1-104 Backup / DR 證據 UI 已接入 automation inventory tab本地 desktop 與 390px mobile 驗證 Backup / DR 證據、準備度矩陣、通知政策、成功抑制、即時升級、Gitea 與 SignOz disruptive guard 可見無載入錯誤horizontalOverflow <= 0。"
},
{
"evidence_id": "package_supply_chain_inventory_schema",
"kind": "schema",

View File

@@ -3370,3 +3370,26 @@ Phase 6 完成後
- P1-103備份通知政策已完成schema 位於 `docs/schemas/backup_notification_policy_v1.schema.json`,快照位於 `docs/evaluations/backup_notification_policy_2026-06-04.json`API 為 `GET /api/v1/agents/backup-notification-policy`8 條規則中 2 條成功即時抑制、4 條 immediate escalation、2 條 action-required每日成功摘要由 06:05 台北時間承載。
**裁決:** P0 基礎已完成P1 產品面已接上分組 UIBackup / DR 目標盤點、準備度矩陣、備份通知政策與 WS5 套件 / 供應鏈自動化已進入只讀 API 並達 `100%`。下一輪推進必須從 P1-104 備份證據 UI 開始,保持只讀;不得執行 restore、不得寫 credential marker、不得送 Telegram / AwoooP 測試通知、不得安裝依賴、不得升級套件、不得寫 lockfile、不得查外部 CVE、不得查外部 license、不得查外部 registry 或 Agent market 來源、不得啟用排程、不得寫 workflow、不得執行 npm audit、不得執行 pnpm install、不得執行 docker build、不得 pull image、不得重建 image、不得 push registry、不得新增 SDK、不得呼叫付費 API、不得改生產路由、不得把任何 Agent 推入 shadow/canary。
### 2026-06-05 凌晨 (台北) — P1-104 Backup / DR 證據 UI 完成
**觸發**:統帥批准繼續,要求持續更新工作清單、完成度與工作狀態,並記得推版到正式環境。
**已推進:**
- P1-104AwoooP / governance automation inventory tab 已接入 Backup / DR 證據區塊,讀取 `GET /api/v1/agents/backup-dr-target-inventory``GET /api/v1/agents/backup-dr-readiness-matrix``GET /api/v1/agents/backup-notification-policy`
- UI 顯示 Backup / DR 目標 `17`、ready `12`、action_required `2`、blocked `2`、成功即時抑制 `2`、immediate escalation `4`,並顯示準備度矩陣、通知政策與關鍵備份目標。
- `docs/evaluations/ai_agent_automation_inventory_snapshot_2026-06-04_static_seed.json` 已將 `current_task_id` 推進到 `P1-104``next_task_id` 推進到 `P1-105`;產品 UI workstream 由 `75%` 推進到 `82%`
- `docs/evaluations/ai_agent_automation_backlog_2026-06-04.json` 新增 `AUTO-P1-104` done itemrollup 更新為 total `19`、P1 `17`、done `12`、read_only_allowed `16`、OpenClaw owner `8`
- `docs/ai/AI_AGENT_AUTOMATION_WORKLIST_2026-06-04.md` 已新增 P1-104 摘要、進度同步紀錄與下一步順序。
**驗證:**
- `pnpm --filter @awoooi/web exec tsc --noEmit --tsBuildInfoFile /tmp/awoooi-p1-104-backup-evidence.tsbuildinfo` 通過。
- 本地 desktop `/zh-TW/governance?tab=automation-inventory&_v=p1-104-backup-evidence-local`Backup / DR 證據、準備度矩陣、通知政策、成功抑制、即時升級、Gitea 與 SignOz disruptive guard 可見;無載入錯誤;`horizontalOverflow=-6`
- 本地 390px mobile `/zh-TW/governance?tab=automation-inventory&_v=p1-104-backup-evidence-local`Backup / DR 證據、準備度矩陣、通知政策、成功抑制、即時升級、Gitea 與 SignOz disruptive guard 可見;無載入錯誤;`horizontalOverflow=-6`
**下一步:**
1. P1-105定義復原演練批准包。
2. P1-106顯示異地 / escrow 準備度狀態。
3. P1-305 / P1-306補任務批准邊界與進度彙總細節。
**裁決:** P1-104 已完成,但仍只屬於 read-only evidence surface。不得執行 backup、restore、offsite sync、credential marker 寫入、排程變更、workflow 寫入或 Telegram 測試通知;不得把 Backup / DR UI 可見解讀成 full DR green。下一步只能產生復原演練與 escrow review 的批准包,必須保留 OpenClaw 仲裁與人工批准邊界。