flex-auth/docs/action-bound-authorization-contract.md
tegwick c473f1971d
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Build and Publish Container Image / build-and-push (push) Successful in 37s
feat(authz): bind decisions to exact actions
Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a02e47-6aac-7ee1-914d-0584c75d3c81
2026-08-23 13:18:26 +02:00

4.4 KiB

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:

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:

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 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.