access-engine/docs/decision-input-freshness.md
tegwick 0ced2995ee
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 1m27s
FLEX-WP-0036: binary renamed to access-engine; flex-auth is a stub naming the rename (FLEX-DEC-2026-020)
- cmd/flex-auth -> cmd/access-engine; usage and serving lines say access-engine
- cmd/flex-auth: prints the rename and exits 2, with a test
- image: entrypoint /usr/local/bin/access-engine, flex-auth stub alongside;
  live pins pass only args, so their next roll is unaffected
- Makefile, CI, verify-posture, tenancy.yaml, live docs updated; workplans,
  decisions and ADRs left as written
- runtime names (module, audience, k8s, image package, /opt paths) unchanged

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 735967@bnt-lap001
Assistant-Session: 5971967f-77f8-4ced-9e02-c6ef67040ffc
2026-10-10 13:26:19 +02:00

89 lines
4.7 KiB
Markdown

# Decision input freshness
Status: published
Date: 2026-09-02
Standard: security-layer-model_v0.7 §9.7.2
A flex-auth decision is a join over sources that refresh independently. One
number at a PDP would be either a fiction or the worst case. This document
states a **visibility deadline per input class**, with the mechanism that
bounds it.
Provenance identifies which snapshot and package a decision read
(`registry_snapshot_digest`, `policy_package_digest`, `directory_etag`,
`input_claim_digests`). Without those digests a deadline is unfalsifiable.
Operational bound shared by every class: an allow is never valid past
`lifetime.expires_at` (default TTL 15m). That is the consumer-visible ceiling.
The class deadlines below say when a **new Check** is guaranteed to see a
revocation of that class of input.
## Approval-claim freshness
| | |
| --- | --- |
| What is joined | Request-time approval and identity claims on `CheckRequest.context` (and related subject attributes) |
| Mechanism | No PDP-side cache of claims. Each Check evaluates the claims as presented. |
| Deadline | **Immediate** on the next Check (0). A revoked claim is visible as soon as the consumer stops sending it. |
| Cached allow | Bounded by `lifetime`. Replay of a previous allow after the claim is revoked is refused once the digest no longer matches or the TTL ends. |
| Provenance | `input_claim_digests.context` |
flex-auth does not store or mutate the approval object (`approval-engine`).
## Registry snapshot cadence
| | |
| --- | --- |
| What is joined | Protected systems, resources, subjects, groups, teams, tenants, relationship facts |
| Mechanism | In-memory `registry.Store` loaded at process start and by `access-engine load-registry`. There is no background watch. |
| Deadline | A registry-borne revocation is visible after the operator reloads the snapshot **or** when the current allow TTL ends, whichever is first. On a running process that has not reloaded, the snapshot is otherwise unchanged. |
| Provenance | `registry_snapshot_digest` — SHA-256 of canonical snapshot JSON |
The digest makes the deadline checkable: a later Check whose digest differs
read a different snapshot.
## Policy package activation
| | |
| --- | --- |
| What is joined | The validated Rego-in-Markdown package (`id` / `version` / compiled module) |
| Mechanism | Loaded at process start and by CLI `--policy`. No hot swap. |
| Deadline | A package change is visible after reload **or** when the current allow TTL ends, whichever is first. |
| Provenance | `policy_package`, `policy_version`, `policy_package_digest` |
## Directory ETag
| | |
| --- | --- |
| What is joined | Delegated directory state (Topaz objects/relations, relationship-PDP tuples) and optional group-resolver evidence |
| Mechanism | Topaz returns `DirectoryETag`; relationship adapters return a consistency token. Group resolvers (`Graph`, `SCIM`, `LDAP`, `Keycloak`) attach `Freshness.MaxAge` when configured. Verdicts are not cached by flex-auth. |
| Deadline | **Next Check** against the directory for ETag-bearing adapters (0 relative to the directory's own visibility). For resolvers, the configured `MaxAge`; if `MaxAge` is unset, directory-borne group evidence is unbounded except by the allow TTL. |
| Provenance | `directory_etag`; resolver freshness is subject-attribute evidence, not a second decision |
A stale-directory signal from Topaz fails closed (`topaz_directory_stale`).
## Lifetime versus an artifact issued under the decision
`lifetime.expires_at` bounds the **decision**, not anything a consumer creates
while acting on it. It is authority to issue, not authority to use.
Where a PEP's enforcement produces a durable credential — ops-warden's SSH
certificate is the shipped case — the credential's own TTL governs it, and no
flex-auth field reaches it. A short decision lifetime bounds how long a revoked
principal can keep obtaining *new* credentials, which is the part a PDP can
bound; it does not recall one already issued. The residue between the two is the
consumer's §9.7.2 window to state, and it is correctly stated at the PEP.
The lever that shortens the artifact's own life is the requested TTL as a policy
input, not the decision lifetime. See `FLEX-DEC-2026-004`.
## How to read this as a consumer
1. Do not cache a verdict past `lifetime.expires_at`.
2. To know whether a stored allow still names the same facts, compare
`registry_snapshot_digest`, `policy_package_digest`, and `directory_etag`
to a fresh Check.
3. Approval revocation is a claim-class problem: stop presenting the claim,
and do not replay a digest that included it.
4. Do not read `lifetime` as a bound on a credential you issued under the
allow. Bound that credential with its own TTL.