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:
parent
6f4bd56ee5
commit
d97e8efa6e
3 changed files with 209 additions and 5 deletions
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue