Report WP-0024 approvals by task
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a02669-87ee-7a31-b111-edc95a16e0fa
This commit is contained in:
codex 2026-08-22 14:57:52 +02:00
parent defb2a556d
commit d8c0cd38a7
5 changed files with 80 additions and 9 deletions

View file

@ -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

View file

@ -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": {

View file

@ -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()),
}

View file

@ -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))

View file

@ -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