gate-house/decisions/decisions.md
tegwick f0f888ca7e Close the binding-correspondence gap as GH-DEC-2026-008
access-engine raised, and declined to solve locally, a hole in the split
GH-DEC-2026-005 ruled on. approval-claim verification item 4 is a
disjunction and neither limb delivers "approved for THIS request" on the
PDP path: limb one requires translating between two engines' vocabularies
and no mapping is published, limb two (pdp_digest) is optional. Where the
digest is absent a consumer can hold valid_now true, receive an ALLOW,
consume and act with nothing establishing that approval and decision
concern the same action and target.

Ruled: the PDP digest is the correspondence and is required on that path;
a claim without one fails closed; the native limb survives only for
consumers already in approval-engine's vocabulary, including T-06. No
mapping is published — a translation can be wrong while still producing a
confident answer, it fails open, it would be owned by neither engine, and
recomputing another layer's binding is the re-derivation GH-DEC-2026-005
already forbids. The cost is stated: an approval issued without a bound
CheckRequest is unusable on this path, which is correct behaviour.

Also: adopted hub row b606e8ce as canonical for GH-DEC-2026-005 rather
than registering a duplicate; recorded approval-engine's narrowing of the
approver-threshold consequence (distinctness is a UNIQUE storage
invariant, so the PEP stopped checking that the engine applied its own
invariant, not whether dual control could be forged); and drafted A7/T08,
a §11 marking obligation and §12 consumer rule for derived summaries,
after four instances in one week across four repositories.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WtJBr77gMFLrN93iEevqQJ

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 425128@bnt-lap001
Assistant-Session: f5944d8b-dac4-4e1a-87eb-8b3d8f314a63
2026-09-06 09:32:20 +02:00

1074 lines
52 KiB
Markdown

# Decision records
## GH-DEC-2026-001 — NetKingdom security layer model, the gate-house re-cut, and the access-engine reframing
```yaml
id: GH-DEC-2026-001
kind: decision
title: NetKingdom security layer model, the gate-house re-cut, and the access-engine
reframing
status: resolved
owner: Bernd Worsch
repo: gate-house
standard: net-kingdom/canon/standards/security-layer-model_v0.1.md
source_note: history/2026-08-28-security-layer-model-and-gate-house-recut.md
requested_dispositions:
- approved
- revised
- rejected
affects:
- gate-house
- flex-auth
- ops-warden
- ops-mason
- kings-guard
- whitehat-security
- net-kingdom
- zone-engine
created: '2026-08-28T19:15:15.607849Z'
updated: '2026-08-28T19:16:01.456127Z'
rationale: 'Approved in session on 2026-08-28. The three rulings were taken interactively:
Staff as the layer name, net-kingdom canon as the model''s home, and access-engine
as the rename target with the lane/rule demarcation accepted as its cost. Approval
covers the doctrine and documents only; the flex-auth rename remains a separate
governed migration, and the standard stays proposed pending assent from flex-auth,
kings-guard, and ops-warden.'
decided_by: Bernd Worsch
decided_at: '2026-08-28T19:16:01.456127Z'
state_hub_decision_id: "2d6509d0-ffd2-4209-89ab-bf68a4945ada"
```
## Context
The security estate acquired overlapping claims to the same responsibility, and
the overlap was invisible in each repository's own documents. `gate-house` was
seeded as a deterministic authority plane — a policy decision point with an
`/authorize` API, grant storage, and a revocation service — while `flex-auth`
already described itself as the authorization control plane and was actively
delivering `FLEX-WP-0017`, an approval contract binding approvals to action,
actor, target, and validity window. Neither repository's INTENT named the other.
`zone-engine` had already been ruled against on the same question.
Full review and evidence: `history/2026-08-28-security-layer-model-and-gate-house-recut.md`.
## Decision requested
Ratify three linked rulings. They are one decision because the second does not
hold without the first, and the third is the first applied to the repository
that was already right.
### Ruling 1 — Adopt the NetKingdom security layer model
The estate is layered **Taxonomy → Tooling → Engines → Staff**, distinguished by
determinism and by the kind of artifact each layer produces. The top layer is
named **Staff** — the general-staff sense of plans and doctrine without
execution — not "Helpers", which undersold a layer holding architecture,
controlling, and change.
The model is published as `net-kingdom/canon/standards/security-layer-model_v0.1.md`,
owned by gate-house, status `proposed`. It belongs in net-kingdom canon rather
than info-tech-canon because it is NetKingdom-flavored security architecture,
not general semantic contract.
It carries two normative rules:
- **Staff never touches Tooling directly. It acts only through Engine APIs.**
- **`access-engine` is the only policy decision point**, generalizing to the
whole estate the ruling first drawn in `zone-engine/INTENT.md` §5.
### Ruling 2 — Re-cut gate-house as the doctrine council
Gate House is Staff: the council where NetKingdom's security and defence
doctrine is established, documented, taught, and supervised. It holds no
runtime position.
The decisive argument is gate-house's own: **a decision point inside gate-house
would place the deterministic authority boundary inside the non-deterministic
management layer, violating INV-02 — the first invariant the repository exists
to defend.** The repository would have been the clearest available
counterexample to the canon it hosts.
Boundary: *the mandate and the operating mode are gate-house's; the decision is
access-engine's; the credential is secrets-engine's; the perimeter is
ops-mason's and ops-warden's.*
gate-house keeps what no other repository owns — the operating modes, the
principal/actor/runtime triple, mandates and authority ceilings, the change
dynamics envelope, the MCP doctrine, the posture asymmetry, the assurance
specifications, and the curriculum. It gives up `/authorize`, policy evaluation,
policy engine selection, grant storage, and revocation.
### Ruling 3 — Reframe flex-auth as an Engine and rename it access-engine
`flex-auth` is Engine-layer and remains the only policy decision point. It is
renamed **`access-engine`**. `auth-engine` was rejected: key-cape owns
authentication, and `auth-` preserves the ambiguity the rename exists to remove.
`permission-engine` was rejected as ageing badly against a future `role-engine`.
The name's one cost is that "access" is already spoken for operationally by
ops-warden and ops-mason. It is paid by a demarcation, now normative in §8 of
the standard: **ops-warden and ops-mason own access lanes — how a worker reaches
a host; access-engine owns access rules — whether they may.**
Sequence is binding: **reframe the INTENT first, rename second**, as a governed
migration. The rename touches `FLEX-WP` prefix ownership, State Hub identifiers,
ops-warden's routing tables, zone-engine's binding boundary text, and
secrets-engine integrations.
The reframing also splits a responsibility flex-auth currently holds whole:
**authoring and governing policy is Staff work (gate-house); evaluating it
deterministically and in-path is access-engine's, exclusively.** This is the
constructive resolution of the `FLEX-WP-0017` overlap — gate-house designs the
approval contract, access-engine validates approvals at decision time.
## What approval authorizes
- Publication of the layer model as a proposed net-kingdom standard.
- The gate-house INTENT re-cut, already applied at `7f13f72`.
- The layering review notes placed at the top of twelve estate INTENT files.
- Starting the flex-auth INTENT reframe.
- Retiring the gate-house artifacts that describe an engine: ADR-003 (policy
engine selection), milestones M0, M3, and M4, and `GH-WP-0001-T04`
(`/authorize` skeleton). `GH-WP-0001` is rewritten against the re-cut before
it is promoted to active.
## What approval does not authorize
- The `flex-auth``access-engine` rename itself. That is a separate governed
migration with its own record, and it must not begin before the INTENT
reframe lands.
- Any change to `zone-engine`'s 2026-08-23 disposition.
- Promoting the standard from `proposed` to `accepted`.
- Any change to another repository's workplans. Work structure stays with the
repository doing the work.
## Assent still required
Two adaptations move vocabulary away from repositories that currently use it,
and follow the estate's precedent that a boundary is drawn on review by the
other side rather than asserted — as flex-auth did to zone-engine:
1. **flex-auth** — Engine framing, the rename, and the authoring/evaluation split.
2. **kings-guard** and **ops-warden** — releasing "control plane" and the
security curriculum respectively to the layers that own them.
## Reversal
The rulings are documents; nothing executable depends on them yet, and no code
exists in gate-house. Reversal is reverting the INTENT and standard commits.
The falsifiers that should trigger reconsideration are in
`gate-house/INTENT.md` § "What Would Make This Repository Wrong" — principally
gate-house becoming a paper generator whose conformance loop never turns, or
the estate declining to adopt the authority vocabulary.
## GH-DEC-2026-002 — Revocation fails closed only when approval-engine's own store is down
```yaml
id: GH-DEC-2026-002
kind: decision
title: Revocation fails closed only when approval-engine's own store is down
status: resolved
owner: Bernd Worsch
repo: gate-house
standard: net-kingdom/canon/standards/security-layer-model_v0.7.md
source_note: history/2026-08-29-approval-evidence-integrity-contracts.md
requested_dispositions:
- approved
- revised
- rejected
affects:
- gate-house
- approval-engine
- audit-core
- flex-auth
created: '2026-08-29T12:50:04.747439Z'
updated: '2026-08-29T12:50:58.873684Z'
rationale: 'Approved as GH-WP-0002-T03. Local-outbox fail-closed is the only remaining
closed path: if approval-engine cannot insert the outbox row, the revocation does
not commit. An audit-core outage must not block a revocation. Synchronous emission
inside the mutation is forbidden even though it is atomic. Proceed-with-gap is rejected
for load-bearing approval evidence.'
decided_by: Bernd Worsch
decided_at: '2026-08-29T12:50:58.873684Z'
```
## Context
`GH-IN-0001` (from `audit-core`) required that the revocation failure mode be a
recorded decision rather than an implementation accident. Both sides are
defensible on their own: fail-closed makes an audit dependency into an
availability risk on the revocation path; proceed-with-gap needs a detectable
marker so the gap is visible rather than silent.
v0.5's local-outbox rule already narrowed this. With the queue in
`approval-engine`'s own store, fail-closed triggers only when that store is
down — where the change could not have been recorded anyway — and an
`audit-core` outage does not block a revocation. Satisfying atomicity by
emitting synchronously to `audit-core` inside the mutation is also atomic, and
turns an audit outage into an inability to revoke: the operation least
tolerable to block during an incident.
The statute states the locality rule at §9.4. This record is the decision the
workplan still owed, so an implementer cannot pick the other side by accident.
Wire: `docs/contracts/approval-outbox.md`.
## Decision
**If the local outbox insert cannot commit, the revocation does not commit.**
The same rule applies to issuance, use, and supersession.
**An `audit-core` outage MUST NOT block a revocation.** Drain is asynchronous
and at-least-once. `audit-core` dedupes on `event_id`.
**Synchronous emission to `audit-core` inside the state-change transaction is
forbidden**, even though it is atomic.
Proceed-with-gap is rejected for load-bearing approval evidence. A revocation
that succeeds while its event is lost is the exact case `AUDIT-IN-0001`
conditioned assent on.
## What this authorizes
- `approval-engine` failing a revoke (and any other mutation) when its own
store cannot insert the outbox row.
- Continuing to revoke while `audit-core` is down, with the row draining later.
- Treating emit-after-commit, a second non-local queue, or a sync `audit-core`
call inside `BEGIN``COMMIT` as out of contract.
## What this does not authorize
- Claiming the outbox closes adversarial omission. Atomicity prevents crash;
cadence and reconciliation detect suppression after the fact (§9.6).
- A validity query on `audit-core`.
- Any change to another repository's workplans.
## Reversal
Revert this record and the outbox contract. The falsifier is operational: if
failing closed on the local store removes the ability to revoke during the
incidents this engine exists for, the trade needs revisiting — but the
alternative is a silent evidence gap on the event an attacker most wants
missing, which is worse.
## GH-DEC-2026-003 — The PEP consumes an approval before the protected side effect
```yaml
id: GH-DEC-2026-003
kind: decision
title: The PEP consumes an approval before the protected side effect
status: resolved
owner: Bernd Worsch
repo: gate-house
standard: net-kingdom/canon/standards/security-layer-model_v0.7.md
source_note: docs/contracts/approval-consumption.md
requested_dispositions:
- approved
- revised
- rejected
affects:
- gate-house
- approval-engine
- flex-auth
- secrets-engine
- ops-warden
created: '2026-08-29T12:50:06.944092Z'
updated: '2026-08-29T12:51:01.355583Z'
rationale: Approved as GH-WP-0002-T06. The PEP consumes by compare-and-swap before
the protected side effect; the PDP never mutates; there is no unconsume. Same request
digest is idempotent success; a different digest against a consumed object is conflict.
This closes the three named races and unblocks APPROVAL-WP-0001-T05 and FLEX-WP-0017-T05.
decided_by: Bernd Worsch
decided_at: '2026-08-29T12:51:01.355583Z'
```
## Context
Statute §16 left open who marks an approval consumed, and at what point
relative to the decision. `flex-auth` named three failure modes neither engine
closes alone: an ALLOW never consumed; a double consumption by racing callers;
consumption after a failed action. `approval-engine` performs the mutation
because `access-engine` never mutates, and refused to wire a public consume by
guessing the contract. `APPROVAL-WP-0001-T05` and `FLEX-WP-0017-T05` are wait
on this record.
§9.7.3 forbids inferring consumption from a decision record. That stands. The
same paragraph currently reads as if the action must precede the consume call.
That reading cannot enforce single use: two racing PEPs can both act, and CAS
then prevents only the second record.
## Decision
**The PEP-shaped consumer consumes, by compare-and-swap, before the protected
side effect.** The PDP never mutates. Staff never calls consume.
**There is no unconsume and no reserve/release.** An approval authorizes one
attempt, not one success. A consume followed by a failed action spends the
object; retry is a new approval.
**Same `request_digest` against an already-consumed object is idempotent
success** (a retry of one logical request). A different digest is conflict,
and the PEP MUST NOT act.
The three failure modes have owners:
1. *Allow never consumed* — PEP duty to consume; PDP lifetime bounds the
window; a stale ALLOW without a matching `use` is a finding, not a consume.
2. *Double consumption*`approval-engine` CAS; the PEP that receives
conflict does not act.
3. *Consumed then the action fails* — accepted as the cost of closing (2).
Protocol: `docs/contracts/approval-consumption.md`. Statute v0.8 will replace
the §9.7.3 implication that action precedes the consume call; until then this
record governs the blocked implementers.
## What this authorizes
- `approval-engine` exposing `POST /v1/approvals/{id}/consume` against this
contract, including the digest column the internal CAS does not yet store.
- `FLEX-WP-0017-T05` requiring the PEP (secrets-engine, for that task) to
consume before the OpenBao call.
- Canon `T-06` consume-side replay (a second, different digest against a
consumed object) becoming in-scope once the endpoint exists.
## What this does not authorize
- The PDP consuming, or inferring consumption from a decision record.
- Unconsume, reserve, or any path that returns a consumed object to `approved`.
- A consume that is a decision ("may this actor do X").
- Any change to another repository's workplans. Work structure stays with the
repository doing the work.
## Reversal
Revert this record and the consumption contract. The falsifier is operational:
if spending an approval on a failed attempt makes the estate unable to
complete the actions this object exists for, a reserve/commit protocol can be
raised then. Unconsume is not the alternative — it reopens replay.
## GH-DEC-2026-004 — Layer emission-cadence ownership between Info Tech Canon and Net Kingdom
```yaml
id: GH-DEC-2026-004
kind: decision
title: Layer emission-cadence ownership between Info Tech Canon and Net Kingdom
status: resolved
owner: Bernd Worsch
repo: gate-house
standard: net-kingdom/canon/standards/security-layer-model_v0.7.md
source_note: kings-guard/specs/EmissionCadenceDeclaration.md
requested_dispositions:
- approved
- revised
- rejected
affects:
- gate-house
- info-tech-canon
- net-kingdom
- kings-guard
- qonto-assistant
created: '2026-09-04T00:55:29Z'
updated: '2026-09-04T00:55:29Z'
rationale: >-
Approved interactively on 2026-09-04. The layered split gives each artifact
one owner: ecosystem-wide semantics belong to info-tech-canon; NetKingdom-
specific MUST/SHOULD rules and the low-volume load-bearing heartbeat-plus-
reconciliation profile belong to net-kingdom. This avoids both a Staff-local
consumer schema and competing Taxonomy definitions.
decided_by: Bernd Worsch
decided_at: '2026-09-04T00:55:29Z'
state_hub_decision_id: "89c73f45-d07b-40ea-b428-5a7375ce4b09"
```
## Context
Security-layer-model v0.7 §§9.6 and 17 require evidence sources to declare an
expected emission cadence so silence can become a finding. King's Guard is the
only current consumer and drafted `EmissionCadenceDeclaration.md` against the
real `qonto-assistant` source, but correctly declined to keep a Staff-local
schema. The standard left ownership between the two Taxonomy repositories:
`info-tech-canon` for ecosystem-wide semantic contracts and `net-kingdom` for
NetKingdom standards of record.
Treating that as joint ownership would leave version authority ambiguous.
Giving the entire artifact to either repository would instead conflate a
reusable evidence/observability contract with the security obligations of one
estate. The split is therefore made by artifact, with one owner for each.
## Decision
**`info-tech-canon` owns the versioned, ecosystem-wide
`EmissionCadenceDeclaration` semantic contract.** It owns the generic forms,
fields, vocabulary, validation semantics, compatibility rules, and evolution
of the contract.
**`net-kingdom` imports that contract and owns the NetKingdom security
profile.** It owns which evidence classes MUST or SHOULD declare cadence, the
rule that rate monitoring is forbidden for rare load-bearing classes, and the
heartbeat-plus-reconciliation obligations that satisfy security-layer-model
§9.6.
The remaining ownership boundaries are unchanged:
- each source repository owns its declaration instance and emission behavior;
- `kings-guard` owns stream evaluation and silence findings, not the schema;
- Gate House owns doctrine and conformance supervision, not publication or
runtime behavior.
The King's Guard draft remains the provenance and handover input. Once the
Taxonomy artifacts are published, it becomes historical rather than a second
contract.
## What this authorizes
- Requesting `info-tech-canon` to adopt, revise, or contest the generic
contract and create its own owning work record.
- Requesting `net-kingdom` to adopt, revise, or contest the security profile
and create its own owning work record.
- Sources such as `qonto-assistant`, `approval-engine`, and `secrets-engine`
publishing instances against the eventual canonical contract and profile.
- King's Guard replacing its draft-shaped fixture with the published contract
without changing its consumer ownership.
## What this does not authorize
- Joint ownership or two independently versioned definitions of the generic
schema.
- Gate House or King's Guard retaining the schema as a local contract.
- Gate House creating workplans in either Taxonomy repository.
- Treating ownership as source conformance: `qonto-assistant` remains
incomplete until it publishes and emits the required positive claim.
## Assent and completion
The ownership ruling is resolved. Publication is complete only when both
Taxonomy owners reply in their own voice and publish or explicitly schedule
their respective artifacts. Until then the prior handover residual remains
open, now with an unambiguous requested disposition.
## Reversal
Supersede this record if the generic contract proves inseparable from the
NetKingdom security profile, or if Info Tech Canon cannot provide a stable
cross-domain contract boundary. Reversal must still name one owner for every
artifact; returning to undifferentiated "Taxonomy ownership" is not an
acceptable outcome.
## GH-DEC-2026-005 — The approval-claim is the step-1 artifact on the PEP consumption path
```yaml
id: GH-DEC-2026-005
kind: decision
title: The approval-claim is the step-1 artifact on the PEP consumption path
status: resolved
owner: Bernd Worsch
repo: gate-house
standard: net-kingdom/canon/standards/security-layer-model_v0.7.md
source_note: docs/contracts/approval-consumption.md
origin: GH-IN-0002
requested_dispositions:
- approved
- revised
- rejected
affects:
- gate-house
- approval-engine
- flex-auth
- secrets-engine
- ops-warden
rationale: 'Confirmed, with one addition. GH-DEC-2026-003 already named step 1 by
endpoint and by field; the approval-claim is what that endpoint serves and valid_now
is its field. ActionAuthorization is a proposed, unratified shape carrying no doctrine
standing here. The addition is that the split validation is stated as doctrine rather
than left implicit: each artifact is checked by the consumer against the layer that
owns its data, and no PIP republishes a PDP decision. The state-hub authority requirement
in the secrets-engine validator is struck because State Hub is a read model and
holds no runtime approval authority.'
decided_by: Bernd Worsch
state_hub_decision_id: "b606e8ce-f1fa-4b39-bfe9-c11e88a3cde6"
created: '2026-09-05T23:28:23.931441Z'
updated: '2026-09-05T23:28:23.931441Z'
```
## Context
`GH-DEC-2026-003` settled the consumption sequence and named step 1 by endpoint and
by field: `GET /v1/approvals/{id}/claim``valid_now` (a fact, not permission).
`approval-engine` serves the approval-claim at that endpoint
(`approval-engine/docs/approval-claim.md`). `secrets-engine`'s PEP validator
(`validate_action_authorization`, `src/secrets_engine/authorization.py`) calls the
same endpoint but expects a flex-auth `ActionAuthorization`. Two different objects
were being assumed on one path, and the divergence would have surfaced at first live
consume rather than at deployment.
`ActionAuthorization` originates in `flex-auth/docs/action-bound-authorization-contract.md`
as a proposed shape for durable approval storage that the same document assigns to
`approval-engine`. It was never ratified, appears nowhere in this repository's
doctrine, and has no `valid_now` field. `GH-DEC-2026-003` was issued after it without
reference to it. There is therefore nothing to reconcile: the claim was the step-1
artifact all along, and this record says so where implementers will find it.
`approval-engine` raised the question rather than guessing, which is the behaviour
§12 asks for. The three substantive assertions in the request were checked here
independently before confirmation.
## Decision
**1. The approval-claim is the step-1 artifact on the `GH-DEC-2026-003` path.**
It carries the approval fact: binding digest, validity window, consumption state,
observation freshness, and issuer. Holding one with `valid_now: true` remains not
authority to act.
**2. `ActionAuthorization` is not required on that path.** `approval-engine` is not
expected to serve it and MUST NOT serve it from the claim endpoint. A step-1 response
cannot contain a step-2 decision; any composed post-decision artifact would be a new
artifact at a new endpoint. The proposal is shelved on this path, not withdrawn.
**3. A PEP validates across the two artifacts it already fetches.** The claim is
checked for the approval fact; the flex-auth `DecisionEnvelope` from step 2 is checked
for exact `CheckRequest` match and policy package/version pin. This is doctrine, not
merely an implementation convenience: **each artifact is validated against the layer
that owns its data.** A PIP republishing the PDP's decision would put a copy of the
authority decision in a repository that did not render it — the same shape of error
the re-cut removed from Gate House itself. No safety property is lost, because
`ActionAuthorization` is a bundle of exactly these two checks.
**4. The `provenance.authority == "state-hub"` requirement is struck.** State Hub is
a read model. It is not the runtime approval authority and never was; flex-auth's own
contract says so. A validator that requires it would fail closed against every
correctly issued claim. `secrets-engine` removes the constant with the validator split.
Consequent changes: `approval-engine` none — the claim schema stands as published;
`secrets-engine` splits its validator and drops the authority constant; `flex-auth`
records that the proposal is shelved on this path. Nothing here gates
`APPROVAL-WP-0002-T03``secrets-engine` is fail-closed until its own gates land,
and this record governs first live consume, not deployment.
## Deferred
A ratified post-decision `ActionAuthorization` — one composed, signed, forwardable
object — remains coherent and is recorded here so it is not rediscovered as new. It
is not adopted now because it has no named issuer or lifecycle owner and the sequence
is already settled without one. Revisit on any of: a third or fourth PEP-shaped
consumer; a requirement for a single signed forwardable artifact (offline
verification, or an audit that must replay one object rather than a join);
~~resolution of flex-auth's open G3 finding by composition rather than by adding a
lifetime to the envelope~~**struck, see the amendment below**; or assent of the
statute §17 Taxonomy request-claim schema
(`APPROVAL-IN-0001`) — in which case converge there and do **not** revive this
separately.
Also rejected, so they are not re-proposed: serving `ActionAuthorization` at the claim
endpoint (structurally impossible for step 1, and it would assert an authority
`approval-engine` does not hold); and extending the claim with `approvals.required_count`
or `entries` (publishing approver identities is a deliberate least-disclosure non-goal,
and it would still not satisfy the validator).
### Amendment, 2026-09-06 — the G3 trigger is struck
Recorded the day after this decision, on corrections volunteered independently by
`approval-engine` and `access-engine`, and verified here against
`flex-auth/schemas/decision_envelope.schema.json`.
`FLEX-WP-0019` closed G3 on 2026-09-02 by **adding** the field: the schema requires
`lifetime` whenever `effect` is `allow`, sourced from the policy package's
`allow_ttl` with a 15-minute engine default, and `allow_ttl: none` renders a DENY
with reason `allow_lifetime_unstated` rather than a standing grant.
The trigger was therefore already spent when this decision was written, and it
resolved on the branch that removes the argument rather than the one that supports
it. This is not bookkeeping. Carrying its own end was the one structural thing the
composed bundle did that the split does not, so the strongest available case for
ratifying option D is not merely unspent — **it is answered, and the answer is no.**
A `DecisionEnvelope` now states its own lifetime without borrowing an
`ActionAuthorizationValidity`.
Three revisit triggers remain: a third or fourth PEP-shaped consumer; a requirement
for a single signed forwardable artifact; and assent of the §17 request-claim schema,
where the instruction is to converge rather than revive.
The error came from sourcing the trigger from a 2026-08-29 alignment record rather
than from current state — the same class of mistake as reading a change log for what
the body says, which the v0.6 findings audit hit on the same day
(`docs/conformance/2026-09-06-v06-findings-audit.md`). Both repositories reported it
against their own interest, which is why it was caught within a day.
### Stated consequence, 2026-09-06 — what the PEP stopped checking
Raised by `secrets-engine` on implementing the split, against its own position, and
recorded here rather than left in a commit message.
The distinct-approver threshold is no longer verified independently at the PEP. The
claim exposes no approver entries — publishing approver identities is the deliberate
least-disclosure non-goal this decision upheld — so `approval-engine` folds the
threshold into `valid_now`. That is the correct layer under this doctrine and it is
also **a genuine reduction in what the PEP checks on its own**, and the second thing
does not stop being true because the first one is.
The estate should hold this consciously. `valid_now` is a summary predicate, and a
consumer of a summary trusts the issuer's evaluation of everything folded into it.
The compensating property is not a second check at the PEP — that is the duplication
the split removes — but reconstructability at the issuer: `approval-engine`'s state
transitions and its `use` outbox row must make the threshold evaluation recoverable
after the fact under §9.6. Detection, not prevention, and it should be named as such
in the same register as §9.6's other residual.
A second consequence, also from `secrets-engine` and also correct: two digests now
cover the same proposed action and are never compared — `approval-engine`'s binding
digest over `{action, actor, principal, purpose, target}` and the flex-auth
`CheckRequest` digest, with `claim.binding.pdp_digest` preferred where the issuer
recorded one. They answer different questions at different layers and collapsing them
would be a layer violation wearing the costume of a refactor. `secrets-engine` holds
a test asserting the two functions disagree, which is the right way to defend a
distinction that looks like duplication.
**Narrowed, 2026-09-06, by `approval-engine` on implementing the §9.6 obligation.**
The framing above was too broad. `entries` is UNIQUE on `(approval_id, subject_id)`,
so a repeat approver is refused at insert: distinctness is a **storage invariant** in
that engine, not something a consumer or an auditor recomputes. What the PEP stopped
checking is therefore that *this engine applied its own invariant correctly* — not
whether dual control could be forged by a repeated signature, which the schema
forecloses. The reduction is real and it is narrower than "the threshold is now
unverified". `approval-engine` found this by writing a test that expected duplicate
entries to collapse to one distinct approver, watching it fail, and reporting the
result against its own earlier framing; it also dropped an `entry_count` field rather
than ship a number that by construction can never differ from the distinct count.
The compensating property is now implemented rather than asserted: `approval.issuance`
and `approval.use` both carry a `threshold` object — `required_count`,
`distinct_approver_count`, `threshold_met`, and approver `subject_id`, `approved_at`
and evidence reference — with tests asserting reconstruction from the `use` row
**alone**, performing the recomputation an auditor would. The disclosure boundary is
pinned by test: identities are on the outbox, never on the claim. The evidence path
carries them because that is what audit is for; the consumer-facing artifact discloses
the least it can. The split did not push identities to consumers by another route.
**Hub row provenance.** `approval-engine`'s `fix-consistency` C-32 check registered
the drafted record block in its own request document as a hub decision
(`b606e8ce-f1fa-4b39-bfe9-c11e88a3cde6`), which it then resolved rather than leave an
open row for a settled question. Gate House **adopts that row as canonical** and
records its id above, rather than registering a second one — the row's content is
correct and a duplicate would be worse than an imperfectly-provenanced single row. A
drafted decision block inside a requesting repository's document is machine-readable
to that repository's own consistency checks, which is a trap in this workflow; the
disposition is recorded here so the next request does not rediscover it.
## Reversal
Revert this record and the amendment to `docs/contracts/approval-consumption.md`. The
falsifier is a demonstrated check that two-artifact validation cannot perform and a
single composed artifact can — most plausibly offline or forwarded verification with
no access to the PDP. That is a trigger for the deferred option above, not for
returning `ActionAuthorization` to the claim endpoint, which stays forbidden on
structural grounds regardless.
## GH-DEC-2026-006 — The §13 registers become pointers only once maturity-engine publishes a readable export
```yaml
id: GH-DEC-2026-006
kind: decision
title: The §13 registers become pointers only once maturity-engine publishes a readable
export
status: resolved
owner: Bernd Worsch
repo: gate-house
standard: net-kingdom/canon/standards/security-layer-model_v0.7.md
source_note: workplans/GH-WP-0003-statute-v08-amendment-set.md (T03)
requested_dispositions:
- approved
- revised
- rejected
affects:
- gate-house
- net-kingdom
- maturity-engine
- ops-warden
- ops-mason
- user-engine
- tenant-engine
- access-engine
rationale: 'A statute of record should not carry state, and §13 and §13.1 are state:
they change without the doctrine changing, and a hand-maintained table inside a
standard is a register that drifts. maturity-engine is the right holder. But a pointer
is only an improvement once there is something readable to point at — a standard
that can only be understood by querying a live engine has traded one defect for
a worse one, and it would make the statute unreadable to exactly the auditor it
exists for. The migration is therefore conditioned on a committed, versioned export
rather than declared outright. Until that export exists the tables stay and rows
are transcribed, so no repository is left without an answer while the condition
is outstanding.'
decided_by: Bernd Worsch
created: '2026-09-05T23:38:49.777532Z'
updated: '2026-09-05T23:38:49.777532Z'
```
## Context
`security-layer-model` v0.7 §13 holds the open-gap register and §13.1 the PEP
stance-map register. Both were created because the obligations above them named a
register that did not exist — §9.1's own logic, that a requirement whose register is
missing is a capability catalogued without a surface. They did their job.
`maturity-engine` (MAT-WP-0001) now holds both as queryable data: the §13 snapshot
with state and owner-status intact, the §13.1 inventory, `ASM-0``ASM-6` registered
as data owned by gate-house, `pep-stance-publication` owned by ops-warden, and
blocked-clean scored equal to conforming by test. It proposes that the statute table
become a pointer.
Four repositories are waiting behind the answer. `user-engine` and `tenant-engine`
have published stance maps and asked for §13.1 rows; ops-warden is published and
ops-mason is not. §6.4 obliges every PEP-shaped consumer to publish and obliges those
maps to be inventoried. Each week the question stays open, the register either
accumulates transcription or accumulates silence.
## Decision
**§13 and §13.1 become pointers to `maturity-engine`, and not before it publishes a
committed, versioned export of both registers that is readable without a live query.**
Two things are true at once and the ruling holds both.
**A statute should not carry state.** §13 and §13.1 change without the doctrine
changing. A table maintained by hand inside a standard of record drifts, and every
row transcribed into it is the manual step `maturity-engine` exists to remove. This
is the same rule as `GH-DEC-2026-005`: the artifact is held by the layer that owns
its data, and a repository that neither computes nor owns a fact should not be its
register of record.
**A standard must be readable on its own.** A pointer to a live engine is not a
register an auditor can read; it is an instruction to run software. Whatever else the
statute is, it must be legible in git, at a version, to a reader with no cluster
access — including a reader auditing the estate precisely because they do not trust
its running systems. Replacing a drifting table with an unreadable reference trades a
known defect for a worse one.
The condition reconciles them. `maturity-engine` computes and remembers; it also
publishes the result as a committed artifact at a stated path and version, and the
statute cites that path. The rows are then computed rather than transcribed, and
still readable without a query. **Publication is the migration's precondition, not
its follow-up.**
### What happens meanwhile
The tables stay, and rows continue to be transcribed into §13.1 at each version cut.
No repository waits on the export to get an answer. `user-engine`, `tenant-engine`,
ops-warden and ops-mason are inventoried in v0.8 whether or not the export has
landed by then.
This is deliberately the slower path. The alternative — strike the tables now and
point at the engine — would close four open requests immediately and leave the
statute stating an obligation whose register no reader can open, which is the exact
defect §13.1 was created to fix. Repeating a defect in the other direction is not
progress.
### What is asked of `maturity-engine`
Not a new capability, a published form of one it has. The export must be a file in
the repository, versioned, generated rather than hand-edited, carrying the same
fields the statute tables carry today (§13: capability, kind, declarer, owner, state,
owner-status; §13.1: consumer, stance map path, shape), and regenerable so drift
between the export and the engine's own state is detectable. `layer.yaml` and
`pep-stance.yaml` are the shape to follow: published, machine-readable, and asserted
equal to shipped behaviour by a test. §6.4 obligation 3 already requires that
equality of every PEP; a register of those maps should not hold itself to less.
When it exists, the §13 and §13.1 tables are replaced by a citation of it, and
`maturity-engine` becomes the register of record.
## Reversal
Revert to tables if the export proves to drift from the engine's computed state
faster than a hand-maintained table drifted from reality — that is, if generation and
publication decouple. The falsifier is mechanical: a regeneration that changes the
committed export without any underlying evidence having changed, or an underlying
change that does not reach the export.
## GH-DEC-2026-007 — The posture/maturity boundary is recomputability, bounded by a criteria-grounding rule
```yaml
id: GH-DEC-2026-007
kind: decision
title: The posture/maturity boundary is recomputability, bounded by a criteria-grounding
rule
status: resolved
owner: Bernd Worsch
repo: gate-house
standard: net-kingdom/canon/standards/security-layer-model_v0.7.md
source_note: kings-guard/docs/PostureMaturityBoundary.md (KG-DEC-2026-002, KG-COM-0001)
requested_dispositions:
- approved
- revised
- rejected
affects:
- gate-house
- net-kingdom
- kings-guard
- maturity-engine
- access-engine
rationale: 'Adopted with one addition. kings-guard is right that volatility describes
the two categories without partitioning them, and right that the discriminator is
already in §9.5''s determinism clause rather than being a new rule. The addition
closes the loophole the test leaves open: recomputability is assessed over the stated
criteria, so any judgment can be made to look recomputable by writing a criterion
that dereferences it. Criteria must bottom out in evidence about the subject, not
in another party''s opinion recorded as evidence. Without that clause the test is
satisfiable in form by exactly the inference it exists to exclude.'
decided_by: Bernd Worsch
created: '2026-09-06T06:07:34.017316Z'
updated: '2026-09-06T06:07:34.017316Z'
```
## Context
`GH-WP-0003-T04`. v0.7 §9.5 states the maturity half of the boundary — *"Given the
same criteria and the same evidence it MUST return the same level; that determinism
is what makes it an Engine rather than an opinion"*, and *"A criterion that cannot be
evaluated by rule is not yet a criterion."* It does not state the posture half.
Gate House had proposed drawing the line on volatility: posture is fast-moving,
maturity is slow. `kings-guard` answered `KG-IN-0003` with a proposed revision
(`KG-DEC-2026-002`, argued in `kings-guard/docs/PostureMaturityBoundary.md`) rejecting
that line and offering recomputability instead. The commentary was also sent to
`maturity-engine`.
The concern behind the original question stands and is why the line has to be exact:
a second grading authority would be the same shape of mistake as a second decision
point, and §6 says it would arrive the same way — gradually, each time for a good
local reason.
## Decision
**The boundary is recomputability, not volatility.**
> Given the same criteria and the same evidence, recompute. If you MUST get the same
> answer, it is maturity and it belongs in an engine. If you CANNOT promise the same
> answer, it is posture and it belongs in Staff.
The volatility line is withdrawn. `kings-guard`'s argument against it is accepted in
full: volatility is an observation about how a value has behaved, not a definition of
what it is. It fails at both edges — a maturity criterion moves fast when evidence
lands in a burst, and a posture sits unchanged for months on a healthy subject — and,
more seriously, it *describes* the two categories without *partitioning* them. Every
case it does not obviously cover becomes an argument, and arguments at a boundary are
the mechanism §6 exists to prevent.
Recomputability is not a new rule. It is §9.5's own determinism clause pointed at the
one boundary where it had not been pointed, which is why it can be adopted without
enlarging the standard. §9.5 already states the maturity half; the posture half is its
mirror, and the pair partitions: **a criterion that cannot be evaluated by rule is not
yet a criterion; a judgment that can be evaluated by rule is not posture — it is a
criterion sitting in the wrong repository.**
### The addition — criteria must be grounded
Adopted with one clause `kings-guard` did not propose, and the reason is the loophole
their own framing opens.
Recomputability is assessed **over the stated criteria**. That makes it mechanical,
which is its virtue, and it also makes it satisfiable in form by the thing it exists
to exclude. Any judgment can be made to look recomputable by writing a criterion that
dereferences it: *"level 2 iff the reviewer marked the control adequate"* is perfectly
deterministic — recompute it and you get the same answer every time — and it has
smuggled an opinion into an engine wearing a rule's clothes. The engine would then be
grading, deterministically, on someone's judgment, which is precisely the second
grading authority the boundary is drawn to prevent.
Therefore:
> **A criterion MUST bottom out in evidence about the subject, not in another party's
> conclusion about the subject.** A recorded judgment may be evidence *that the
> judgment was made* — a fact with an issuer and a timestamp. It MUST NOT be evidence
> *that the thing judged is so*.
This is the same distinction §9.6 draws between what an archive proves and what it is
read as proving, and the same one this repository applies to `valid_now` in
`GH-DEC-2026-005`: a summary predicate is a fact about the issuer's evaluation, not a
substitute for the evaluation. Without the clause the test is mechanical and
circumventable; with it, the test is mechanical and the circumvention is itself
checkable.
### Consequences carried
The three `kings-guard` names are adopted as stated.
1. **The migration direction is permanent.** Anything called posture that turns out to
be recomputable moves to `maturity-engine` as a criterion; anything in
`maturity-engine` that needs judgment is not yet a criterion and moves back to
Staff. The boundary maintains itself under change because the test applies to each
item rather than to the category.
2. **Two authorities cannot grade the same subject property**, because a property is
either recomputable or it is not, and that fact does not depend on which repository
claims it. The failure becomes structurally prevented rather than conventionally
avoided.
3. **Capability readiness MUST NOT be an input to posture.** Readiness is
deterministic; posture is not; feeding one into the other would make posture partly
recomputable and blur the boundary from the `kings-guard` side. Volunteered by
`kings-guard` as a constraint on itself and accepted as normative.
Consequence 3 also answers the second half of `KG-IN-0003`: tracking kings-guard's
own gaps in `maturity-engine` creates no incident-time dependency, because posture
evaluation never consults readiness. The dependency would exist only if the mistake
consequence 3 forbids had already been made.
### The limit is recorded, not resolved
`kings-guard` flagged, against its own proposal, that *"the same evidence"* is not
well defined anywhere in the estate, so until §17's request-claim and gap-record
schemas exist, recomputability is a thought experiment rather than a check. That is
recorded in the statute alongside the test rather than left in the commentary. A
boundary that is correct but not yet mechanically checkable does beat one that is
checkable and wrong — but a reader is entitled to know which of the two they are
holding. It makes §17 load-bearing for this rule.
The caution is also recorded: *"posture"* has the drift profile *"control plane"* had
— it sounds specific and quietly absorbs whatever sits next to it. §8 exists because
the estate has been bitten by exactly that, and this repository described itself as a
control plane before the re-cut. The recomputability test is a defence against that
drift because it can be applied to a candidate *before* the word is stretched to cover
it. `kings-guard` asked to be held to it rather than trusted about it, and that is the
standing it gets.
No change to §8's three-way split and no §4 catalog change. Posture stays
non-deterministic and stays Staff; the test explains why, which is what was missing.
## Reversal
Revert if the grounding clause proves to exclude criteria the estate needs — that is,
if a legitimate maturity criterion cannot be expressed without dereferencing a
judgment. The falsifier is a concrete criterion, not an argument that one might exist.
The likely candidates are human-attested controls (a policy was reviewed, a drill was
run); the expected resolution is that the *attestation event* is the evidence and the
criterion grades on its existence, freshness, and issuer rather than on its verdict.
If that resolution does not hold for some real criterion, this clause is wrong.
## GH-DEC-2026-008 — The PDP digest is the binding correspondence; no cross-engine vocabulary mapping is published
```yaml
id: GH-DEC-2026-008
kind: decision
title: The PDP digest is the binding correspondence; no cross-engine vocabulary mapping
is published
status: resolved
owner: Bernd Worsch
repo: gate-house
standard: net-kingdom/canon/standards/security-layer-model_v0.7.md
source_note: docs/contracts/approval-consumption.md; approval-engine/docs/approval-claim.md
requested_dispositions:
- approved
- revised
- rejected
affects:
- gate-house
- approval-engine
- access-engine
- secrets-engine
- ops-warden
rationale: 'Raised by access-engine. approval-claim verification item 4 is a disjunction:
the consumer recomputes approval-engine''s native binding from the proposed action,
OR compares binding.pdp_digest to the decision''s request digest. The first limb
needs a cross-engine vocabulary mapping nobody owns and nobody has published; the
second is optional in the schema. Together that leaves ''approved for THIS request''
unestablished whenever the PDP digest is absent, while the claim still reads valid_now
true. The mapping is rejected because a translation between two engines'' vocabularies
can be silently wrong and would be owned by neither, and because recomputing another
layer''s binding is the re-derivation GH-DEC-2026-005 forbids. The digest is identity
rather than translation, so it is made required on the PDP path and consumers fail
closed without it.'
decided_by: Bernd Worsch
created: '2026-09-06T07:30:42.213794Z'
updated: '2026-09-06T07:30:42.213794Z'
```
## Context
Raised by `access-engine` while implementing `GH-DEC-2026-005`, and declined by it
rather than solved locally — the right call, and the reason it reached a decision
record instead of a commit.
`approval-engine/docs/approval-claim.md` verification item 4 is a **disjunction**:
> `binding.digest` equals the digest of the binding the consumer computed from the
> proposed action, **or** `binding.pdp_digest` equals the `NewDecisionBinding` digest
> of that request.
Both limbs are load-bearing for one property — *approved for **this** request*, as
distinct from *approved*. Neither limb currently delivers it on the PDP path.
**Limb one requires a mapping nobody owns.** The claim's `binding.action` and
`binding.target` are in `approval-engine`'s vocabulary, over
`{action, actor, principal, purpose, target}`. `access-engine`'s policy package and
`CheckRequest` are in its own. To compute limb one, a consumer must translate between
them. No mapping is published. `access-engine` declined to invent one on the correct
ground that a wrong mapping fails **open** in the worst way available: it silently
accepts a claim approved for something else.
**Limb two is optional.** `binding.pdp_digest` is present only *"when recorded at
issue"*.
So on the path this estate actually runs, correspondence rests entirely on an optional
field, and where that field is absent a consumer can hold a claim with
`valid_now: true`, receive an ALLOW, consume, and act — with nothing having
established that the approval and the decision concern the same action and target.
The claim would not be lying; `valid_now` answers a different question.
## Decision
**`binding.pdp_digest` is the binding correspondence on the `GH-DEC-2026-003` path,
and it is required there. No cross-engine vocabulary mapping is published.**
1. **A consumer on the PDP path MUST verify `claim.binding.pdp_digest` equals the
decision's `NewDecisionBinding.request_digest`.** It is no longer a preferred
comparison; it is the comparison.
2. **A claim carrying no `pdp_digest` MUST NOT be used on that path.** The consumer
fails closed. It is not an error to be worked around, and specifically it is not a
licence to fall back to limb one.
3. **Limb one survives only where the consumer is natively in `approval-engine`'s
vocabulary** — that engine's own tests, and the `T-06` assurance case, which uses
the native binding precisely because no PDP digest exists there. It is not a
fallback for a PDP-path consumer.
4. **`approval-engine` MUST record the PDP digest at issue for any approval intended
for use on the PDP path**, and the claim schema states which approvals those are
rather than leaving it to the requester's memory.
### Why not publish the mapping
`access-engine` offered to co-author one. Declining is the substantive half of this
record.
**A mapping is a translation; a digest is an identity.** A translation can be wrong in
a way that still produces a confident answer — the failure mode `access-engine` named,
and it fails open. A digest comparison has no wrong-but-plausible result: it matches
or it does not.
**A published mapping would be owned by neither engine.** It would sit between two
vocabularies, each of which is its own repository's to evolve under §2, and it would
have to be revised whenever either side changed. That artifact is a third authority on
what a request *is* — the shape §6 exists to prevent, arriving as plumbing rather than
as a decision point.
**And computing limb one is the re-derivation `GH-DEC-2026-005` forbids.** A consumer
recomputing `approval-engine`'s native binding from its own vocabulary is a consumer
deriving another layer's data instead of validating against the layer that owns it.
The rule this estate just adopted answers this question on its own terms.
### The cost, stated
An approval requested without a known `CheckRequest` cannot be used on the PDP path,
because there was no PDP digest to record. That is a real constraint and it is
**correct behaviour, not a defect**: an approval granted against an unspecified action
does not become an approval for a specific one because a consumer later found a use
for it. Any workflow needing detached approval must either bind the request at issue
or accept that its approvals are not usable here.
This narrows what `approval-engine` may issue for this path. It does not narrow what
it may issue.
## Reversal
Revert if a legitimate workflow genuinely cannot bind the `CheckRequest` at approval
time — a standing human authorization for an action whose exact parameters are not
knowable until execution is the plausible candidate. The falsifier is a concrete
workflow, not the possibility of one. The expected resolution is that such a case
needs a *class* binding with its own contract, not a relaxation of this one to an
optional field, because an optional correspondence is indistinguishable at the
consumer from no correspondence at all.