From d8c0cd38a76a6f422cc5ed7d324a3126eb3f3ef7 Mon Sep 17 00:00:00 2001 From: codex Date: Sat, 22 Aug 2026 14:57:52 +0200 Subject: [PATCH] Report WP-0024 approvals by task Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a02669-87ee-7a31-b111-edc95a16e0fa --- docs/wp0024-owner-review-interface.md | 16 +++++++-- .../RAILIANCE-WP-0024-owner-reviews.json | 23 +++++++++++-- scripts/wp0024-owner-review.py | 34 +++++++++++++++++-- tests/test_wp0024_owner_review.py | 13 +++++++ ...-core-custody-and-recovery-coordination.md | 3 ++ 5 files changed, 80 insertions(+), 9 deletions(-) diff --git a/docs/wp0024-owner-review-interface.md b/docs/wp0024-owner-review-interface.md index d083477..3466c96 100644 --- a/docs/wp0024-owner-review-interface.md +++ b/docs/wp0024-owner-review-interface.md @@ -27,6 +27,15 @@ resource metadata, but they never read Secret data or OpenBao lease payloads. They cannot revoke a lease, restart a workload, create a snapshot, or reboot a node. A successful review is still not a live execution window. +The task-to-owner interface is explicit in the contract: + +- T02 requires `audit-core` and `rapp-postgres`. +- T03 requires `audit-core`, `rapp-postgres`, `railiance-cluster`, and + `railiance-infra`. + +One owner receipt may approve both tasks when that owner is responsible for +both; its receipt is bound to all artifacts and checks in that owner's review. + To request changes instead: ```bash @@ -46,9 +55,10 @@ Anyone can collect current receipts without interpreting owner messages: python3 scripts/wp0024-owner-review.py status ``` -The result reports `approve`, `request-changes`, or `missing` per owner and an -`all_approved` aggregate. Receipts for an older contract digest or different -artifact hashes are counted as stale and cannot satisfy the aggregate. +The result reports `approve`, `request-changes`, or `missing` per owner, then +computes `all_approved` separately for T02 and T03 as well as for the whole +interface. Receipts for an older contract digest or different artifact hashes +are counted as stale and cannot satisfy an aggregate. The interface uses `STATE_HUB_URL` when set and otherwise connects to `http://127.0.0.1:8000`. The same value can be supplied explicitly with the diff --git a/interfaces/RAILIANCE-WP-0024-owner-reviews.json b/interfaces/RAILIANCE-WP-0024-owner-reviews.json index 46e612f..b57c473 100644 --- a/interfaces/RAILIANCE-WP-0024-owner-reviews.json +++ b/interfaces/RAILIANCE-WP-0024-owner-reviews.json @@ -9,6 +9,18 @@ "approve", "request-changes" ], + "task_owners": { + "RAILIANCE-WP-0024-T02": [ + "audit-core", + "rapp-postgres" + ], + "RAILIANCE-WP-0024-T03": [ + "audit-core", + "rapp-postgres", + "railiance-cluster", + "railiance-infra" + ] + }, "owners": { "audit-core": { "tasks": [ @@ -34,10 +46,12 @@ }, "rapp-postgres": { "tasks": [ - "RAILIANCE-WP-0024-T02" + "RAILIANCE-WP-0024-T02", + "RAILIANCE-WP-0024-T03" ], "artifacts": [ "docs/audit-core-database-lease-recovery.md", + "docs/railiance01-coordinated-reboot.md", "scripts/audit-core-recovery-preflight.py", "scripts/audit-core-database-lease-recovery.py", "docs/audit-core-database-lease-approval.example.json" @@ -45,11 +59,14 @@ "assertions": [ "Lease selection aborts unless exactly one live handle exists below database/creds/audit-core-runtime and its issue time coheres with the ExternalSecret refresh.", "The action revokes only that exact lease handle; it never revokes the role prefix, an External Secrets parent lease, or another database consumer.", - "Recovery uses ordinary ExternalSecret refresh and mounted-file reread, with no pod restart, database restore or credential value in evidence." + "Recovery uses ordinary ExternalSecret refresh and mounted-file reread, with no pod restart, database restore or credential value in evidence.", + "After the coordinated reboot, the same platform-pg PVC and cluster identity return 1/1 Ready with ContinuousArchiving true and no re-bootstrap or in-place restore shortcut.", + "The reboot hold point requires a fresh completed Barman backup and healthy WAL archiving before the host action." ], "checks": [ "focused-unit-tests", - "database-lease-preflight" + "database-lease-preflight", + "node-reboot-preflight" ] }, "railiance-cluster": { diff --git a/scripts/wp0024-owner-review.py b/scripts/wp0024-owner-review.py index a660763..7e1f8b7 100644 --- a/scripts/wp0024-owner-review.py +++ b/scripts/wp0024-owner-review.py @@ -65,8 +65,17 @@ def load_contract(path: Path = DEFAULT_CONTRACT) -> dict[str, Any]: raise ReviewError("unsupported owner-review interface/version") owners = value.get("owners") hashes = value.get("artifact_sha256") - if not isinstance(owners, dict) or not owners or not isinstance(hashes, dict): - raise ReviewError("review contract requires owners and artifact_sha256 maps") + task_owners = value.get("task_owners") + if ( + not isinstance(owners, dict) + or not owners + or not isinstance(hashes, dict) + or not isinstance(task_owners, dict) + or not task_owners + ): + raise ReviewError( + "review contract requires owners, task_owners and artifact_sha256 maps" + ) for owner, review in owners.items(): if not isinstance(owner, str) or not isinstance(review, dict): raise ReviewError("invalid owner review entry") @@ -81,6 +90,16 @@ def load_contract(path: Path = DEFAULT_CONTRACT) -> dict[str, Any]: raise ReviewError(f"{owner}: unknown or missing read-only check") if not assertions or not all(isinstance(item, str) for item in assertions): raise ReviewError(f"{owner}: assertions must be non-empty strings") + expected_tasks = sorted( + task for task, required in task_owners.items() if owner in required + ) + if sorted(review.get("tasks") or []) != expected_tasks: + raise ReviewError(f"{owner}: tasks disagree with task_owners") + for task, required in task_owners.items(): + if not isinstance(task, str) or not isinstance(required, list) or not required: + raise ReviewError("task_owners entries must be non-empty owner lists") + if len(set(required)) != len(required) or any(owner not in owners for owner in required): + raise ReviewError(f"{task}: task_owners contains unknown or duplicate owners") return value @@ -421,6 +440,14 @@ def aggregate_status( "created_at": latest.get("created_at") if latest else None, "stale_receipt_count": stale[owner], } + tasks = { + task: { + "required_owners": required, + "decisions": {owner: owners[owner]["decision"] for owner in required}, + "all_approved": all(owners[owner]["decision"] == "approve" for owner in required), + } + for task, required in contract["task_owners"].items() + } try: artifact_current = all( file_sha256(ROOT / path) == expected @@ -435,8 +462,9 @@ def aggregate_status( "contract_digest": digest, "contract_artifacts_current": artifact_current, "owners": owners, + "tasks": tasks, "all_approved": artifact_current - and all(item["decision"] == "approve" for item in owners.values()), + and all(item["all_approved"] for item in tasks.values()), } diff --git a/tests/test_wp0024_owner_review.py b/tests/test_wp0024_owner_review.py index ca812db..801a753 100644 --- a/tests/test_wp0024_owner_review.py +++ b/tests/test_wp0024_owner_review.py @@ -33,6 +33,18 @@ class OwnerReviewTests(unittest.TestCase): for check in review["checks"] } self.assertLessEqual(checks, module.KNOWN_CHECKS) + self.assertEqual( + { + "RAILIANCE-WP-0024-T02": ["audit-core", "rapp-postgres"], + "RAILIANCE-WP-0024-T03": [ + "audit-core", + "rapp-postgres", + "railiance-cluster", + "railiance-infra", + ], + }, + self.contract["task_owners"], + ) self.assertEqual(64, len(module.contract_digest(self.contract))) def test_current_artifacts_match_contract(self) -> None: @@ -110,6 +122,7 @@ class OwnerReviewTests(unittest.TestCase): current = module.aggregate_status(self.contract, [message]) self.assertEqual("approve", current["owners"][owner]["decision"]) self.assertTrue(current["contract_artifacts_current"]) + self.assertFalse(current["tasks"]["RAILIANCE-WP-0024-T02"]["all_approved"]) self.assertFalse(current["all_approved"]) stale = json.loads(json.dumps(receipt)) diff --git a/workplans/RAILIANCE-WP-0024-audit-core-custody-and-recovery-coordination.md b/workplans/RAILIANCE-WP-0024-audit-core-custody-and-recovery-coordination.md index 2677ce6..9c1cd9d 100644 --- a/workplans/RAILIANCE-WP-0024-audit-core-custody-and-recovery-coordination.md +++ b/workplans/RAILIANCE-WP-0024-audit-core-custody-and-recovery-coordination.md @@ -221,6 +221,9 @@ aggregate `status` operations. Approvals are valid only for the canonical contract digest and the owner's pinned artifact hashes, so a changed procedure invalidates the old receipt. The interface runs only closed-set, read-only checks and cannot perform any T02/T03 live mutation. +The task map requires audit-core plus rapp-postgres for T02, and audit-core, +rapp-postgres, railiance-cluster, plus railiance-infra for T03; aggregate status +is computed independently for each task. ## Acceptance