feat(recovery): add p0 dr escrow checklist
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Failing after 2m47s
CD Pipeline / build-and-deploy (push) Has been skipped
CD Pipeline / post-deploy-checks (push) Has been skipped
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Failing after 2m47s
CD Pipeline / build-and-deploy (push) Has been skipped
CD Pipeline / post-deploy-checks (push) Has been skipped
This commit is contained in:
@@ -49349,3 +49349,18 @@ production browser smoke:
|
||||
- 沒有讀、複製、貼上、外送 runner token / secret / `.env` / raw sessions / SQLite / auth。
|
||||
- 沒有啟動 188 runner service、沒有重開 110 runner、沒有 GitHub API / gh / GitHub Actions、沒有 force push。
|
||||
- Gitea CD 仍需等 non-110 runner 完成外部安全註冊並讀回 `AWOOOI_NON110_RUNNER_READY=1` 後才可承接。
|
||||
|
||||
## 2026-06-29 — 13:56 P0-005 DR escrow evidence checklist single intake
|
||||
|
||||
**完成內容**:
|
||||
- 新增 `scripts/reboot-recovery/dr-escrow-evidence-checklist.py`,把 P0-005 五個缺口收斂成單一 no-secret checklist:`restic_repository_password`、`offsite_provider_credentials`、`break_glass_admin_credentials`、`dns_registrar_recovery`、`oauth_ai_provider_recovery`。
|
||||
- checklist 同時輸出 owner packet、owner response skeleton、單一 preflight command、scorecard command 與 exit criteria;所有 runtime / host write / secret collection / credential marker write 授權欄位預設維持 `false`。
|
||||
- 更新 `docs/operations/awoooi-priority-work-order-readback.snapshot.json`,把 P0-005 safe next step 從「建立 checklist」推進為「填五個非秘密 evidence refs 後跑單一 preflight」,並修正 exit criteria 為 single response `owner_response_accepted_count=1`。
|
||||
|
||||
**驗證目標**:
|
||||
- `python3.11 -m pytest scripts/reboot-recovery/tests/test_dr_escrow_evidence_checklist.py -q`
|
||||
- `python3 scripts/reboot-recovery/dr-escrow-evidence-checklist.py --output /tmp/awoooi-dr-escrow-evidence-checklist-20260629.json`
|
||||
- `jq empty /tmp/awoooi-dr-escrow-evidence-checklist-20260629.json`
|
||||
|
||||
**未做**:
|
||||
- 沒有讀 secret、token、`.env`、raw sessions / SQLite / auth;沒有寫 credential marker;沒有 host / Docker / Nginx / firewall / K3s / DB 操作;沒有使用 GitHub。
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"schema_version": "awoooi_priority_work_order_readback_v1",
|
||||
"generated_at": "2026-06-29T13:28:25+08:00",
|
||||
"status": "integrated_p0_register_next_blocker_credential_escrow",
|
||||
"generated_at": "2026-06-29T14:00:19+08:00",
|
||||
"status": "p0_005_dr_escrow_checklist_ready_waiting_redacted_refs",
|
||||
"source_refs": {
|
||||
"global_scorecard": "~/.codex/product-runtime-governance-completion-scorecard.snapshot.json",
|
||||
"workstation_dashboard": "~/.codex/codex-workstation-sync-dashboard.snapshot.json",
|
||||
@@ -9,12 +9,13 @@
|
||||
"full_stack_cold_start_check": "scripts/reboot-recovery/full-stack-cold-start-check.sh --monitor-read-only --no-color",
|
||||
"delivery_closure_workbench": "https://awoooi.wooo.work/api/v1/agents/delivery-closure-workbench",
|
||||
"public_gitea_queue_readback": "ops/runner/read-public-gitea-actions-queue.py --json",
|
||||
"credential_escrow_scorecard": "/tmp/awoooi-credential-escrow-intake-scorecard-20260629-1200-priority.json"
|
||||
"credential_escrow_scorecard": "/tmp/awoooi-credential-escrow-intake-scorecard-20260629-1200-priority.json",
|
||||
"dr_escrow_evidence_checklist_generator": "scripts/reboot-recovery/dr-escrow-evidence-checklist.py"
|
||||
},
|
||||
"current_head": {
|
||||
"gitea_main_sha": "780587810c8b241ae60a153bdea1e557ba06218f",
|
||||
"latest_successful_deploy_marker": "780587810 chore(cd): deploy a19b659 [skip ci]",
|
||||
"latest_successful_deployed_source_sha": "a19b659f07c8f212a0514bf4af1affe657fa0fe1",
|
||||
"gitea_main_sha": "bd55386e6edc46ce0b188011b171191b7773c5ba",
|
||||
"latest_successful_deploy_marker": "9362588ce chore(cd): deploy a423301 [skip ci]",
|
||||
"latest_successful_deployed_source_sha": "a4233017ad5fd03977233f3db6a4bb45d71507ed",
|
||||
"latest_source_readiness_commit_sha": "0c8d4e88c39157b92322fa41a92e6b15c317ac49",
|
||||
"latest_source_readiness_cd_run_id": "3882",
|
||||
"latest_source_readiness_cd_run_status": "Success",
|
||||
@@ -102,8 +103,8 @@
|
||||
{
|
||||
"workplan_id": "P0-005",
|
||||
"title": "產品資料與備份 contract",
|
||||
"status": "blocked_waiting_non_secret_credential_escrow_evidence",
|
||||
"reason": "Backup core and product freshness are green, but DR completion still requires five non-secret credential escrow evidence markers.",
|
||||
"status": "checklist_ready_waiting_non_secret_credential_escrow_evidence",
|
||||
"reason": "Backup core and product freshness are green, and the P0-005 lane now has one no-secret checklist packet for the five remaining credential escrow evidence markers.",
|
||||
"evidence": {
|
||||
"product_data_green": true,
|
||||
"stock_freshness_status": "ok",
|
||||
@@ -115,7 +116,9 @@
|
||||
"rclone_configured": true,
|
||||
"script_missing_count": 0,
|
||||
"credential_marker_write_authorized_count": 0,
|
||||
"secret_value_collection_allowed": false
|
||||
"secret_value_collection_allowed": false,
|
||||
"checklist_generator_present": true,
|
||||
"checklist_schema_version": "awoooi_dr_escrow_evidence_checklist_v1"
|
||||
},
|
||||
"missing_items": [
|
||||
"restic_repository_password",
|
||||
@@ -126,10 +129,11 @@
|
||||
],
|
||||
"professional_fix": {
|
||||
"owner": "DR evidence lane",
|
||||
"action": "Create one DR escrow checklist packet with the five required item ids, accept only redacted evidence refs, then run the existing preflight once.",
|
||||
"action": "Use the single DR escrow checklist packet with the five required item ids, accept only redacted evidence refs, then run the existing preflight once.",
|
||||
"exit_criteria": [
|
||||
"effective_escrow_missing_count=0",
|
||||
"owner_response_accepted_count=5",
|
||||
"owner_response_accepted_count=1",
|
||||
"forbidden_true_field_count=0",
|
||||
"secret_value_collection_allowed=false",
|
||||
"post_reboot_readiness OVERALL_DECLARATION no longer includes DR_ESCROW_BLOCKED"
|
||||
],
|
||||
@@ -138,7 +142,7 @@
|
||||
"Do not create additional owner-package variants for the same five refs."
|
||||
]
|
||||
},
|
||||
"safe_next_step": "create_single_dr_escrow_evidence_checklist_then_rerun_preflight"
|
||||
"safe_next_step": "fill_single_dr_escrow_evidence_checklist_with_five_non_secret_refs_then_run_one_preflight"
|
||||
},
|
||||
{
|
||||
"workplan_id": "P0-003",
|
||||
@@ -240,7 +244,7 @@
|
||||
"database_write_or_restore_performed": false
|
||||
},
|
||||
"next_execution_order": [
|
||||
"P0-005: create a single DR escrow evidence checklist for the five missing refs and rerun one preflight.",
|
||||
"P0-005: fill the single DR escrow evidence checklist with five non-secret refs and rerun one preflight.",
|
||||
"P0-003: convert private/internal inventory to Gitea-only readback and remove retired GitHub from active P0 blocker math.",
|
||||
"P0-006: run source-to-runtime drift cleanup using product manifest, committed runtime sources, production readback, and public route evidence."
|
||||
]
|
||||
|
||||
170
scripts/reboot-recovery/dr-escrow-evidence-checklist.py
Normal file
170
scripts/reboot-recovery/dr-escrow-evidence-checklist.py
Normal file
@@ -0,0 +1,170 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build the single P0-005 DR escrow evidence checklist."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
SCHEMA_VERSION = "awoooi_dr_escrow_evidence_checklist_v1"
|
||||
OWNER_PACKET_SCHEMA = "awoooi_post_reboot_next_gate_owner_packets_v1"
|
||||
OWNER_RESPONSE_SCHEMA = "awoooi_post_reboot_next_gate_owner_response_v1"
|
||||
GATE_ID = "credential_escrow_evidence"
|
||||
ITEMS = [
|
||||
"restic_repository_password",
|
||||
"offsite_provider_credentials",
|
||||
"break_glass_admin_credentials",
|
||||
"dns_registrar_recovery",
|
||||
"oauth_ai_provider_recovery",
|
||||
]
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Emit one no-secret checklist for the P0-005 DR escrow lane.",
|
||||
)
|
||||
parser.add_argument("--output", type=Path, help="Write JSON to this path.")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def evidence_item(item_id: str) -> dict[str, Any]:
|
||||
return {
|
||||
"item_id": item_id,
|
||||
"required_fields": [
|
||||
"non_secret_evidence_ref",
|
||||
"recovery_owner",
|
||||
"reviewer",
|
||||
"last_reviewed_at",
|
||||
"contains_secret_value=false",
|
||||
],
|
||||
"accepted_ref_examples": [
|
||||
f"vault-item-id-for-{item_id}",
|
||||
f"sealed-envelope-id-for-{item_id}",
|
||||
f"recovery-checklist-id-for-{item_id}",
|
||||
f"ticket-id-for-{item_id}",
|
||||
],
|
||||
"rejected_values": [
|
||||
"passwords",
|
||||
"tokens",
|
||||
"private keys",
|
||||
"recovery codes",
|
||||
"secret URLs",
|
||||
"session cookies",
|
||||
],
|
||||
"marker_dry_run_command": (
|
||||
"/backup/scripts/mark-credential-escrow-verified.sh "
|
||||
f"--item {item_id} --evidence-id <NON_SECRET_REF_FOR_{item_id}> --dry-run"
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def owner_packet() -> dict[str, Any]:
|
||||
return {
|
||||
"schema_version": OWNER_PACKET_SCHEMA,
|
||||
"source": {"next_required_gates": [GATE_ID]},
|
||||
"owner_packets": [
|
||||
{
|
||||
"packet_id": GATE_ID,
|
||||
"title": "P0-005 DR credential escrow evidence",
|
||||
"priority": "P0",
|
||||
"required_items": ITEMS,
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def owner_response_skeleton() -> dict[str, Any]:
|
||||
return {
|
||||
"schema_version": OWNER_RESPONSE_SCHEMA,
|
||||
"generated_at": datetime.now().astimezone().isoformat(timespec="seconds"),
|
||||
"responses": [
|
||||
{
|
||||
"gate_id": GATE_ID,
|
||||
"owner_role": "backup_dr_owner",
|
||||
"owner_team": "platform_security",
|
||||
"decision": "pending",
|
||||
"decision_reason": "fill_only_after_all_redacted_refs_are_present",
|
||||
"affected_scope": "P0-005 DR credential escrow evidence",
|
||||
"redacted_evidence_refs": ["<ONE_PARENT_TICKET_OR_REVIEW_REF>"],
|
||||
"followup_owner": "backup_dr_owner",
|
||||
"runtime_action_requested": False,
|
||||
"runtime_action_authorized": False,
|
||||
"host_write_requested": False,
|
||||
"host_write_authorized": False,
|
||||
"secret_value_included": False,
|
||||
"secret_value_collection_allowed": False,
|
||||
"credential_marker_write_requested": False,
|
||||
"credential_marker_write_authorized": False,
|
||||
"escrow_items": [
|
||||
{
|
||||
"item_id": item_id,
|
||||
"non_secret_evidence_ref": f"<NON_SECRET_REF_FOR_{item_id}>",
|
||||
"recovery_owner": "backup_dr_owner",
|
||||
"reviewer": "security_reviewer",
|
||||
"last_reviewed_at": "YYYY-MM-DD",
|
||||
"contains_secret_value": False,
|
||||
}
|
||||
for item_id in ITEMS
|
||||
],
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def build_payload() -> dict[str, Any]:
|
||||
return {
|
||||
"schema_version": SCHEMA_VERSION,
|
||||
"generated_at": datetime.now().astimezone().isoformat(timespec="seconds"),
|
||||
"workplan_id": "P0-005",
|
||||
"status": "waiting_for_five_redacted_non_secret_evidence_refs",
|
||||
"active_gate": GATE_ID,
|
||||
"required_item_count": len(ITEMS),
|
||||
"required_items": [evidence_item(item_id) for item_id in ITEMS],
|
||||
"owner_packet": owner_packet(),
|
||||
"owner_response_skeleton": owner_response_skeleton(),
|
||||
"single_preflight_command": (
|
||||
"python3 scripts/reboot-recovery/post-reboot-owner-response-preflight.py "
|
||||
"--owner-packet-file <owner-packet.json> "
|
||||
"--response-file <filled-owner-response.json> --json --no-color"
|
||||
),
|
||||
"scorecard_command": (
|
||||
"python3 scripts/reboot-recovery/post-reboot-credential-escrow-intake-scorecard.py "
|
||||
"--summary-file <summary.txt> --owner-packet-file <owner-packet.json> "
|
||||
"--response-file <filled-owner-response.json> "
|
||||
"--offsite-report-file <offsite-report.txt> "
|
||||
"--escrow-status-file <escrow-status.txt> --json --no-color"
|
||||
),
|
||||
"exit_criteria": [
|
||||
"preflight_status=ready_for_independent_reviewer_acceptance",
|
||||
"owner_response_received_count=1",
|
||||
"owner_response_accepted_count=1",
|
||||
"forbidden_true_field_count=0",
|
||||
"effective_escrow_missing_count=0 after marker dry-runs and accepted marker writes",
|
||||
],
|
||||
"execution_rules": [
|
||||
"Use this as the only P0-005 intake packet.",
|
||||
"Do not create per-item owner packets.",
|
||||
"Do not include secret values in evidence refs.",
|
||||
"Do not reopen cold-start or CI/CD while this checklist is pending.",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
payload = build_payload()
|
||||
text = json.dumps(payload, indent=2, ensure_ascii=False) + "\n"
|
||||
if args.output:
|
||||
args.output.parent.mkdir(parents=True, exist_ok=True)
|
||||
args.output.write_text(text, encoding="utf-8")
|
||||
else:
|
||||
print(text, end="")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,75 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[3]
|
||||
SCRIPT = ROOT / "scripts" / "reboot-recovery" / "dr-escrow-evidence-checklist.py"
|
||||
|
||||
ITEMS = {
|
||||
"restic_repository_password",
|
||||
"offsite_provider_credentials",
|
||||
"break_glass_admin_credentials",
|
||||
"dns_registrar_recovery",
|
||||
"oauth_ai_provider_recovery",
|
||||
}
|
||||
|
||||
|
||||
def load_checklist() -> dict:
|
||||
result = subprocess.run(
|
||||
[sys.executable, str(SCRIPT)],
|
||||
text=True,
|
||||
capture_output=True,
|
||||
check=True,
|
||||
)
|
||||
return json.loads(result.stdout)
|
||||
|
||||
|
||||
def test_checklist_is_single_p0_005_intake_packet() -> None:
|
||||
payload = load_checklist()
|
||||
|
||||
assert payload["schema_version"] == "awoooi_dr_escrow_evidence_checklist_v1"
|
||||
assert payload["workplan_id"] == "P0-005"
|
||||
assert payload["active_gate"] == "credential_escrow_evidence"
|
||||
assert payload["required_item_count"] == 5
|
||||
assert {item["item_id"] for item in payload["required_items"]} == ITEMS
|
||||
assert payload["owner_packet"]["source"]["next_required_gates"] == [
|
||||
"credential_escrow_evidence"
|
||||
]
|
||||
assert len(payload["owner_packet"]["owner_packets"]) == 1
|
||||
assert len(payload["owner_response_skeleton"]["responses"]) == 1
|
||||
|
||||
|
||||
def test_checklist_never_authorizes_runtime_or_secret_collection() -> None:
|
||||
payload = load_checklist()
|
||||
response = payload["owner_response_skeleton"]["responses"][0]
|
||||
|
||||
forbidden_true_fields = [
|
||||
"runtime_action_requested",
|
||||
"runtime_action_authorized",
|
||||
"host_write_requested",
|
||||
"host_write_authorized",
|
||||
"secret_value_included",
|
||||
"secret_value_collection_allowed",
|
||||
"credential_marker_write_requested",
|
||||
"credential_marker_write_authorized",
|
||||
]
|
||||
for field in forbidden_true_fields:
|
||||
assert response[field] is False
|
||||
for item in response["escrow_items"]:
|
||||
assert item["contains_secret_value"] is False
|
||||
|
||||
|
||||
def test_checklist_outputs_marker_dry_run_commands_only() -> None:
|
||||
payload = load_checklist()
|
||||
|
||||
for item in payload["required_items"]:
|
||||
command = item["marker_dry_run_command"]
|
||||
assert "--dry-run" in command
|
||||
assert "--item " + item["item_id"] in command
|
||||
assert " --evidence-id <NON_SECRET_REF_FOR_" in command
|
||||
assert "--token" not in command
|
||||
assert "password=" not in command.lower()
|
||||
Reference in New Issue
Block a user