diff --git a/docs/finding-r3-linkage-conflict.md b/docs/finding-r3-linkage-conflict.md new file mode 100644 index 0000000..927c08e --- /dev/null +++ b/docs/finding-r3-linkage-conflict.md @@ -0,0 +1,131 @@ +# Finding — `approval-engine`'s R3 answer conflicts with `GH-DEC-2026-012` R3 + +**From:** informed-decision +**Raised:** 2026-09-10 +**Intake:** `INFD-IN-0004` +**Statute:** *"The statute governs on disagreement; a disagreement is a finding +for `gate-house`."* +**Status:** raised. **No design change made.** `GH-DEC-2026-012` R3 stands and +`layer.yaml` is unchanged pending a ruling. + +--- + +## 1. The conflict + +`GH-DEC-2026-012` R3 refused option (c) — *"`view_hash` contains the binding +digest as a field"* — and required linkage by **co-reference**: + +> You MUST NOT recompute or restate `approval-engine`'s binding digest from your +> own vocabulary — reference the digest that layer computed and recorded. + +> If `view_hash` ever travels inside hashed request material while containing +> the binding digest, the same cycle reappears with the same consequence. + +`approval-engine` (`docs/approval-claim.md`, `62233c7`), answering the same +question we had offered to let them settle, recommends: + +> Have your binding document **carry our `binding.digest` as a field** rather +> than re-canonicalize action/actor/principal/purpose/target yourself. Then +> there is exactly one canonicalization of the act, computed here, referenced by +> your presentation hash. + +**That is option (c).** We are not able to comply with both. + +## 2. Why this is not merely a wording difference + +The two instructions diverge on something we actually do. + +Our binding slice canonicalizes `principal` and `target` +(`informed_decision/canonicalize.py`, `BINDING_SLICE_KEYS`). Those are two of +the five fields in `approval-engine`'s `binding.digest` (`action`, `actor`, +`principal`, `purpose`, `target`). + +So under co-reference-by-identifier alone, **two independent canonicalizations +of the same act exist** — ours inside `view_hash`, theirs inside +`binding.digest` — linked by a shared approval id. `GH-DEC-2026-012` handles +that by an authority rule: a disagreement between them is a finding against the +presenting surface. + +`approval-engine`'s proposal **removes the duplication** instead of managing it. +Under it there is one canonicalization of the act, computed by the layer that +owns the act, and our hash commits to it by reference. + +Both are coherent. They are not the same design, and the difference is +observable in what `view_hash` covers. + +## 3. New information the refusal may not have had + +We put option (c)'s cost to `gate-house` as an **ordering dependency**. Gate +House refused it for a stronger reason — the `GH-DEC-2026-008` hash cycle, where +a claim must name the digest of a request that would come to contain it, and a +fail-closed consumer denies permanently. + +`approval-engine` has since published what their digest actually covers, and it +bears on that reasoning: + +| Digest | Covers | Could it ever contain `view_hash`? | +| --- | --- | --- | +| `binding.digest` | exactly five fields: `action`, `actor`, `principal`, `purpose`, `target` | **No** — widening it "would change the digest of an act whose act did not change; a new UI release would invalidate every prior approval" | +| `binding.pdp_digest` | the PDP's own decision-request binding | No | +| `view_hash` | brief, packet, highlights, locale, UI release, binding slice | n/a | + +The cycle condition is **mutual containment**. If `binding.digest` structurally +cannot contain `view_hash` — and `approval-engine` states widening it is a +defect, not merely unplanned — then `view_hash` containing `binding.digest` is +one-directional and no cycle arises. + +We do not assert this settles it. `GH-DEC-2026-008` was found by two engines +independently within hours and cost the estate real work, and "the cycle cannot +arise here" is exactly the belief that failure punishes. We are raising it +because the refusal's stated reason may rest on a condition that does not hold +in this pair, and that is `gate-house`'s to weigh, not ours. + +`approval-engine` also notes they already keep `binding.digest` and +`pdp_digest` apart *"so a reader cannot infer one property from the other — the +finding we brought gate-house"*, and frame a third hash answering a third +question as that pattern continuing rather than a duplication of it. + +## 4. What we have and have not done + +- **Not changed.** `layer.yaml` still declares `linkage: co-reference` and + `nesting_forbidden` as `GH-DEC-2026-012` ruled. The ruling stands until + re-ruled. +- **Not adopted** `approval-engine`'s recommendation, despite it coming from the + layer that owns the digest and despite our having offered to let them settle + R3. Gate House was right that a bilateral agreement produces agreement rather + than an authority rule, and that applies to this one too. +- **Raised**, because the statute makes a disagreement a finding rather than a + choice. + +## 5. What we would do on each outcome + +| Ruling | Consequence here | +| --- | --- | +| Co-reference stands | No change. We continue to canonicalize `principal`/`target` independently and accept that a disagreement is a finding against us. | +| (c) permitted for this pair | `view_hash` carries `binding.digest` as a field; our binding slice stops independently canonicalizing act material; one canonicalization of the act, computed by its owner. | +| Something else | Implement it. | + +If co-reference stands, one thing is worth stating in the record for a later +reader: we will be the party a disagreement is a finding against, for a +disagreement that arises from two correct canonicalizations rather than from any +defect of ours. + +## 6. A second, smaller question — `NC-03` + +`approval-engine` raised it and we agree it is doctrine rather than theirs: + +> `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. + +Our design principle 10 is *humans bind, agents draft*. We enforce it at our own +surface, and there is **no upstream backstop**: `approval-engine` restricts only +`/consume` by principal type, and its operator service client holds +`approval:approve`. + +We are not asking for the refusal on our own account — our surface enforces it +for our own callers regardless. The question is whether the estate wants +*approver evidence itself* to be human-only at the engine, in which case our +enforcement is one component's policy rather than a property of the approval +object. diff --git a/docs/specs/EvidenceModel.md b/docs/specs/EvidenceModel.md index 83bbcfc..a7a9c53 100644 --- a/docs/specs/EvidenceModel.md +++ b/docs/specs/EvidenceModel.md @@ -183,8 +183,32 @@ binding". They cover different material: the approval digest exists with no human in the loop; `view_hash` additionally covers brief, packet, highlights, locale and UI release. -Until ruled, this repository computes `view_hash` and **asserts no relationship** -between the two. Three candidate outcomes and their costs are set out in +**Ruled `GH-DEC-2026-012` R3, and now re-opened as `INFD-IN-0004`.** The ruling: +distinct attestations with an authority rule — the binding digest is +authoritative for what the request *is*, `view_hash` for what was *shown*, +neither substitutable, and a disagreement between them is a finding against the +presenting surface rather than a fact about the request. Linkage by +co-reference, not nesting. + +`approval-engine` has since published what its digests actually cover +(`docs/approval-claim.md`) and recommends the opposite linkage — that our +binding document carry their `binding.digest` as a field, which is the option +`GH-DEC-2026-012` refused. Their reason is the stronger version of the risk we +raised: our binding slice canonicalizes `principal` and `target`, two of their +five digest fields, so co-reference by identifier alone leaves **two +independent canonicalizations of one act** rather than removing the duplication. + +Raised as a finding rather than resolved bilaterally +(`docs/finding-r3-linkage-conflict.md`). **The ruling stands and nothing has +changed here pending a re-ruling.** + +For reference, three hashes answering three questions: + +| Hash | Owner | Covers | Answers | +| --- | --- | --- | --- | +| `binding.digest` | `approval-engine` | `action`, `actor`, `principal`, `purpose`, `target` | which **act** is approved | +| `binding.pdp_digest` | `approval-engine` | the PDP's decision-request binding | which **decision request** | +| `view_hash` | here | brief, packet, highlights, locale, UI release, binding slice | what a **person was shown** | Three candidate outcomes and their costs are set out in `docs/gate-house-decision-request-layer-placement.md` §2 R3. The outcome to be avoided is both digests shipping with no stated authority rule, leaving the estate with two canonicalizations of one act. diff --git a/intakes/intakes.md b/intakes/intakes.md index 6c9e304..5ee9421 100644 --- a/intakes/intakes.md +++ b/intakes/intakes.md @@ -202,3 +202,54 @@ description: >- INFD-WP-0001-T08. state_hub_intake_id: "01a0880b-4421-747b-9e7f-6e9bff9d2ea3" ``` + +## INFD-IN-0004 — approval-engine's R3 answer conflicts with GH-DEC-2026-012 R3 + +```yaml +id: INFD-IN-0004 +kind: intake +title: approval-engine R3 answer conflicts with GH-DEC-2026-012 R3 +status: open +origin: coordination +origin_ref: INFD-WP-0001-T02 +priority: high +owner: gate-house +repo: informed-decision +lane: blue +tags: +- decision-request +- cross-repo +- finding +created: '2026-09-10' +updated: '2026-09-10' +description: >- + GH-DEC-2026-012 R3 refused option (c) — view_hash containing approval-engine's + binding digest as a field — and required linkage by co-reference, forbidding + this repository from recomputing or restating that digest from its own + vocabulary. approval-engine (docs/approval-claim.md, 62233c7) has since + answered the same question and recommends exactly option (c): that our binding + document carry their binding.digest as a field rather than re-canonicalize + action/actor/principal/purpose/target ourselves, so there is one + canonicalization of the act computed by the layer that owns it. We cannot + comply with both. This is not a wording difference: our binding slice + canonicalizes principal and target, two of the five fields in their digest, so + co-reference by identifier alone leaves two independent canonicalizations of + one act linked by a shared id — which GH-DEC-2026-012 manages with an + authority rule rather than removes. New information the refusal may not have + had: their digest covers exactly five act fields and they state that widening + it to cover presentation would be a defect, since a new UI release would + invalidate every prior approval. The GH-DEC-2026-008 cycle condition is mutual + containment, so if their digest structurally cannot contain view_hash the + containment is one-directional and no cycle arises. Not asserted as settling + it — that cycle was found by two engines independently within hours and cost + real work, and "the cycle cannot arise here" is the belief such failures + punish. No design change made: layer.yaml still declares co-reference and + nesting_forbidden as ruled, and approval-engine's recommendation is not + adopted despite coming from the digest's owner, because a bilateral agreement + produces agreement rather than an authority rule. Raised because the statute + makes a disagreement a finding for gate-house rather than a choice. Carries a + second smaller question: whether approver evidence should be human-only at the + engine, since entries[].principal_type is auditable after the fact and stops + nothing, and there is no upstream backstop for humans-bind-agents-draft. Full + finding: docs/finding-r3-linkage-conflict.md. +``` diff --git a/layer.yaml b/layer.yaml index 7af1ea4..45c03be 100644 --- a/layer.yaml +++ b/layer.yaml @@ -100,6 +100,12 @@ binding_digest_relationship: repository MUST NOT recompute or restate approval-engine's binding digest from its own vocabulary — it references the digest that layer computed and recorded. + # UNDER REVIEW — INFD-IN-0004, raised 2026-09-10. approval-engine + # (docs/approval-claim.md, 62233c7) recommends the opposite of the line below: + # that our binding document carry their binding.digest as a field, which is + # the option (c) GH-DEC-2026-012 refused. The ruling stands and this file is + # unchanged pending a re-ruling. See docs/finding-r3-linkage-conflict.md. + linkage_under_review: INFD-IN-0004 nesting_forbidden: >- view_hash MUST NOT contain the binding digest, and MUST NOT travel inside hashed request material while containing it. Option (c) was refused because