# Action-bound authorization contract Status: implemented for execute-time request binding; durable approval storage requires State Hub support. Date: 2026-08-23 ## Ownership and endpoints flex-auth owns authorization evaluation and the canonical request/decision vocabulary. A protected system obtains a current decision from: ```text POST /v1/check ``` The response now includes `binding`, the exact normalized subject, action, resource, context, and full SHA-256 request digest evaluated by the policy. Consumers must compare structured fields; `reason`, `diagnostics`, titles, and other prose are never an authorization contract. State Hub owns durable organizational-decision identity, resolution, supersession, and availability. Its current endpoint is: ```text GET /decisions/{uuid} ``` That object is not yet sufficient for privileged action execution. As of 2026-08-23 it has lifecycle fields and `superseded_by`, but action details live only in prose and it records only one free-form `decided_by` value. Production consumers must not infer action binding or dual control from it. ## Durable object [`../schemas/action_authorization.schema.json`](../schemas/action_authorization.schema.json) defines the proposed `ActionAuthorization` storage and transport object. It combines: - a canonical UUID and explicit lifecycle status; - one complete `CheckRequest` containing the actor, action, resource, exact target attributes, stage, purpose, and other policy inputs; - a bounded validity window; - approval count and individual approval evidence; - the flex-auth `DecisionEnvelope` whose structured binding and digest match that request. The schema is generic. For secrets-engine, use this profile: | Requirement | Canonical field | | --- | --- | | catalog id | `request.resource.id` | | stage | `request.resource.attributes.stage` | | lifecycle operation | `request.action` | | exact secret fields | `request.resource.attributes.fields` | | policy/auth targets | `request.resource.attributes.policy_targets` / `auth_targets` | | requesting actor | `request.subject` | | purpose | `request.context.purpose` | | execution window | `validity.not_before` / `validity.expires_at` | | dual control | `approvals.required_count` and distinct `entries[].subject_id` | Array values that express exact targets are sets: producers sort and de-duplicate them before evaluation; consumers require exact set equality. Wildcards are policy inputs and should be denied for destructive production actions. ## Required verification A production consumer may execute only when all of the following hold: 1. The durable object resolves from its canonical authority; an outage fails the action closed. 2. `status` is `approved`, `superseded_by` is absent, and the current time is inside the validity window. 3. The caller's proposed action exactly matches `request`, including resource id, system, stage, target sets, actor, and purpose. 4. `decision.effect` is `allow`; `decision.binding` matches the request and its digest; the policy package/version are accepted for the deployment. 5. Approval entries contain at least `required_count` distinct authenticated subjects. Repeated entries from one subject count once. Local fixtures, CCR labels, workplan ids, prose, and a caller-supplied flex-auth decision id are provenance only. They cannot unlock a production action. ## Outage and supersession semantics - State Hub or future authorization-object store unreachable: privileged live action denied; read-only inspection and dry-run may continue with a clear non-authoritative status. - flex-auth unavailable when a fresh decision is required: privileged live action denied. A previously stored decision is usable only inside its bounded validity window and only if the authoritative lifecycle object can still be checked for supersession or revocation. - `superseded`, `expired`, `revoked`, `denied`, or unknown status: deny. - `superseded_by` present regardless of status: deny and resolve the replacement explicitly; never follow it silently during execution. ## Remaining external work State Hub needs a structured object/endpoint capable of storing this schema or equivalent fields, authenticated approval entries, and atomic supersession. Until that lands, secrets-engine is correct to keep live destructive actions disabled. The standalone evaluator and the Topaz, relationship, rule, and Keycloak delegated adapter boundaries now all populate the same binding.