flex-auth/workplans/FLEX-WP-0031-decision-record-emission.md
tegwick 92981698d8
Some checks are pending
CI Smoke / host-smoke (push) Waiting to run
CI Smoke / container-smoke (push) Waiting to run
Close FLEX-WP-0029 second edition; correct cadence.yaml; block human/external-gated workplans
FLEX-WP-0029: publish the second stance-register edition across five rows
(a third scope axis, not a converging two — tenant-engine scopes on
engine-reachability, not security-zone), record Finding 1 as resolved by
gate-house doctrine rather than by either side, and note Finding 3 as still
open in secrets-engine's file. First edition marked superseded, not amended.
SCOPE.md's G3 gap closed accordingly.

FLEX-WP-0031: correct cadence.yaml to declare one heartbeat per rare
load-bearing class instead of a single combined class (tests pass unchanged).
Acknowledged audit-core's AUDIT-IN-0006 reply on T02 and recorded its
corrections; the remaining work (drain, reconciliation, PVC rollout, G2
closure) stays wait/blocked pending the founder's attended OpenBao mint and
gate-house's atomicity ruling, so the workplan moves to blocked.

FLEX-WP-0027: marked blocked — the sole remaining task needs the operator's
own signed-in account, an irreducible human action.

FLEX-WP-0020: recorded net-kingdom's T04 update (NK-WP-0039-T02 done,
runtime.yaml digests current) and replied with no objection to their
ADR-0015 values-pointer proposal for the drifted runtime.yaml reference.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 250108@bnt-lap001
Assistant-Session: bab3d5bd-b0bb-42d0-bf80-94ed6fc2b08a
2026-09-27 22:12:35 +02:00

9.1 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 blocked implementation claude netkingdom P1 310
FLEX-WP-0030
FLEX-WP-0019
2026-09-21 2026-09-27 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
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. Heartbeat and drain to audit-core

id: FLEX-WP-0031-T05
status: wait
priority: high
state_hub_task_id: "14bd6648-11da-53d7-a512-027005bd4772"

2026-09-27: cadence.yaml's heartbeat declaration corrected — it now names one class per rare load-bearing event (deny, redact, not_applicable, audit_only) instead of a single combined flex-auth.decision.heartbeat class. go build/go test ./... pass unchanged. The remaining scope — the actual per-class heartbeat sender and the drain to POST /v1/events — is correctly blocked on T02's sender registration with audit-core; the task stays wait rather than in progress here.

  • 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

id: FLEX-WP-0031-T06
status: wait
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.

4. Close G2

id: FLEX-WP-0031-T04
status: wait
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.