Report WP-0024 approvals by task
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a02669-87ee-7a31-b111-edc95a16e0fa
This commit is contained in:
parent
defb2a556d
commit
d8c0cd38a7
5 changed files with 80 additions and 9 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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()),
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue