RPF-WP-0015-T02: lease harness ignores attest Job pods and avoids attest-chain schedule

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

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 680842@bnt-lap001
Assistant-Session: 54eb9e8e-7d6b-4f0c-b7a6-a6db62075883
This commit is contained in:
codex 2026-10-14 11:16:16 +02:00
parent 63df822f6c
commit aee6fe79d7
4 changed files with 86 additions and 5 deletions

View file

@ -220,11 +220,52 @@ def secret_state(remote: Any) -> dict[str, Any]:
}
ATTEST_CRONJOB = "audit-core-attest-chain"
ATTEST_GUARD = timedelta(minutes=20)
def next_daily_run(schedule: str, time_zone: str | None, now: datetime) -> datetime:
"""Next run of a fixed daily "M H * * *" UTC schedule; anything else fails closed."""
fields = schedule.split()
if (
time_zone not in ("Etc/UTC", "UTC")
or len(fields) != 5
or fields[2:] != ["*", "*", "*"]
or not fields[0].isdigit()
or not fields[1].isdigit()
):
raise ProcedureError("attest-chain schedule is not a fixed daily UTC time; choose the window manually")
run = now.replace(hour=int(fields[1]), minute=int(fields[0]), second=0, microsecond=0)
return run if run >= now - ATTEST_GUARD else run + timedelta(days=1)
def assert_attest_chain_quiet(cronjob: dict[str, Any], now: datetime) -> None:
"""The attest CronJob mounts the same database Secret; never revoke under it."""
spec = cronjob.get("spec", {})
if cronjob.get("status", {}).get("active"):
raise ProcedureError("an attest-chain Job is active; wait for it to finish")
if spec.get("suspend"):
return
run = next_daily_run(str(spec.get("schedule", "")), spec.get("timeZone"), now)
if abs(run - now) < ATTEST_GUARD:
raise ProcedureError("window is within 20 minutes of the attest-chain schedule")
def attest_chain_state(remote: Any) -> None:
cronjob = remote.kubectl_json(
["-n", "audit-core", "get", "cronjob", ATTEST_CRONJOB],
label="read attest-chain schedule",
)
assert_attest_chain_quiet(cronjob, datetime.now(UTC))
def pod_state(remote: Any) -> dict[str, Any]:
pods = remote.kubectl_json(
["-n", "audit-core", "get", "pods", "-l", "app.kubernetes.io/name=audit-core"],
label="read audit-core pod metadata",
).get("items", [])
pods = PREFLIGHT.serving_pods(
remote.kubectl_json(
["-n", "audit-core", "get", "pods", "-l", "app.kubernetes.io/name=audit-core"],
label="read audit-core pod metadata",
)
)
if len(pods) != 1:
raise ProcedureError(f"expected one audit-core pod, observed {len(pods)}")
statuses = pods[0].get("status", {}).get("containerStatuses", [])
@ -304,6 +345,7 @@ def exercise(args: argparse.Namespace) -> dict[str, Any]:
if not preflight["ready_for_live_execution"]:
raise ProcedureError("database lease recovery preflight is not ready")
attest_chain_state(remote)
before_pod = pod_state(remote)
before_secret = secret_state(remote)
lease = authority.single_runtime_lease()