From 63df822f6c49e342314bae2baff39d9021228055 Mon Sep 17 00:00:00 2001 From: codex Date: Wed, 14 Oct 2026 11:12:16 +0200 Subject: [PATCH] RPF-WP-0015: repair recovery preflight for attest-chain Job pods and current audit-core digest Co-Authored-By: Claude Opus 5.5 Assistant: claude-code Assistant-Model: opus Assistant-Process: 680842@bnt-lap001 Assistant-Session: 54eb9e8e-7d6b-4f0c-b7a6-a6db62075883 --- .../RAILIANCE-WP-0024-owner-reviews.json | 2 +- scripts/audit-core-recovery-preflight.py | 16 +++++++++++++-- tests/test_audit_core_recovery_preflight.py | 20 +++++++++++++++++++ ...-core-custody-and-recovery-coordination.md | 15 +++++++++++++- 4 files changed, 49 insertions(+), 4 deletions(-) diff --git a/interfaces/RAILIANCE-WP-0024-owner-reviews.json b/interfaces/RAILIANCE-WP-0024-owner-reviews.json index 0b7712c..44f7317 100644 --- a/interfaces/RAILIANCE-WP-0024-owner-reviews.json +++ b/interfaces/RAILIANCE-WP-0024-owner-reviews.json @@ -141,6 +141,6 @@ "docs/openbao-reboot-snapshot-receipt.example.json": "b95903a3e1aacb607eecc3f0440f08e62480d09da47bd7ab96c3eb8814f2eaee", "docs/railiance01-coordinated-reboot.md": "9969c2f498587710154e8d9a650fb8a13159e9e7b99918291dbdf577b131b7e7", "scripts/audit-core-database-lease-recovery.py": "c87fb026c527fc17116067c5742d324340707ce4aecf440d0d4e618db9b872a1", - "scripts/audit-core-recovery-preflight.py": "802f73fa4352f64bc0972d813e3dd874f3ec2f428e162b23158c9d8d6fd8e710" + "scripts/audit-core-recovery-preflight.py": "248c663bad31860dd4f62870db00f6d624143d18c18f86634a39ceda12c7bc66" } } diff --git a/scripts/audit-core-recovery-preflight.py b/scripts/audit-core-recovery-preflight.py index b2cbb5b..a422bc6 100755 --- a/scripts/audit-core-recovery-preflight.py +++ b/scripts/audit-core-recovery-preflight.py @@ -21,7 +21,7 @@ from typing import Any EXPECTED_IMAGE = ( "forgejo.coulomb.social/coulomb/audit-core@" - "sha256:c2fe39a0185b99be3fc0cb14d2de69772b8e66e20490097c9d11d90cc39719a6" + "sha256:ec15f63d49226bfe507af2bc38ffbd5e83f549ba2c6d1ae7a338e7e053f34615" ) EXPECTED_DB_KEYS = {"username", "password", "host", "port", "dbname"} @@ -30,6 +30,18 @@ class PreflightError(RuntimeError): pass +def serving_pods(pod_list: dict[str, Any]) -> list[dict[str, Any]]: + """Deployment pods only: audit-core attest-chain Job pods share the app label.""" + return [ + pod + for pod in pod_list.get("items", []) + if not any( + owner.get("kind") == "Job" + for owner in pod.get("metadata", {}).get("ownerReferences", []) + ) + ] + + class Remote: def __init__(self, host: str) -> None: self.host = host @@ -204,7 +216,7 @@ def common_state(remote: Remote, now: datetime) -> dict[str, Any]: ["-n", "audit-core", "get", "pods", "-l", "app.kubernetes.io/name=audit-core"], label="read audit-core pod metadata", ) - pods = pod_list.get("items", []) + pods = serving_pods(pod_list) if len(pods) != 1: raise PreflightError(f"expected one audit-core pod, observed {len(pods)}") pod = pods[0] diff --git a/tests/test_audit_core_recovery_preflight.py b/tests/test_audit_core_recovery_preflight.py index 4632629..132c42d 100644 --- a/tests/test_audit_core_recovery_preflight.py +++ b/tests/test_audit_core_recovery_preflight.py @@ -19,6 +19,26 @@ SPEC.loader.exec_module(module) class RecoveryPreflightTests(unittest.TestCase): + def test_serving_pods_ignores_job_owned_attest_chain_pods(self) -> None: + def pod(name: str, kind: str) -> dict: + return {"metadata": {"name": name, "ownerReferences": [{"kind": kind}]}} + + pods = { + "items": [ + pod("audit-core-66767ddd58-x", "ReplicaSet"), + pod("audit-core-attest-chain-1", "Job"), + pod("audit-core-attest-chain-2", "Job"), + ] + } + self.assertEqual( + ["audit-core-66767ddd58-x"], + [item["metadata"]["name"] for item in module.serving_pods(pods)], + ) + + def test_serving_pods_keeps_two_deployment_pods_visible(self) -> None: + pods = {"items": [{"metadata": {"ownerReferences": [{"kind": "ReplicaSet"}]}}] * 2} + self.assertEqual(2, len(module.serving_pods(pods))) + def test_condition_selects_named_condition(self) -> None: resource = { "status": { diff --git a/workplans/RPF-WP-0015-audit-core-custody-and-recovery-coordination.md b/workplans/RPF-WP-0015-audit-core-custody-and-recovery-coordination.md index 1dcc54e..8f68770 100644 --- a/workplans/RPF-WP-0015-audit-core-custody-and-recovery-coordination.md +++ b/workplans/RPF-WP-0015-audit-core-custody-and-recovery-coordination.md @@ -9,7 +9,7 @@ flavor: residual owner: codex topic_slug: railiance created: "2026-08-22" -updated: "2026-10-03" +updated: "2026-10-14" related: - AUDIT-WP-0008 - WH-ENG-20260822-AUDIT-E2-01 @@ -365,3 +365,16 @@ T02/T03 remain waiting on fresh attended execution windows, named abort operator > critical path (identity → authz → secrets → SecurityCase execution). Tasks are unchanged; > promote back when demand or risk appears. See the-custodian > `history/20261011-netkingdom-security-progress-assessment.md`. + +## Window preparation — 2026-10-14 + +The founder chose to prepare attended windows for T02 and T03 (D5 `2fd5849c` already +authorizes the outage exercise; each window still needs its own GO). The read-only +preflight had drifted: audit-core's attest-chain CronJob pods share the app label, so +the single-pod check failed, and the reviewed digest pin was stale. The preflight now +ignores Job-owned pods and pins audit-core's current deploy digest `ec15f63d…` +(audit-core `deploy/audit-core.yaml`, descendant of the `abd22fa` expiry prerequisite). +Both preflights then pass every automated check with no secret value observed; only +operator gates remain. The review contract's preflight hash is refreshed, so owner +receipts must be renewed against it. Unrelated pre-existing test failures (credential +change, factory audit delivery, informed_decision import) are unchanged.