Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 994874@bnt-lap001 Assistant-Session: 6017c416-d619-47eb-bd1a-f670a02d7d87
8.5 KiB
Bounded-executor execution-attribution receipts — owner answer
Answers NetKingdom NK-WP-0056-T02 (owner request
net-kingdom/docs/proposals/2026-10-31-nk56-t02-owner-requests.md, section
"audit-core"; hub message 755cbeed-ecbd-46d9-883e-958fbd88378b). Reviewed
2026-10-31 against net-kingdom/canon/standards/bounded-execution-admission_v0.1.md
section 6 and playbook-capability-contract_v0.2.md "Execution Attribution
Receipt (proposed)". Work: AUDIT-WP-0018. Canonical receipt validation remains
AUDIT-WP-0014-T02.
This is an owner proposal. It activates nothing: registration, ingress and the sender bearer require the owner deliveries and the approved live change listed at the end.
Answer 1 — will audit-core hold the receipts?
Yes. Audit-core accepts the evidence-holder role for the bounded executor's
receipts, as it already did for NK-WP-0040 (docs/cross-repo-evidence-admission.md,
"Execution-attribution receipts"). What changes is the emitter: no longer a
Railiance component but the NetKingdom-written executor, deployed by the new
rapp-* package, running as service:nk-bind-executor in namespace sso.
Holding means what it means for every other sender (docs/event-envelope.md):
audit-core authenticates the sender, checks source and tenant against the
registered identity, stores the event verbatim, and chains it. It records that
the executor said the run happened this way. It does not decide whether the
decision_refs authorized the run, and it does not authenticate the initiator
chain. That check belongs to the independent verifier in BEA section 6
("Verify"), which reads the receipt from audit-core and compares it with the
carrier record.
Until AUDIT-WP-0014-T02 ships canonical schema validation, an accepted receipt is generic JSON custody, not a schema-validated receipt. Audit-core will not describe it otherwise.
Answer 2 — one event, or separate events by run_id?
Both, in a fixed shape: one receipt event per run carries all per-step detail,
plus one started event emitted when the executor has consumed the approval and
before it takes its first step. Do not split steps into separate events.
Why the steps stay inside the receipt:
- The envelope stores
dataverbatim as one object (docs/event-envelope.md). Per-step outcomes, check results, revert yes/no and budget used are the sender's claims. They belong indata, where they read as claims. The storedoutcomeis alwaysrecordedandactoris alwaysnull(docs/event-envelope.md, "What the receiver derives"). - One receipt is the retained evidence unit. BEA section 6 says the receipt is "kept by hash". If steps were split into separate events, nobody could tell whether a step event was missing without a declared count. The receipt would then become a reconstruction, not a record.
- The validator then sees one document, so
decision_refs, budget and step list are checked together.
Why a separate started event is still needed: a receipt is emitted at the end
of a run. If the executor crashes mid-run after it has consumed its single
approval, it emits nothing. Without a second event, a run that partly executed
leaves no record in custody. With a started event, a started that has no
matching receipt after the admission window is detectable. Both events share the
run_id.
Event mapping (proposal)
Both events use the existing eight-field envelope. No envelope change is needed.
| Envelope field | started event |
Receipt event |
|---|---|---|
type |
execution.run.started |
execution.attribution.receipt |
id / Idempotency-Key |
run:<run_id>:started |
run:<run_id>:receipt |
source |
net-kingdom.bounded-executor (exact; emitter confirms) |
same |
subject |
admission:<spec.id> |
same |
tenant |
the admission's tenant, exact string (owner-bound, never inferred from namespace) | same; must equal data.tenant |
correlation_id |
<run_id> |
<run_id> |
occurred_at |
consume time, offset-aware | time_interval.end, offset-aware |
data |
run_id, admission_id, admission_digest, approval_ref, executing_workload, runtime_principal, plan_digest, steps_planned |
the canonical receipt (below) |
Each id is derived deterministically from run_id. A retry is therefore a
200 duplicate. A second, different body under the same id is a 409 conflict
(audit_core/ingestion.py response table), which exposes reuse of a run id.
Proposed receipt data. The schema belongs to NetKingdom (NK-WP-0040-T02);
this lists what audit-core needs to see in it:
{
"receipt_version": "<canonical schema version>",
"run_id": "…",
"initiator": {"iss": "…", "sub": "admin"},
"executing_workload": "service:nk-bind-executor",
"runtime_principal": "system:serviceaccount:sso:<sa>",
"tenant": "…",
"environment": "…",
"source_commit": "<plan.source.commit>",
"artifact_digest": "<plan_digest>",
"target": "<admission-level target>",
"action": "bounded-execution",
"decision_refs": [
{"kind": "admission", "id": "<spec.id>"},
{"kind": "admission_digest", "id": "sha256:…"},
{"kind": "approval", "id": "<carrier approval reference>"}
],
"time_interval": {"start": "…+00:00", "end": "…+00:00", "clock_source": "…"},
"steps": [
{"index": 1, "operation": "…", "target": "…", "action": "…",
"outcome": "succeeded|failed|skipped",
"checks": [{"id": "…", "result": "pass|fail|unknown"}]}
],
"reverted": false,
"budget": {"limit": {"…": 0}, "used": {"…": 0}},
"outcome": "succeeded|failed|reverted"
}
The points that fit the existing model and need NetKingdom to decide:
target/actionare singular in the canonical field list but per-step in BEA section 6. Proposal: the receipt-leveltargetis the admission's target and the receipt-levelactionisbounded-execution. Each step's target and action go insteps[]. Audit-core needs one answer in the schema. It will not pick one.decision_refsmust be typed. The admission id, the admission digest and the approval reference are three different things. An untyped list cannot be checked against the carrier.delegationstays absent when there is none.time_interval.boundstays absent when no bound is established. Unknown attribution stays the explicitunknown. This is unchanged fromdocs/cross-repo-evidence-admission.md.- Check results carry an id and a result only. They carry no response bodies, tokens, password values or LLDAP/OpenBao payloads.
Registration (proposal)
| Registry field | Proposed value | Reason |
|---|---|---|
name |
nk-bounded-executor |
one identity per executor deployment |
sources |
["net-kingdom.bounded-executor"] |
exact, no wildcard |
tenants |
the exact admission tenant(s) | not *; the executor acts for named tenants only |
may_write / may_read |
true / false |
the verifier reads with its own read identity |
secret_policy |
reject |
a receipt is kept by hash. If audit-core redacted it silently, the stored body would no longer match the hash the executor computed, so a secret-shaped field must refuse the event (400 secret_shaped_field). The executor treats that as a defect |
evidence_kind |
load-bearing, if the started + receipt pair is adopted. Otherwise attributive with an explicit completeness_trade |
the pair is what makes a missing receipt detectable |
Ingress: a new NetworkPolicy in deploy/networkpolicies.yaml admitting
namespace sso and the executor's exact pod labels, following
audit-core-tenant-engine-ingress. Do not admit the whole sso namespace,
because Authelia, LLDAP and KeyCape run there too. The bearer is
ESO-projected by railiance-platform, like the other in-cluster senders. No token
value appears in this repository.
What audit-core needs back
- NetKingdom (NK-WP-0040-T02 / NK-WP-0056):
- the versioned receipt schema, validator, path and digest, with positive and
negative fixtures, covering
steps,reverted,budgetand the typeddecision_refs; - an answer to point 1;
- the exact
sourceand event types; - the tenant binding;
- a decision on the
startedevent and the emission guarantee.
- the versioned receipt schema, validator, path and digest, with positive and
negative fixtures, covering
- The rapp-* package owner: the exact pod labels and ServiceAccount, and the image revision that emits.
- railiance-platform: the ESO projection of the sender bearer
(catalog/route via
warden route), and the attended seed of the registry entry. - Founder: the live registry entry and NetworkPolicy rollout are an approved live change. Audit-core prepares them; the founder runs them.