gate-house/decisions/decisions.md
tegwick b7dfb84c0b chore: sync hub identifiers and work record
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012viPor8WJNCbV64ipwewrm

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 1754332@bnt-lap001
Assistant-Session: 9c8ac536-ff5e-46a3-8ab1-a548bde25fc0
2026-09-10 15:28:51 +02:00

2670 lines
139 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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'
state_hub_decision_id: "3b7c8336-ed0b-48ed-84e4-dee8cfdb50ae"
```
## 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'
state_hub_decision_id: "f768c894-7e68-4e23-93b5-f475f4b072e5"
```
## 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'
state_hub_decision_id: "780e1117-57aa-49c7-bf59-f69df2a67a4e"
```
## 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'
state_hub_decision_id: "9ece3338-0fc7-4741-bdfc-7fdea170dc0b"
```
## 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'
state_hub_decision_id: "a36b3f02-fb08-47b5-9de3-c2d9e37678a4"
```
## 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.
## GH-DEC-2026-009 — Unknown is not a zone and fails closed; stance maps declare their scoping axis
```yaml
id: GH-DEC-2026-009
kind: decision
title: Unknown is not a zone and fails closed; stance maps declare their scoping axis
status: resolved
owner: Bernd Worsch
repo: gate-house
standard: net-kingdom/canon/standards/security-layer-model_v0.7.md
source_note: flex-auth/docs/stance-register-review.md
requested_dispositions:
- approved
- revised
- rejected
affects:
- gate-house
- net-kingdom
- ops-warden
- secrets-engine
- user-engine
- tenant-engine
- ops-mason
- access-engine
- zone-engine
rationale: 'Raised by access-engine on the first occasion the §13.1 register held
enough rows to diverge. Two rulings. First: an unreachable engine and an unclassifiable
subject are different failure cases, and §9.3''s per-zone availability trade is
only available for the first. Trading openness for a zone requires knowing the zone;
where the scope is unknown the trade cannot have been made for it, and fail_open
on unknown hands the most permissive stance to exactly the request an attacker can
most easily make unclassifiable. unknown is therefore not a zone and MUST fail closed.
Second: the register cannot aggregate across incommensurable scoping axes, so each
map declares its axis and its relationship to zone, and the register states what
it cannot answer rather than implying it can.'
decided_by: Bernd Worsch
created: '2026-09-06T12:17:19.104531Z'
updated: '2026-09-06T12:17:19.104531Z'
state_hub_decision_id: "009bc960-9029-4052-aaba-4c5e27a328dc"
```
## Context
On 2026-08-29 `access-engine` told this repository it was the party positioned to
notice when the aggregate of consumer stances diverged from what the policy packages
say — and then recorded that the claim was sound but the data was not there, because
§13.1 held one row and one row cannot diverge from anything. `secrets-engine`'s
publication made it two, and the divergence appeared immediately.
That is worth noting on its own: the register earned its existence the first time it
had two entries.
**`ops-warden` declares `unknown``fail_open`.** Explicit, versioned, under
`ADR-0009`. **`secrets-engine` declares `unknown``fail_closed`.** Both maps are
total, published, test-pinned, and §6.4-conformant. Neither is a defect under v0.7.
They disagree about the one case that by construction is the one nobody planned for.
They are also **not comparable**: `ops-warden` scopes by security zone,
`secrets-engine` by catalog stage — explicitly as the "equivalent scope" §6.4 permits,
until zone membership arrives as a claim. So the register cannot answer *"what is the
estate's stance for a `z2`-protected workload"*, which is the question an inventory
exists to answer.
`access-engine` reported both as observations and explicitly **not** as a request that
either repository change its stance, on the ground that a PDP does not set a
consumer's stance — §9.3's two-owner split, which was its own finding, cutting against
its own convenience. That restraint is correct and it is why the question arrives here:
setting a consumer's stance is not the PDP's to do, but doctrine is Gate House's.
## Decision
### 1. `unknown` is not a zone, and it fails closed
**A stance map MUST resolve `unknown` to `fail_closed`.** It is not a scope over which
the §9.3 availability trade may be made.
The reasoning is a distinction v0.7 does not draw, and the two maps are the proof it
is needed. §9.3 sanctions trading availability for openness **per zone** — knowingly,
for a named scope, at a declared cost. `ops-warden`'s `fail_open` for `z0``z2` is
exactly that and remains sanctioned: it is a *known* request in a *degraded* system.
The consumer knows what it is being asked and cannot reach the engine to ask.
`unknown` is a different failure. It is not a degraded system; it is an
**unclassified subject**. The consumer does not know what it is being asked.
**A per-zone trade requires knowing the zone.** Where the scope is unknown, the trade
cannot have been made *for* it — there was no zone in hand when the profile was
written, so no one weighed this request's cost. Resolving `unknown` to the permissive
answer therefore does not extend a considered decision; it invents one, and it invents
the most permissive one available.
The adversarial reading settles it. `unknown` is the cheapest state for an attacker to
induce: an unregistered workload, a malformed label, a resource created before
classification, a race against registry propagation. `fail_open` on `unknown` makes
"be unclassifiable" a privilege escalation, and it is the one escalation that requires
no credential. This is the §8 asymmetry — nothing may manufacture privilege — applied
where the estate had left a gap open by symmetry with a rule that does not reach it.
`ops-warden`'s map is conformant today and this makes one cell of it non-conformant at
v0.8. That is a doctrine change with a cost to a repository that did everything asked
of it — published early, built the reference form, and offered it estate-wide — and it
should be argued in the assent round rather than imposed quietly. Their `z0``z2`
zone stances are untouched.
### 2. A stance map declares its scoping axis and its relation to zone
**Each published map MUST name the axis it scopes over, and state its relationship to
security zone: a mapping, or an explicit declaration that none exists yet and why.**
The register is not repaired by forcing everyone onto zones. `secrets-engine` chose
catalog stage because zone membership is not yet available to it as a claim; making it
assert zones now would produce a fiction, and a fiction in a runtime-read,
test-pinned file is worse than an honest incommensurability.
**The register instead states what it cannot answer.** §13.1 carries the axis per row
and records that cross-axis aggregation is not available. An inventory that silently
cannot answer its own question is the §9.1 defect again — a surface catalogued without
the capability behind it.
`access-engine` notes the convergence path and it is the right one: zone *membership*
compiles into the registry snapshot it already consumes, while per-zone *stance*
belongs to the consumer. If membership reaches the decision as a claim,
`secrets-engine`'s axis converges onto zones with neither side inventing a mapping.
Until then the axes are recorded, not reconciled.
The cost of two incommensurable axes is small; the cost of five is not. This lands
before the third row rather than after the fifth.
### What is not decided here
Neither repository is asked to change its zone stances. `secrets-engine`'s stale
reference to a durable `ActionAuthorization` record in its map — the artifact
`GH-DEC-2026-005` shelved — is theirs to correct and `access-engine` has already told
them; it is a naming error in a runtime-read file, where a stale name outlives a stale
comment, and it does not change the stance.
## Reversal
Reverse ruling 1 if a consumer demonstrates a scope that is genuinely unknown *and*
genuinely low-consequence, where failing closed on it costs availability with no
security gain — the plausible candidate is a read-only diagnostic path under §5.1. The
expected resolution is that such a path is not a protected side effect and therefore
has no PEP obligation at all, rather than that it needs a permissive `unknown`. If
that resolution fails on a real path, this ruling is too broad.
Reverse ruling 2 if declaring the axis proves to be all the register needed, in which
case the "states what it cannot answer" half is redundant rather than wrong.
---
### Amendment to GH-DEC-2026-008, 2026-09-06 — the comparison as written could never pass
Found by `secrets-engine` re-verifying a replay fixture within hours of the ruling,
and reported independently by `access-engine` and `approval-engine`. Verified here
against `flex-auth/schemas/decision_envelope.schema.json` and `pkg/api/canonical.go`
before amending.
**The defect.** The ruling required `claim.binding.pdp_digest` to equal the decision's
`NewDecisionBinding.request_digest`. `access-engine` hashes context into the request
digest, and the dual-control pattern carries the claim in `context.approval`.
Embedding the claim therefore changes the digest of the request that carries it, so a
digest recorded at issue can never equal the final one.
This is a **hash cycle**, and `approval-engine` put the resolution correctly: it is
forced rather than chosen. A claim cannot carry the digest of a document containing
that claim — the value would have to be known before it could be computed. The
recorded digest is necessarily of the underlying action *before* any claim was
embedded.
**The consequence had it shipped.** A consumer obeying the rule and failing closed
would have denied `destroy` permanently — failing closed forever on a check that
cannot pass. The ruling's own fail-closed requirement is what would have made it
harmful rather than merely wrong.
**The fix, and whose it was.** `access-engine` publishes
`binding.approval_binding_digest` — the same canonical material with
`context.approval` removed, emitted only where a claim was carried
(`FLEX-DEC-2026-007`). An approval issued against a claim-free `Check` records that
`Check`'s digest, and the claim-bearing request reproduces it. The comparison becomes
implementable without changing what it requires: correspondence by identity, no
translation, nobody asserting an equivalence between vocabularies. **The A5 property
holds exactly as drafted.**
`approval-engine` was right that what was missing was a published statement of which
fields the PDP excludes, and right not to make that statement on `access-engine`'s
behalf. **A consumer MUST NOT guess the exclusion rule**: a digest computed under an
assumed rule produces a confident wrong answer, and comparing two digests derived
under different rules fails open toward accepting a claim bound to a different
request — the same failure direction as the vocabulary mapping this decision rejected,
reached by another road. Until a PDP publishes its exclusion rule, the path is
correctly fail-closed rather than complete: a gap in a dependency, not in the ruling.
**What this changes in the record.** The ruling stands. Its four numbered obligations
stand. Obligation 1's comparison target is corrected from the request digest to the
PDP's published exclusion-scoped digest. v0.8 §9.7.3 carries the corrected form.
**And a general property, flagged by `access-engine` as a near miss it does not ask
for.** The obvious fix is to drop `context.approval` from the request digest
entirely. That is unsafe, and the reason generalises: **an evidence-bearing input may
be excluded from a correspondence digest but never from the replay identity.** Two
requests differing only in which approval was presented decide differently — one
allows, one denies `dual_control_required` — so collapsing them would let an allow
obtained with a valid claim be replayed against a request carrying none. A fail-open
hole reached by a refactor that looks like simplification. It is in v0.8 §6.4
obligation 5 because the shape recurs wherever evidence travels inside a hashed
request, and because a property discovered by nearly getting it wrong is worth more
written down than remembered.
**Implementation status.** `access-engine`: `FLEX-DEC-2026-007`, `dd3ce4c`.
`approval-engine`: schema v3 adds a declared `pdp_path`; `create()` refuses
`pdp_path: true` without a `pdp_digest`, so an approval that would be unusable on the
path fails **at issue** rather than at the protected side effect. Intent is declared,
never inferred — a digest that happens to be present is not a declaration anybody
made, legacy rows migrate to `false` rather than being back-filled, and a successor
inherits its predecessor's declaration. Back-filling would have manufactured a
statement no requester made, which is this decision's own objection to translation in
another form.
**What this says about the ruling process.** The ruling was right and its mechanism
was missing, and that combination is only survivable because the repositories
implementing it read it against their own fixtures rather than accepting it. The
defect was found in hours by the repository that would have been broken by it, and
reported by two others who had no obligation to look.
## GH-DEC-2026-010 — A PEP must be able to attribute a decision to access-engine; the digest test does not do it
```yaml
id: GH-DEC-2026-010
kind: decision
title: A PEP must be able to attribute a decision to access-engine; the digest test
does not do it
status: resolved
owner: Bernd Worsch
repo: gate-house
standard: net-kingdom/canon/standards/security-layer-model_v0.8.md
source_note: flex-auth FLEX-DEC-2026-010; approval-engine docs/reviews/2026-09-07-security-layer-model-v08.md
requested_dispositions:
- approved
- revised
- rejected
affects:
- gate-house
- net-kingdom
- access-engine
- approval-engine
- secrets-engine
- ops-warden
- ops-mason
- user-engine
- tenant-engine
- audit-core
decided_by: Bernd Worsch
rationale: 'Raised by access-engine against its own artifact during the v0.8 assent
round, having recorded it as its own defect (FLEX-DEC-2026-010) before reviewing
this text. Section 6.4 obligation 1 required a PEP to hold a decision from access-engine,
and obligation 2 supplied a test emphatic that it was mechanical rather than a matter
of implementer judgement. That test establishes which request a decision was rendered
for and nothing about who rendered it, and it cannot: every input to it is either
sent by the caller or published, so a responder knowing a published package id and
version reproduces all of it and returns a well-formed allow. Fail-closed protects
against a decision point that is absent, not against one that lies. The asymmetry
with obligation 5 is the sharp form, since section 9.4 requires the approval object
to have authenticated entries and nothing required it of the decision, leaving an
obligation written over a pair of artifacts satisfiable for only one half. The obligation
now requires attribution and states that the digest comparison does not discharge
it; the mechanism is the owner s under section 17 and the gap is declared in section
13 rather than papered by a rule the standard does not get to choose.'
created: '2026-09-09T18:11:20.071372Z'
updated: '2026-09-09T18:11:20.071372Z'
state_hub_decision_id: "046aa117-5b33-4fc2-b88c-313ad40f8e45"
```
## Context
`access-engine` returned its v0.8 review on 2026-09-07 and ranked this finding
blocking for calling §6.4's PEP obligations complete — explicitly **not** blocking
anyone's adoption, since the condition already exists under v0.7 and v0.8 does not
create it. It had already recorded the defect against its own artifact as
`FLEX-DEC-2026-010`, with `FLEX-WP-0024` carrying the fix, before it read this text.
The finding is that the standard is not merely silent. Obligation 1 says a PEP must
hold *"a decision from `access-engine` identifying the request it was rendered for"*,
and obligation 2 supplies a test and is emphatic that it is not a judgement call:
replay is permitted iff the canonical request digest matches and the lifetime holds,
*"mechanical, not a matter of implementer judgement"*. A careful implementer reads the
mechanical test as discharging the obligation above it. It does not.
Every input to that comparison is either sent by the caller or published by the PDP.
The request material is what the PEP transmitted; `policy_package_digest` and
`registry_snapshot_digest` are computable from files in a public repository. A
responder that knows the package id and version — both published — reproduces all
three and returns a well-formed allow. Publishing more digests makes a forged envelope
look **more** authenticated, not less.
`secrets-engine` put the consequence better than either of us: its posture *"silently
assumes the PDP is the PDP"*. `approval-engine` carried it into the correspondence
chain — an approval whose `pdp_digest` matches a **forged** decision matches perfectly.
Identity comparison proves two artifacts describe one request. It proves nothing about
whether the decision is authentic.
**The gap was visible from inside and was recorded against the wrong artifact.** §16
already carried *"publication integrity of the Taxonomy layer itself… while its own
publication path has no digest, freeze, or rollback discipline"*. That is this
observation one layer up. Applied to the artifact the standard regulates rather than to
the standard, it is this decision.
## Decision
### 1. Attribution is required, and is a separate property from identity
**A PEP MUST hold a decision that is *attributable to* `access-engine`, not merely
present and well-formed.** Obligation 2's digest test establishes **which request** a
decision was rendered for. It establishes nothing about **who rendered it**.
**Obligation 1 is not discharged by a digest comparison**, and an implementer MUST NOT
read obligation 2's mechanical test as discharging it. This is the whole of the change:
the obligation stops reading as satisfied by a check that does not satisfy it.
### 2. Fail-closed does not reach this
§9.3's apparatus — two failure cases, two owners, published stance maps — addresses
**absence**. An unreachable PDP denies. A responder impersonating one allows. Nothing
in the standard addressed a responder, and no stance value can: the stance is consulted
when the engine is unreachable, and a lying responder is reachable by construction.
### 3. Obligation 5 was written over a pair it could only half satisfy
§9.4 requires the approval object to have *"durable, authenticated entries"*. §6.4
obligation 5 then requires that where a PEP's decision rests on more than one artifact,
each MUST be validated against the layer that owns its data — and names the
approval-claim / `DecisionEnvelope` pair as the live instance.
So the standard required authenticity of the **PIP's** artifact and not of the
**PDP's**, and wrote an obligation across both. That asymmetry was argued nowhere. It
is oversight, not position, and it is corrected here rather than defended.
### 4. The mechanism is not the standard's to choose
`access-engine` did not ask for a signature scheme in the statute, and it does not get
one. The shape is the owner's under §17. What the standard names is the property.
Until a mechanism ships, the condition is a **declared §13 gap** with `access-engine`
as owner (`FLEX-DEC-2026-010`, `FLEX-WP-0024`; `FLEX-DEC-2026-009` puts the
authenticated caller in provenance, so a record eventually attests both ends of the
channel rather than neither). A declared gap is honest. An unstated assumption inside a
test called mechanical is not.
## What this does not do
It does not close the gap. `access-engine`'s note that Glas completed operator
caller-auth mid-review is correctly self-limited: an authenticated API-server
port-forward authenticates the responder for one operator path and produces no signed
portable artifact.
It also does not create an adoption blocker. The condition predates v0.8 and every
PEP-shaped consumer is in it today. What changes is that they can now see they are.
## Reversal condition
If a PEP can verify the decision's origin by a mechanism that does not itself rest on
material the caller supplies or the PDP publishes openly, the §13 gap closes and this
record's second half becomes historical. The first half — that identity is not
attribution — does not revert; it is a property of hashing, not of the current channel.
## Provenance
Raised by `access-engine` (`FLEX-DEC-2026-011`, v0.8 assent, F1), against its own
artifact and having recorded the defect on its own side first. Independently framed by
`secrets-engine`. Carried into the correspondence chain by `approval-engine`, which
noted its own `docs/approval-claim.md` said what `pdp_digest` cannot cover without
saying it cannot detect a forged decision. Applied to
`security-layer-model_v0.8.md` §6.4 obligation 1 and §13 at `net-kingdom@64394e9`.
## GH-DEC-2026-011 — Classification coverage is published beside the stance; a transitional fail_open is declined
```yaml
id: GH-DEC-2026-011
kind: decision
title: Classification coverage is published beside the stance; a transitional fail_open
is declined
status: resolved
owner: Bernd Worsch
repo: gate-house
standard: net-kingdom/canon/standards/security-layer-model_v0.8.md
source_note: ops-warden history/2026-09-09-layer-model-v08-review.md @ 8e1b621; flex-auth
FLEX-DEC-2026-011 F2
requested_dispositions:
- approved
- revised
- rejected
affects:
- gate-house
- net-kingdom
- ops-warden
- ops-mason
- secrets-engine
- user-engine
- tenant-engine
- access-engine
- zone-engine
decided_by: Bernd Worsch
rationale: 'ops-warden assented to GH-DEC-2026-009 on its own terms and then reported
what adopting it costs: zero of three signing targets and three of twenty-one routing
lanes resolve to a zone, so unknown fail_closed adopted today would fail closed
on essentially every certificate it issues whenever access-engine is unreachable,
including the continuity path an operator needs to repair that unreachability. It
asked for a dated transitional unknown fail_open converting on coverage rather than
calendar. Declined: a sanctioned transitional fail_open is indistinguishable at
runtime from the stance the rule forbids and would make the rule optional at the
only moment it costs anything, while section 11 s declared-gap mark already expresses
a correct rule whose adoption is not yet affordable. Its second preference is adopted
instead. Section 13.1 records a dated classification-coverage figure beside each
stance, because unknown fail_closed over an entirely unclassified population is
conformant and materially misleading. access-engine reached the same place from
the other side: totality satisfied by a catch-all is satisfied vacuously, so a map
must enumerate its axis and an absent scope must be distinguishable in the record
from an unknown one and surface as a conformance failure.'
created: '2026-09-09T18:12:02.553370Z'
updated: '2026-09-09T18:12:02.553370Z'
state_hub_decision_id: "6777f9d1-566d-4bc7-815e-e08582b8719a"
```
## Context
`GH-DEC-2026-009` made `unknown``fail_closed` normative and made one cell of
`ops-warden`'s published stance map non-conformant. That ruling was circulated with
v0.8 rather than imposed, precisely because the repository bearing its cost had not
reviewed it. `ops-warden` returned the review on 2026-09-09
(`history/2026-09-09-layer-model-v08-review.md`, `8e1b621`).
**It assented to the rule, on the falsifier's own terms.** The reversal clause
anticipated that such a path would be a §5.1 read-only diagnostic carrying no PEP
obligation. `ops-warden` went looking for that escape hatch on its own side and
reported that it does not have one: the stance map governs exactly one protected
action, `warden sign`, which is a credential-issuing side effect that §5.1 does not
reach. The predicted defence is absent and the argument stands unrebutted.
**Then it priced the adoption.** Measured 2026-09-09:
```text
signing targets (actor resources in snapshot): 0 resolved, 3 unknown, 1 n/a
routing catalog lanes: 3 resolved, 18 unknown, 12 n/a
```
Zero of its signing targets resolve to a zone. So `unknown``fail_closed` adopted
today does not fail closed on an edge case; it fails closed on essentially every
certificate `ops-warden` issues, whenever `access-engine` is unreachable. That is the
configuration `ADR-0006` rejected, reached by another route: one value making the
decision engine a uniform dependency of every signing path, **including the continuity
path needed to repair that dependency**. Engine down → operator needs an SSH
certificate to reach the host and restore it → the repair target is unknown because
nobody classified it → denied.
Eighteen of its eighteen unknown lanes are unknown because **another** repository has
not published a workload-identity declaration, and `ADR-0009` rule 3 forbids closing
that by inference.
`access-engine` arrived at the same register from the opposite side (`FLEX-DEC-2026-011`,
F2): a map carrying `unknown``fail_closed` satisfies obligation 3's totality
requirement **vacuously**. Every scope the author never enumerated lands in `unknown`,
fails closed, and nobody ever learns which those were. It recognised the shape because
it published it — `FLEX-DEC-2026-008`, a policy package shipped with no tenant rule at
all while 29 fixtures passed, because every fixture carried the same tenant.
## Decision
### 1. A dated transitional `unknown: fail_open` is declined
`ops-warden`'s first preference was that obligation 3 name a transition: a consumer may
declare `unknown: fail_open` as a dated, published transitional state with a coverage
figure attached, converting on coverage rather than on calendar.
**Declined.** A sanctioned transitional `fail_open` is indistinguishable at runtime from
the stance the rule forbids. It makes the rule optional at the moment of adoption — the
only moment it costs anything — and `unknown` is the cheapest state for an attacker to
induce whether or not the consumer has dated its intention to stop being permissive.
The mechanism asked for already exists and does not have that defect: §11's
**declared-gap** mark says *"correct rule, adoption not yet affordable"* without
inverting the rule's effect. `ops-warden` proposed exactly that as its own second
preference and named `WARDEN-WP-0040` as the route.
The deadlock is real and the answer is classification, not a permissive axis: continuity
paths are classified into a scope whose stance is open, and the deadlock does not arise.
`ops-warden` accepted that answer and correctly observed that it is work not yet done.
Work not yet done is a gap. It is not a reason to hold the axis open.
**And a stricter stance is not a licence to manufacture the membership that makes it
survivable.** `ADR-0009` rule 3 is right and must hold under pressure from this ruling.
A consumer MUST NOT infer a scope another repository has not declared.
### 2. Classification coverage is published beside the stance
**§13.1 records a dated classification-coverage figure for each consumer.** A row
reading `unknown``fail_closed` while 100% of that consumer's targets are unknown is
conformant and materially misleading: a reader cannot distinguish a strict consumer from
an unclassified one. It is §11's published-map-equals-shipped-behaviour rule one level
up — the map becomes accurate about itself and inaccurate about its effect.
Coverage is **disclosure, not a licence**. It does not soften the stance, gate it, or
create a state in which a non-conformant cell becomes conformant. It reports.
Coverage is reported by the consumer and never computed here. A blank means *"not
reported"*, never *"complete"*.
### 3. A map must enumerate its axis; `absent` is not `unknown`
**Totality MUST NOT be satisfied by a catch-all.** A published map enumerates the axis
it scopes over. Otherwise obligation 3's totality requirement is unfalsifiable and its
drift test passes by exercising the default rather than the axis.
**`unknown` and `absent` are one runtime behaviour and two meanings.** Both fail closed
`absent` for the stronger reason, since it is the branch reached by discovering that
the author's model of their own axis was wrong, and §8's asymmetry forbids being more
permissive on surprise. But an `unknown` hit is normal operation under a considered
stance, while an `absent` hit is evidence this obligation is violated. **An `absent` hit
MUST be distinguishable in the record and MUST surface as a conformance failure** rather
than be absorbed by the catch-all.
This closes the question §16 opened at the v0.8 cut.
### 4. `ops-mason` is marked
An unpublished stance map is a plainer violation of obligation 3 than a wrongly-valued
cell in a published one, and §13.1 stated it as bare fact while bolding the other. A
reader scanning for marks found one row and concluded the other four were fine. That is
Gate House's own §11 marking obligation applied to Gate House's own register, returned
unchanged by `access-engine` after receiving the same argument about a stale row of its
own. `ops-mason`'s row is now marked, with no route recorded.
## Reversal condition
If a consumer demonstrates a scope that is genuinely unclassifiable, genuinely
low-consequence, and whose permissive resolution cannot be induced by an unprivileged
party, §1 is open to revision — that falsifier is `GH-DEC-2026-009`'s and survives here.
`ops-warden` went looking for it and reported its absence, which is evidence and not
proof.
§2 reverses if coverage figures start being read as a stance qualifier rather than as
disclosure. If a row is ever argued to be conformant *because* its coverage is low, the
column is doing harm and comes out.
## Provenance
Raised by `ops-warden` (v0.8 assent, two findings and one agreement), which bears the
entire cost of the ruling it assented to and asked for the transition it did not get.
Its measured figures are the first entries in the coverage column. §3 raised by
`access-engine` (`FLEX-DEC-2026-011`, F2); §4 by `access-engine` (F4). Applied to
`security-layer-model_v0.8.md` §6.4 obligation 3 and §13.1 at `net-kingdom@64394e9`.
`access-engine` added, unprompted, that its concurrence with `GH-DEC-2026-009` cost it
nothing — it is not PEP-shaped and publishes no stance map — and that a PDP's assent to
a ruling falling entirely on other repositories is weak evidence for it. That is
recorded because it is right: the strength here is the asymmetry argument and
`ops-warden`'s assent against its own interest, not the count of repositories agreeing.
## GH-DEC-2026-012 — informed-decision is PEP-shaped and may emit a presentation claim; view_hash and the binding digest are distinct and non-substitutable
```yaml
id: GH-DEC-2026-012
kind: decision
title: informed-decision is PEP-shaped and may emit a presentation claim; view_hash
and the binding digest are distinct and non-substitutable
status: resolved
owner: Bernd Worsch
repo: gate-house
standard: net-kingdom/canon/standards/security-layer-model_v0.8.md
source_note: informed-decision/docs/gate-house-decision-request-layer-placement.md
(INFD-IN-0001; hub intake 01a08610)
requested_dispositions:
- approved
- revised
- rejected
affects:
- gate-house
- informed-decision
- approval-engine
- access-engine
- audit-core
- key-cape
- net-kingdom
decided_by: Bernd Worsch
rationale: 'Three rulings requested before any architecture was written. R1: informed-decision
is PEP-shaped under section 6.4 and owes companion section 5, because it causes
a protected side effect on the far side of a decision. It is not an Engine and holds
no state another layer reads at runtime for a verdict. R2: a component may be PEP-shaped
and also emit a claim; PEP and PIP are shapes a repository has, not layers it occupies,
and the catalog rows are layers. The presentation claim is permitted as a claim
about presentation only, subject to three limits: it must not carry, restate or
imply the decision or the approval verdict, it must not be an input to the decision
it presents for, and its evidence copy reaches audit-core independently of the emitter.
R3: candidate (b). view_hash and the approval binding digest are distinct attestations
with an explicit authority rule. (a) is refused because merging them would make
one repository the authority on what another layer computes over a request, which
is the objection GH-DEC-2026-008 made to translation. (c) is refused because nesting
the binding digest inside view_hash creates an ordering dependency and reproduces
the hash-cycle shape that made GH-DEC-2026-008 unimplementable. The binding digest
is authoritative for what the request is; view_hash is authoritative only for what
was shown; neither may be substituted for the other, and a disagreement between
them is a finding against the presenting surface rather than a fact about the request.'
created: '2026-09-09T20:09:49.402289Z'
updated: '2026-09-09T20:09:49.402289Z'
state_hub_decision_id: "8c3c0d14-91ca-43fd-bef3-3bd01ff4213f"
```
## Context
`informed-decision` is a new repository claiming the browser-facing approver UI that
`approval-engine` names under its own Non-Goals. It filed `INFD-IN-0001` **before
writing any architecture**, asking for three rulings and stating what it would do on
each possible answer, including the answers it did not want. That order — ruling first,
architecture second — is what §17 exists to produce and it is worth recording as the
reason this was answered inside a day.
It also argued the self-dealing objection against itself in §3 of its request, and asked
that no ruling credit it with closing the residual it named. That request is honoured
below.
## Decision
### R1 — `informed-decision` is PEP-shaped, and is not an Engine
**Confirmed as proposed.** It causes a protected side effect on the far side of a
decision: it submits an approval entry carrying an authenticated human's identity. §6.4
applies in full, and companion §5 is owed.
It is **not** an Engine and takes no catalog row as one. It holds no state another layer
reads at runtime to reach a verdict, which is the test, and it renders no decision.
Being PEP-shaped does not move a repository out of its layer (§6.4). Its layer is its
own to declare in its own voice; this ruling settles its *shape*, which is what was
asked and what was blocking.
Consequences it should expect, stated so they are not discovered later:
- A published unreachable-engine stance map at a path named in its layer declaration,
and a row in statute §13.1. Build to **v0.8's** obligation 3 rather than v0.7's:
`unknown` MUST resolve to `fail_closed`, the map MUST enumerate its axis rather than
lean on a catch-all, an `absent` scope MUST be distinguishable in the record from an
`unknown` one, and the test asserting published-equals-shipped is a `MUST`. Publish a
classification-coverage figure beside the stance (`GH-DEC-2026-011`).
- §6.4 obligation 1 in its v0.8 form, including the attribution property it cannot
satisfy today (`GH-DEC-2026-010`). It inherits a declared gap, not a clean path, and
its own documents must say so rather than describing validation as complete.
### R2 — Yes, and it is not a second catalog row
**A component may be PEP-shaped and also emit a claim.** PEP and PIP are *shapes* a
repository has; the §4 catalog records *layers* a repository occupies. Nothing in the
statute forbids one component having two shapes, and the rule that looks like it does —
§6.4 obligation 5's *"a PIP MUST NOT republish the PDP's decision"* — is a prohibition on
**republishing a decision**, not on holding two shapes. `informed-decision` proposed
exactly this reading and it is right.
Three limits, and they are the substance of the permission:
1. **The presentation claim carries presentation and nothing else.** It MUST NOT carry,
restate, summarise, or imply the decision, the approval verdict, or whether the act
was permitted. A consumer must not be able to learn from this claim anything about
what was *decided* — only about what was *shown*. This is obligation 5 applied at
issue rather than at consumption.
2. **It MUST NOT be an input to the decision it presents for.** Presentation is evidence
about conduct, never a source of authority. A policy that reads `view_hash` to decide
whether an act is permitted would let the presenting surface contribute to its own
authorization, which is the estate's oldest rule in a new place: cognition proposes,
authority disposes.
3. **The evidence copy reaches `audit-core` independently of the emitter.** The claim
endpoint and the evidence path are different things and one does not substitute for
the other. Audit evidence is protected from the actor being audited, and here the
actor and the source are the same component — so the copy that is evidence must not
be reachable only through the party it is evidence about.
**The §17 yield is accepted as offered.** Whatever shape `informed-decision` publishes
yields to the shared request-claim schema when that schema has an owner, which it does
not yet (§17 records request-claim and gap-record as the two unsettled artifacts). The
position matches `approval-engine`'s in `APPROVAL-IN-0001` and is the right one: a local
invention declared temporary is tolerable, a local invention declared permanent is the
drift §17 exists to prevent.
### R3 — Candidate (b): distinct attestations, with the authority rule stated
**`view_hash` and `approval-engine`'s binding digest are two attestations over one act,
and neither may be substituted for the other.**
- **The binding digest is authoritative for what the request *is*.** Replay identity,
correspondence with the decision, and everything §6.4 obligation 5 governs.
- **`view_hash` is authoritative only for what was *shown*.** It attests presentation:
brief, packet, highlights, locale, UI release, and the binding material as rendered.
- **A disagreement between them is a finding against the presenting surface, never a
fact about the request.** This is the rule that keeps the two from becoming rival
answers to one question, and it is the thing `informed-decision` asked us to prevent.
**(a) is refused, and for a doctrinal reason rather than the cost given.** One digest
means one canonicalization authoritative for both, and the material is owned by
different layers: what a request *is* is computed by the decision path, what was *shown*
is known only to the presenting surface. Merging them makes one repository the authority
on what another layer computes over a request — a third authority on what a request is,
which is precisely the objection `GH-DEC-2026-008` made to cross-engine translation. The
cost `informed-decision` names is real and secondary: the binding digest must exist where
no human is in the loop, and `view_hash` cannot exist there at all.
**(c) is refused for two reasons, the second the stronger.** First, the ordering
dependency it admits: an approval would have to exist before a memo could be rendered,
which inverts the flow this repository exists to serve. Second, and this is why we will
not take it even if the ordering were acceptable — nesting one hash inside the other
reproduces the shape that made `GH-DEC-2026-008` unimplementable. That ruling required a
claim to name the digest of a request that would come to contain the claim: a hash cycle,
found by `secrets-engine` and reported independently by two engines within hours. If
`view_hash` ever travels inside hashed request material while containing the binding
digest, the same cycle reappears with the same fail-closed consequence. We have paid for
that lesson once this quarter and are not buying it a second time.
**How the two are linked, since they are no longer linked by nesting.** By
**co-reference, not by translation**: the presentation record carries the approval or
binding identifier explicitly, and both attestations are read against that one reference.
`informed-decision` MUST NOT recompute or restate `approval-engine`'s binding digest from
its own vocabulary — it references the digest the other layer computed and recorded. That
is `GH-DEC-2026-008`'s identity-not-translation rule applied here, and it is why the two
canonicalizations covering overlapping material is not a defect: they answer different
questions and are compared to nothing.
## The residual, not closed and not credited
`informed-decision` stated it against itself and asked not to be credited with closing
it. **It is not closed, and this ruling does not close it.** A compromised surface can
present X and attest Y, because `view_hash` is computed by the component that renders.
That is structurally the same residual `approval-engine` names for adversarial omission
at a compromised source, and it takes the same disposition: attestation and atomicity
cover accident and later tampering, never a compromised source. It belongs in the same
register as §6.4 obligation 5's summary-predicate residual and §9.6's — detection, not
prevention.
The §3 argument that this is *not* the failure that kept the approval object out of
`access-engine` is accepted. An evaluator owning what it evaluates can grade its own
inputs; a renderer attesting its own rendering is the only party able to produce the
record at all. The distinction holds — but limit 2 above is what keeps it holding, and
without it the two collapse.
## Reversal condition
R2's permission reverses if a presentation claim is ever found carrying decision
content, or if a policy is found reading `view_hash` as an authorization input. Either
would mean the shape cannot be held safely by one component and the claim moves to
`audit-core` as the request's own R2-refused branch describes.
R3 §(b) reverses if the shared request-claim schema (§17) arrives with a presentation
attestation already in it, in which case the local shape yields as offered.
## Provenance
Requested by `informed-decision` (`INFD-IN-0001`, hub intake `01a08610`) before writing
its architecture, with three candidate rulings, their costs, the self-dealing objection
argued against itself, and an explicit list of what it was *not* asking for. Unblocks
`INFD-WP-0001` T05 and T07, and `KEY-WP-0013-T02`.
## GH-DEC-2026-013 — A client registration may supply a human principal's tenant only as a declared bounded gap, and the claim must carry its provenance
```yaml
id: GH-DEC-2026-013
kind: decision
title: A client registration may supply a human principal's tenant only as a declared
bounded gap, and the claim must carry its provenance
status: resolved
owner: Bernd Worsch
repo: gate-house
standard: net-kingdom/canon/standards/security-layer-model_v0.8.md
source_note: key-cape/docs/tenant-claim-contract.md and src/internal/server/oidc/token.go
humanTenant (KEY-WP-0013-T05); relayed by informed-decision
requested_dispositions:
- approved
- revised
- rejected
affects:
- gate-house
- key-cape
- approval-engine
- access-engine
- user-engine
- informed-decision
- net-kingdom
- ops-warden
decided_by: Bernd Worsch
rationale: 'key-cape has no adapter populating the directory user tenant, so every
human token falls back to the default tenant and approval-engine refuses it by exact
match. Two resolutions were possible: source the tenant from the directory, or let
a client registration bind it. Directory-sourced is the terminal state, because
a tenant is a property of the principal and a registration is a statement about
the actor; sourcing a principal attribute from the actor collapses two identities
the estate keeps distinct, and supplying a tenant the directory has not assigned
widens rather than attenuates. The registration-bound shape is nonetheless admissible
as a declared bounded gap rather than refused, because its distinguishing case fails
closed: where the registration and the directory disagree, issuance is refused rather
than resolved in either direction. That is the general property this record states
— a transitional shape is admissible where it fails closed on exactly the case that
distinguishes it from the correct resolution, and inadmissible where it fails open
there, which is why GH-DEC-2026-011 declined ops-warden a transitional fail_open
and this record grants key-cape its transitional binding. Three conditions attach:
refusal on disagreement is normative rather than a design choice, the exclusion
of dynamic client registration is a normative precondition whose lifting voids the
rule rather than reopening it, and the gap is registered with an owner for the directory
adapter. One finding is added that was not asked for: the tenant claim is emitted
as a bare string, so a consumer cannot tell a tenant the directory asserted about
the person from one a registration supplied about the client, and a consumer relying
on it for the first is relying on the second. The claim MUST carry its provenance.'
created: '2026-09-09T21:18:11.679012Z'
updated: '2026-09-09T21:18:11.679012Z'
state_hub_decision_id: "fcc0c595-7081-404e-b2aa-1536e91a371c"
```
## Context
`key-cape` binds the approval chain to the landlord zone (`tenant:platform`), and
`approval-engine` compares the tenant claim by exact string equality. **No adapter
populates `domain.User.Tenant`**, so every human token fell back to `tenant:coulomb` and
an approver token would have been refused downstream — presenting as a *failed approval*
rather than as a *registration defect*, which is the failure mode that makes this worth
ruling on rather than leaving to implementation.
Two resolutions were available: source the tenant from the directory, or let a client
registration declare it. `key-cape` declined to choose unilaterally on the ground that
the second writes a cross-tenant capability into the issuer, which reads as doctrine
rather than implementation. That reading is correct and the question is Gate House's:
it is about what may be a source of a principal's identity, which is a Core Rule, not a
runbook.
It reached us relayed by `informed-decision`, which was asking about its own client
registration and passed on a question that was not its own. `key-cape` has already
built the registration-bound shape, and built it carefully — this record rules on a
design that exists rather than on a proposal.
## Decision
### 1. Directory-sourced is the terminal state
**A principal's tenant is sourced from the identity layer's record of the principal.**
A client registration is a statement about the **actor** — the client the person
authenticated through. A tenant is a property of the **principal**. Sourcing the second
from the first collapses two of the three identities the estate keeps distinct
(principal, actor, runtime identity), and it collapses them in the direction that
widens: a registration supplying a tenant the directory has not assigned gives the
principal an attribute the identity layer never asserted about them. **Delegation
attenuates and never widens.**
The condition being routed around is a missing adapter, not an absent owner. A missing
implementation in the identity layer is fixed in the identity layer.
### 2. The registration-bound shape is admissible as a declared, bounded gap
**Not refused, and not blessed as permanent.** `key-cape` may ship what it has built,
under §3's conditions and registered as a gap.
The reason it is admissible rather than forbidden is precise, and is not that the design
is careful. It is that **the case distinguishing it from the correct resolution fails
closed.** Where the registration declares a zone and the directory has placed the user
in a different one, `humanTenant` refuses to issue rather than picking a winner. Either
answer would be a silent cross-tenant assertion, and the design says so. A registration
can bind a zone for a user the directory has not placed; it can never relabel one it has.
`key-cape`'s further observation is what makes the gap bounded rather than open-ended:
the same code turns from *supplying* the zone into *enforcing agreement with* it the
moment the directory carries tenants — no second migration, and no window in which a
stale registration silently wins. A transitional shape that converges on the terminal
state by subtraction is a different object from one that will have to be unwound.
### 3. The general property, which is why this is granted and `GH-DEC-2026-011` was declined
**A transitional shape is admissible where it fails closed on exactly the case that
distinguishes it from the correct resolution, and inadmissible where it fails open
there.**
This is §8's asymmetry applied to transitions, and it is the rule that keeps these two
decisions consistent rather than merely both defensible:
- `ops-warden` asked for a dated transitional `unknown: fail_open` (`GH-DEC-2026-011`).
**Declined.** The distinguishing case — an unclassified subject reaching an unreachable
engine — is exactly where the transitional shape fails **open**. It is behaviourally
identical to the violation for the whole of its life, so the transition licenses the
thing the rule forbids and merely dates it.
- `key-cape` binds a tenant at registration. **Granted.** The distinguishing case —
registration and directory disagreeing — fails **closed**. The transitional shape and
the terminal state differ only where nobody is served either way.
A transition is a promise about the future. **A promise that fails open is a permission;
a promise that fails closed is a gap.** Only the second is a thing a register can hold.
### 4. Three conditions
1. **Refusal on disagreement is normative.** Row four of `key-cape`'s table is not a
design choice it may later optimise. A registration MUST NOT relabel a
directory-placed principal, and MUST NOT resolve the conflict in either direction.
Any future change that picks a winner — in either direction, including preferring the
directory — voids this permission, because it converts a refusal into a silent
cross-tenant assertion.
2. **The exclusion of dynamic client registration is a normative precondition, and
lifting it VOIDS this rule rather than reopening it.** `key-cape` writes that a
self-service client able to name its users' tenant would be a straightforward
escalation, and that the rule "must be revisited" if the exclusion is lifted. That is
too weak and we are strengthening it: revisiting implies the answer might survive
review. It does not. If dynamic client registration is ever admitted, the
registration-bound shape is void on that day and the directory becomes the only
source, whatever state the adapter is in. Stated that way so that lifting the
exclusion cannot be a small change made by someone who never reads this record.
3. **Registered as a declared gap under §13**, with an intended owner for the directory
adapter, so it cannot quietly become the permanent answer by nobody minding it. Per
§13, an intended owner is a proposal **to** a repository, not an assignment **onto**
it; this record does not name one.
### 5. The finding that was not asked for: the claim must carry its provenance
`key-cape` emits `tenant` as a bare string. A consumer cannot tell a tenant **the
directory asserted about the person** from a tenant **a registration supplied about the
client they came through**. `approval-engine` admits an approver by exact-matching that
string, and it is therefore relying on the second while its contract reads as though it
relies on the first.
**This is the shape of `GH-DEC-2026-010`, one layer down.** There, a PEP held a decision
and could establish which request it was for but not who issued it, while a mechanical
test read as though it discharged both. Here a consumer holds a tenant and can establish
that the string matches, but not whether the identity layer ever said it about this
person. In both cases the check is sound and the property a reader infers from it is not
present.
**The `tenant` claim MUST carry its provenance** — whether the value was asserted by the
directory about the principal, or supplied by the registration — and a consumer MUST NOT
treat the two as equivalent for any decision that turns on a fact about the *person*.
**Amended 2026-09-10 — `key-cape` emitted three values where this record named two, and
it was right to.** Its third is `default`: nobody asserted a zone and the profile's
non-empty fallback supplied one. Folding that into `directory` would have reproduced this
very finding one level down, since a consumer would read an assertion the identity layer
never made. `key-cape` applied A-16 to a route this record did not examine, and asked to
be corrected rather than assume — the correction is that there was nothing to correct.
Its second judgement is also endorsed: where registration and directory **agree**, the
value resolves to `directory`, because the directory did assert it about the person and
reporting the weaker source would understate what is known. That also makes the claim
strengthen on its own the day the adapter lands, with no reissue.
Exact-match admission on a registration-supplied tenant is admission on the client's
say-so.
The mechanism is `key-cape`'s: this record names the property, not the field. Note that
the same rule from `GH-DEC-2026-011` §3 applies to how it is recorded — two sources, one
claim value, and the record must distinguish them even though the string is identical.
It is `unknown` and `absent` again, in the identity layer.
**Sequencing:** this does not gate `KEY-WP-0013-T02`. The registration may be created
under §2 today. What must not happen is the path being described as validated while a
consumer cannot see which fact it is relying on.
### 6. `informed-decision`'s argument for registration-bound, answered
`INFD-IN-0002` arrived after this record was written, arguing for the registration-bound
resolution on a ground `key-cape` did not raise. `informed-decision`'s object model
separates a **pre-sign binding slice** — which commits which identity and which
scope/tenant is being entered — from awareness material shown but never signed. Under
registration-bound, it argues, the tenant is a property of the surface and its
registration, which is exactly what the binding slice commits; under directory-sourced it
describes the person, which sits closer to awareness than to binding.
**The argument is accepted, and it does not change §1. It sharpens the defect.**
What `informed-decision` describes is a real fact that deserves to be committed: *which
scope is this act being entered into*. That is a property of the **act**. It is not the
same fact as *which tenant is this person a member of*, which is a property of the
**principal** and is what `approval-engine` exact-matches to admit an approver.
The trouble is that one claim named `tenant` is being asked to carry both. That is why
the registration-bound shape feels correct to `informed-decision` and wrong to the
identity layer: each is looking at a different fact through the same field.
**Amended 2026-09-10: there are three facts, not two.** `approval-engine` answered this
record by naming the one it actually uses, and it is neither of the two above:
1. **Store isolation***did this caller arrive through a channel this store serves.* A
property of the **channel**. This is what `approval-engine`'s exact-match gate tests,
and it is what a registration-supplied tenant **is** adequate for: admission means
arrived through a registered channel, the approver client is static and
deployment-owned, and dynamic client registration is excluded.
2. **Act scope***which scope is this act being entered into.* A property of the
**act**. `informed-decision`'s binding slice needs this, and it resolved the point by
finding that its own `binding.target` already commits it — so the ruling required no
new field, only a statement that `binding.target` is the act-scope and MUST NOT be
derived from the token's `tenant` claim.
3. **Principal membership***is this person a member of this zone.* A property of the
**principal**. This is the one a registration-supplied tenant cannot carry, and the
one §1 reserves to the directory.
`approval-engine` committed to the consequence without being asked: a registration-
supplied tenant is admissible for (1) and **not** for any doctrine turning on (3), and it
asked to be held to that as *"the point at which a sound check silently becomes an unsound
one"*. Held. Note `GH-DEC-2026-016` §5 is the first place it bites.
Three facts in one field is a stronger case for §5 than two was. It also explains why
each party's intuition was locally correct: `approval-engine` reading (1) was right that
the registration suffices, `informed-decision` reading (2) was right that the fact is
act-scoped, and the identity layer reading (3) was right that a registration cannot supply
it. Nobody was wrong about their own fact; the field was wrong to hold all three. A binding
slice that must commit the scope being entered should **commit that scope**, not borrow
the principal's membership claim to stand in for it.
So `informed-decision` is right that its binding needs the fact, right that the fact is
act-scoped rather than person-scoped, and wrong that this makes the principal's `tenant`
claim the place to put it. Its argument is the best available evidence that §5's
provenance requirement is necessary: two facts sharing one field is exactly the condition
under which a consumer cannot tell what it is relying on.
This record does not design the second field. Whether the act-scope commitment is a
separate claim, part of the binding document, or something the request-claim schema
carries when §17 finds it an owner, is not doctrine and is not settled here.
## What this does not rule
Not ruled: whether `approval-engine`'s exact-match admission is the right gate, which is
`approval-engine`'s. Not ruled: which repository owns the directory adapter. Not ruled:
the choice of `tenant:platform` itself, which is settled by operator decision
`5ed3fb35-eca9-413a-82b9-95171ba85bf6` and is not doctrine.
## Reversal condition
§2 reverses on the day the directory populates the principal's tenant: the permission is
spent, not merely unused, and §1 governs alone.
§3's general property reverses if a case is found where a transitional shape failing
closed on its distinguishing case is nonetheless the more dangerous option — which would
mean the asymmetry rule has an exception, and §8 would be the thing under review rather
than this record.
§5 reverses if the two provenances are ever proven indistinguishable in effect, i.e. if
no consumer's decision turns on the difference. That is a claim about every present and
future consumer and we do not expect it to be made good.
## Provenance
Raised by `key-cape` (`KEY-WP-0013-T05`, `docs/tenant-claim-contract.md`), which built
the shape, documented its own escalation risk, and declined to ratify its own design.
Relayed by `informed-decision` while asking about its own registration — a repository
passing on a question that was not its to carry. `GH-DEC-2026-011`'s decline and this
record's grant are the two halves of §3, which neither request asked for and which is
the part of this record most likely to matter later.
## GH-DEC-2026-014 — A commitment-only evidence record satisfies non-alteration, never reconstructability, and its erasure must be detectable
```yaml
id: GH-DEC-2026-014
kind: decision
title: A commitment-only evidence record satisfies non-alteration, never reconstructability,
and its erasure must be detectable
status: resolved
owner: Bernd Worsch
repo: gate-house
standard: net-kingdom/canon/standards/security-layer-model_v0.8.md
source_note: informed-decision INFD-IN-0003
requested_dispositions:
- approved
- revised
- rejected
affects:
- gate-house
- informed-decision
- audit-core
- approval-engine
- net-kingdom
decided_by: Bernd Worsch
rationale: 'informed-decision asked what travels on the independent evidence path
that GH-DEC-2026-012 limit 3 requires, proposing commitment-only for stage 1 rather
than the full binding document, because the binding document carries commercial
and personal material that would enter the audit fabric under retention and export
entitlements designed for audit events. The payload schema and its retention are
audit-core custody and are not ruled here. Two things are doctrine. First, a commitment-only
record satisfies non-alteration and never reconstructability, and MUST NOT be described
as satisfying the second; this is the existing bound that an archive proves records
were not altered or truncated after arrival and never that one was never sent, applied
to a record that additionally does not carry what it commits to. Second, the gap
commitment-only leaves is availability rather than integrity, and it sits with the
actor being audited, so erasure or non-production of the committed content MUST
be detectable as a finding rather than present as an unremarkable absence. The independent
path therefore carries a declaration that content exists and where custody sits,
so that absence at retrieval is a conformance failure. Commitment-only is admissible
on the GH-DEC-2026-013 test because its distinguishing case fails closed: a reviewer
who cannot obtain the content gets no reconstruction rather than a wrong one. informed-decision
refusal of a separate evidence store is endorsed, on its own ground that it would
route around the section 16 archival-custody question by building a parallel archive
under another name.'
created: '2026-09-09T21:23:46.190170Z'
updated: '2026-09-09T21:23:46.190170Z'
state_hub_decision_id: "ae933280-873e-4226-b001-df94a1f00f50"
```
## Context
`GH-DEC-2026-012` limit 3 requires the evidence copy of a presentation record to reach
`audit-core` **independently of the emitter**, because in `informed-decision`'s case the
actor being audited and the evidence source are the same component. `INFD-IN-0003` asks
what travels on that path.
The candidate it rejects is emitting the **full binding document**, which would put
commercial and personal material — at L4, contract text — into the audit fabric under
retention and export entitlements designed for audit events. That objection is sound and
is not merely operational: an evidence path that forces content into a custody regime
built for a different class of data is a defect in the evidence path, not a cost of using
it.
It proposes **commitment-only** for Stage 1: hashes, principal, timestamps,
acknowledgements, co-referenced approval id, disposition verb, stance application. It
declares plainly that this leaves it able to **erase** the content, and asks not to be
credited with closing that.
It also refused a third option on its own initiative — a separate evidence store — on
the ground that it has no owner and would route around §16's open question on stronger
archival custody by building a parallel archive under another name.
## What is ruled here, and what is not
**Not ruled: the payload.** Which fields travel, in what schema, under what retention, is
`audit-core`'s custody question and it has it. This record does not design the record.
**Ruled: what may be claimed from a record of that shape, and what must remain visible
when it fails.** That is doctrine because it governs what a later reader is entitled to
conclude, and a reader's entitlement is not a custody decision.
## Decision
### 1. Commitment-only is admissible for Stage 1
Granted, on `GH-DEC-2026-013` §3's test: **its distinguishing case fails closed.** Where
the committed content is needed and cannot be obtained, a reviewer gets *no
reconstruction* rather than a *wrong* one. The record does not produce a confident
answer built on material nobody can check. Compare the shape refused there — a
transitional state that fails open on the case that distinguishes it is a permission
wearing a date.
The data-protection reason is accepted as a reason of the right kind. Forcing L4 contract
text into an audit fabric to satisfy an evidence obligation would trade one control for a
breach of another, and doctrine that produces that trade is wrong rather than merely
expensive.
### 2. It satisfies non-alteration. It does not satisfy reconstructability, and MUST NOT be described as doing so
This repository already holds that **an archive proves records were not altered or
truncated after arrival, never that one was never sent.** A commitment-only record is
weaker again: it additionally does not carry what it commits to. So it establishes that
the *commitment* was made when it says and has not changed since. It establishes nothing
about what was committed to, except conditionally — *if* a document is later produced,
whether it is the one.
**A commitment-only record MUST NOT be described as satisfying reconstructability**, in
`informed-decision`'s documents, in `audit-core`'s, or in any conformance claim. Every
privileged action being reconstructable from the evidence is a property of the estate's
audit obligation; a hash of an absent document does not have it and no amount of
integrity on the hash supplies it.
This is the same discipline the §6.4 obligation-1 gap has: the check is sound, and the
property a reader infers from it is not present unless it is stated to be absent.
### 3. The gap is availability, not integrity — and it sits with the audited party
Say it in those words, because "we can erase the content" understates where the problem
is. Commitment-only moves **integrity** out of the emitter's control and leaves
**availability** entirely inside it. The party that can withhold the content is the party
the evidence is about.
That is precisely the condition limit 3 exists to prevent, reduced but not removed. The
reduction is real — the emitter can no longer alter the record, which is the more common
failure — and it is not sufficient on its own.
### 4. Non-production MUST be detectable as a finding, not present as an absence
**This is the condition that makes §1's grant safe, and it is the part `INFD-IN-0003` did
not propose.**
If the independent path holds only a commitment, a reviewer who asks for the content and
receives nothing cannot distinguish *erased*, *withheld*, *lost*, and *never held*. The
absence reads as an unremarkable blank rather than as evidence of anything.
The independent path MUST therefore carry, alongside the commitment, **a declaration that
committed content exists and where custody sits**, such that failure to produce it at
retrieval is a **conformance failure** attributable to the custodian rather than a silent
gap in the record. A commitment with no accompanying assertion that something is being
committed to is indistinguishable from a commitment to nothing.
**Amended 2026-09-10 — detection sits with the reviewer, not with the archive.**
`audit-core` accepted this condition and corrected its wording, and the correction is
load-bearing enough that leaving it as an assumption would have made this section wrong.
The archive **carries** the declaration; it does not **detect** non-production. It
performs no retrieval, holds no client for the emitting repository, and its egress policy
permits nothing that would let it try — asserted by a test, because the claim silently
stops being true the day someone adds one.
So the mechanism is: the **reviewer** discovers non-production at retrieval, and the
stored declaration is what makes that discovery a **finding** rather than a blank —
because the reviewer holds a chained, timestamped statement that content existed and where
custody sat. Nothing in this section requires an archive to chase content, and it MUST NOT
be read as requiring it. An archive that fetched from the parties it audits would be
acquiring exactly the dependency that makes it corruptible by them.
**And the residual this creates, stated rather than left to be found.** A custodian that
never held the content can emit a false `content_exists`; the archive validates the
declaration's **shape**, never its **truth**. That is the same class as omission at source
— an archive cannot retrofit a property the boundary did not have — and it is closed by
neither the hash chain, nor attestation, nor `T-04`/`T-06`.
What §4 actually buys is narrower than it first reads, and this is the honest statement of
it: **the declaration converts an unattributable absence into an attributable false
statement.** Strictly better, and not the same as proof. Raised by `audit-core` against
its own delivered work, with the observation that the distinction is better in this text
now than in a conformance argument later.
This is `GH-DEC-2026-011` §3's rule in its third setting: two states, one observable
appearance, and the record must distinguish them. There it was `unknown` versus `absent`
in a stance map; in `GH-DEC-2026-013` §5 it was directory-asserted versus
registration-supplied in an identity claim; here it is *erased* versus *never held* in an
evidence path. The recurrence is the point — wherever a system can reach one appearance
by two routes, the record must say which route, or the safer reading of the appearance
becomes unavailable to everyone.
### 5. The refusal of a separate evidence store is endorsed
`informed-decision` refused it on the ground that it has no owner and would route around
§16's archival-custody question by building a parallel archive under another name. That
reasoning is correct and we would keep it as written.
Add one thing to it: an evidence store owned by the party whose conduct it evidences is
not an evidence store, whatever its integrity properties. The ownership objection is
prior to the custody one.
### 6. The residual is not closed and is not credited
`informed-decision` asked, again, not to be credited with closing what it has not closed.
Honoured, again. The erasure gap stands alongside the compromised-surface residual from
`GH-DEC-2026-012`, and §4 narrows the first without closing it: a detectable
non-production tells a reviewer that something is missing and who owed it. It does not
produce the missing thing.
## Reversal condition
§1 reverses if commitment-only is found to be load-bearing for a reconstruction that
actually had to be performed and could not be — i.e. if the availability gap is realised
rather than theorised. Stage 1 is a stage; the burden is on the shape to keep earning it.
§4 is not a reversal candidate. If it turns out to be expensive, the answer is a cheaper
mechanism for the same property, never the property's removal.
## Provenance
Raised by `informed-decision` (`INFD-IN-0003`), which proposed the shape, declared the
gap it leaves, refused a third option against its own convenience, and asked which half
of the question was doctrine. The half that was is ruled here; the payload is
`audit-core`'s and it has it. §4 was not requested by anyone.
## GH-DEC-2026-015 — GH-DEC-2026-012 R3 revised: nesting is permitted for this pair, conditioned on the exclusion being normative
```yaml
id: GH-DEC-2026-015
kind: decision
title: 'GH-DEC-2026-012 R3 revised: nesting is permitted for this pair, conditioned
on the exclusion being normative'
status: resolved
owner: Bernd Worsch
repo: gate-house
standard: net-kingdom/canon/standards/security-layer-model_v0.8.md
source_note: informed-decision/docs/finding-r3-linkage-conflict.md (INFD-IN-0004);
approval-engine docs/approval-claim.md @ 62233c7
requested_dispositions:
- approved
- revised
- rejected
affects:
- gate-house
- informed-decision
- approval-engine
- access-engine
- net-kingdom
decided_by: Bernd Worsch
rationale: 'GH-DEC-2026-012 R3 refused option (c) and required linkage by co-reference.
approval-engine, answering the same question from the layer that owns the digest,
recommended exactly (c). informed-decision could not comply with both and raised
it as a finding rather than choosing, which is the correct handling. The refusal
is revised on two grounds. First, the hash-cycle objection requires mutual containment,
and approval-engine has since published that its binding digest covers exactly five
act fields and that widening it to cover presentation would be a defect for an independent
reason - a new UI release would invalidate every prior approval - so one-directional
nesting raises no cycle. Second and decisively, co-reference by identifier alone
leaves informed-decision independently canonicalizing principal and target, two
of the five fields in the other layer digest, which is a partial recomputation of
one act in a second vocabulary and is closer to the translation R3 forbade than
nesting is. The original ruling therefore worked against its own rule. Nesting is
permitted for this pair, conditioned on approval-engine stating the presentation
exclusion as normative and testable rather than as design intent, because the cycle
cannot arise here is a belief and the cycle may not arise here is a rule with an
owner. The ordering objection is withdrawn as mistaken: the digest is over act material
and is computable before a human is presented anything.'
created: '2026-09-10T13:19:48.436344Z'
updated: '2026-09-10T13:19:48.436344Z'
state_hub_decision_id: "5063b6a3-5f99-4558-beb6-f4c494a46bfc"
```
## Context
`GH-DEC-2026-012` R3 ruled that `view_hash` and `approval-engine`'s binding digest are
distinct attestations linked by **co-reference**, and refused option (c) — `view_hash`
carrying the binding digest as a field — on two grounds: an ordering dependency, and the
`GH-DEC-2026-008` hash cycle.
`approval-engine` then answered the same question in `docs/approval-claim.md` (`62233c7`)
and recommended **exactly (c)**: that `informed-decision`'s binding document carry
`binding.digest` as a field rather than re-canonicalize `action`/`actor`/`principal`/
`purpose`/`target` itself, so there is one canonicalization of the act, computed by the
layer that owns it.
`informed-decision` could not comply with both, **made no design change**, and raised it
as a finding. That is the correct handling and it is worth naming: it declined to adopt a
recommendation that came from the layer owning the artifact, having previously offered to
let that layer settle R3 bilaterally, on the ground that a bilateral agreement produces
agreement rather than an authority rule. It applied that reasoning against its own
convenience twice.
## Decision
### 1. The refusal of (c) is revised. Nesting is permitted for this pair
**`view_hash` MAY carry `binding.digest` as a field**, and `informed-decision`'s binding
slice then stops independently canonicalizing act material. One canonicalization of the
act, computed by the layer that owns the act, committed to by reference.
### 2. The cycle objection does not hold here, and the reason it does not is narrow
`GH-DEC-2026-008`'s cycle required **mutual containment**: a claim had to name the digest
of a request that would come to contain that claim. `approval-engine` has since published
that `binding.digest` covers exactly five act fields, and that widening it to cover
presentation material **would be a defect** — a new UI release would change the digest of
an act whose act did not change, invalidating every prior approval. So the containment is
one-directional and no cycle arises.
`informed-decision` declined to assert that this settles it, on the ground that *"the
cycle cannot arise here"* is precisely the belief such failures punish. That caution is
correct and is why §4 exists rather than a bare permission.
### 3. The decisive ground is that the original ruling worked against its own rule
R3 required: *"You MUST NOT recompute or restate `approval-engine`'s binding digest from
your own vocabulary."* But `informed-decision`'s binding slice canonicalizes `principal`
and `target`**two of the five fields in that digest**. Under co-reference by
identifier alone, two independent canonicalizations of one act exist, linked by a shared
id, and the presenting surface is performing a partial recomputation of the other layer's
material in its own vocabulary.
**That is closer to the translation R3 forbade than nesting is.** Co-reference by id
*manages* the duplication with an authority rule; nesting *removes* it. Removing a
failure mode beats managing one, and the original ruling reached for the management
option while stating the rule that recommends the removal.
This is the second time this month a Gate House ruling has been correct in substance and
wrong in the mechanism it prescribed, found by the repository that had to build on it.
The pattern is recorded rather than smoothed over: `GH-DEC-2026-008` mandated a
comparison that could never pass; this one mandated a linkage that reintroduced the thing
it forbade.
### 4. Condition: the exclusion becomes normative, not intentional
**Permission under §1 is conditioned on `approval-engine` stating, as a rule rather than
as design intent, that `binding.digest` MUST NOT cover presentation material** — and on
that constraint being asserted by a test, so that widening fails loudly rather than
silently.
`approval-engine` already holds the reason, and it is a good one that does not depend on
this ruling: widening would invalidate every prior approval on a UI release. What it does
not yet hold is the *rule*. The difference is exactly A-17's precondition — the
distinguishing case here is *someone widens the digest*, and today that case is
unobservable until approvals start failing. Making it observable is a precondition of the
permission, not a follow-up to it.
Until that lands, `GH-DEC-2026-012` R3's co-reference form remains in force.
`informed-decision` changes nothing on its own initiative; the permission activates when
the condition is met.
### 5. What does not change
- **The authority rule stands.** The binding digest is authoritative for what the request
*is*; `view_hash` is authoritative only for what was *shown*. Under §1 the two can no
longer disagree about the act, which retires the disagreement case rather than
reversing the rule that governed it.
- **(a) remains refused**, on its original and undisturbed ground: merging the two
canonicalizations would make one repository authoritative over another layer's
material.
- **`view_hash` still MUST NOT travel inside hashed request material while containing the
binding digest.** §2's one-directionality is a fact about the present pair, not a
licence. If a future design puts `view_hash` inside a hashed request, the cycle
reappears and this permission is void for that path.
### 6. The ordering objection is withdrawn as mistaken
`GH-DEC-2026-012` gave an ordering dependency as (c)'s first cost, taking it from
`informed-decision`'s own statement of it. It does not hold: `binding.digest` is computed
over **act material**, which is determined before a human is presented anything. The
approval need not exist before the memo is rendered — only the act need be determined,
which it is by construction.
Recorded rather than dropped, because a cost accepted from the requester and never
checked is how a wrong reason survives into a ruling.
## On the exposure `informed-decision` asked to have visible
It asked that if co-reference stood, the record show that it would be the party a
disagreement was a finding against, for a disagreement arising from **two correct
canonicalizations** rather than from any defect of its own — the design creating the
exposure, not its conduct.
That request is granted in a better form: §1 removes the exposure rather than documenting
it. The asymmetry it accepted was right in principle and it should not have had to carry
it in practice.
## Reversal condition
§1 reverses if `approval-engine`'s digest ever widens to cover presentation material,
which §4's rule exists to prevent and which would restore mutual containment. It also
reverses for any path that places `view_hash` inside hashed request material.
## Provenance
Raised by `informed-decision` (`INFD-IN-0004`), which made no design change, declined a
recommendation from the layer owning the artifact, applied Gate House's own bilateral-
settlement reasoning against its own convenience, and supplied the published-coverage
fact the original refusal did not have. `approval-engine` supplied the recommendation and
the structural reason behind it.
## GH-DEC-2026-016 — Where an approval discharges a human-in-the-loop control, the approver must be a human principal and the engine must refuse at bind time
```yaml
id: GH-DEC-2026-016
kind: decision
title: Where an approval discharges a human-in-the-loop control, the approver must
be a human principal and the engine must refuse at bind time
status: resolved
owner: Bernd Worsch
repo: gate-house
standard: net-kingdom/canon/standards/security-layer-model_v0.8.md
source_note: informed-decision INFD-IN-0004 section 6 (NC-03); approval-engine, which
declined to invent the rule
requested_dispositions:
- approved
- revised
- rejected
affects:
- gate-house
- approval-engine
- informed-decision
- access-engine
- audit-core
- net-kingdom
decided_by: Bernd Worsch
rationale: 'approval-engine records the approver principal type as auditable metadata
but restricts only /consume by principal type, and its own operator service client
holds approval:approve, so a service principal can supply approver evidence for
a control whose purpose is to put human judgment in the path. informed-decision
enforces humans-bind-agents-draft at its own surface and correctly observed that
without an engine-side refusal its enforcement is one component policy rather than
a property of the approval object. Both referred the question up rather than settling
it. Ruled: where an approval is declared as discharging a human-in-the-loop or dual-control
requirement, the approver MUST be a human principal and approval-engine MUST refuse
a non-human bind at issue rather than record it. Recording the principal type after
the fact is detection and not prevention, which is the distinction this repository
applies everywhere else, and an approval control satisfiable by the same class of
actor it exists to check is theatre. The rule attaches to approvals declared as
discharging such a control rather than to all approvals, because service-to-service
approvals are legitimate and the requirement must be declared at issue and never
inferred - the same shape approval-engine established for pdp_path. Enforcement
at one surface is insufficient because a second caller reaching the engine directly
bypasses it, and the property belongs to the object rather than to whichever component
happens to create it.'
created: '2026-09-10T13:20:35.560626Z'
updated: '2026-09-10T13:20:35.560626Z'
state_hub_decision_id: "1139bb2f-7774-4559-bd71-e9d5376b58f4"
```
## Context
`approval-engine` put the question rather than answering it, which is the right instinct:
> `entries[].principal_type` is the auditable half only. It tells you what bound an
> approval after the fact; it does not stop anything. If your doctrine needs a service
> principal refused at bind time, that is a `gate-house` question and we would implement
> a ruling — we simply will not invent one.
`informed-decision` enforces *"humans bind, agents draft"* at its own surface and observed
that there is **no upstream backstop**: `approval-engine` restricts only `/consume` by
principal type, and its own operator service client holds `approval:approve`. It is not
asking for the refusal on its own account — its surface enforces it for its own callers
regardless. Its question is whether the estate wants *approver evidence itself* to be
human-only at the engine, in which case its enforcement is a property of the approval
object rather than one component's policy.
Both referred it up rather than settling it between them. That is the same restraint that
produced `GH-DEC-2026-015`, and it is the reason this question is answerable at all.
## Decision
### 1. Where an approval discharges a human-in-the-loop control, the approver MUST be a human principal
**And `approval-engine` MUST refuse a non-human bind at issue, rather than record it.**
An approval control exists to put a judgment of a particular kind into the path of an
act. Where that kind is *human*, an approval bound by a service principal does not
weakly satisfy the control — it does not engage it at all. A control satisfiable by the
same class of actor it exists to check is theatre, and the estate's first rule is that
**no privilege comes from cognition**: an agent determining that an action is appropriate
never makes it authorized, and it does not become authorized because the agent recorded
its determination in an approval object.
### 2. Recording is detection. Refusal is prevention. The distinction is not new here
`entries[].principal_type` is exactly right as a record and is not the control.
`approval-engine`'s own framing — *"the auditable half only… it does not stop anything"*
is the same distinction this repository applies to reconstructability at the issuer, to
audit completeness, and to `GH-DEC-2026-014`'s commitment-only records. Detection belongs
in the same register as the other residuals. It is not a substitute for a refusal that
can be made cheaply at issue.
The bind is the cheap moment. Refusing at `/consume` is refusing after the approval has
been presented as satisfying a control it never satisfied, and after anything relying on
its existence has relied on it.
### 3. The rule attaches to declared approvals, not to all approvals
**Service-to-service approvals are legitimate** and this ruling does not disturb them.
The requirement attaches where an approval is **declared** as discharging a human-in-the-
loop or dual-control requirement.
**Declared at issue, never inferred.** A human approver who happens to be present is not
a declaration, and an approval MUST NOT be retroactively read as human-discharging
because its entries turn out to be human. This is the shape `approval-engine` itself
established for `pdp_path`: intent is declared, `create()` refuses the declaration
without what it requires, legacy rows migrate to the negative rather than being
back-filled, and a successor inherits its predecessor's declaration. Back-filling would
manufacture a statement no requester made — `GH-DEC-2026-008`'s objection to translation,
in another form, and `approval-engine`'s own argument returned to it.
Where the requirement is declared and the binding principal is not human, `create()`
refuses — the same failure-at-issue shape that already refuses `pdp_path: true` without a
`pdp_digest`, so an approval that would be unusable fails when it is made rather than at
the protected side effect.
### 4. One surface enforcing it is not the property being held
`informed-decision`'s enforcement is correct and insufficient, exactly as it said. A
second caller reaching `approval-engine` directly bypasses it, and the estate's operator
service client holds `approval:approve` today. **The property belongs to the approval
object, not to whichever component happened to create it** — otherwise the guarantee is
"human-approved unless someone used a different client", which is not a guarantee.
`informed-decision` should keep its surface enforcement. Defence at the surface and at
the engine are not redundant: the surface refuses earlier and with a better error, and
the engine is what makes the refusal a property of the object.
### 5. What this does not rule
Not ruled: what makes a principal *human* — that is `key-cape`'s and the identity
layer's, and note it interacts with `GH-DEC-2026-013` §5. A principal-type claim is
subject to A-16 like any other: if `human` is reachable by two routes — asserted by the
directory about the person, or supplied by a registration about the client — the record
must say which, and a human-in-the-loop control MUST NOT be discharged on a
registration-supplied claim. Refusing a service principal while accepting an unverified
assertion of humanity would move the defect rather than close it.
Not ruled: which acts require human approval. That is doctrine, it is Gate House's, and
it is not settled here — this record says what a *declared* human control requires, not
where such controls are mandatory.
Not ruled: `approval-engine`'s scope model, or whether `approval:approve` on an operator
service client is itself correct. Flagged as worth their review, since it is the concrete
path by which the gap exists today.
## Reversal condition
§1 reverses if a case is found where a human-in-the-loop control is legitimately
discharged by a non-human principal — which would mean the control was misdeclared rather
than that the rule is wrong, and the correct repair is the declaration.
§3's declared-only scoping reverses if declarations turn out to be systematically omitted
where they are needed, making the rule vacuous in practice. That would be evidence for
requiring the declaration rather than for widening the enforcement.
## Provenance
Raised by `approval-engine`, which named the gap in its own enforcement, stated plainly
that its own operator client holds the permission that makes it reachable, and declined
to invent the rule. Carried by `informed-decision`, which enforces the property already,
does not need the ruling for itself, and asked anyway because a property held by one
component is not a property of the object. Neither repository asked for anything that
benefits it.