Collect v0.8 assent findings; correct GH-DEC-2026-008 for implementability

Four findings returned so far, all from access-engine and
approval-engine. F1 is the serious one: GH-DEC-2026-008 as written
mandated a comparison that could never pass, because a claim travelling
inside a hashed request cannot name the digest of the request containing
it. A fail-closed consumer obeying it would have denied destroy
permanently — the ruling and its own fail-closed requirement compounded
rather than cancelled.

Ruling and its four obligations stand; the comparison target is corrected
to the PDP's published exclusion-scoped digest, verified in flex-auth's
schema and canonical.go before amending. A consumer must not guess the
exclusion rule, and until a PDP publishes one the path is fail-closed
rather than complete.

F2 adds the general property access-engine flagged as a near miss it was
not asking to have written: an evidence-bearing input may be excluded
from a correspondence digest but never from the replay identity.

The round record notes what this says about the process. GH-DEC-2026-008
was correct in substance, argued from doctrine, and verified against
another repository's schema before issue — and none of that caught a
defect three repositories found within hours of building on it.

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
This commit is contained in:
tegwick 2026-09-06 15:28:01 +02:00
parent 6f4bd56ee5
commit d97e8efa6e
3 changed files with 209 additions and 5 deletions

View file

@ -1219,3 +1219,79 @@ 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.

View file

@ -0,0 +1,112 @@
# v0.8 assent round — findings and dispositions
**Repository:** gate-house
**Project family:** NetKingdom security layer
**Status:** open — round in progress
**Version:** 0.1
**Date:** 2026-09-06
**Subject:** `net-kingdom/canon/standards/security-layer-model_v0.8.md` (proposed)
**Workplan:** `GH-WP-0003-T06`
*Derived artifact: this record states the round's status as of the date above, not
current state. Authoritative bodies are the decision records and the cut itself.*
## Circulated
`ops-warden`, `approval-engine`, `access-engine`, `kings-guard`, `audit-core`, and
`net-kingdom` (publisher). Each was told what the version costs them and what to
attack, rather than sent a summary to nod at.
## Findings returned
### F1 — `GH-DEC-2026-008` was unimplementable as written · **adopted, corrected**
Reported by `access-engine`, independently by `approval-engine`, originally found by
`secrets-engine` re-verifying a replay fixture — within hours of the ruling and before
the assent round had properly opened.
The ruling required `claim.binding.pdp_digest` to equal the decision's
`NewDecisionBinding.request_digest`. `access-engine` hashes context into that digest
and the dual-control pattern carries the claim in `context.approval`, so embedding the
claim changes the digest of the request carrying it. A hash cycle: a claim cannot name
the digest of a document containing that claim.
**A fail-closed consumer obeying the rule would have denied `destroy` permanently.**
The ruling's own fail-closed requirement is what would have made it harmful rather
than merely wrong — the rule and the failure mode compounded instead of cancelling.
**Disposition.** Ruling stands; its four obligations stand; obligation 1's comparison
target is corrected to the PDP's published exclusion-scoped digest
(`binding.approval_binding_digest`, `FLEX-DEC-2026-007`). v0.8 §9.7.3 rewritten. A
consumer MUST NOT guess the exclusion rule, and until a PDP publishes one the path is
fail-closed rather than complete — a gap in a dependency, not in the ruling.
Verified independently against `flex-auth/schemas/decision_envelope.schema.json` and
`pkg/api/canonical.go` before amending.
### F2 — replay identity versus correspondence digest · **adopted, added**
Flagged by `access-engine` explicitly as a near miss it was *not* asking to have
written down. The obvious fix to F1 is to drop `context.approval` from the request
digest entirely, and that is unsafe: two requests differing only in which approval was
presented decide differently, so collapsing them lets an allow obtained with a valid
claim be replayed against a request carrying none.
**An evidence-bearing input may be excluded from a correspondence digest but never
from the replay identity.** Added to v0.8 §6.4 obligation 5. A property discovered by
nearly getting it wrong is worth more written down than remembered, and the shape
recurs wherever evidence travels inside a hashed request.
### F3 — `GH-DEC-2026-009` adopted, with a reason we had not stated
`access-engine` records that the `unknown` ruling is right for the adversarial reason
`unknown` is the cheapest state to induce, so failing open on it makes being
unclassifiable a credential-free escalation — and notes it had reported the divergence
without reaching that reading. No change; recorded because the reasoning, not the
conclusion, is what a later reader needs.
It also endorses sending `ops-warden`'s non-conformant cell to the round rather than
imposing it.
### F4 — A7 accepted by its own worked example
`access-engine` accepts its dual-control instance as the one carrying the argument,
and notes the §11 marking obligation would have caught the source: the stale G3 row in
its own 2026-08-29 review, handed on as current. No change.
## Implementation ahead of acceptance
Both engines implemented against the ruling before the round closed.
- `access-engine``FLEX-DEC-2026-007`, `dd3ce4c`: publishes
`binding.approval_binding_digest`, and asserts by test that the two digests
**disagree** on a claim-bearing request, so a later refactor cannot collapse them.
- `approval-engine``7e75677`, 102 tests: schema v3 declares `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 and never inferred — a digest that happens to be present is not a
declaration, 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 `GH-DEC-2026-008`'s own
objection to translation in another form.
## Still outstanding
`ops-warden` (the `unknown` cell it acquires), `kings-guard` (the criteria-grounding
clause, and whether §12's step-four paragraph is now stale), `audit-core` (whether
§11's emission-guarantee wording lets a source imply completeness), `net-kingdom`
(whether §17 says what it would say in its own voice, and whether §11 tracks its
published profile).
## What the round has shown so far
The first finding against v0.8 was a rule that was **right and unimplementable**, and
it was found by the repository that would have been broken by it, then reported by two
others under no obligation to look. That combination is only survivable because the
implementers read the ruling against their own fixtures instead of accepting it — the
behaviour §12 describes, arriving without being asked for.
It is also an argument for the round itself. `GH-DEC-2026-008` was correct in
substance, argued from doctrine, and verified against another repository's schema
before it was issued. None of that caught a defect that three repositories found in
hours of trying to build on it.

View file

@ -5,7 +5,7 @@
**PDP:** access-engine (currently flex-auth)
**Status:** normative for GH-WP-0002-T06; recorded as `GH-DEC-2026-003`,
amended by `GH-DEC-2026-005` (step-1 artifact and split validation) and
`GH-DEC-2026-008` (the PDP digest is the binding correspondence)
`GH-DEC-2026-008` (the binding correspondence, amended for implementability)
**Date:** 2026-09-06
**Statute:** `net-kingdom/canon/standards/security-layer-model_v0.7.md` §9.4, §9.7.34, §16
**Blocks:** `APPROVAL-WP-0001-T05`, `FLEX-WP-0017-T05`
@ -61,10 +61,26 @@ claim.
holds no runtime approval authority; the issuer to check is the engine that
served the claim.
**The correspondence is the PDP digest.** Amended by `GH-DEC-2026-008`. A consumer
on this path MUST verify that `claim.binding.pdp_digest` equals the decision's
`NewDecisionBinding.request_digest`, and MUST NOT use a claim that carries no
`pdp_digest`. Recomputing `approval-engine`'s native `binding.digest` from a
**The correspondence is a digest, and it is the PDP's to define.** Amended by
`GH-DEC-2026-008`. A consumer on this path MUST verify that the approval's recorded
PDP digest equals the digest the PDP publishes **for the request with the approval
evidence excluded** — `binding.approval_binding_digest` in `access-engine` — and MUST
NOT use a claim carrying no such digest.
The comparison is **not** against the full request digest. Where the claim travels
inside the hashed request, embedding it changes that request's digest, so a digest
recorded at issue can never equal the final one: a hash cycle whose resolution is
forced, not chosen. The first form of this rule mandated a check that could never
pass, and a fail-closed consumer obeying it would have denied the action permanently.
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. Until a PDP
publishes its exclusion rule, this path is correctly fail-closed rather than complete.
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, so one digest cannot serve both purposes. Recomputing `approval-engine`'s native `binding.digest` from a
`CheckRequest` is **not** a permitted fallback here: it requires translating between
two engines' vocabularies, no mapping is published, and a wrong translation fails
open by silently accepting a claim approved for something else. The native limb