access-engine/workplans/FLEX-WP-0031-decision-record-emission.md
custodian-sync 3d6b52e8d7 chore(consistency): renormalize lifecycle state [auto]
Updated by fix-consistency on 2026-10-11:
  - workplan status: backlog → active

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1370019@bnt-lap001
Assistant-Session: d462dd63-16de-4189-ae70-b9cd6566bf87
2026-10-11 20:37:55 +02:00

17 KiB

id type title domain repo status flavor owner topic_slug planning_priority planning_order related_workplans created updated state_hub_workstream_id
FLEX-WP-0031 workplan The decision record has a declared emission guarantee and nothing that delivers it infotech flex-auth active residual claude netkingdom P1 310
FLEX-WP-0030
FLEX-WP-0019
2026-09-21 2026-10-10 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

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

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.<pin>), 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

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

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 <explicit /v1/events endpoint> --audit-token-file <mount> 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

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

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

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

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

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.