flex-auth/docs/decision-record-contract.md
tegwick 56940727bf
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Build and Publish Container Image / build-and-push (push) Successful in 57s
Finish FLEX-WP-0019 layer-model v0.7 conformance
Close the remaining PDP obligations: mechanical layer declaration check,
registry-snapshot digest in provenance, explicit allow TTL, per-input-class
freshness deadlines, and the published decision-record contract. Document
the canonical request digest as the §6.4.2 replay test.

Assistant: grok
Assistant-Session: 01a06256-fb71-7102-b3a9-27e6734257d0
2026-09-03 23:48:45 +02:00

57 lines
2.5 KiB
Markdown

# Decision-record contract
Status: published
Contract: `flex-auth.decision-record.v1`
Schema: [`../schemas/decision_envelope.schema.json`](../schemas/decision_envelope.schema.json)
Date: 2026-09-02
This is flex-auth's output artifact under the NetKingdom Security Layer Model
v0.7 §17. Taxonomy holds only the shared field vocabulary. Consumers may rely
on this schema.
A decision record is a `DecisionEnvelope` returned by `POST /v1/check` and the
CLI `check` / `batch-check` / `list-allowed` commands. Standalone evaluation
and every delegated adapter (Topaz, relationship, rule, Keycloak) emit the
same shape.
## Required fields
| Field | Meaning |
| --- | --- |
| `id` | Deterministic decision identifier |
| `effect` | `allow`, `deny`, `redact`, `audit_only`, or `not_applicable` |
| `subject` / `resource` | Normalized refs the evaluator used |
| `provenance` | Who evaluated, over which policy and facts |
## Contract fields consumers may rely on
| Field | Meaning |
| --- | --- |
| `contract_version` | `flex-auth.decision-record.v1` |
| `binding` | Structured subject, action, resource, context, and `request_digest` |
| `lifetime` | **Required on every allow.** A TTL with `not_before` and `expires_at` |
| `provenance.policy_package` / `policy_version` | Named package pin |
| `provenance.policy_package_digest` | SHA-256 of package metadata plus compiled Rego |
| `provenance.registry_snapshot_digest` | SHA-256 of the canonical registry snapshot |
| `provenance.directory_etag` | Directory consistency token when a delegated directory was joined |
| `provenance.input_claim_digests` | SHA-256 per request-time claim class (`context`, `caring_context`) |
| `provenance.decision_time` | UTC timestamp used to compute `lifetime` |
`reason`, `diagnostics`, and CARING prose are not an authorization contract.
## Allow lifetime
Every allow carries `lifetime.kind = ttl`. The duration comes from the policy
package `allow_ttl` field, or from the engine default of `15m` when the package
omits it. A package that declares `allow_ttl: none` (or `0s`) produces a deny
with reason `allow_lifetime_unstated` instead of a standing grant.
Replay is permitted only while `lifetime.expires_at` is still in the future.
See [`canonical-request-digest.md`](canonical-request-digest.md) and
[`decision-input-freshness.md`](decision-input-freshness.md).
## Versioning
This is contract version 1. Additive optional fields may appear. Removing or
redefining a required field requires a new `contract_version` value and a new
schema id.