Answer ops-warden and secrets-engine; open FLEX-WP-0021
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 1s

Two cross-repo questions arrived in the flex-auth inbox and both are
answered as decision records rather than as prose in a message.

FLEX-DEC-2026-004 answers ops-warden WARDEN-WP-0034-T05. A decision
lifetime shorter than the SSH certificate TTL is meaningful, but only as
authority to issue, never as authority to use an already-issued
certificate. The pre-sign gate is the only consumer of the shorter
lifetime: no replay past expires_at, fresh Check per sign. The lever that
shortens effective access is the requested TTL as a policy input, which
is already deployed as the ttl_out_of_bounds deny. ops-warden's section
9.7.2 window through certificate TTL is correct as written and correctly
owned by the PEP; flex-auth does not want that residue moved to the PDP.

docs/decision-input-freshness.md gains the same boundary as published
contract text, so the ruling is not only in the decision log.

FLEX-DEC-2026-005 answers secrets-engine. A real policy package is
expected and flex-auth authors it here as it does for every consumer; the
reserved coordinate is secrets-engine.catalog-lane.lifecycle v1 and it
does not exist yet. Their choice not to default the pin was correct and
is endorsed explicitly. POST /v1/check is deployed but has no
estate-wide address by design -- per-consumer cluster-local pins with
default-deny ingress -- so their 2026-09-06 probe found the design
working, not an outage.

FLEX-WP-0021 carries that work: obtain the real action vocabulary from
secrets-engine, publish the package with fixtures, confirm the digest
join against a real decision record, then stand up a
flex-auth-secrets-engine pin in warn without moving the other two pins.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JTbVXpEiXA7mNJVpDnEPcB

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 412054@bnt-lap001
Assistant-Session: 3968fae1-8d59-4209-9bd6-c22594b8ab19
This commit is contained in:
tegwick 2026-09-06 01:12:50 +02:00
parent 56940727bf
commit 6a6464fcc9
4 changed files with 659 additions and 15 deletions

View file

@ -2,9 +2,9 @@
"schema": "repo_manager.index.v1",
"slug": "flex-auth",
"repo_root": "/home/worsch/flex-auth",
"head_sha": "5753b47ccb72f96689df9ec5eeb7be100f6817cd",
"observed_at": "2026-08-29T08:19:42.754823Z",
"source_fingerprint": "2d3773fc38408c64d1a9a4efc7335ed792f361cdf6464f04389615c9b443fa53",
"head_sha": "56940727bfb36aec705318b0128599f367256c23",
"observed_at": "2026-09-05T23:08:53.160637Z",
"source_fingerprint": "727c449ea8eb418a198b97d34244e377936060c84024b65b2af950ea71714259",
"source_files": [
".repo-classification.yaml",
"INTENT.md",
@ -27,7 +27,9 @@
"workplans/FLEX-WP-0015-tenancy-posture-conformance.md",
"workplans/FLEX-WP-0016-ops-warden-incluster-policy-pin.md",
"workplans/FLEX-WP-0017-action-bound-authorization-contract.md",
"workplans/FLEX-WP-0018-inbound-auth-corrections.md"
"workplans/FLEX-WP-0018-inbound-auth-corrections.md",
"workplans/FLEX-WP-0019-layer-model-conformance.md",
"workplans/FLEX-WP-0020-repository-identity-migration.md"
],
"work_records": [
{
@ -937,7 +939,7 @@
{
"kind": "workplan",
"id": "FLEX-WP-0017",
"status": "active",
"status": "finished",
"title": "Action-bound authorization and durable approval contract",
"source_path": "workplans/FLEX-WP-0017-action-bound-authorization-contract.md",
"uuid": "d75b7256-8b3d-5797-911c-96c3199b8baa",
@ -967,7 +969,7 @@
{
"kind": "task",
"id": "FLEX-WP-0017-T03",
"status": "wait",
"status": "cancel",
"title": "Add durable storage and authenticated approval evidence",
"source_path": "workplans/FLEX-WP-0017-action-bound-authorization-contract.md",
"uuid": "82d39961-8140-5a7f-9bd8-5164dd1742e5",
@ -987,7 +989,7 @@
{
"kind": "task",
"id": "FLEX-WP-0017-T05",
"status": "wait",
"status": "cancel",
"title": "Consumer handoff and live destructive-action proof",
"source_path": "workplans/FLEX-WP-0017-action-bound-authorization-contract.md",
"uuid": "8c3fc0a2-0855-5ac9-afa5-d03b8b1f0bf9",
@ -1024,6 +1026,196 @@
"parent_id": "FLEX-WP-0018",
"extra": {}
},
{
"kind": "workplan",
"id": "FLEX-WP-0019",
"status": "finished",
"title": "Layer model v0.7 conformance: provenance, lifetimes, deadlines, and the decision contract",
"source_path": "workplans/FLEX-WP-0019-layer-model-conformance.md",
"uuid": "84b9dc5a-71f2-5c70-ace6-78242b13d0f1",
"parent_id": null,
"extra": {}
},
{
"kind": "task",
"id": "FLEX-WP-0019-T01",
"status": "done",
"title": "Assert the layer declaration mechanically",
"source_path": "workplans/FLEX-WP-0019-layer-model-conformance.md",
"uuid": "f72b1305-114e-5ba2-b84c-f6cc0b524178",
"parent_id": "FLEX-WP-0019",
"extra": {}
},
{
"kind": "task",
"id": "FLEX-WP-0019-T02",
"status": "done",
"title": "Add the registry-snapshot digest to decision provenance",
"source_path": "workplans/FLEX-WP-0019-layer-model-conformance.md",
"uuid": "7a980074-8488-5ab1-9202-60878adb261d",
"parent_id": "FLEX-WP-0019",
"extra": {}
},
{
"kind": "task",
"id": "FLEX-WP-0019-T03",
"status": "done",
"title": "Give every allow an explicit lifetime",
"source_path": "workplans/FLEX-WP-0019-layer-model-conformance.md",
"uuid": "9d9c0e7a-56e2-5c71-9110-cea973243c22",
"parent_id": "FLEX-WP-0019",
"extra": {}
},
{
"kind": "task",
"id": "FLEX-WP-0019-T04",
"status": "done",
"title": "State revocation visibility deadlines per input class",
"source_path": "workplans/FLEX-WP-0019-layer-model-conformance.md",
"uuid": "d5917b24-2efb-503e-9a9d-837702cafc1c",
"parent_id": "FLEX-WP-0019",
"extra": {}
},
{
"kind": "task",
"id": "FLEX-WP-0019-T05",
"status": "done",
"title": "Publish the decision-record schema as flex-auth's contract",
"source_path": "workplans/FLEX-WP-0019-layer-model-conformance.md",
"uuid": "f7f501d7-6862-5542-88f7-78b704524706",
"parent_id": "FLEX-WP-0019",
"extra": {}
},
{
"kind": "task",
"id": "FLEX-WP-0019-T06",
"status": "done",
"title": "Publish the request digest as the replay test",
"source_path": "workplans/FLEX-WP-0019-layer-model-conformance.md",
"uuid": "bc109ee3-14b0-5603-a655-0d7376c2a41c",
"parent_id": "FLEX-WP-0019",
"extra": {}
},
{
"kind": "workplan",
"id": "FLEX-WP-0020",
"status": "proposed",
"title": "Repository identity migration from flex-auth to access-engine",
"source_path": "workplans/FLEX-WP-0020-repository-identity-migration.md",
"uuid": "99a661a8-b36c-5c1c-b78b-1e8930bcd0a9",
"parent_id": null,
"extra": {}
},
{
"kind": "task",
"id": "FLEX-WP-0020-T01",
"status": "todo",
"title": "1. Capture immutable cleanliness and identity baseline",
"source_path": "workplans/FLEX-WP-0020-repository-identity-migration.md",
"uuid": "bb989019-50a7-5273-ba09-b2df2e2602a4",
"parent_id": "FLEX-WP-0020",
"extra": {}
},
{
"kind": "task",
"id": "FLEX-WP-0020-T02",
"status": "todo",
"title": "2. Prepare repository metadata and work-record frontmatter",
"source_path": "workplans/FLEX-WP-0020-repository-identity-migration.md",
"uuid": "102e9dc7-4724-5e80-84c4-092e6ecfbb2b",
"parent_id": "FLEX-WP-0020",
"extra": {}
},
{
"kind": "task",
"id": "FLEX-WP-0020-T03",
"status": "todo",
"title": "3. Decide product and runtime naming separately",
"source_path": "workplans/FLEX-WP-0020-repository-identity-migration.md",
"uuid": "5095ddbc-b69c-5a52-b97f-08fca9b610c3",
"parent_id": "FLEX-WP-0020",
"extra": {}
},
{
"kind": "task",
"id": "FLEX-WP-0020-T04",
"status": "todo",
"title": "4. Inventory consumers and create owned handoffs",
"source_path": "workplans/FLEX-WP-0020-repository-identity-migration.md",
"uuid": "9bb138e1-5edb-5714-8d89-3b1b7039a7ce",
"parent_id": "FLEX-WP-0020",
"extra": {}
},
{
"kind": "task",
"id": "FLEX-WP-0020-T05",
"status": "todo",
"title": "5. Renew State Hub preflight and record approval",
"source_path": "workplans/FLEX-WP-0020-repository-identity-migration.md",
"uuid": "86fecbb6-b008-5354-8d70-0f4ba5077a58",
"parent_id": "FLEX-WP-0020",
"extra": {}
},
{
"kind": "task",
"id": "FLEX-WP-0020-T06",
"status": "wait",
"title": "6. Execute the Forgejo repository rename",
"source_path": "workplans/FLEX-WP-0020-repository-identity-migration.md",
"uuid": "aa7ccd59-ddd3-5af0-a866-30e88552fc09",
"parent_id": "FLEX-WP-0020",
"extra": {}
},
{
"kind": "task",
"id": "FLEX-WP-0020-T07",
"status": "wait",
"title": "7. Record the State Hub identity rebind",
"source_path": "workplans/FLEX-WP-0020-repository-identity-migration.md",
"uuid": "155041ed-8cba-5e5e-ac59-fc03ef6dd0ce",
"parent_id": "FLEX-WP-0020",
"extra": {}
},
{
"kind": "task",
"id": "FLEX-WP-0020-T08",
"status": "wait",
"title": "8. Establish and register a fresh canonical clone",
"source_path": "workplans/FLEX-WP-0020-repository-identity-migration.md",
"uuid": "c8afe078-41cf-5d8c-84d1-a49e4b618d34",
"parent_id": "FLEX-WP-0020",
"extra": {}
},
{
"kind": "task",
"id": "FLEX-WP-0020-T09",
"status": "wait",
"title": "9. Verify identity, history, routes, builds, and deployments",
"source_path": "workplans/FLEX-WP-0020-repository-identity-migration.md",
"uuid": "fdb50a96-f7fa-5d4a-9efa-231750dcb2f1",
"parent_id": "FLEX-WP-0020",
"extra": {}
},
{
"kind": "task",
"id": "FLEX-WP-0020-T10",
"status": "wait",
"title": "10. Exercise rollback decision points",
"source_path": "workplans/FLEX-WP-0020-repository-identity-migration.md",
"uuid": "3dc49eb4-1f39-5250-87c9-716a9a97790d",
"parent_id": "FLEX-WP-0020",
"extra": {}
},
{
"kind": "task",
"id": "FLEX-WP-0020-T11",
"status": "wait",
"title": "11. Soak, hand off residuals, and clean up the old checkout",
"source_path": "workplans/FLEX-WP-0020-repository-identity-migration.md",
"uuid": "46d8318b-d1fb-52e3-b632-52230ed1438f",
"parent_id": "FLEX-WP-0020",
"extra": {}
},
{
"kind": "decision",
"id": "FLEX-DEC-2026-001",
@ -1112,7 +1304,7 @@
"status": "resolved",
"title": "Review of security layer model v0.6 and companion v0.1: assent, two answers, five findings",
"source_path": "decisions/decisions.md",
"uuid": null,
"uuid": "def6a31b-4cd7-4474-8cfa-339b0fc9fcc6",
"parent_id": null,
"extra": {
"record": {
@ -1141,7 +1333,72 @@
"updated": "2026-08-29T08:19:41.832549Z",
"rationale": "Assent to v0.6 and companion v0.1, with two answers and five findings, none blocking. Q1: section 6.4.2 is right but collides with 9.7.1's session-bound allow, needs the request digest as its mechanical replay test, and should rule explicitly on deny-caching. Q2: the visibility deadline does land on a PDP and harder than at a PEP, but must be per input class rather than one number, and it makes flex-auth's registry-provenance gap load-bearing rather than untidy. Findings: 6.4's stance-map register does not exist in section 13 and neither document says where a map is published; the companion omits it too, which is the sufficiency gap gate-house asked for; section 17 puts the decision-record schema in Taxonomy when it is the PDP's output artifact, inverting the section 2 rule flex-auth used to decline authentication evidence; sections 17-19 are H1 outside the hierarchy; section 19 grades the document it lives in and will age.",
"decided_by": "flex-auth (reviewing side)",
"decided_at": "2026-08-29T08:19:41.832549Z"
"decided_at": "2026-08-29T08:19:41.832549Z",
"state_hub_decision_id": "def6a31b-4cd7-4474-8cfa-339b0fc9fcc6"
}
}
},
{
"kind": "decision",
"id": "FLEX-DEC-2026-004",
"status": "resolved",
"title": "Decision lifetime does not reach past issuance: answer to ops-warden WARDEN-WP-0034-T05",
"source_path": "decisions/decisions.md",
"uuid": null,
"parent_id": null,
"extra": {
"record": {
"id": "FLEX-DEC-2026-004",
"kind": "decision",
"title": "Decision lifetime does not reach past issuance: answer to ops-warden WARDEN-WP-0034-T05",
"status": "resolved",
"origin": "cross-repo",
"origin_ref": "WARDEN-WP-0034-T05",
"owner": "flex-auth",
"affects": [
"flex-auth",
"ops-warden"
],
"requested_dispositions": [
"answer",
"decline"
],
"decided_by": "flex-auth (access-engine / PDP)",
"rationale": "Answered, not declined. A decision lifetime shorter than the SSH certificate TTL is meaningful, but only as an authority-to-issue window, never as an authority-to-use window over an already-issued certificate. flex-auth lifetime.expires_at bounds how long that one allow may be relied on to authorise a sign; it cannot bound an artifact ops-warden issued under it, and flex-auth does not claim it does. The downstream contract that consumes the shorter lifetime is the pre-sign gate itself: no replay of an allow past expires_at, and a fresh Check per sign. The lever that actually shortens effective access is the requested TTL as a policy input, which is already deployed as the ttl_out_of_bounds deny; ops-warden section 9.7.2 window through cert TTL is correctly stated and correctly owned by the PEP.",
"created": "2026-09-05T23:08:38.877787Z",
"updated": "2026-09-05T23:08:38.877787Z"
}
}
},
{
"kind": "decision",
"id": "FLEX-DEC-2026-005",
"status": "resolved",
"title": "secrets-engine policy package is expected but unpublished; /v1/check has no estate-wide endpoint by design",
"source_path": "decisions/decisions.md",
"uuid": null,
"parent_id": null,
"extra": {
"record": {
"id": "FLEX-DEC-2026-005",
"kind": "decision",
"title": "secrets-engine policy package is expected but unpublished; /v1/check has no estate-wide endpoint by design",
"status": "resolved",
"origin": "cross-repo",
"origin_ref": "secrets-engine SECRETS-WP-0009-T03",
"owner": "flex-auth",
"affects": [
"flex-auth",
"secrets-engine",
"approval-engine"
],
"requested_dispositions": [
"answer"
],
"decided_by": "flex-auth (access-engine / PDP)",
"rationale": "Two answers. (1) Yes, a real package is expected, and flex-auth authors it in this repo as it did for every other consumer; the reserved coordinate is secrets-engine.catalog-lane.lifecycle at version v1, and it does not exist yet. Until it is published and pinned, secrets-engine keeping SECRETS_ENGINE_AUTHORIZATION_POLICY_PACKAGE and _VERSION as required configuration with no fallback is the correct shape and flex-auth endorses it. (2) POST /v1/check is deployed, but there is no estate-wide PDP address and there is not meant to be one: each consumer gets its own cluster-local pin whose NetworkPolicy default-denies ingress except from that one approved workload. The 2026-09-06 probe finding no reachable PDP is the design working, not an outage. A reachable endpoint for secrets-engine is a per-consumer pin that follows its policy package.",
"created": "2026-09-05T23:08:51.144287Z",
"updated": "2026-09-05T23:08:51.144287Z"
}
}
},
@ -1220,17 +1477,17 @@
"events": [
{
"type": "repo.command.applied",
"command": "repo.work.resolve_decision",
"operation": "resolve",
"correlation_id": "fe430c1e-83bb-4dba-b6fc-337baa1a0059",
"command": "repo.work.create_decision",
"operation": "create",
"correlation_id": "ff360be5-e7bf-4914-95c3-1ea6df9f6957",
"kind": "decision",
"id": "FLEX-DEC-2026-003",
"git_sha": "5753b47ccb72f96689df9ec5eeb7be100f6817cd",
"id": "FLEX-DEC-2026-005",
"git_sha": null,
"files_touched": [
"decisions/decisions.md"
],
"source": "repo-manager",
"emitted_at": "2026-08-29T08:19:42.754925Z"
"emitted_at": "2026-09-05T23:08:53.160770Z"
}
]
}

View file

@ -537,3 +537,208 @@ questions already carry the same content as questions rather than as a grade.
conformance prerequisite under §9.7.2, and will be planned as one.
- flex-auth offers the canonical request digest as the §6.4.2 replay test and
offers to publish the decision-record schema as its own contract.
## FLEX-DEC-2026-004 — Decision lifetime does not reach past issuance: answer to ops-warden WARDEN-WP-0034-T05
```yaml
id: FLEX-DEC-2026-004
kind: decision
title: 'Decision lifetime does not reach past issuance: answer to ops-warden WARDEN-WP-0034-T05'
status: resolved
origin: cross-repo
origin_ref: WARDEN-WP-0034-T05
owner: flex-auth
affects:
- flex-auth
- ops-warden
requested_dispositions:
- answer
- decline
decided_by: flex-auth (access-engine / PDP)
rationale: 'Answered, not declined. A decision lifetime shorter than the SSH certificate
TTL is meaningful, but only as an authority-to-issue window, never as an authority-to-use
window over an already-issued certificate. flex-auth lifetime.expires_at bounds
how long that one allow may be relied on to authorise a sign; it cannot bound an
artifact ops-warden issued under it, and flex-auth does not claim it does. The downstream
contract that consumes the shorter lifetime is the pre-sign gate itself: no replay
of an allow past expires_at, and a fresh Check per sign. The lever that actually
shortens effective access is the requested TTL as a policy input, which is already
deployed as the ttl_out_of_bounds deny; ops-warden section 9.7.2 window through
cert TTL is correctly stated and correctly owned by the PEP.'
created: '2026-09-05T23:08:38.877787Z'
updated: '2026-09-06T00:00:00.000000Z'
decided_at: '2026-09-06T00:00:00.000000Z'
```
## Context
ops-warden now states its §9.7.2 revocation window explicitly: an allow can
remain effective through an already-issued SSH certificate until TTL expiry
(`adm` 48h, `agt` 24h, `atm` 8h), because there is no CRL/KRL recall channel.
It asked flex-auth, as the access engine, whether a decision lifetime shorter
than the certificate TTL is meaningful under that PEP behaviour, and if so what
downstream contract should consume the shorter lifetime.
## Disposition
**Answered, not declined.** The lifetime is meaningful, and the mismatch
ops-warden noticed is not a defect on either side. It is two different objects
with two different revocation stories.
### A decision lifetime is authority to issue, not authority to use
`lifetime.expires_at` (`docs/decision-record-contract.md`, default TTL 15m)
bounds one thing: how long that particular allow may be relied on as the
authority for the action it decided. For ops-warden the action is `sign`, and
the action completes at issuance. Once the certificate exists, the decision has
been consumed; nothing in the decision record reaches the artifact.
flex-auth does not claim otherwise, and a PDP that did would be lying. The
estate already ruled that fail-open is not expressible by a PDP because there
is no evaluator in that path; recall of an issued credential is the same shape
of claim. There is no flex-auth in the path of an SSH session already
authenticated by a valid certificate.
So a 15m decision lifetime against a 48h `adm` certificate is not an
inconsistency to reconcile. It says the gate's answer goes stale in 15 minutes;
it says nothing about the certificate, and was never asked to.
### The downstream contract is the pre-sign gate itself
That is the only consumer of the shorter lifetime, and it consumes it two ways:
1. **No replay past `expires_at`.** An allow may not be reused to authorise a
second `warden sign`, and a cached verdict must not outlive its TTL. This is
already the published rule (`docs/decision-input-freshness.md`, "How to read
this as a consumer") and it is the whole of what the lifetime binds.
2. **Fresh Check per sign.** Because the lifetime is short and claims are not
cached PDP-side, revocation of a subject, actor, or approval claim is visible
to the *next* sign immediately — deadline 0 for the claim class. That is the
real value of the short lifetime: it bounds how long a revoked principal can
keep obtaining *new* certificates, which is the part flex-auth can bound.
### The lever that shortens effective access is already deployed
If the goal is to shrink the window in which a revoked principal retains live
access, the mechanism is not a shorter decision lifetime — it is a shorter
**certificate** TTL, and the requested TTL is already a policy input. The
shipped `ops-warden.ssh-certificate.sign` package denies `ttl_out_of_bounds`
before OpenBao is reached (verified 2026-06-29). Capping the requested TTL per
actor class or per zone in the policy package is a policy change flex-auth can
make and explain; asking the decision lifetime to reach past issuance is not.
flex-auth is not proposing that change here. Certificate TTLs are ops-warden's
to set, and this record does not reopen them.
## Consequences
- ops-warden's §9.7.2 statement — allow effective through certificate TTL,
bounded by 48h/24h/8h, no recall channel — is **correct as written and
correctly owned by the PEP**. flex-auth does not want that residue moved to
the PDP's side of the line.
- No change to `flex-auth.decision-record.v1`. `lifetime` keeps its published
meaning; this record states the boundary it already had rather than adding
one.
- If ops-warden ever wants a certificate TTL bound to policy rather than to
actor class, that is a policy-package change under a new workplan, not a
lifetime semantics change.
## FLEX-DEC-2026-005 — secrets-engine policy package is expected but unpublished; /v1/check has no estate-wide endpoint by design
```yaml
id: FLEX-DEC-2026-005
kind: decision
title: secrets-engine policy package is expected but unpublished; /v1/check has no
estate-wide endpoint by design
status: resolved
origin: cross-repo
origin_ref: secrets-engine SECRETS-WP-0009-T03
owner: flex-auth
affects:
- flex-auth
- secrets-engine
- approval-engine
requested_dispositions:
- answer
decided_by: flex-auth (access-engine / PDP)
rationale: 'Two answers. (1) Yes, a real package is expected, and flex-auth authors
it in this repo as it did for every other consumer; the reserved coordinate is secrets-engine.catalog-lane.lifecycle
at version v1, and it does not exist yet. Until it is published and pinned, secrets-engine
keeping SECRETS_ENGINE_AUTHORIZATION_POLICY_PACKAGE and _VERSION as required configuration
with no fallback is the correct shape and flex-auth endorses it. (2) POST /v1/check
is deployed, but there is no estate-wide PDP address and there is not meant to be
one: each consumer gets its own cluster-local pin whose NetworkPolicy default-denies
ingress except from that one approved workload. The 2026-09-06 probe finding no
reachable PDP is the design working, not an outage. A reachable endpoint for secrets-engine
is a per-consumer pin that follows its policy package.'
created: '2026-09-05T23:08:51.144287Z'
updated: '2026-09-06T00:00:00.000000Z'
decided_at: '2026-09-06T00:00:00.000000Z'
```
## Context
secrets-engine applied flex-auth's earlier PDP answers in commit `627810b`
(`resource.type: secret-catalog-lane`, `resource.system: secrets-engine`,
catalog id as `request.resource.id`, and a join that validates
`ActionAuthorization.decision.binding.request_digest` against
`flex-auth.decision-record.v1`). It then asked two questions, with Glas
real-key execution (`SECRETS-WP-0009-T03`) waiting on the answers.
## Disposition
### Answer 1 — yes, a package is expected; it does not exist yet
Every shipped consumer's policy package is authored **in this repository**, not
in the consumer's, and baked into the image: `ops-warden.ssh-certificate.sign`
(v2), `tenant-engine.write-api.mutate`, `user-engine.portal.authorize`,
`railiance-platform.credential-grant.issue`, `qonto-assistant.finance-read`.
secrets-engine follows the same path. The reserved coordinate is:
| | |
| --- | --- |
| Package | `secrets-engine.catalog-lane.lifecycle` |
| Version | `v1` |
This is a **reservation, not a publication.** No such package exists in
`examples/` today, and until one is published and pinned, a Check naming it
evaluates nothing. `FLEX-WP-0021` carries the work.
secrets-engine's decision not to default the pin was the right call and
flex-auth endorses it explicitly: keeping
`SECRETS_ENGINE_AUTHORIZATION_POLICY_PACKAGE` and `_VERSION` as required
configuration with no fallback is exactly correct. Defaulting them would have
pinned production to example vocabulary nobody publishes, and the failure would
have been a silent evaluation against a package that is not there rather than a
loud missing-configuration error.
### Answer 2 — /v1/check is deployed; there is no estate-wide address, by design
The 2026-09-06 probe finding no reachable PDP found the design working.
flex-auth runs as **per-consumer cluster-local pins**, not as one shared
service. Three are live (`flex-auth-ops-warden`, `flex-auth-tenant-engine`,
`flex-auth-user-engine`). Each is a three-document manifest whose third document
is a default-deny `NetworkPolicy` admitting ingress from exactly one approved
consumer workload, and each is pinned to its own image digest so one consumer's
policy can roll without moving another's (`deploy/README.md`). Caller
authentication is per-pin: on the ops-warden pin it is `enforce`, so an
anonymous `/v1/check` is 401 and a token bound to another protected system is
403 (`FLEX-WP-0016`).
There is therefore no address for secrets-engine to reach *because none has been
created for it*. A reachable endpoint is a per-consumer pin, and a pin follows
its policy package — so both questions resolve to the same piece of work, in
that order: publish the package, then pin it.
## Consequences
- `FLEX-WP-0021` is opened in this repo to publish
`secrets-engine.catalog-lane.lifecycle` v1 and stand up a
`flex-auth-secrets-engine` pin. It needs secrets-engine's real action
vocabulary as input; the example `secrets-engine.lifecycle/v1` vocabulary is
explicitly not it.
- secrets-engine should keep the pin required and unset until the package is
published. Nothing in this record authorises a fallback value.
- No change to `flex-auth.decision-record.v1`. The digest join secrets-engine
built against it is correct as described.

View file

@ -62,6 +62,21 @@ read a different snapshot.
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`.
@ -70,3 +85,5 @@ A stale-directory signal from Topaz fails closed (`topaz_directory_stale`).
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.

View file

@ -0,0 +1,165 @@
---
id: FLEX-WP-0021
type: workplan
title: "secrets-engine consumer policy package and cluster-local pin"
domain: infotech
repo: flex-auth
status: proposed
owner: claude
topic_slug: netkingdom
planning_priority: P1
planning_order: 210
depends_on_workplans:
- FLEX-WP-0012
- FLEX-WP-0016
related_workplans:
- SECRETS-WP-0009
created: "2026-09-06"
updated: "2026-09-06"
---
# FLEX-WP-0021 — secrets-engine consumer policy package and cluster-local pin
Opened by `FLEX-DEC-2026-005`, answering secrets-engine's two questions of
2026-09-05. Both resolve to one piece of work in one order: **publish the
package, then pin it.**
## Why this exists
secrets-engine has already built its side. Commit `627810b` applies flex-auth's
PDP answers — `resource.type: secret-catalog-lane`, `resource.system:
secrets-engine`, catalog id as `request.resource.id` — and validates
`ActionAuthorization.decision.binding.request_digest` against
`flex-auth.decision-record.v1` with `status: approved` inside validity and
distinct approvers.
Two things it cannot supply itself:
1. `SECRETS_ENGINE_AUTHORIZATION_POLICY_PACKAGE` / `_VERSION` are required
configuration with **no fallback**, deliberately. `secrets-engine.lifecycle/v1`
is example vocabulary, not a published package. Until flex-auth publishes a
real one, that configuration cannot be set to anything true.
2. There is no address for secrets-engine to call. flex-auth runs as
per-consumer cluster-local pins with default-deny ingress admitting one
approved workload each (`deploy/README.md`); no pin exists for
secrets-engine. Its 2026-09-06 probe finding no reachable PDP found the
design working, not an outage.
Glas real-key execution (`SECRETS-WP-0009-T03`) is the workload waiting.
## Boundary
flex-auth authors the policy package; secrets-engine owns the action vocabulary
it encodes and the enforcement around the verdict. flex-auth never mutates the
approval object — that is `approval-engine`'s (`security-layer-model_v0.7` §9.4)
— and validates approvals only as input claims. This plan does not default,
weaken, or supply a fallback for the consumer's package pin.
## 1. Obtain the real action vocabulary from secrets-engine
```task
id: FLEX-WP-0021-T01
status: todo
priority: high
```
Owner: `flex-auth` to request and document; `secrets-engine` owns the answer.
- Ask secrets-engine for the authoritative action list over
`secret-catalog-lane` — the operations it will actually gate, not the
`secrets-engine.lifecycle/v1` example set.
- Record it in `docs/secrets-engine-action-vocabulary.md`, following
`docs/tenant-engine-action-vocabulary.md`.
- Record the subject classes that may appear, and which of them are service
accounts versus humans behind an approval.
Gate: every action in the vocabulary is named by secrets-engine, not inferred
here. An inferred action is a blocker, not a default.
## 2. Publish `secrets-engine.catalog-lane.lifecycle` v1
```task
id: FLEX-WP-0021-T02
status: todo
priority: high
```
Owner: `flex-auth`.
- Write `examples/secrets-engine/policy_package.md` with
`id: secrets-engine.catalog-lane.lifecycle`, `version: v1`, and an explicit
`allow_ttl`. An omitted TTL takes the 15m engine default; `none`/`0s` denies
with `allow_lifetime_unstated` rather than granting standing access.
- Add `protected_system_manifest.yaml`, `subject_manifest.yaml`,
`resource_manifest.yaml`, and `registry_snapshot.json` for the namespace.
- Add allow and deny fixtures in `policy_fixtures.yaml` plus
`check_request_*.json`, covering at minimum: an allow per vocabulary action,
wrong-tenant deny, unknown-subject deny, and an approval-claim-absent deny.
- Add `examples/secrets-engine/README.md`.
Gate: `flex-auth validate` passes, the fixture tests pass, and the package
digest is stable across two runs.
## 3. Confirm the digest join against a real decision record
```task
id: FLEX-WP-0021-T03
status: todo
priority: high
```
Owner: `flex-auth` to emit; `secrets-engine` to verify.
- Emit a real `DecisionEnvelope` from the package above and hand it to
secrets-engine as the replay fixture for the join it built in `627810b`.
- Verify `binding.request_digest` reproduces as SHA-256 over canonical JSON of
tenant/subject/action/resource/context per `docs/canonical-request-digest.md`.
- Verify `provenance.registry_snapshot_digest` is present — §9.7.2 promotes it
to a conformance prerequisite and `FLEX-WP-0019` closed that gap.
Gate: secrets-engine confirms its validator accepts the real record unchanged.
A validator change on their side is their work-record, not closed from here.
## 4. Stand up the `flex-auth-secrets-engine` pin
```task
id: FLEX-WP-0021-T04
status: wait
priority: high
```
Owner: `flex-auth`; deployment approval required.
- Add `deploy/flex-auth-secrets-engine.yaml` as a three-document manifest —
`Deployment`, `Service`, default-deny `NetworkPolicy` admitting ingress from
the secrets-engine workload only — following the two existing pins.
- Promote through the Railiance overlay (`railiance/app.toml`,
`charts/flex-auth`, `values/`), not by editing the flattened emergency files.
- Build in CI and deploy **by immutable digest**, never by tag. Do not roll the
other pins: they are pinned to their own digests precisely so one policy
change does not move another consumer.
- Start `callerAuth.mode` in `warn`. Flip to `enforce` only after secrets-engine
adopts a calling identity and its warn logs are clean, as `FLEX-WP-0016` did
for ops-warden.
Gate: the pin answers `POST /v1/check` for the secrets-engine workload and
default-denies every other source; the other two pins are unchanged.
## 5. Hand the pin coordinates back and close
```task
id: FLEX-WP-0021-T05
status: wait
priority: medium
```
Owner: `flex-auth`.
- Reply to secrets-engine with the Service DNS, the package id and version to
set in `SECRETS_ENGINE_AUTHORIZATION_POLICY_PACKAGE` / `_VERSION`, and the
`callerAuth` mode currently in force.
- Update `SCOPE.md` to list secrets-engine among the shipped consumers, and log
a progress event.
Gate: secrets-engine can set its required configuration to published values and
reach a pin; nothing about the fallback-free shape of that configuration changed.