From da520615e58115835658742195ad9d42bd4f80d9 Mon Sep 17 00:00:00 2001 From: tegwick Date: Wed, 23 Sep 2026 20:06:27 +0200 Subject: [PATCH] FLEX-WP-0031-T01: decide emission atomicity (FLEX-DEC-2026-018) 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 Assistant: claude-code Assistant-Model: opus Assistant-Process: 307130@bnt-lap001 Assistant-Session: 270c79f7-0823-4b0d-990d-aad5af9935ce --- decisions/decisions.md | 62 +++++++++++++++++++ .../FLEX-WP-0031-decision-record-emission.md | 15 ++++- 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/decisions/decisions.md b/decisions/decisions.md index d595c02..e222bef 100644 --- a/decisions/decisions.md +++ b/decisions/decisions.md @@ -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. diff --git a/workplans/FLEX-WP-0031-decision-record-emission.md b/workplans/FLEX-WP-0031-decision-record-emission.md index 37050d4..503dacf 100644 --- a/workplans/FLEX-WP-0031-decision-record-emission.md +++ b/workplans/FLEX-WP-0031-decision-record-emission.md @@ -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