Emit T03 replay fixtures from the published package
Two real DecisionEnvelopes in examples/secrets-engine/replay/ for secrets-engine to verify its digest join unchanged: a plain allow (rotate, empty context) and the dual-control allow (destroy with a valid approval-claim). Both are included deliberately. input_claim_digests.context appears only when the request carries a non-empty context, so a consumer asserting the field is always present would pass on destroy and fail on rotate. One fixture would have hidden that. request_digest, policy_package_digest, registry_snapshot_digest and the context claim digest are verified identical across two runs and are the fields to pin. id, decision_time and the lifetime bounds move with the clock; the README says so rather than leaving a consumer to discover it by flake. lifetime.ttl is 15m from the package allow_ttl. Emitted from flex-auth/local in standalone mode, not from a cluster pin. T03 is progress, not done -- it closes when secrets-engine confirms their validator accepts the records unchanged. 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:
parent
68ad039a3f
commit
9e10d1cec1
4 changed files with 288 additions and 1 deletions
47
examples/secrets-engine/replay/README.md
Normal file
47
examples/secrets-engine/replay/README.md
Normal file
|
|
@ -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`.
|
||||
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
110
examples/secrets-engine/replay/decision_rotate.json
Normal file
110
examples/secrets-engine/replay/decision_rotate.json
Normal file
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue