NK-WP-0056-T15: receipt schema v0.1, validator and executor fixtures; audit sender exercise

- canon/schemas/execution-attribution-receipt_v0.1.schema.json: the bounded-executor profile
  of the PCC v0.2 receipt, both audit-core events (started, receipt), every object closed.
- tools/execution-attribution-receipt: EAR-1..EAR-10 validator, fixtures generated from the
  real executor (completed, reverted, completed-on-retry) with a drift check.
- executor: started carries receipt_version, digests, steps_planned and data.window.not_after
  (audit-core AUDIT-WP-0018-T03 request); receipt carries receipt_version and initiator.iss
  (configured approver_issuer, else unknown).
- bounded-executor/exercise_audit_sender.py: real executor and client against audit-core's
  real ingestion and the Git scope overlay: 202, duplicate 200, conflict 409 as a defect,
  wrong source/tenant/token refused and not stored, a receipt-less start flagged by its own
  window.

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

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 523928@bnt-lap001
Assistant-Session: 6c737afb-9c14-4152-9f02-477214b9c350
This commit is contained in:
tegwick 2026-11-04 01:39:57 +01:00
parent 1d4b7e84d7
commit d7133e3a04
15 changed files with 1506 additions and 7 deletions

View file

@ -43,6 +43,7 @@ _spec.loader.exec_module(bea)
ACTION = "bounded-execution"
STARTED = "execution.run.started"
RECEIPT = "execution.attribution.receipt"
RECEIPT_VERSION = "0.1" # canon/schemas/execution-attribution-receipt_v0.1.schema.json
class Refused(Exception):
@ -81,6 +82,7 @@ class Config:
approver_subjects: dict[str, str] # admission approver name -> carrier subject_id (no guessing)
source_root: pathlib.Path = REPO
clock_source: str = "system-utc"
approver_issuer: str = "unknown" # the approvers' token issuer; recorded as `unknown` unless configured
@dataclasses.dataclass
@ -320,8 +322,9 @@ class Run:
def _receipt(self, outcome: str, attempts: int) -> dict:
end = self.d.wall()
return self._event(RECEIPT, occurred_at=end, body={
"receipt_version": RECEIPT_VERSION,
"run_id": self.run_id,
"initiator": {"sub": self.approver_subject, "role": "approver"},
"initiator": {"iss": self.cfg.approver_issuer, "sub": self.approver_subject, "role": "approver"},
"executing_workload": self.cfg.executor_identity,
"runtime_principal": self.cfg.runtime_principal,
"tenant": self.cfg.tenant, "environment": self.cfg.environment,
@ -358,10 +361,16 @@ class Run:
self.d.alert("consume_unconfirmed", {"admission": self.spec["id"], "run_id": self.run_id})
return {"outcome": "refused", "reason": str(refusal), "run_id": self.run_id, "effects": "consume-only"}
try:
self.d.audit.emit(self._event(STARTED, {"run_id": self.run_id, "admission": self.spec["id"],
"approval": self.approval_id, "consumed_at": self.consumed_at,
"executing_workload": self.cfg.executor_identity},
occurred_at=self.d.wall()))
self.d.audit.emit(self._event(STARTED, {
"receipt_version": RECEIPT_VERSION, "run_id": self.run_id, "admission": self.spec["id"],
"admission_digest": bea.admission_digest(self.doc), "plan_digest": self.spec["plan_digest"],
"approval": self.approval_id, "consumed_at": self.consumed_at,
"executing_workload": self.cfg.executor_identity, "runtime_principal": self.cfg.runtime_principal,
"steps_planned": len(self.spec["plan"]["steps"]),
# audit-core holds each run to its own window (AUDIT-WP-0018-T03): a started event without a receipt
# becomes a finding after the earlier of this and the sender's declared bound.
"window": {"not_after": iso(bea.parse_time(self.spec["window"]["not_after"]))}},
occurred_at=self.d.wall()))
except Exception:
self.d.alert("started_not_in_custody", {"admission": self.spec["id"], "run_id": self.run_id})
return {"outcome": "stopped", "reason": "audit_unavailable", "run_id": self.run_id, "effects": "consume-only"}