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