Route the sender lane's cluster writes through AUDIT_KUBECONFIG too.

Round-10 review (railiance-platform) found that ContainedOperator inherited
an ssh-only kubectl, so an E2 would write OpenBao and then fail its sync,
restart and rollback. Also: a bad AUDIT_KUBECONFIG now leaves window-error
evidence, and the receipt script validates bodies and lets a later
request-changes withdraw an approval.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 236506@bnt-lap001
Assistant-Session: 09087018-91e1-48c5-ab2f-acfbb62c92e4
This commit is contained in:
codex 2026-11-03 15:09:59 +01:00
parent 8d574be1bf
commit dad91e8c44
6 changed files with 119 additions and 12 deletions

View file

@ -31,6 +31,7 @@ import os
import re
import secrets
import stat
import subprocess
import sys
import time
from datetime import UTC, datetime, timedelta
@ -280,6 +281,17 @@ class ContainedOperator(CREDS.Operator):
def __init__(self, remote: str) -> None: # noqa: D107 - deliberately no super().__init__
self.remote = remote
self.bao_env = LEASE.contained_bao_env()
# Same rule as PREFLIGHT.Remote: with AUDIT_KUBECONFIG set, cluster calls
# (including the projection/cleanup sync and restart writes) run locally
# against the attended admin kubeconfig; unset keeps ssh <remote> kubectl.
self.kubeconfig = PREFLIGHT.attended_kubeconfig()
def kubectl(self, args: list[str], *, label: str, input_text: str | None = None,
allow_missing: bool = False) -> subprocess.CompletedProcess[str]:
if self.kubeconfig is None:
return super().kubectl(args, label=label, input_text=input_text, allow_missing=allow_missing)
return CREDS.safe_run(["kubectl", "--kubeconfig", self.kubeconfig, *args], label=label,
input_text=input_text, allow_missing=allow_missing)
def contained_operator(args: argparse.Namespace) -> Any:

View file

@ -7,12 +7,13 @@ The window is 15 minutes and starts now, or N minutes from now. Writing the
receipt and running the window is the founder GO for that window; D5
(2fd5849c) authorizes the exercise class only.
The owner acknowledgements are the owners' latest State Hub `approve` receipts
for the *current* review contract
The owner acknowledgements are the owners' State Hub receipts for the
*current* review contract
(interfaces/RAILIANCE-WP-0024-owner-reviews.json). They are found by subject
`WP0024-OWNER-RECEIPT/v1/<owner>/approve/<contract digest>`. The script
refuses when any pinned artifact differs from the contract, or when a
required owner has no approval for this digest. The receipt holds no
`WP0024-OWNER-RECEIPT/v1/<owner>/<decision>/<contract digest>`, each body
validated by the review module. An owner's latest receipt must be `approve`.
The script refuses when any pinned artifact differs from the contract, or when
a required owner has no current approval for this digest. The receipt holds no
credential values, and the script never overwrites an existing file.
"""
import importlib.util
@ -42,12 +43,17 @@ def approvals(review, contract, api_base):
messages = json.load(response)
found = {}
for owner in OWNERS:
subject = f"{review.RECEIPT_PREFIX}/v1/{owner}/approve/{digest}"
hits = sorted((m for m in messages if m.get("subject") == subject and m.get("from_agent") == owner),
key=lambda m: m["created_at"])
if not hits:
sys.exit(f"no {owner} approval for contract {digest[:8]}; run the owner round first")
found[owner] = {"acknowledged": True, "message_id": hits[-1]["id"]}
# Validate each body with the review module's own parser (subject, sender,
# schema). The latest receipt for this digest decides: a request-changes
# after an approval withdraws it.
receipts = []
for message in messages:
receipt = review.parse_receipt_message(message)
if receipt and receipt["owner"] == owner and receipt["contract_digest"] == digest:
receipts.append((receipt["created_at"], receipt["decision"], message["id"]))
if not receipts or max(receipts)[1] != "approve":
sys.exit(f"no current {owner} approval for contract {digest[:8]}; run the owner round first")
found[owner] = {"acknowledged": True, "message_id": max(receipts)[2]}
return digest, found

View file

@ -271,7 +271,8 @@ def main(argv: list[str] | None = None) -> int:
# so this script never prints; its outcome lives only in the evidence dir.
try:
window(args)
except (OSError, KeyError, WindowError, LANE.LaneError, LANE.LEASE.ProcedureError) as exc:
except (OSError, KeyError, WindowError, LANE.LaneError, LANE.LEASE.ProcedureError,
LANE.PREFLIGHT.PreflightError) as exc:
try:
LANE.emit_result(
{"status": "failed", "error": str(exc), "secret_values_observed": False},