feat: split the validator by owning layer per GH-DEC-2026-005
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

gate-house resolved APPROVAL-IN-0002. Two changes fell to this repo.

1. Split validate_action_authorization. The claim from approval-engine now
   carries the approval fact (issuer, valid_now, consumption, binding digest,
   freshness, reason_code) via approval_claim.validate_approval_claim; the
   flex-auth DecisionEnvelope carries the decision (effect, binding match,
   request digest, lifetime, policy pin) via validate_decision_envelope.
   ActionAuthorization is deferred and never ratified (FLEX-DEC-2026-006) and
   cannot be served from a step-1 call; nothing validates it now.

2. Dropped AUTHORITY = "state-hub" and the provenance.authority requirement.
   State Hub is a read model with no runtime approval authority, so the check
   failed closed against every correctly issued record. flex-auth traced the
   constant to their own fixture and fixed it at source.

Two consequences recorded rather than buried: there are now two distinct
digests over the same action (approval-engine native over
{action,actor,principal,purpose,target}, and the flex-auth CheckRequest
digest) which are never compared to each other; and the distinct-approver
threshold is no longer checked here, since the claim exposes no approver
entries and approval-engine folds it into valid_now.

The canonical request digest is unchanged and its contract test is preserved
verbatim. Production still fails closed. 251 tests pass.

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 08:02:01 +02:00
parent dbd3694f71
commit 7b4b9e386e
8 changed files with 694 additions and 325 deletions

View file

@ -311,6 +311,47 @@ artifact and must not be extended until gate-house rules.
pins, and `flex-auth-secrets-engine` has not been created yet, so the
2026-09-06 probe finding was the design working rather than an outage.
Resolved 2026-09-06 by gate-house `GH-DEC-2026-005` (GH-IN-0002), settling
approval-engine's `APPROVAL-IN-0002`. Both changes it assigned to this repo are
implemented.
1. The validator is split by owning layer. `approval_claim.validate_approval_claim`
consumes approval-engine's approval-claim for the approval fact (issuer,
`valid_now`, consumption state, binding digest, freshness, `reason_code`).
`authorization.validate_decision_envelope` consumes the flex-auth
DecisionEnvelope for the decision (effect, exact CheckRequest binding,
canonical request digest, lifetime, policy package/version pin). Neither
republishes the other's data. `ActionAuthorization` is deferred and never
ratified (`FLEX-DEC-2026-006`); nothing validates it any more.
2. `AUTHORITY = "state-hub"` and the `provenance.authority` requirement are
gone. flex-auth traced the constant to their own fixture
(`examples/caring/action_authorization.json`), which contradicted their
ownership section — their bug, fixed at source. State Hub is a read model and
holds no runtime approval authority, so the check failed closed against every
correctly issued record.
Two consequences worth stating rather than burying:
- There are now **two different digests** over the same proposed action, by
contract, never compared to each other: the approval-engine native binding
digest over `{action, actor, principal, purpose, target}`, and the flex-auth
canonical CheckRequest digest. `claim.binding.pdp_digest` is preferred when
the issuer recorded one. The CheckRequest digest itself is unchanged and its
contract test is preserved verbatim — flex-auth confirmed only the envelope
went away, not the digest join.
- The distinct-approver threshold is **no longer a consumer-side check**. The
claim does not expose approver entries; approval-engine folds that requirement
into `valid_now`. We now rely on the issuer for it, which is the correct layer
but is a real reduction in what this engine verifies independently.
Still outstanding, unchanged by the ruling: step 2 needs the
`flex-auth-secrets-engine` Service DNS (not created — per-consumer cluster-local
pins are the design, so the earlier "no reachable PDP" probe was not an outage)
and the published package from `FLEX-WP-0021-T02`. The pin stays unset.
`docs/gated-actions.md` delivered the twelve-action vocabulary that unblocks
`FLEX-WP-0021-T01`. Separately tracked: production requires a KeyCape RS256 JWT,
not the static Bearer token this engine currently sends.
Define and enforce the decision contract needed by production commands. A
resolved approval must bind at least: