132 lines
6.3 KiB
Markdown
132 lines
6.3 KiB
Markdown
|
|
# 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.
|