FLEX-WP-0031-T01: decide emission atomicity (FLEX-DEC-2026-018)
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 7s

Commit before release for every class; on commit failure restrictive
effects are released and counted released_uncommitted, allow/audit_only
are withheld. Withholding a deny would be fail-open at open-stance
consumers. Measured cost ~1.1 ms per durable append on railiance01.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 307130@bnt-lap001
Assistant-Session: 270c79f7-0823-4b0d-990d-aad5af9935ce
This commit is contained in:
tegwick 2026-09-23 20:06:27 +02:00
parent 68b3d74df3
commit da520615e5
2 changed files with 74 additions and 3 deletions

View file

@ -2010,3 +2010,65 @@ the act scope. T03's own 57 checks pass.
**Rollout.** Deploying v3 changes the pin serving a live human-review surface,
and is confirmed with the operator before it runs. informed-decision updates its
admission input to the version and digest above once the pin serves v3.
---
## FLEX-DEC-2026-018 — A decision is released after its record commits, except that a restriction is never withheld
**Date:** 2026-09-23
**Status:** accepted
**Workplan:** `FLEX-WP-0031-T01`
**Authority:** `GH-DEC-2026-018` (left atomicity open pending the class
inventory, §9.4); inventory: `cadence.yaml` `classes`
**Question.** May a decision, and in particular a rare load-bearing `deny`, be
returned to the caller before its record is committed to the emission outbox?
**Today.** `Engine.Check` already appends the record before it returns, and
fails the check if the append fails. But no production pin passes
`--decision-log`, so the record lands only in process memory: every class is
released today without a durable record. Nothing below is a regression on
anything live.
**Ruling.**
1. **Commit before release, for every class.** A decision envelope leaves
flex-auth only after its record is durably committed to the local outbox.
Draining to `audit-core` stays asynchronous. Atomicity is with the local
commit, not with delivery.
2. **On a failed commit the rule splits by direction:**
| Effect | On commit failure | Why |
| --- | --- | --- |
| `deny`, `redact`, `not_applicable` | Released anyway, and counted as `released_uncommitted` per class | Withholding turns the decision into an error, and a consumer whose stance is open for that scope treats an error as proceed. That is `ops-warden` for `z0`-`z2` and `unknown`. Withholding a restriction would therefore manufacture a permission. |
| `allow`, `audit_only` | Withheld. The check answers 503 and the consumer's declared stance applies | Authority must never be granted without its evidence. `audit_only` exists only for its record. |
The asymmetry is the finding of this ruling. Strict atomicity applied to a
`deny` would be a fail-open path at open-stance consumers. A missing record
is a missed detection, which fails closed. A withheld deny can become a
permission, which fails open. The estate's fail-closed direction wins.
3. **The failure is visible, not silent.** `released_uncommitted` is exposed
next to the committed counts that reconciliation compares. A released but
unrecorded restriction shows up as divergence rather than disappearing.
Every non-zero value is a finding.
**Latency cost.** One durable local append per decision. Measured on
railiance01 on 2026-09-23: about **1.1 ms** per synchronous 1 KB write (`dd
oflag=dsync`, 2,000 writes in 2.26 s). The current check path is in-memory, so
a single caller pays up to about +1.1 ms per check. Group commit amortises the
cost under concurrency. `BatchCheck` commits the whole batch in one sync,
not one per resource.
**Consequences for `FLEX-WP-0031-T03`.**
- The outbox needs storage that survives pod deletion (a PVC). An `emptyDir`
loses the records and their counts together on reschedule. Reconciliation
cannot see that loss; only the heartbeat gap can.
- The node's root filesystem is at 84 % (2026-09-23). Size the volume and the
retention after drain before requesting it.
- `released_uncommitted` counters are in memory until the outbox is healthy
again, so a restart during an outbox outage loses them. This is the same
residual as the stated bound, not a new one.
**Bound.** Unchanged from `cadence.yaml`: none of this detects a compromised
flex-auth suppressing a record together with its own count.

View file

@ -4,7 +4,7 @@ type: workplan
title: "The decision record has a declared emission guarantee and nothing that delivers it"
domain: infotech
repo: flex-auth
status: ready
status: active
flavor: implementation
owner: claude
topic_slug: netkingdom
@ -14,7 +14,7 @@ related_workplans:
- FLEX-WP-0030
- FLEX-WP-0019
created: "2026-09-21"
updated: "2026-09-21"
updated: "2026-09-23"
state_hub_workstream_id: "84f5d9fe-b4c9-584a-b964-efe3e48af095"
---
@ -37,7 +37,7 @@ a compromised flex-auth suppressing a record and its own count together.
```task
id: FLEX-WP-0031-T01
status: todo
status: done
priority: high
state_hub_task_id: "2dbc225f-d762-537b-9a24-ab2b17fc2fa2"
```
@ -48,6 +48,15 @@ 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