feat(web): surface reboot drill preflight
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 21s
CD Pipeline / build-and-deploy (push) Successful in 4m48s
CD Pipeline / post-deploy-checks (push) Has been cancelled

This commit is contained in:
Your Name
2026-06-30 00:34:18 +08:00
parent 308ad4c602
commit e7482c402a
5 changed files with 472 additions and 13 deletions

View File

@@ -106,7 +106,8 @@
"1": { "error": "Gitea private inventory 資料未回讀" },
"2": { "error": "Gitea / runner 資料未回讀" },
"3": { "error": "Runtime surface 資料未回讀" },
"4": { "error": "Backup readiness 資料未回讀" }
"4": { "error": "Backup readiness 資料未回讀" },
"5": { "error": "P0-006 reboot drill preflight 資料未回讀" }
},
"metrics": {
"loaded": "資料來源",
@@ -123,9 +124,35 @@
"lanesDetail": "每張卡只回答完成度、阻擋數、下一步入口。",
"next": "下一步焦點",
"nextDetail": "只列需要處理的主線,不列文件清單。",
"drill": "P0-006 reboot drill preflight",
"drillDetail": "fresh all-host reboot window 仍是唯一 blocker這裡只顯示預檢、驗證與硬邊界。",
"boundary": "保留硬邊界",
"boundaryDetail": "這些仍需明確授權,但不得阻擋低風險 coding / UI / test。"
},
"drill": {
"title": "10 分鐘恢復 SLO 預檢",
"detail": "目前 blocker{blocker}。",
"unavailable": "P0-006 drill preflight 尚未回讀。",
"operations": "Operations",
"metrics": {
"preflight": "預檢狀態",
"target": "目標主機",
"readiness": "SLO readiness",
"execution": "本端點執行"
},
"values": {
"ready": "ready",
"blocked": "blocked",
"enabled": "enabled",
"disabled": "disabled"
},
"flags": {
"breakGlass": "break-glass required: {value}",
"hostReboot": "host reboot performed: {value}",
"runtimeWrite": "runtime write: {value}",
"secret": "secret read: {value}"
}
},
"lanes": {
"release": {
"title": "乾淨 release 工作流",
@@ -147,6 +174,16 @@
"description": "確認 P0 dev/prod baseline source 已齊,讓後續 workflow template apply gate 有真實來源。",
"metric": "source {present}/{required}"
},
"reboot_auto_recovery": {
"title": "P0-006 reboot recovery SLO",
"description": "服務、資料與備份已綠燈;剩下 fresh all-host reboot window 或另行批准 drill 才能證明 10 分鐘恢復。",
"metric": "hosts {hosts} · stale {stale} · Stock {freshness}"
},
"credential_escrow": {
"title": "P0-005 credential escrow",
"description": "已收斂 non-secret evidence refs 與 reviewer acceptance readback不寫 credential marker、不收 secret。",
"metric": "evidence {accepted}/{required}"
},
"gitea": {
"title": "Gitea / CI-CD",
"description": "確認 workflow、runner label、通知與 dev / prod 發版線是真實可跑。",

View File

@@ -106,7 +106,8 @@
"1": { "error": "Gitea private inventory 資料未回讀" },
"2": { "error": "Gitea / runner 資料未回讀" },
"3": { "error": "Runtime surface 資料未回讀" },
"4": { "error": "Backup readiness 資料未回讀" }
"4": { "error": "Backup readiness 資料未回讀" },
"5": { "error": "P0-006 reboot drill preflight 資料未回讀" }
},
"metrics": {
"loaded": "資料來源",
@@ -123,9 +124,35 @@
"lanesDetail": "每張卡只回答完成度、阻擋數、下一步入口。",
"next": "下一步焦點",
"nextDetail": "只列需要處理的主線,不列文件清單。",
"drill": "P0-006 reboot drill preflight",
"drillDetail": "fresh all-host reboot window 仍是唯一 blocker這裡只顯示預檢、驗證與硬邊界。",
"boundary": "保留硬邊界",
"boundaryDetail": "這些仍需明確授權,但不得阻擋低風險 coding / UI / test。"
},
"drill": {
"title": "10 分鐘恢復 SLO 預檢",
"detail": "目前 blocker{blocker}。",
"unavailable": "P0-006 drill preflight 尚未回讀。",
"operations": "Operations",
"metrics": {
"preflight": "預檢狀態",
"target": "目標主機",
"readiness": "SLO readiness",
"execution": "本端點執行"
},
"values": {
"ready": "ready",
"blocked": "blocked",
"enabled": "enabled",
"disabled": "disabled"
},
"flags": {
"breakGlass": "break-glass required: {value}",
"hostReboot": "host reboot performed: {value}",
"runtimeWrite": "runtime write: {value}",
"secret": "secret read: {value}"
}
},
"lanes": {
"release": {
"title": "乾淨 release 工作流",
@@ -147,6 +174,16 @@
"description": "確認 P0 dev/prod baseline source 已齊,讓後續 workflow template apply gate 有真實來源。",
"metric": "source {present}/{required}"
},
"reboot_auto_recovery": {
"title": "P0-006 reboot recovery SLO",
"description": "服務、資料與備份已綠燈;剩下 fresh all-host reboot window 或另行批准 drill 才能證明 10 分鐘恢復。",
"metric": "hosts {hosts} · stale {stale} · Stock {freshness}"
},
"credential_escrow": {
"title": "P0-005 credential escrow",
"description": "已收斂 non-secret evidence refs 與 reviewer acceptance readback不寫 credential marker、不收 secret。",
"metric": "evidence {accepted}/{required}"
},
"gitea": {
"title": "Gitea / CI-CD",
"description": "確認 workflow、runner label、通知與 dev / prod 發版線是真實可跑。",

View File

@@ -13,6 +13,7 @@ import {
RefreshCw,
Rocket,
Server,
ShieldCheck,
} from 'lucide-react'
import { AppLayout } from '@/components/layout'
import { GlassCard } from '@/components/ui/glass-card'
@@ -22,11 +23,13 @@ import {
type BackupDrReadinessMatrixSnapshot,
type DeliveryClosureWorkbenchSnapshot,
type GiteaPrivateInventoryP0ScorecardSnapshot,
type RebootAutoRecoveryDrillPreflightSnapshot,
type GiteaWorkflowRunnerHealthSnapshot,
type RuntimeSurfaceInventorySnapshot,
} from '@/lib/api-client'
type DeliveryTone = 'ok' | 'warn' | 'danger' | 'neutral'
type DeliveryTranslator = ReturnType<typeof useTranslations>
interface DeliveryData {
statusCleanup: AwoooIStatusCleanupDashboardSnapshot | null
@@ -50,7 +53,7 @@ interface DeliveryLane {
Icon: typeof Rocket
}
const SOURCE_COUNT = 5
const SOURCE_COUNT = 6
const EMPTY_DATA: DeliveryData = {
statusCleanup: null,
@@ -161,6 +164,94 @@ function MetricTile({
)
}
function DrillPreflightPanel({
preflight,
locale,
t,
}: {
preflight: RebootAutoRecoveryDrillPreflightSnapshot | null
locale: string
t: DeliveryTranslator
}) {
if (!preflight) {
return (
<section className="delivery-drill-panel" data-testid="delivery-reboot-drill-preflight">
<GlassCard variant="subtle" padding="md">
<div className="delivery-drill-empty">
<ShieldCheck size={18} aria-hidden="true" />
<span>{t('drill.unavailable')}</span>
</div>
</GlassCard>
</section>
)
}
const readiness = clampPercent(preflight.current_readback.readiness_percent)
const gateTone: DeliveryTone = preflight.execution_authorized_by_this_endpoint ? 'danger' : preflight.preflight_ready ? 'warn' : 'danger'
const boundaryTone: DeliveryTone = preflight.execution_authorized_by_this_endpoint ? 'danger' : 'ok'
const targetHosts = preflight.target_selector.required_host_aliases.join(' / ')
const activeBlocker = preflight.current_readback.active_blockers[0] ?? preflight.status
return (
<section className="delivery-drill-panel" data-testid="delivery-reboot-drill-preflight">
<div className="delivery-section-heading">
<div>
<h2>{t('sections.drill')}</h2>
<p>{t('sections.drillDetail')}</p>
</div>
<StatusPill tone={gateTone} label={preflight.status} />
</div>
<GlassCard variant="default" padding="md">
<div className="delivery-drill-grid">
<div className="delivery-drill-primary">
<div className="delivery-drill-icon">
<ShieldCheck size={22} aria-hidden="true" />
</div>
<div style={{ minWidth: 0 }}>
<h3>{t('drill.title')}</h3>
<p>{t('drill.detail', { blocker: activeBlocker })}</p>
</div>
</div>
<div className="delivery-drill-metrics">
<div>
<span>{t('drill.metrics.preflight')}</span>
<strong>{preflight.preflight_ready ? t('drill.values.ready') : t('drill.values.blocked')}</strong>
</div>
<div>
<span>{t('drill.metrics.target')}</span>
<strong>{targetHosts}</strong>
</div>
<div>
<span>{t('drill.metrics.readiness')}</span>
<strong>{readiness}%</strong>
</div>
<div>
<span>{t('drill.metrics.execution')}</span>
<strong>{preflight.execution_authorized_by_this_endpoint ? t('drill.values.enabled') : t('drill.values.disabled')}</strong>
</div>
</div>
<div className="delivery-drill-boundaries">
<StatusPill tone={gateTone} label={t('drill.flags.breakGlass', { value: String(preflight.break_glass_authorization_required) })} />
<StatusPill tone={boundaryTone} label={t('drill.flags.hostReboot', { value: String(preflight.operation_boundaries.host_reboot_performed) })} />
<StatusPill tone={boundaryTone} label={t('drill.flags.runtimeWrite', { value: String(preflight.operation_boundaries.runtime_write_allowed) })} />
<StatusPill tone={boundaryTone} label={t('drill.flags.secret', { value: String(preflight.operation_boundaries.secret_value_collection_allowed) })} />
</div>
<div className="delivery-drill-footer">
<span>{preflight.safe_next_step}</span>
<Link href={`/${locale}/operations`}>
<ArrowRight size={15} aria-hidden="true" />
<span>{t('drill.operations')}</span>
</Link>
</div>
</div>
</GlassCard>
</section>
)
}
function LaneCard({ lane, locale }: { lane: DeliveryLane; locale: string }) {
return (
<GlassCard variant="default" padding="md" data-testid={`delivery-lane-${lane.id}`}>
@@ -225,6 +316,7 @@ function LaneCard({ lane, locale }: { lane: DeliveryLane; locale: string }) {
export default function DeliveryPage({ params }: { params: { locale: string } }) {
const t = useTranslations('delivery')
const [workbench, setWorkbench] = useState<DeliveryClosureWorkbenchSnapshot | null>(null)
const [drillPreflight, setDrillPreflight] = useState<RebootAutoRecoveryDrillPreflightSnapshot | null>(null)
const [data, setData] = useState<DeliveryData>(EMPTY_DATA)
const [errors, setErrors] = useState<string[]>([])
const [loading, setLoading] = useState(true)
@@ -235,13 +327,19 @@ export default function DeliveryPage({ params }: { params: { locale: string } })
setLoading(true)
setErrors([])
try {
const summary = await apiClient.getDeliveryClosureWorkbench()
if (cancelled) return
setWorkbench(summary)
setData(EMPTY_DATA)
setErrors([])
setLoading(false)
return
const [summaryResult, drillResult] = await Promise.allSettled([
apiClient.getDeliveryClosureWorkbench(),
apiClient.getRebootAutoRecoveryDrillPreflight(),
])
if (summaryResult.status === 'fulfilled') {
if (cancelled) return
setWorkbench(summaryResult.value)
setDrillPreflight(drillResult.status === 'fulfilled' ? drillResult.value : null)
setData(EMPTY_DATA)
setErrors(drillResult.status === 'fulfilled' ? [] : [t('sources.5.error')])
setLoading(false)
return
}
} catch {
// Summary endpoint may not exist until the API release lands; keep the page useful with legacy reads.
}
@@ -252,6 +350,7 @@ export default function DeliveryPage({ params }: { params: { locale: string } })
apiClient.getGiteaWorkflowRunnerHealth(),
apiClient.getRuntimeSurfaceInventory(),
apiClient.getBackupDrReadinessMatrix(),
apiClient.getRebootAutoRecoveryDrillPreflight(),
])
if (cancelled) return
@@ -262,12 +361,14 @@ export default function DeliveryPage({ params }: { params: { locale: string } })
runtime: results[3].status === 'fulfilled' ? results[3].value : null,
backup: results[4].status === 'fulfilled' ? results[4].value : null,
}
const nextDrillPreflight = results[5].status === 'fulfilled' ? results[5].value : null
const nextErrors = results
.map((result, index) => ({ result, index }))
.filter(({ result }) => result.status === 'rejected')
.map(({ index }) => t(`sources.${index}.error`))
setWorkbench(null)
setDrillPreflight(nextDrillPreflight)
setData(nextData)
setErrors(nextErrors)
setLoading(false)
@@ -282,11 +383,13 @@ export default function DeliveryPage({ params }: { params: { locale: string } })
const lanes = useMemo<DeliveryLane[]>(() => {
if (workbench) {
return workbench.lanes.map(lane => {
const iconMap = {
const iconMap: Record<string, typeof Rocket> = {
release: Rocket,
production_deploy: Rocket,
gitea_private_inventory: PackageCheck,
cicd_baseline: PackageCheck,
reboot_auto_recovery: RefreshCw,
credential_escrow: ShieldCheck,
gitea: PackageCheck,
runtime: Server,
backup: HardDrive,
@@ -310,6 +413,17 @@ export default function DeliveryPage({ params }: { params: { locale: string } })
present: lane.metric.present_required_source_count,
required: lane.metric.required_source_count,
})
: lane.metric.kind === 'reboot_auto_recovery_slo'
? t('lanes.reboot_auto_recovery.metric', {
hosts: lane.metric.observed_host_count,
stale: lane.metric.stale_host_count,
freshness: lane.metric.stockplatform_freshness_status,
})
: lane.metric.kind === 'credential_escrow_evidence'
? t('lanes.credential_escrow.metric', {
accepted: lane.metric.accepted_item_count,
required: lane.metric.required_item_count,
})
: lane.metric.kind === 'workflow_count'
? t('lanes.gitea.metric', { count: lane.metric.count })
: lane.metric.kind === 'surface_count'
@@ -327,7 +441,7 @@ export default function DeliveryPage({ params }: { params: { locale: string } })
nextAction: lane.next_action,
href: lane.href,
tone: lane.tone,
Icon: iconMap[lane.id],
Icon: iconMap[lane.id] ?? Rocket,
}
})
}
@@ -417,7 +531,7 @@ export default function DeliveryPage({ params }: { params: { locale: string } })
}, [data, t, workbench])
const sourceTotal = workbench?.summary.source_count ?? SOURCE_COUNT
const loadedCount = workbench?.summary.loaded_source_count ?? Object.values(data).filter(Boolean).length
const loadedCount = workbench?.summary.loaded_source_count ?? Object.values(data).filter(Boolean).length + (drillPreflight ? 1 : 0)
const highRiskBlockers = workbench?.summary.high_risk_blocker_count ?? lanes.reduce((sum, lane) => sum + lane.blockerCount, 0)
const averageCompletion = workbench?.summary.average_completion_percent ?? clampPercent(lanes.reduce((sum, lane) => sum + lane.percent, 0) / Math.max(lanes.length, 1))
const pageTone: DeliveryTone = highRiskBlockers > 0 ? 'danger' : loadedCount === sourceTotal ? 'ok' : 'warn'
@@ -471,6 +585,8 @@ export default function DeliveryPage({ params }: { params: { locale: string } })
<MetricTile label={t('metrics.execution')} value="0" detail={t('metrics.executionDetail')} tone="ok" />
</section>
<DrillPreflightPanel preflight={drillPreflight} locale={params.locale} t={t} />
{errors.length > 0 && (
<section className="delivery-alert" data-testid="delivery-partial-errors">
<AlertTriangle size={18} aria-hidden="true" />
@@ -552,6 +668,117 @@ export default function DeliveryPage({ params }: { params: { locale: string } })
grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
gap: 12px;
}
:global(.delivery-drill-panel) {
min-width: 0;
}
:global(.delivery-drill-grid) {
display: grid;
grid-template-columns: minmax(0, 1.05fr) minmax(280px, 1fr);
gap: 14px;
align-items: start;
}
:global(.delivery-drill-primary) {
display: grid;
grid-template-columns: 44px minmax(0, 1fr);
gap: 12px;
align-items: start;
min-width: 0;
}
:global(.delivery-drill-icon) {
width: 44px;
height: 44px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
color: #9a6a22;
background: rgba(154, 106, 34, 0.1);
}
:global(.delivery-drill-primary h3) {
margin: 0;
font-size: 18px;
font-weight: 900;
color: #141413;
overflow-wrap: anywhere;
}
:global(.delivery-drill-primary p) {
margin: 6px 0 0;
color: #595852;
font-size: 13px;
line-height: 1.55;
overflow-wrap: anywhere;
}
:global(.delivery-drill-metrics) {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px;
}
:global(.delivery-drill-metrics div) {
min-height: 68px;
border: 0.5px solid #e0ddd4;
border-radius: 8px;
padding: 9px;
background: rgba(255, 255, 255, 0.56);
min-width: 0;
}
:global(.delivery-drill-metrics span) {
display: block;
color: #706f68;
font-size: 11px;
font-weight: 800;
margin-bottom: 6px;
}
:global(.delivery-drill-metrics strong) {
color: #141413;
font-size: 16px;
line-height: 1.25;
overflow-wrap: anywhere;
}
:global(.delivery-drill-boundaries) {
grid-column: 1 / -1;
display: flex;
flex-wrap: wrap;
gap: 8px;
min-width: 0;
}
:global(.delivery-drill-footer) {
grid-column: 1 / -1;
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 12px;
align-items: center;
border-top: 0.5px solid #e0ddd4;
padding-top: 12px;
color: #45443f;
font-size: 12px;
line-height: 1.5;
min-width: 0;
}
:global(.delivery-drill-footer > span) {
display: block;
min-width: 0;
max-width: 100%;
overflow-wrap: anywhere;
word-break: break-word;
}
:global(.delivery-drill-footer a) {
display: inline-flex;
align-items: center;
gap: 6px;
color: #141413;
font-weight: 800;
text-decoration: none;
white-space: nowrap;
}
:global(.delivery-drill-empty) {
min-height: 58px;
display: flex;
gap: 10px;
align-items: center;
color: #706f68;
font-size: 13px;
line-height: 1.45;
}
.delivery-section-heading {
display: flex;
align-items: flex-end;
@@ -632,9 +859,18 @@ export default function DeliveryPage({ params }: { params: { locale: string } })
}
@media (max-width: 900px) {
.delivery-hero,
:global(.delivery-drill-grid),
.delivery-grid-two {
grid-template-columns: 1fr;
}
:global(.delivery-drill-footer) {
grid-template-columns: 1fr;
}
}
@media (max-width: 560px) {
:global(.delivery-drill-metrics) {
grid-template-columns: 1fr;
}
}
`}</style>
</AppLayout>

View File

@@ -1122,6 +1122,11 @@ export const apiClient = {
return handleResponse<DeliveryClosureWorkbenchSnapshot>(res)
},
async getRebootAutoRecoveryDrillPreflight() {
const res = await fetch(`${API_BASE_URL}/agents/reboot-auto-recovery-drill-preflight`, { cache: 'no-store' })
return handleResponse<RebootAutoRecoveryDrillPreflightSnapshot>(res)
},
async getGiteaPrivateInventoryP0Scorecard() {
const res = await fetch(`${API_BASE_URL}/agents/gitea-private-inventory-p0-scorecard`)
return handleResponse<GiteaPrivateInventoryP0ScorecardSnapshot>(res)
@@ -2579,6 +2584,73 @@ export interface DeliveryOperatorUnblock {
safe_handoff: string
}
export interface RebootAutoRecoveryDrillPreflightSnapshot {
schema_version: 'reboot_auto_recovery_drill_preflight_readback_v1'
generated_at: string
priority: 'P0-006'
scope: 'reboot_auto_recovery_drill_preflight'
status: string
preflight_ready: boolean
preflight_blocker_count: number
break_glass_authorization_required: boolean
execution_authorized_by_this_endpoint: boolean
safe_next_step: string
target_selector: {
scope: string
required_host_aliases: string[]
required_host_count: number
observed_host_count: number
missing_host_count: number
unreachable_host_count: number
stale_host_count: number
selector_source: string
}
preconditions: Record<string, boolean>
current_readback: {
scorecard_status: string
readiness_percent: number
active_blocker_count: number
active_blockers: string[]
service_green: boolean
product_data_green: boolean
backup_core_green: boolean
post_start_blocked: number
latest_verify_only_metric_ready: number
latest_verify_only_metric_blocker_count: number
latest_verify_only_metric_max_host_uptime_seconds: number
stockplatform_freshness_status: string
stockplatform_ingestion_status: string
}
check_mode: {
verify_only_available: boolean
verify_only_source: string
post_apply_verifier_endpoint: string
expected_after_real_fresh_boot_or_approved_drill: {
status: string
active_blocker_count: number
latest_verify_only_metric_ready: number
latest_verify_only_metric_blocker_count: number
max_host_uptime_seconds_lte: number
}
}
rollback_plan: {
preflight_is_read_only: boolean
rollback_required_for_this_endpoint: boolean
if_separately_approved_drill_fails: string[]
}
operation_boundaries: {
host_reboot_authorized_by_this_endpoint: boolean
host_reboot_performed: boolean
service_restart_performed: boolean
database_write_or_restore_performed: boolean
workflow_trigger_performed: boolean
secret_value_collection_allowed: boolean
github_api_used: boolean
runtime_write_allowed: boolean
}
forbidden_without_separate_break_glass: string[]
}
export interface DeliveryClosureWorkbenchSnapshot {
schema_version: 'delivery_closure_workbench_v1'
generated_at: string
@@ -2697,6 +2769,8 @@ export interface DeliveryClosureWorkbenchSnapshot {
| 'production_deploy'
| 'gitea_private_inventory'
| 'cicd_baseline'
| 'reboot_auto_recovery'
| 'credential_escrow'
| 'gitea'
| 'runtime'
| 'backup'
@@ -2784,6 +2858,67 @@ export interface DeliveryClosureWorkbenchSnapshot {
workflow_trigger_allowed: boolean
safe_next_step: string
}
| {
kind: 'reboot_auto_recovery_slo'
workplan_id: string
target_minutes: number
can_claim_all_services_recovered_within_target: boolean
active_blockers: string[]
post_start_blocked: number
service_green: boolean
product_data_green: boolean
backup_core_green: boolean
observed_host_count: number
missing_host_count: number
unreachable_host_count: number
stale_host_count: number
stockplatform_freshness_status: string
stockplatform_ingestion_status: string
stockplatform_freshness_blocker_count: number
stockplatform_ingestion_blocker_count: number
stockplatform_freshness_blockers: string[]
stockplatform_ingestion_blockers: string[]
stockplatform_eod_classification: string
stockplatform_eod_next_action: string
stockplatform_final_retry_window_end_local: string
stockplatform_final_retry_window_passed: boolean
stockplatform_controlled_recovery_gate_required: boolean
stockplatform_controlled_recovery_gate_status: string
host_reboot_performed: boolean
service_restart_performed: boolean
database_write_or_restore_performed: boolean
secret_value_collection_allowed: boolean
}
| {
kind: 'credential_escrow_evidence'
workplan_id: string
required_item_count: number
effective_missing_count: number
active_gate_present: boolean
preflight_status: string
accepted_item_count: number
owner_response_received_count: number
owner_response_accepted_count: number
runtime_gate_count: number
secret_value_collection_allowed: boolean
credential_marker_write_authorized_count: number
forbidden_true_field_count: number
controlled_closeout_status: string
controlled_closeout_redacted_receipt_writeback_ready_count: number
controlled_closeout_source_ref: string
controlled_closeout_projected_effective_missing_count: number
single_preflight_intake_ready: boolean
single_preflight_intake_ready_count: number
single_preflight_intake_schema_version: string
single_preflight_required_item_count: number
single_preflight_secret_value_collection_allowed: boolean
single_preflight_credential_marker_write_performed: boolean
single_preflight_runtime_action_performed: boolean
owner_response_skeleton_required_item_count: number
owner_response_skeleton_secret_value_collection_allowed: boolean
scorecard_schema_version: string
scorecard_verifier: string
}
| { kind: 'workflow_count'; count: number }
| { kind: 'surface_count'; total: number }
| {

View File

@@ -1,3 +1,17 @@
## 2026-06-30 — 00:32 P0-006 drill preflight surfaced in Delivery Workbench
**照優先順序完成的實作**
- P0-006 仍是 active P0production drill preflight endpoint 已可讀,前台 `/zh-TW/delivery` 現在直接顯示 `ready_for_break_glass_reboot_drill_authorization`、target hosts、SLO readiness、execution disabled 與 hard boundary flags。
- Delivery Workbench 前台補齊 `reboot_auto_recovery``credential_escrow` lane metric mapping不再把主線 lane 當成未知 fallback。
- 前台 safe-next-step / blocker 類長字串完成 desktop/mobile 換行修正,避免手機版水平溢出。
**驗證**
- JSON parse、`git diff --check`、focused eslint通過。
- `pnpm --dir apps/web typecheck`:通過。
- Playwright proxy production APIdesktop/mobile `/zh-TW/delivery` 皆 HTTP 200、console error 0、水平溢出 0drill preflight panel 讀到 production status 與 `runtime write: false``secret read: false`
**邊界**:未操作 host / Docker / K8s / DB / firewall / Wazuh runtime未重啟主機未使用 GitHub / `gh` / GitHub API未讀 secret / token / raw sessions / SQLite / `.env`
## 2026-06-30 — 00:16 P0-004 warning-step template copy receipt
**照優先順序完成的實作**