--- id: FLEX-WP-0031 type: workplan title: "The decision record has a declared emission guarantee and nothing that delivers it" domain: infotech repo: flex-auth status: active flavor: residual owner: claude topic_slug: netkingdom planning_priority: P1 planning_order: 310 related_workplans: - FLEX-WP-0030 - FLEX-WP-0019 created: "2026-09-21" updated: "2026-10-10" state_hub_workstream_id: "84f5d9fe-b4c9-584a-b964-efe3e48af095" --- # FLEX-WP-0031 — Deliver the decision-record emission guarantee `GH-DEC-2026-018` ruled that flex-auth is the §4 source of evidence for the decision record, and that it is not conforming on §11 until it declares and delivers a per-event-class emission guarantee. The declaration is published (`cadence.yaml`, `FLEX-WP-0030-T07`). Nothing emits: the decision record reaches consumers only in the `/v1/check` response, no sender named flex-auth or `access-engine` is registered with `audit-core`, and there is no outbox, heartbeat or reconciliation count. This plan closes declared gap **G2** (`docs/conformance/security-layer-conformance.md`, review 2026-10-19). Bound, stated up front so no argument rests on more: heartbeat and reconciliation detect loss, outage, drain failure and accident. Neither detects a compromised flex-auth suppressing a record and its own count together. ## 1. Decide emission atomicity ```task id: FLEX-WP-0031-T01 status: done priority: high state_hub_task_id: "2dbc225f-d762-537b-9a24-ab2b17fc2fa2" ``` `GH-DEC-2026-018` left open whether decision-record emission must be atomic with the decision (§9.4), pending the class inventory. It exists now. Decide whether a rare load-bearing decision (`deny`) may be returned before its record is committed to the outbox, and record the answer as a `FLEX-DEC`. Gate: decided, with the latency cost stated. Done 2026-09-23: `FLEX-DEC-2026-018`. Every decision is released only after its record commits to the local outbox. When the commit fails, restrictive effects (`deny`, `redact`, `not_applicable`) are still released and counted as `released_uncommitted`, while `allow` and `audit_only` are withheld (503). Withholding a deny would turn it into an error that an open-stance consumer treats as proceed. The measured cost is about 1.1 ms per durable append on railiance01. The ruling puts two requirements on T03: the outbox needs a PVC, and the node disk is at 84 %. ## 2. Register flex-auth as an audit-core sender ```task id: FLEX-WP-0031-T02 status: wait blocked_on: message-from:audit-core blocking_reason: "GH-WP-0004-T12 refused the load-bearing failure-path exception (GH-DEC-2026-024). AUDIT-IN-0006 sender rows and governed token delivery remain pending." priority: high state_hub_task_id: "89661908-ca9a-5e4a-a0a5-62d1a9e02568" ``` Open an intake with `audit-core` (worked examples `AUDIT-IN-0002`, `AUDIT-IN-0003`): sender registration, `evidence_kind`, `heartbeat_classes` per class exactly as `cadence.yaml` publishes them, and a token lane routed via `warden route find`. audit-core has said it accepts the classification as supplied and will not infer it. Gate: sender registered; no secret in any file. Requested 2026-09-23 (hub message `6044ed35`). The request asks for six senders, one per pin (`flex-auth.`), because each pin can only reconcile its own count. It also asks for `tenants: ["*"]` with a justification, `may_read: true` for `GET /v1/reconciliation`, and per-class `heartbeat_classes` at 86400 s for the four rare classes. It asks audit-core to rule on whether FLEX-DEC-2026-018's failure-path exception counts as a `completeness_trade`, which `senders.py` forbids for a load-bearing source. warden route has no catalog lane for audit-core sender tokens, and audit-core was asked to name one. Waiting on audit-core. 2026-09-27: audit-core replied (`AUDIT-IN-0006`, thread `6044ed35`), accepted with corrections: `may_read` must be `false`, not `true` (a writer already counts its own sources without it); `tenants: ["*"]` accepted on the stated justification; per-class heartbeats and the `cadence.yaml` correction accepted (done, see T05); each registration must also carry an `emission_cadence` declaration (info-tech-canon wire schema 0.1, contract digest `b08b4d95fc4b0bd3`) with `allow` as expected-rate, or its rate is only declared and not evaluated. Two envelope corrections land before submission: the heartbeat event needs its own `correlation_id` (no decision id exists for it), and `occurred_at` needs an explicit offset. The atomicity ruling (whether the `FLEX-DEC-2026-018` failure-path release is a `completeness_trade`) is referred to `gate-house` by audit-core, not decided here — flex-auth agreed in reply to wait for that referral. The token lane is an attended OpenBao mint in the founder's terminal (same path `tenant-engine` used, `AUDIT-WP-0010-T02`); `ops-warden` has no catalog lane for it yet and will add one when registration opens. flex-auth acknowledged all of this in reply. Still `wait`: the mint needs the founder, and `ops-warden`'s catalog entry is not yet in place. ## 3. Durable outbox and the release rule ```task id: FLEX-WP-0031-T03 status: done priority: high state_hub_task_id: "a59603d5-8954-5b05-b1a0-b143c82e439b" ``` Split on 2026-09-23 under the per-task budget. T05 and T06 carry the rest of the original scope, and the original gate is divided between the three. Done 2026-09-23: - `internal/emission`: a durable outbox with one fsync per commit. A batch commits once. Each event carries exactly the eight fields audit-core's `normalize()` requires. - Event ids are random and the decision id travels as `correlation_id`. Decision ids are content digests, so two identical checks share one, and idempotency would merge them. - A torn tail is cut on open, and a failed commit is cut back to the last committed size. Committed counts per class are rebuilt on restart. - The engine applies `FLEX-DEC-2026-018` in `Check`, `BatchCheck` and `ListAllowed`. A withheld decision answers 503. - `serve --outbox-dir --emission-source` enables it, and `GET /v1/emission` reports committed and `released_uncommitted` counts per class. - `api.DecisionEffects()` lists the effect vocabulary. A source-parsing test fails on any constant it misses, and a second test fails unless the load-bearing classes in `cadence.yaml` equal that vocabulary. - `make test` (race) passes, and a local serve smoke test committed and counted one deny and one allow. Nothing is deployed and no chart changed. ## 5. Implement heartbeat and drain to audit-core ```task id: FLEX-WP-0031-T05 status: done priority: high state_hub_task_id: "14bd6648-11da-53d7-a512-027005bd4772" ``` 2026-10-04: split implementation from native delivery acceptance (new T08). `internal/emission/delivery.go` implements daily per-class `audit-core.heartbeat` events with independent correlation IDs and offset timestamps, persisted in our durable outbox. Restart rebuilds each class's last heartbeat from the log. Heartbeats expose committed and released-uncommitted counts; a known failure changes the assertion to `released-uncommitted-finding`. The asynchronous drain sends the eight-field envelope with event-ID idempotency, reads a mounted token file per event for rotation, refuses redirects, and advances a fsynced/renamed cursor only on 200 or 202. Every other response, transport failure or cursor-write failure retains the event. A bounded drain runs each minute, without holding the decision lock during HTTP. `GET /v1/emission` exposes pending depth, oldest pending timestamp and lag-bound exceedance (100 events / 1h). No response body or token is logged. `serve --audit-url --audit-token-file ` enables the worker only with an outbox. Both default empty, and no pin enables it. Local tests cover per-class cadence, restart, rejection, stable replay, ambiguous acceptance, token rotation and cursor durability. The current outbox retains acknowledged log entries; retention/compaction is not implemented, so capacity planning must include cumulative log growth. ## 6. Reconciliation, profile check, and storage ```task id: FLEX-WP-0031-T06 status: wait depends_on: [FLEX-WP-0031-T02] blocked_on: message-from:audit-core blocking_reason: "Receiver counts use accepted_at, while local counts use commit time; audit-core must supply compatible window semantics or durable acceptance timestamps before a valid comparator can be implemented." priority: high state_hub_task_id: "bf92a951-3b69-59dd-8907-f6748c91a264" ``` 2026-09-27: reconciliation compare needs `audit-core`'s `GET /v1/reconciliation`, which does not exist until T02's sender is registered. The profile checker (`net-kingdom/tools/emission-cadence-profile`) is runnable locally but needs a `--contract-schema` for the decision-record cadence contract that is not yet published in this repo or referenced by any sibling's invocation found — inventing one here would be exactly the kind of schema flex-auth should publish deliberately, not improvise for a validation run. The PVC chart change is explicitly a production change needing the founder's go-ahead and was left undone. Stays `wait`. - Compare committed counts per class and window with audit-core's `GET /v1/reconciliation`. Divergence is a finding, and undrained events count as lag, not divergence. - Validate `cadence.yaml` with the net-kingdom emission-cadence profile checker, supplying the inventory as `--rare-load-bearing`/`--load-bearing`. - Add an optional PVC to the chart for the outbox, sized against the node disk (84 % on 2026-09-23). Rolling it out to any pin is a production change and needs the founder's go-ahead. 2026-10-04: storage/profile preparation is now complete as T07. The remaining comparator is independently blocked by receiver time semantics, not just a missing registration: audit-core counts accepted_at in half-open windows, while our log timestamps local commits. Ingest returns status/reference only. A receiver-approved matching basis or stable acceptance-time receipts (also on duplicates) is required before implementing a comparison. Do not widen may_read to fetch archive payloads as a workaround. T02 and T08 still gate native custody/rollout evidence; see the dependency record for exact returns. ## 4. Close G2 ```task id: FLEX-WP-0031-T04 status: wait depends_on: [FLEX-WP-0031-T08, FLEX-WP-0031-T06] blocking_reason: "G2 needs T08 native heartbeat/drain and silence-finding evidence plus T06 reconciliation; local tests cannot close the delivery gap." priority: medium state_hub_task_id: "423b3090-1b72-58fd-9353-5c907c7683bb" ``` 2026-09-27: closing G2 requires the silence-finding gate, which requires T05's sender and T06's reconciliation to actually exist. Both are blocked on T02 (external, `audit-core`). Stays `wait`. Change `cadence.yaml` `state` to emitting, move G2 out of the gap table with the evidence, and tell `gate-house`, `audit-core` and `kings-guard`. Gate: a silence finding is observed on a deliberately withheld heartbeat in a non-production run. ## 7. Prepare persistent storage and validate the cadence contract ```task id: FLEX-WP-0031-T07 status: done priority: high state_hub_task_id: "151fe609-ba68-506b-9c2c-9fdb0b149eef" ``` Split from T06 on 2026-10-04; its historical schema/storage blockers above are superseded by this implementation. `charts/flex-auth` has optional outbox PVC, mount, source and flags, defaults disabled, a one-replica constraint and Recreate strategy to avoid two writers during rollout. It supports an existing claim. Rendering is tested with caller auth enabled and disabled. No values pin or live deployment is changed; 1Gi is a configurable preparation value, not an assertion of available node space. Published `docs/contracts/decision-emission-cadence.json` against the actual InfoTechCanon wire schema and NetKingdom profile. All five decision classes are explicitly supplied; contract/profile assessment passes. The generic expected-rate schema forbids reconciliation on that entry and the profile forbids duplicate classes, so allow's additional reconciliation guarantee is in the `extensions.flex-auth` namespace. Receiver acceptance of that mapping remains part of T02; a profile pass does not establish observer delivery. ## 8. Admit and exercise native heartbeat/drain delivery ```task id: FLEX-WP-0031-T08 status: wait priority: high depends_on: [FLEX-WP-0031-T02] needs_human: true blocking_reason: "Needs admitted sender/token mounts, founder approval of exact image/PVC/network rollout, and audit-core observer evidence; no production delivery is enabled." state_hub_task_id: "b3b52b30-c73d-5a90-9935-d98afe5a9eaf" ``` Remaining acceptance split from T05: audit-core's owner must return admitted exact per-pin sources, scope, heartbeat classes and cadence mapping under AUDIT-IN-0006. Platform must provide governed attended token custody and mount references, never values. The founder must approve exact immutable image, PVC capacity and rollout; network owners must admit the decision pins to the receiver. Record native 202 and idempotent 200 delivery, restart cursor/lag behaviour, and an audit-core silence finding from a separately authorized non-production withheld-heartbeat exercise. No test engagement/window is created by this workplan. T06 still owns the compatible-time reconciliation implementation and its native acceptance. See `docs/blocked-task-dependencies-2026-10-04.md` for the concrete dependency gates. 2026-10-04 review: workplan stays blocked. Local heartbeat/drain and storage preparation no longer wait on another repository, but admission, statutory ruling, valid reconciliation time basis and approved native acceptance do. `cadence.yaml` remains `declared-not-yet-emitting` and G2 remains open. ## 9. Enforce the accepted mandatory-emission ruling in source ```task id: FLEX-WP-0031-T09 status: done priority: high state_hub_task_id: "02a8d006-cc70-5a0e-b773-6924f05540ed" ``` 2026-10-10: implemented `GH-DEC-2026-024` §3, the accepted refusal of load-bearing failure-path release. This corrects the source release behavior described under T01/T03; those dated entries remain historical evidence of `FLEX-DEC-2026-018`, not the current implementation. No new human decision is needed to implement the accepted doctrine in source. With a required recorder attached, a failed durable append now withholds every effect, including deny/redact/not_applicable. Single checks, restriction-only batches, list-allowed and application readback retain the existing `ErrRecordNotCommitted` / HTTP 503 availability representation. No replacement allow or deny is manufactured. Uncommitted results are not inserted into Explain's history, and no released-uncommitted counter is incremented. Existing historical outbox counters and their reporting remain readable. Standalone operation without an attached recorder remains unchanged; no source change enables production emission or claims that an unconfigured pin is conformant. Tests inject commit failure across the complete five-effect vocabulary, check that Explain cannot expose uncommitted results and can expose committed ones, exercise actual restrictive single/batch/list policy paths, and verify restrictive enforcement and readback HTTP 503. Affected package suites pass: `go test ./internal/decision ./internal/emission ./internal/readback ./cmd/access-engine ./pkg/api`. This source fix is not deployed. Consumer outage behavior remains governed by each declared PEP stance: a consumer with an open-for-scope stance can treat unavailable evaluation as proceed, so the exact rollout must review those stances rather than claim that a 503 enforces denial. T02 native sender/custody, T06 compatible reconciliation windows, T08 exact approved rollout and silence proof, and T04 G2 closure remain unfinished. `cadence.yaml` remains declared, and the emission completeness gap remains open until native acceptance. ### T08 independent chart delivery preparation — 2026-10-11 Prepared inert chart `auditDelivery` configuration for existing admitted per-pin Secret/key directory mount, explicit /v1/events endpoint and narrow receiver/DNS egress. Supports existing `serve` pins only; unsupported readback flags refuse. No pin defaults, native rollout, credential or sender registration changes. Renderer tests pass for auth on/off, safe mounts/scoped peers, disabled defaults and invalid/partial configurations. Mandatory-commit/emission/readback/HTTP/API Go suites pass. Exact remaining image/PVC/custody/network/receiver and same-trial native receipts are in `docs/audit-delivery-rollout-preparation.md`. T08 remains wait; T06 receiver time basis and T04 native G2 acceptance remain open. T08 preparation revision 2: independent Platform review found fractional YAML/ JSON receiver ports could survive integer truncation and render invalid Kubernetes NetworkPolicy. Corrected integer-shape guard plus 22 YAML/JSON rejection cases and integer boundary rendering passed. Original stage retained; no native change. > 2026-10-11: demoted to `flavor: residual` / `backlog` by founder-authorized triage — off the > critical path (identity → authz → secrets → SecurityCase execution). Tasks are unchanged; > promote back when demand or risk appears. See the-custodian > `history/20261011-netkingdom-security-progress-assessment.md`.