diff --git a/decisions/decisions.md b/decisions/decisions.md index c45b9cb..799f9c0 100644 --- a/decisions/decisions.md +++ b/decisions/decisions.md @@ -488,3 +488,85 @@ decided_by: Bernd Worsch created: '2026-09-05T23:28:23.931441Z' updated: '2026-09-05T23:28:23.931441Z' ``` + +## Context + +`GH-DEC-2026-003` settled the consumption sequence and named step 1 by endpoint and +by field: `GET /v1/approvals/{id}/claim` → `valid_now` (a fact, not permission). +`approval-engine` serves the approval-claim at that endpoint +(`approval-engine/docs/approval-claim.md`). `secrets-engine`'s PEP validator +(`validate_action_authorization`, `src/secrets_engine/authorization.py`) calls the +same endpoint but expects a flex-auth `ActionAuthorization`. Two different objects +were being assumed on one path, and the divergence would have surfaced at first live +consume rather than at deployment. + +`ActionAuthorization` originates in `flex-auth/docs/action-bound-authorization-contract.md` +as a proposed shape for durable approval storage that the same document assigns to +`approval-engine`. It was never ratified, appears nowhere in this repository's +doctrine, and has no `valid_now` field. `GH-DEC-2026-003` was issued after it without +reference to it. There is therefore nothing to reconcile: the claim was the step-1 +artifact all along, and this record says so where implementers will find it. + +`approval-engine` raised the question rather than guessing, which is the behaviour +§12 asks for. The three substantive assertions in the request were checked here +independently before confirmation. + +## Decision + +**1. The approval-claim is the step-1 artifact on the `GH-DEC-2026-003` path.** +It carries the approval fact: binding digest, validity window, consumption state, +observation freshness, and issuer. Holding one with `valid_now: true` remains not +authority to act. + +**2. `ActionAuthorization` is not required on that path.** `approval-engine` is not +expected to serve it and MUST NOT serve it from the claim endpoint. A step-1 response +cannot contain a step-2 decision; any composed post-decision artifact would be a new +artifact at a new endpoint. The proposal is shelved on this path, not withdrawn. + +**3. A PEP validates across the two artifacts it already fetches.** The claim is +checked for the approval fact; the flex-auth `DecisionEnvelope` from step 2 is checked +for exact `CheckRequest` match and policy package/version pin. This is doctrine, not +merely an implementation convenience: **each artifact is validated against the layer +that owns its data.** A PIP republishing the PDP's decision would put a copy of the +authority decision in a repository that did not render it — the same shape of error +the re-cut removed from Gate House itself. No safety property is lost, because +`ActionAuthorization` is a bundle of exactly these two checks. + +**4. The `provenance.authority == "state-hub"` requirement is struck.** State Hub is +a read model. It is not the runtime approval authority and never was; flex-auth's own +contract says so. A validator that requires it would fail closed against every +correctly issued claim. `secrets-engine` removes the constant with the validator split. + +Consequent changes: `approval-engine` none — the claim schema stands as published; +`secrets-engine` splits its validator and drops the authority constant; `flex-auth` +records that the proposal is shelved on this path. Nothing here gates +`APPROVAL-WP-0002-T03` — `secrets-engine` is fail-closed until its own gates land, +and this record governs first live consume, not deployment. + +## Deferred + +A ratified post-decision `ActionAuthorization` — one composed, signed, forwardable +object — remains coherent and is recorded here so it is not rediscovered as new. It +is not adopted now because it has no named issuer or lifecycle owner and the sequence +is already settled without one. Revisit on any of: a third or fourth PEP-shaped +consumer; a requirement for a single signed forwardable artifact (offline +verification, or an audit that must replay one object rather than a join); resolution +of flex-auth's open G3 finding by composition rather than by adding a lifetime to the +envelope; or assent of the statute §17 Taxonomy request-claim schema +(`APPROVAL-IN-0001`) — in which case converge there and do **not** revive this +separately. + +Also rejected, so they are not re-proposed: serving `ActionAuthorization` at the claim +endpoint (structurally impossible for step 1, and it would assert an authority +`approval-engine` does not hold); and extending the claim with `approvals.required_count` +or `entries` (publishing approver identities is a deliberate least-disclosure non-goal, +and it would still not satisfy the validator). + +## Reversal + +Revert this record and the amendment to `docs/contracts/approval-consumption.md`. The +falsifier is a demonstrated check that two-artifact validation cannot perform and a +single composed artifact can — most plausibly offline or forwarded verification with +no access to the PDP. That is a trigger for the deferred option above, not for +returning `ActionAuthorization` to the claim endpoint, which stays forbidden on +structural grounds regardless. diff --git a/docs/contracts/approval-consumption.md b/docs/contracts/approval-consumption.md index 1860eb8..5837e4d 100644 --- a/docs/contracts/approval-consumption.md +++ b/docs/contracts/approval-consumption.md @@ -3,8 +3,9 @@ **Owner:** gate-house **PIP:** approval-engine **PDP:** access-engine (currently flex-auth) -**Status:** normative for GH-WP-0002-T06; recorded as `GH-DEC-2026-003` -**Date:** 2026-08-29 +**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) +**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` @@ -30,6 +31,35 @@ companion §5 already treats as PEP-shaped. Holding a claim with `valid_now: true`, or an ALLOW against that claim, is not authority to act. The consume is. +## What each step's artifact is + +Amended by `GH-DEC-2026-005`. + +**Step 1 serves the approval-claim** (`approval-engine/docs/approval-claim.md`). +That is the artifact `valid_now` belongs to, and it is the only object the claim +endpoint serves. flex-auth's proposed `ActionAuthorization` is **not** required +here, is not served here, and MUST NOT be served from this endpoint: a step-1 +response cannot contain a step-2 decision. + +**A PEP validates across both artifacts it already fetches**, and each against +the layer that owns its data: + +| Artifact | Source | What the PEP checks | +| --- | --- | --- | +| approval-claim | step 1, `approval-engine` (PIP) | binding digest, validity window, consumption state, observation freshness, issuer | +| `DecisionEnvelope` | step 2, `access-engine` (PDP) | exact `CheckRequest` match, policy package and version pin | + +A PIP MUST NOT republish the PDP's decision, and a PEP MUST NOT accept the +approval fact from the decision artifact or the decision from the approval +artifact. Composing the two into one served object is a separate, post-decision +artifact at a separate endpoint if it is ever ratified — not a change to the +claim. + +**Claim provenance is not State Hub.** A PEP MUST NOT require +`provenance.authority == "state-hub"` on the claim. State Hub is a read model and +holds no runtime approval authority; the issuer to check is the engine that +served the claim. + ## Why consume is before the side effect §9.7.3 says consumption MUST NOT be inferred from a decision record. That diff --git a/intakes/intakes.md b/intakes/intakes.md index 428e8ad..971fd14 100644 --- a/intakes/intakes.md +++ b/intakes/intakes.md @@ -131,7 +131,7 @@ state_hub_intake_id: "01a04b04-dd82-73a4-a8bf-9aeba5ef575f" id: GH-IN-0002 kind: intake title: Claim envelope on the PEP consumption path — which artifact is step 1 of GH-DEC-2026-003 -status: open +status: closed origin: cross-repo origin_ref: approval-engine APPROVAL-IN-0002 (decision request) priority: high