AUDIT-WP-0018: answer NK-WP-0056-T02 — hold bounded-executor receipts, propose event mapping
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s

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
This commit is contained in:
tegwick 2026-10-31 14:19:10 +01:00
parent d46e89ee21
commit 1ef939b38a
4 changed files with 258 additions and 0 deletions

View file

@ -28,6 +28,7 @@
| workplan | AUDIT-WP-0015 | blocked | — | workplans/AUDIT-WP-0015-independent-evidence-returns.md | | workplan | AUDIT-WP-0015 | blocked | — | workplans/AUDIT-WP-0015-independent-evidence-returns.md |
| workplan | AUDIT-WP-0016 | finished | — | workplans/AUDIT-WP-0016-wave2-resource-right-sizing.md | | workplan | AUDIT-WP-0016 | finished | — | workplans/AUDIT-WP-0016-wave2-resource-right-sizing.md |
| workplan | AUDIT-WP-0017 | proposed | — | workplans/AUDIT-WP-0017-production-audit-sources-and-correlation.md | | workplan | AUDIT-WP-0017 | proposed | — | workplans/AUDIT-WP-0017-production-audit-sources-and-correlation.md |
| workplan | AUDIT-WP-0018 | proposed | — | workplans/AUDIT-WP-0018-bounded-executor-receipt-custody.md |
| task | ADHOC-2026-09-14-T01 | done | — | workplans/ADHOC-2026-09-14.md | | task | ADHOC-2026-09-14-T01 | done | — | workplans/ADHOC-2026-09-14.md |
| task | ADHOC-2026-09-24-T01 | done | — | workplans/ADHOC-2026-09-24.md | | task | ADHOC-2026-09-24-T01 | done | — | workplans/ADHOC-2026-09-24.md |
| task | ADHOC-2026-10-15-T01 | done | — | workplans/ADHOC-2026-10-15.md | | task | ADHOC-2026-10-15-T01 | done | — | workplans/ADHOC-2026-10-15.md |
@ -111,6 +112,9 @@
| task | AUDIT-WP-0017-T05 | todo | — | workplans/AUDIT-WP-0017-production-audit-sources-and-correlation.md | | task | AUDIT-WP-0017-T05 | todo | — | workplans/AUDIT-WP-0017-production-audit-sources-and-correlation.md |
| task | AUDIT-WP-0017-T06 | todo | — | workplans/AUDIT-WP-0017-production-audit-sources-and-correlation.md | | task | AUDIT-WP-0017-T06 | todo | — | workplans/AUDIT-WP-0017-production-audit-sources-and-correlation.md |
| task | AUDIT-WP-0017-T07 | wait | — | workplans/AUDIT-WP-0017-production-audit-sources-and-correlation.md | | task | AUDIT-WP-0017-T07 | wait | — | workplans/AUDIT-WP-0017-production-audit-sources-and-correlation.md |
| task | AUDIT-WP-0018-T01 | done | — | workplans/AUDIT-WP-0018-bounded-executor-receipt-custody.md |
| task | AUDIT-WP-0018-T02 | wait | — | workplans/AUDIT-WP-0018-bounded-executor-receipt-custody.md |
| task | AUDIT-WP-0018-T03 | wait | — | workplans/AUDIT-WP-0018-bounded-executor-receipt-custody.md |
| intake | AUDIT-IN-0001 | closed | — | intakes/intakes.md | | intake | AUDIT-IN-0001 | closed | — | intakes/intakes.md |
| intake | AUDIT-IN-0002 | closed | — | intakes/intakes.md | | intake | AUDIT-IN-0002 | closed | — | intakes/intakes.md |
| intake | AUDIT-IN-0003 | closed | — | intakes/intakes.md | | intake | AUDIT-IN-0003 | closed | — | intakes/intakes.md |

View file

@ -0,0 +1,165 @@
# 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 `data` verbatim 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 in `data`, where they read as claims. The stored
`outcome` is always `recorded` and `actor` is always `null`
(`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:
```json
{
"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:
1. **`target`/`action` are singular in the canonical field list but per-step in
BEA section 6.** Proposal: the receipt-level `target` is the admission's
target and the receipt-level `action` is `bounded-execution`. Each step's
target and action go in `steps[]`. Audit-core needs one answer in the schema.
It will not pick one.
2. **`decision_refs` must 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.
3. **`delegation` stays absent** when there is none. `time_interval.bound` stays
absent when no bound is established. Unknown attribution stays the explicit
`unknown`. This is unchanged from `docs/cross-repo-evidence-admission.md`.
4. **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`, `budget` and the typed
`decision_refs`;
- an answer to point 1;
- the exact `source` and event types;
- the tenant binding;
- a decision on the `started` event and the emission guarantee.
- **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.

View file

@ -139,6 +139,10 @@ ordered offset-aware start/end if that is the canonical rule. No independent
local receipt schema is shipped ahead of the owner schema, and generic JSON local receipt schema is shipped ahead of the owner schema, and generic JSON
acceptance is not advertised as receipt-schema validation. acceptance is not advertised as receipt-schema validation.
NK-WP-0056 names a new emitter, the NetKingdom bounded executor in `sso`. Its
mapping (one receipt per run plus a `started` event, correlated by `run_id`) is in
`docs/bounded-executor-receipts.md` (AUDIT-WP-0018).
## Independent chain-head custody — AUDIT-WP-0015-T01 ## Independent chain-head custody — AUDIT-WP-0015-T01
RESOURCE-WP-0002-T06 is done for encrypted logical database copies. That RESOURCE-WP-0002-T06 is done for encrypted logical database copies. That

View file

@ -0,0 +1,85 @@
---
id: AUDIT-WP-0018
type: workplan
title: "Hold bounded-executor execution-attribution receipts"
domain: infotech
repo: audit-core
status: proposed
flavor: implementation
owner: audit-core
topic_slug: audit-evidence-dependencies
created: "2026-10-31"
updated: "2026-10-31"
related:
- NK-WP-0056
- NK-WP-0040
- AUDIT-WP-0014
---
# AUDIT-WP-0018 — Hold bounded-executor execution-attribution receipts
NetKingdom's Bounded Execution Admission v0.1 makes a non-human executor
(`service:nk-bind-executor`, namespace `sso`) emit one execution-attribution
receipt per run. Audit-core holds those receipts. The owner answer and the event
mapping are in `docs/bounded-executor-receipts.md`. Canonical schema validation
is still AUDIT-WP-0014-T02 and is not duplicated here.
## Answer the NK-WP-0056-T02 owner request
```task
id: AUDIT-WP-0018-T01
status: done
priority: high
```
Hub message 755cbeed-ecbd-46d9-883e-958fbd88378b. Audit-core accepts custody.
Each run produces one receipt event that carries the per-step outcomes, check
results, `reverted` and `budget`. Each run also produces an
`execution.run.started` event, correlated with the receipt by `run_id`. Event
ids are deterministic from `run_id`. The proposed `secret_policy` is `reject`.
NetKingdom has to decide how a singular `target`/`action` relates to per-step
targets and actions, and has to make `decision_refs` typed.
## Prepare sender registration and ingress for the executor
```task
id: AUDIT-WP-0018-T02
status: wait
priority: high
blocking_reason: "NetKingdom must confirm source, event types, tenant binding and the started event; the rapp-* package must name pod labels and ServiceAccount; railiance-platform must route the ESO-projected bearer."
blocked_on: message-from:net-kingdom
```
Prepare the following:
- a non-secret registry entry (`nk-bounded-executor`: exact source, exact
tenants, write only, `secret_policy: reject`, and either `evidence_kind`
`load-bearing` or `attributive` with a `completeness_trade`);
- a NetworkPolicy that admits only the executor's pods in `sso`.
Then test on synthetic fixtures:
- accept on 202;
- a retry returns a 200 duplicate;
- a reused `run_id` with a different body is a 409 conflict;
- wrong source or tenant is refused;
- a secret-shaped step check is refused with 400.
The live registry seed and the rollout are founder-run.
## Detect runs that started without a receipt
```task
id: AUDIT-WP-0018-T03
status: wait
priority: medium
blocking_reason: "Depends on NetKingdom adopting the execution.run.started event and stating the admission window bound after which a missing receipt is a finding."
blocked_on: message-from:net-kingdom
```
Report every `execution.run.started` that has no
`execution.attribution.receipt` with the same `correlation_id` after the
declared window. Reuse the existing stream-completeness/cadence surface
(`docs/stream-completeness.md`, `docs/cadence-registration.md`) instead of
adding a new monitor. The report is a finding for the verifier and the
founder. Audit-core does not infer what the run did.