flex-auth/examples/secrets-engine/replay/README.md

54 lines
2.3 KiB
Markdown
Raw Normal View History

# 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` |
| --- | --- | --- |
Add schemaguard; correct check_request subject.type against shipped reality approval-engine suggested a conformance check after the examples-contradict- prose class hit a third repository -- theirs. Fifteen lines, they said, and it would have caught our caring fixture and our provenance omission. Worth stealing, so we stole it. internal/schemaguard validates published examples against published schemas. It implements only the JSON Schema subset these schemas use, and the property that makes it trustworthy is that an unrecognised keyword FAILS rather than skips: a validator that silently approves what it does not understand invites reliance it cannot support. It found three things on first run. ONE, AND THE LARGEST: check_request.schema.json pointed subject.type at CARING's subject_type enum (Human, Service, ...), and no consumer sends that vocabulary. user-engine sends human, tenant-engine and secrets-engine send service, and ops-warden sends adm/agt/atm -- an actor-type vocabulary CARING does not model at all. Our published schema declared three live integrations non-conformant. A rule that outlaws shipped correct behaviour is the rule that is wrong, so the $ref is replaced with an opaque non-empty string and a description saying why. CARING's enum remains correct where it belongs: the registry's subject_manifest.yaml, where Service is right. TWO: policy_package_note, which this session added to the caring example's decision provenance, is undeclared under additionalProperties:false. Our own annotation broke the conformance it was annotating. Moved to the envelope's outer provenance. THREE: the secrets-engine fixtures carried partial approval-claims, missing binding, freshness and validity. A partial claim in a fixture is how a consumer learns the wrong shape -- the same mechanism that produced the destroy defect. They are now complete and valid against approval-engine's schema, including the now-required binding.pdp_digest, and a test validates them against that schema when the sibling repo is present. The destroy replay fixture is regenerated accordingly and the replay README's pinned digests updated, since a stale digest table is the same defect wearing a different hat. Closed the binding-mapping open item. approval-engine declined to publish a vocabulary mapping and their reasoning is better than the request: a PIP asserting secrets.kv.destroy MEANS destroy would author semantics over two vocabularies it owns neither of, and a wrong mapping silently accepts a claim approved for a different action. pdp_digest is the mapping precisely because it does not translate. It is now always present and nullable, so the destroy gate is pdp_digest non-null and equal, enforced at the PEP. 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
2026-09-06 14:21:28 +02:00
| `binding.request_digest` | `sha256:de67324f…4345` | `sha256:fc155db…bdf3` |
| `provenance.policy_package_digest` | `sha256:fe0070b7…bd8c` | same |
| `provenance.registry_snapshot_digest` | `sha256:f5a309bc…40bb` | same |
Add schemaguard; correct check_request subject.type against shipped reality approval-engine suggested a conformance check after the examples-contradict- prose class hit a third repository -- theirs. Fifteen lines, they said, and it would have caught our caring fixture and our provenance omission. Worth stealing, so we stole it. internal/schemaguard validates published examples against published schemas. It implements only the JSON Schema subset these schemas use, and the property that makes it trustworthy is that an unrecognised keyword FAILS rather than skips: a validator that silently approves what it does not understand invites reliance it cannot support. It found three things on first run. ONE, AND THE LARGEST: check_request.schema.json pointed subject.type at CARING's subject_type enum (Human, Service, ...), and no consumer sends that vocabulary. user-engine sends human, tenant-engine and secrets-engine send service, and ops-warden sends adm/agt/atm -- an actor-type vocabulary CARING does not model at all. Our published schema declared three live integrations non-conformant. A rule that outlaws shipped correct behaviour is the rule that is wrong, so the $ref is replaced with an opaque non-empty string and a description saying why. CARING's enum remains correct where it belongs: the registry's subject_manifest.yaml, where Service is right. TWO: policy_package_note, which this session added to the caring example's decision provenance, is undeclared under additionalProperties:false. Our own annotation broke the conformance it was annotating. Moved to the envelope's outer provenance. THREE: the secrets-engine fixtures carried partial approval-claims, missing binding, freshness and validity. A partial claim in a fixture is how a consumer learns the wrong shape -- the same mechanism that produced the destroy defect. They are now complete and valid against approval-engine's schema, including the now-required binding.pdp_digest, and a test validates them against that schema when the sibling repo is present. The destroy replay fixture is regenerated accordingly and the replay README's pinned digests updated, since a stale digest table is the same defect wearing a different hat. Closed the binding-mapping open item. approval-engine declined to publish a vocabulary mapping and their reasoning is better than the request: a PIP asserting secrets.kv.destroy MEANS destroy would author semantics over two vocabularies it owns neither of, and a wrong mapping silently accepts a claim approved for a different action. pdp_digest is the mapping precisely because it does not translate. It is now always present and nullable, so the destroy gate is pdp_digest non-null and equal, enforced at the PEP. 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
2026-09-06 14:21:28 +02:00
| `provenance.input_claim_digests.context` | absent (empty context) | `sha256:b0d2203…c221` |
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`.
Add schemaguard; correct check_request subject.type against shipped reality approval-engine suggested a conformance check after the examples-contradict- prose class hit a third repository -- theirs. Fifteen lines, they said, and it would have caught our caring fixture and our provenance omission. Worth stealing, so we stole it. internal/schemaguard validates published examples against published schemas. It implements only the JSON Schema subset these schemas use, and the property that makes it trustworthy is that an unrecognised keyword FAILS rather than skips: a validator that silently approves what it does not understand invites reliance it cannot support. It found three things on first run. ONE, AND THE LARGEST: check_request.schema.json pointed subject.type at CARING's subject_type enum (Human, Service, ...), and no consumer sends that vocabulary. user-engine sends human, tenant-engine and secrets-engine send service, and ops-warden sends adm/agt/atm -- an actor-type vocabulary CARING does not model at all. Our published schema declared three live integrations non-conformant. A rule that outlaws shipped correct behaviour is the rule that is wrong, so the $ref is replaced with an opaque non-empty string and a description saying why. CARING's enum remains correct where it belongs: the registry's subject_manifest.yaml, where Service is right. TWO: policy_package_note, which this session added to the caring example's decision provenance, is undeclared under additionalProperties:false. Our own annotation broke the conformance it was annotating. Moved to the envelope's outer provenance. THREE: the secrets-engine fixtures carried partial approval-claims, missing binding, freshness and validity. A partial claim in a fixture is how a consumer learns the wrong shape -- the same mechanism that produced the destroy defect. They are now complete and valid against approval-engine's schema, including the now-required binding.pdp_digest, and a test validates them against that schema when the sibling repo is present. The destroy replay fixture is regenerated accordingly and the replay README's pinned digests updated, since a stale digest table is the same defect wearing a different hat. Closed the binding-mapping open item. approval-engine declined to publish a vocabulary mapping and their reasoning is better than the request: a PIP asserting secrets.kv.destroy MEANS destroy would author semantics over two vocabularies it owns neither of, and a wrong mapping silently accepts a claim approved for a different action. pdp_digest is the mapping precisely because it does not translate. It is now always present and nullable, so the destroy gate is pdp_digest non-null and equal, enforced at the PEP. 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
2026-09-06 14:21:28 +02:00
The `destroy` request's `context.approval` is a **complete** approval-claim,
valid against `approval-engine/schemas/approval_claim.schema.json` including the
now-required `binding.pdp_digest`. It was regenerated on 2026-09-06 when that
claim was completed, so its digests differ from the first emission — a partial
claim in a fixture is how a consumer learns the wrong shape.
## 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`.