diff --git a/decisions/decisions.md b/decisions/decisions.md index a685782..0b6bd85 100644 --- a/decisions/decisions.md +++ b/decisions/decisions.md @@ -978,3 +978,97 @@ 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. diff --git a/docs/amendments/v0.8-amendment-set.md b/docs/amendments/v0.8-amendment-set.md index 82d9574..b3fba4e 100644 --- a/docs/amendments/v0.8-amendment-set.md +++ b/docs/amendments/v0.8-amendment-set.md @@ -27,8 +27,9 @@ Assembly and circulation are `GH-WP-0003-T06`. | A2 | §11 | `docs/contracts/approval-emission-detection.md` | T02 | | A3 | §13, §13.1 | `GH-DEC-2026-006` | T03 | | A4 | §9.5 | `GH-DEC-2026-007` | T04 | -| A5 | §6.4, §8 | `GH-DEC-2026-005` | T05 | +| A5 | §6.4, §8 | `GH-DEC-2026-005`, `GH-DEC-2026-008` | T05 | | A6 | §17 | `GH-DEC-2026-004` | T07 | +| A7 | §11, §12 | four observed instances; proposed by `approval-engine` | T08 | --- @@ -230,8 +231,16 @@ join two artifacts will re-derive it or get it wrong. > second check at the consumer. That is detection, not prevention, and it belongs > in the same register as §9.6's other residual. > -> Raised by `approval-engine`, and by `access-engine` against its own proposed -> artifact. Settled in `GH-DEC-2026-005`. +> **Correspondence is established by identity, not by translation.** Where two +> artifacts use different vocabularies for the same request, the consumer compares +> a digest one layer computed and the other recorded — it MUST NOT recompute one +> layer's binding from the other's vocabulary, and no cross-engine mapping is +> published for it to use. A mapping can be wrong in a way that still produces a +> confident answer, and it would be a third authority on what a request is. +> +> Raised by `approval-engine`, and by `access-engine` twice against its own +> interest — once on its proposed artifact and once on a gap it declined to close +> locally. Settled in `GH-DEC-2026-005` and `GH-DEC-2026-008`. **Add to §8's demarcations:** @@ -274,6 +283,61 @@ statute is told the estate has not decided something it has. --- +## A7 — §11 and §12, derived summaries must be marked (T08) + +**Why.** Four instances in one week, in four repositories, each caught only by another +repository reading the authoritative body: + +- gate-house sourced a revisit trigger in `GH-DEC-2026-005` from a 2026-08-29 + alignment record rather than from `flex-auth`'s current schema; +- gate-house read a change log for what §3.4 contained, which is the defect + `kings-guard` raised against v0.6 and the reason + `docs/conformance/2026-09-06-v06-findings-audit.md` checked the body instead; +- `access-engine` published a dual-control rule written against its own assumption of + the approval claim rather than `approval-engine`'s published schema — it required a + status field and an approver list that do not exist, and every live destroy would + have denied regardless of the approval (failed closed, fixed in `68ad039`); +- `secrets-engine` read a fixture for its contract's prose, and separately carried two + workplans asserting that remaining authorization work was external, which kept an + unimplemented local stub invisible. + +Different artifact classes — change log, alignment record, fixture, workplan summary — +one shape. As `approval-engine` put it, at four instances this is not coincidence but +**a property of how this estate publishes**: authoritative bodies with derived +summaries and examples alongside them, and no staleness marker on the derivatives. A +reader cannot tell from the artifact whether it is current, so the diligent and the +careless read it the same way. + +Three of the four were reported by the repository that had made the error, against its +own interest. That is the loop working, and it is also the argument for a check: a +control that depends on four repositories volunteering corrections is not a control. + +**Add to §11's "Mechanically checkable" list:** + +> - every derived artifact in an estate repository — change log, review record, +> alignment note, example, fixture, or summary that restates a normative body — is +> marked as derived, names the authoritative artifact it derives from, and carries +> the version or commit it was derived at. + +**Add to §12, as a paragraph of the loop:** + +> **A derived summary is not evidence of what the body says.** A repository acting on +> another repository's contract, schema, or standard MUST read the authoritative +> artifact, not a summary, example, or review record of it. This is not a counsel of +> care: the estate has produced four instances in a single week, in four repositories, +> each caught only because a second repository read the body. Diligence did not +> prevent any of them, and in three the erring repository reported itself. +> +> The obligation on the publishing side is §11's marking rule; this is its consumer +> half. Where a derivative is unmarked, treat it as stale. + +**Note on scope.** This is the cheap half. It does not detect a derivative that is +marked and *still* wrong, and it should not claim to — the marking makes staleness +visible, it does not measure it. Naming that limit here so the check is not later read +as stronger than it is, which would be the same defect one layer up. + +--- + ## Not in this set Recorded so the omissions are deliberate rather than forgotten. diff --git a/docs/contracts/approval-consumption.md b/docs/contracts/approval-consumption.md index 5837e4d..2839683 100644 --- a/docs/contracts/approval-consumption.md +++ b/docs/contracts/approval-consumption.md @@ -4,7 +4,8 @@ **PIP:** approval-engine **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) +amended by `GH-DEC-2026-005` (step-1 artifact and split validation) and +`GH-DEC-2026-008` (the PDP digest is the binding correspondence) **Date:** 2026-09-06 **Statute:** `net-kingdom/canon/standards/security-layer-model_v0.7.md` §9.4, §9.7.3–4, §16 **Blocks:** `APPROVAL-WP-0001-T05`, `FLEX-WP-0017-T05` @@ -60,6 +61,20 @@ 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 +`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 +survives only for consumers already in `approval-engine`'s vocabulary, including the +`T-06` assurance case. + +Without this, `valid_now: true` plus an ALLOW establishes *approved* and *permitted* +but never *approved for this request*, which is the property the binding exists to +carry. + ## Why consume is before the side effect §9.7.3 says consumption MUST NOT be inferred from a decision record. That diff --git a/workplans/GH-WP-0003-statute-v08-amendment-set.md b/workplans/GH-WP-0003-statute-v08-amendment-set.md index 8156af1..1621eba 100644 --- a/workplans/GH-WP-0003-statute-v08-amendment-set.md +++ b/workplans/GH-WP-0003-statute-v08-amendment-set.md @@ -163,3 +163,22 @@ cite the decision; keep §17's drafter credit to `kings-guard`. Surfaced by `docs/conformance/2026-09-06-v06-findings-audit.md`, which also confirms that all fifteen v0.6 review findings are dispositioned in v0.7 — this is the one paragraph a later decision made stale, not a missed finding. + +```task +id: GH-WP-0003-T08 +status: done +priority: medium +``` + +**Derived summaries must be marked (A7).** Four instances in one week, in four +repositories, of a repository acting on a derived summary rather than the +authoritative body — a stale revisit trigger here, a change log read for section text +here, a dual-control rule written against an assumed schema in `access-engine`, a +fixture read for contract prose in `secrets-engine`. Proposed by `approval-engine` on +the observation that at four instances it is a property of how the estate publishes +rather than four separate lapses: authoritative bodies with derived summaries beside +them and no staleness marker on the derivatives. + +Drafted as A7 — a §11 marking obligation on the publisher and a §12 paragraph on the +consumer. The limit is stated in the draft: marking makes staleness visible, it does +not detect a marked derivative that is still wrong.