diff --git a/examples/secrets-engine/replay/README.md b/examples/secrets-engine/replay/README.md new file mode 100644 index 0000000..20368e5 --- /dev/null +++ b/examples/secrets-engine/replay/README.md @@ -0,0 +1,47 @@ +# T03 replay fixtures + +Real `DecisionEnvelope`s emitted from the published package, for secrets-engine +to verify its digest join (`627810b`) unchanged. `FLEX-WP-0021-T03`. + +| File | From | +| --- | --- | +| `decision_rotate.json` | `../check_request_allow_rotate.json` — plain allow, empty context | +| `decision_destroy_dual_control.json` | `../check_request_allow_destroy_dual_control.json` — dual control, valid approval-claim | + +Regenerate either with: + +```bash +go run ./cmd/flex-auth check \ + -policy examples/secrets-engine/policy_package.md \ + -registry examples/secrets-engine/registry_snapshot.json \ + -request examples/secrets-engine/check_request_allow_rotate.json +``` + +## What is stable and what is not + +**Stable across runs** — these are the fields to pin a contract test against: + +| Field | `rotate` | `destroy` | +| --- | --- | --- | +| `binding.request_digest` | `sha256:de67324f…4345` | `sha256:570d1128…7f56` | +| `provenance.policy_package_digest` | `sha256:fe0070b7…bd8c` | same | +| `provenance.registry_snapshot_digest` | `sha256:f5a309bc…40bb` | same | +| `provenance.input_claim_digests.context` | absent (empty context) | `sha256:45fa9f41…fdc8` | + +Verified identical across two runs. + +**Not stable:** `id`, `provenance.decision_time`, and `lifetime.not_before` / +`lifetime.expires_at` move with the clock. `lifetime.ttl` is `15m` from the +package's `allow_ttl`. Do not pin the record as a whole. + +`input_claim_digests.context` appears only when the request carries a non-empty +context — which is why both fixtures are here rather than just one. A consumer +asserting the field is always present would pass on `destroy` and fail on +`rotate`. + +## Not a deployment + +These come from `flex-auth/local` in `standalone` mode +(`provenance.evaluator` / `mode`), not from a cluster pin. No +`flex-auth-secrets-engine` pin exists yet (`FLEX-WP-0021-T04`), and the consumer +policy pin stays unset until `T05`. diff --git a/examples/secrets-engine/replay/decision_destroy_dual_control.json b/examples/secrets-engine/replay/decision_destroy_dual_control.json new file mode 100644 index 0000000..c4e8ef4 --- /dev/null +++ b/examples/secrets-engine/replay/decision_destroy_dual_control.json @@ -0,0 +1,121 @@ +{ + "id": "decision:669a12badaa3d82e", + "contract_version": "flex-auth.decision-record.v1", + "request_id": "check:secrets-engine-destroy", + "effect": "allow", + "reason": "catalog_lane_policy_matched", + "matched_policy_version": "v1", + "matched_rule": "catalog_lane_policy_matched", + "resource": { + "id": "lane:glas-primary", + "type": "secret-catalog-lane", + "system": "secrets-engine", + "tenant": "tenant:platform", + "attributes": { + "auth_targets": [], + "fields": [], + "policy_targets": [], + "stage": "prod" + } + }, + "subject": { + "id": "secrets-engine", + "type": "service", + "tenant": "tenant:platform", + "attributes": { + "description": "secrets-engine's own service identity, the single calling identity for the twelve gated catalog-lane actions it sends to POST /v1/check. Because it is the only subject, the package has no action_not_granted branch (FLEX-WP-0021-T02); registering a second identity is the revisit trigger.", + "display_name": "secrets-engine service principal", + "groups": [ + "group:secrets-engine-lane-operators" + ], + "organization_relation": "ServiceProvider", + "roles": [ + "Operator" + ] + } + }, + "binding": { + "tenant": "tenant:platform", + "subject": { + "id": "secrets-engine", + "type": "service", + "tenant": "tenant:platform", + "attributes": { + "description": "secrets-engine's own service identity, the single calling identity for the twelve gated catalog-lane actions it sends to POST /v1/check. Because it is the only subject, the package has no action_not_granted branch (FLEX-WP-0021-T02); registering a second identity is the revisit trigger.", + "display_name": "secrets-engine service principal", + "groups": [ + "group:secrets-engine-lane-operators" + ], + "organization_relation": "ServiceProvider", + "roles": [ + "Operator" + ] + } + }, + "action": "destroy", + "resource": { + "id": "lane:glas-primary", + "type": "secret-catalog-lane", + "system": "secrets-engine", + "tenant": "tenant:platform", + "attributes": { + "auth_targets": [], + "fields": [], + "policy_targets": [], + "stage": "prod" + } + }, + "context": { + "approval": { + "approval_id": "3d1c0a8e-6b7f-4c21-9a0e-1f2b3c4d5e6f", + "consumed": false, + "issuer": "approval-engine", + "kind": "approval-claim", + "reason_code": "ok", + "schema_version": "0.1", + "state": "valid", + "valid_now": true + } + }, + "request_digest": "sha256:570d112890586d3cbf00c0e81c85ae7806f40f00afa1a2c0a23fd5e077a27f56" + }, + "lifetime": { + "kind": "ttl", + "ttl": "15m", + "not_before": "2026-09-06T06:13:43Z", + "expires_at": "2026-09-06T06:28:43Z" + }, + "diagnostics": { + "action": "destroy", + "matched_relationship": "", + "policy_package": "secrets-engine.catalog-lane.lifecycle", + "policy_status": "ready", + "registry_resource": false, + "registry_subject": true + }, + "provenance": { + "evaluator": "flex-auth/local", + "mode": "standalone", + "policy_package": "secrets-engine.catalog-lane.lifecycle", + "policy_version": "v1", + "policy_package_digest": "sha256:fe0070b79f66442ae6c218697a49c470c6c8f670aa57a30c078a5284d097bd8c", + "registry_snapshot_digest": "sha256:f5a309bc0b36721fd6d9ad7f53eb21222162bc2eac62a0ab0802a9a1d51340bb", + "input_claim_digests": { + "context": "sha256:45fa9f41271c684f2c26570be11869a8eda28e7b3b25f11e01f9378b6f89fdc8" + }, + "decision_time": "2026-09-06T06:13:43Z" + }, + "caring": { + "profile": "caring-0.4.0-rc2", + "conformance_findings": [ + { + "code": "CARING-DESCRIPTOR-MISSING", + "severity": "warning", + "message": "no CARING descriptor matched the request", + "fields": [ + "caring_context" + ] + } + ] + } +} diff --git a/examples/secrets-engine/replay/decision_rotate.json b/examples/secrets-engine/replay/decision_rotate.json new file mode 100644 index 0000000..e5d04d4 --- /dev/null +++ b/examples/secrets-engine/replay/decision_rotate.json @@ -0,0 +1,110 @@ +{ + "id": "decision:49309356905a2ad3", + "contract_version": "flex-auth.decision-record.v1", + "request_id": "check:secrets-engine-rotate", + "effect": "allow", + "reason": "catalog_lane_policy_matched", + "matched_policy_version": "v1", + "matched_rule": "catalog_lane_policy_matched", + "resource": { + "id": "lane:glas-primary", + "type": "secret-catalog-lane", + "system": "secrets-engine", + "tenant": "tenant:platform", + "attributes": { + "auth_targets": [], + "fields": [ + "password" + ], + "policy_targets": [], + "stage": "prod" + } + }, + "subject": { + "id": "secrets-engine", + "type": "service", + "tenant": "tenant:platform", + "attributes": { + "description": "secrets-engine's own service identity, the single calling identity for the twelve gated catalog-lane actions it sends to POST /v1/check. Because it is the only subject, the package has no action_not_granted branch (FLEX-WP-0021-T02); registering a second identity is the revisit trigger.", + "display_name": "secrets-engine service principal", + "groups": [ + "group:secrets-engine-lane-operators" + ], + "organization_relation": "ServiceProvider", + "roles": [ + "Operator" + ] + } + }, + "binding": { + "tenant": "tenant:platform", + "subject": { + "id": "secrets-engine", + "type": "service", + "tenant": "tenant:platform", + "attributes": { + "description": "secrets-engine's own service identity, the single calling identity for the twelve gated catalog-lane actions it sends to POST /v1/check. Because it is the only subject, the package has no action_not_granted branch (FLEX-WP-0021-T02); registering a second identity is the revisit trigger.", + "display_name": "secrets-engine service principal", + "groups": [ + "group:secrets-engine-lane-operators" + ], + "organization_relation": "ServiceProvider", + "roles": [ + "Operator" + ] + } + }, + "action": "rotate", + "resource": { + "id": "lane:glas-primary", + "type": "secret-catalog-lane", + "system": "secrets-engine", + "tenant": "tenant:platform", + "attributes": { + "auth_targets": [], + "fields": [ + "password" + ], + "policy_targets": [], + "stage": "prod" + } + }, + "request_digest": "sha256:de67324f54187055307a833235f83ced9fcd3a20952a27b3d19493ed39734345" + }, + "lifetime": { + "kind": "ttl", + "ttl": "15m", + "not_before": "2026-09-06T06:13:21Z", + "expires_at": "2026-09-06T06:28:21Z" + }, + "diagnostics": { + "action": "rotate", + "matched_relationship": "", + "policy_package": "secrets-engine.catalog-lane.lifecycle", + "policy_status": "ready", + "registry_resource": false, + "registry_subject": true + }, + "provenance": { + "evaluator": "flex-auth/local", + "mode": "standalone", + "policy_package": "secrets-engine.catalog-lane.lifecycle", + "policy_version": "v1", + "policy_package_digest": "sha256:fe0070b79f66442ae6c218697a49c470c6c8f670aa57a30c078a5284d097bd8c", + "registry_snapshot_digest": "sha256:f5a309bc0b36721fd6d9ad7f53eb21222162bc2eac62a0ab0802a9a1d51340bb", + "decision_time": "2026-09-06T06:13:21Z" + }, + "caring": { + "profile": "caring-0.4.0-rc2", + "conformance_findings": [ + { + "code": "CARING-DESCRIPTOR-MISSING", + "severity": "warning", + "message": "no CARING descriptor matched the request", + "fields": [ + "caring_context" + ] + } + ] + } +} diff --git a/workplans/FLEX-WP-0021-secrets-engine-consumer-policy-gate.md b/workplans/FLEX-WP-0021-secrets-engine-consumer-policy-gate.md index 7446985..79c0056 100644 --- a/workplans/FLEX-WP-0021-secrets-engine-consumer-policy-gate.md +++ b/workplans/FLEX-WP-0021-secrets-engine-consumer-policy-gate.md @@ -126,7 +126,7 @@ all twelve actions could never reach it. ```task id: FLEX-WP-0021-T03 -status: todo +status: progress priority: high state_hub_task_id: "8f7e5cdd-777e-5f54-9b92-c72b79f65672" ``` @@ -143,6 +143,15 @@ Owner: `flex-auth` to emit; `secrets-engine` to verify. 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. +**Emitted 2026-09-06**, awaiting their confirmation. +`examples/secrets-engine/replay/` carries two real envelopes — a plain allow +(`rotate`, empty context) and the dual-control allow (`destroy` with a valid +approval-claim). All three digests plus `input_claim_digests.context` verified +identical across two runs; `id`, `decision_time`, and the `lifetime` bounds move +with the clock and are documented as unpinnable. Both fixtures are included +because `input_claim_digests.context` appears only for a non-empty context, so a +consumer asserting it is always present would pass on one and fail on the other. + ## 4. Stand up the `flex-auth-secrets-engine` pin ```task