fix: exclude correlation fields from the flex-auth request digest
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s

Verified the digest join against flex-auth's T03 replay fixtures and found
request_digest was hashing fields docs/canonical-request-digest.md excludes.
The material is tenant, subject, action, resource, context only: id is
correlation, policy_version lives in provenance, caring_context is hashed
separately. This engine included all three when present.

Because the join adopts the served request id, every real production request
would have carried one, so the computed digest would have matched no issued
decision and failed closed against every correct allow. Same unsatisfiable
shape as the removed AUTHORITY constant.

The old pinned constant was computed with the id inside the material, so it
was wrong and its passing proved nothing. Replaced with fixture-driven tests
over two real envelopes (vendored with provenance) plus a structural test
that correlation fields do not move the digest. Both fixtures are needed:
input_claim_digests.context appears only with a non-empty context.

Also stops computing the native claim digest. The claim's binding.action and
binding.target speak approval-engine's vocabulary while ours speaks the
catalog's, and no mapping is published; flex-auth makes no cross-check and
states the correspondence is ours via pdp_digest. A claim recording no
pdp_digest now fails closed naming the missing mapping rather than comparing
two different languages. That mapping is a prerequisite for destroy.

274 tests pass. Production still fails closed.

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

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 393550@bnt-lap001
Assistant-Session: 4bb359f9-1f12-4410-9e76-079cf23c82e4
This commit is contained in:
tegwick 2026-09-06 14:17:38 +02:00
parent 083bee7333
commit 6e9c15228c
11 changed files with 562 additions and 24 deletions

View file

@ -409,6 +409,50 @@ package/version to configure. Next inbound is the `T03` replay fixture — a rea
DecisionEnvelope from this package including `provenance.registry_snapshot_digest`
— to verify the digest join unchanged.
Verified 2026-09-06 against flex-auth's `T03` replay fixtures
(`FLEX-WP-0021-T03`, commit `9e10d1c`), vendored to
`tests/fixtures/flex-auth-replay/`. Running them found a defect of exactly the
class we had just been warning about.
- **`request_digest` was hashing fields the contract excludes.** The material is
`tenant`, `subject`, `action`, `resource`, `context` only; `id` is correlation,
`policy_version` lives in provenance, and `caring_context` is hashed
separately. This engine included all three when present. Because the join
adopts the served request id, every real production request would have carried
one, so the computed digest would have matched no issued decision and failed
closed against every correct allow — the same unsatisfiable-rule shape as the
removed `AUTHORITY` constant and as flex-auth's own destroy rule in `68ad039`.
Fixed by `digest_material`; both fixtures now reproduce their published
digests exactly.
- The previously pinned digest constant was computed *with* the id inside the
material, so it was wrong and its passing was not evidence. It is replaced by
fixture-driven contract tests over two real envelopes plus a structural test
that correlation fields do not move the digest. Both fixtures are required:
`provenance.input_claim_digests.context` appears only with a non-empty context.
- **The native claim digest is unusable and is no longer computed.** The claim's
`binding.action`/`binding.target` speak approval-engine's vocabulary while ours
speaks the catalog's, and no mapping is published; flex-auth makes no
cross-check and states the correspondence is ours via `pdp_digest`. A claim
recording no `pdp_digest` now fails closed naming the missing mapping rather
than comparing two vocabularies. **A published mapping is a prerequisite for
making `destroy` reachable under this task.** flex-auth offered to co-author it
with approval-engine.
- Narrowing accepted from approval-engine: `entries` is UNIQUE on
`(approval_id, subject_id)`, so approver distinctness is a storage invariant,
not a count this engine was previously protecting. What we stopped verifying is
that approval-engine applies its own invariant correctly — still a real
reduction in defence in depth, but narrower than "the threshold is now
unverified". They also added `threshold` objects to issuance and use events
(`87e55e2`) so the evaluation is reconstructable under §9.6.
- flex-auth corrected the `destroy` dual-control rule (`68ad039`) before
generating these fixtures: it had required `context.approval.status ==
'approved'` and an approver list, neither of which exists in the claim schema.
Our acknowledgement of the earlier rule crossed that fix. The corrected rule
requires only kind/issuer/`valid_now`, and our claim validation is unchanged.
- One global pin confirmed correct: `secrets-engine.catalog-lane.lifecycle` v1 is
one package covering all twelve actions. Do not build per-action pin
configuration for a distinction that does not exist.
Define and enforce the decision contract needed by production commands. A
resolved approval must bind at least: