--- 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: implementation 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-09-23" 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 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. ## 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. Heartbeat and drain to audit-core ```task id: FLEX-WP-0031-T05 status: todo priority: high state_hub_task_id: "14bd6648-11da-53d7-a512-027005bd4772" ``` - Emit a daily `audit-core.heartbeat` event per rare class, with `data.class` set to the class, following audit-core's `stream_findings` shape. - Correct `cadence.yaml`, which still names a single `flex-auth.decision.heartbeat` class. - Drain committed events to `POST /v1/events` with `Idempotency-Key` set to the event id, keep a drain cursor, and expose outbox depth and age against the `lag_bound`. - The drain stays disabled until T02 admits the senders. ## 6. Reconciliation, profile check, and storage ```task id: FLEX-WP-0031-T06 status: todo priority: high state_hub_task_id: "bf92a951-3b69-59dd-8907-f6748c91a264" ``` - 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. ## 4. Close G2 ```task id: FLEX-WP-0031-T04 status: todo priority: medium state_hub_task_id: "423b3090-1b72-58fd-9353-5c907c7683bb" ``` 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.