diff --git a/workplans/SECRETS-WP-0007-production-lifecycle-hardening.md b/workplans/SECRETS-WP-0007-production-lifecycle-hardening.md index a5ffe96..b1ded12 100644 --- a/workplans/SECRETS-WP-0007-production-lifecycle-hardening.md +++ b/workplans/SECRETS-WP-0007-production-lifecycle-hardening.md @@ -273,6 +273,44 @@ deployment: approval-engine must serve the claim endpoint and access-engine must serve Check. Probed 2026-09-06 — neither is reachable, and State Hub exposes only `/decisions/`. +Inbound 2026-09-06 (approval-engine `APPROVAL-IN-0002`, flex-auth +`FLEX-DEC-2026-005`). The join committed in `627810b` validates the wrong +artifact and must not be extended until gate-house rules. + +- `ActionAuthorization` is a PROPOSED, unratified object from flex-auth's own + contract. It appears zero times in gate-house and state-hub. `GH-DEC-2026-003` + already names step 1 as `GET /v1/approvals/{id}/claim -> valid_now`, an + approval-claim field that `ActionAuthorization` does not carry. The claim body + is an approval-claim envelope, not an `ActionAuthorization`. +- Confirmed defect, ours, independent of the ruling: `authorization.py` pins + `AUTHORITY = "state-hub"` and enforces it unconditionally, contradicting + flex-auth's statement that State Hub decision records are not the runtime + approval authority and our own read-model doctrine. Not patched yet: under + option A the authority check belongs on the claim issuer, so it moves with the + split rather than being guessed twice. +- If gate-house confirms option A, `validate_action_authorization` splits across + the two artifacts already fetched: the claim carries approval fact (binding + digest, validity window, consumption state, freshness, issuer); the step-2 + DecisionEnvelope carries exact CheckRequest match and the package/version pin. + No check is lost. Do not start until the ruling. +- Auth gap, tracked separately so it does not ride the envelope decision: we + send a static mode-0600 token as Bearer. Production verifies a KeyCape RS256 + JWT against JWKS and refuses opaque tokens. Requested registration clientId + `secrets-engine-approval`, scopes `approval:read` + `approval:consume`, + client_credentials, confidential. `aud` MUST be the resource server + `approval-engine`, never the clientId. `service_auth.py` already implements + this exchange for `secrets-engine-openbao`, so this is a second registration. +- approval-engine `APPROVAL-WP-0002-T03` will publish the base URL; it is not + deployed anywhere today and proceeds on its own evidence. The envelope + question gates our first live consume, not their rollout. +- flex-auth endorsed the no-default-pin decision explicitly. The reserved + coordinate `secrets-engine.catalog-lane.lifecycle` / `v1` is a RESERVATION, + not a publication; our pin stays unset. `docs/gated-actions.md` delivers the + authoritative twelve-action list `FLEX-WP-0021-T01` was blocked on. No + estate-wide PDP exists by design: flex-auth runs per-consumer cluster-local + pins, and `flex-auth-secrets-engine` has not been created yet, so the + 2026-09-06 probe finding was the design working rather than an outage. + Define and enforce the decision contract needed by production commands. A resolved approval must bind at least: