approval-engine/docs/keycape-service-registrations.md

120 lines
5 KiB
Markdown
Raw Normal View History

# Requested KeyCape registrations
Status: requested by `APPROVAL-WP-0002-T01`. Non-secret. KeyCape owns issuance,
client disablement, and the exact claim contract. This file is a consumer
request, not a live registration.
Tokens presented to approval-engine MUST use resource-server audience
`approval-engine`. Do not reuse the OpenBao service-auth pattern that sets
`aud` to the OAuth `clientId`.
Required claims remain those in `docs/caller-authentication.md`: `iss`, `sub`,
`aud`, `exp`, `iat`, `principal_type`, `tenant`, `roles`, `scope`, `assurance`.
`principal_type` for consume callers must be `service` or `agent`.
## Resource server
| Field | Value |
| --- | --- |
| Audience | `approval-engine` |
Name the execute-time digest target; record the tenant collision flex-auth published binding.approval_binding_digest (FLEX-DEC-2026-007) after secrets-engine found that a claim-bearing request's request_digest covers the carried claim, so it can never equal a pdp_digest recorded before that claim existed. A consumer obeying GH-DEC-2026-008 against request_digest would have failed closed permanently on every claim rather than on a bad one. The value recorded at issue was already correct, so no code changes. What was wrong was this repo's description of the comparison target: a reader would reach for request_digest and fail closed forever. The schema and docs/approval-claim.md now name approval_binding_digest as the execute-time target and state that request_digest is never it, while leaving the issue-time description as it stood. Separately, docs/keycape-service-registrations.md now records a live collision in the deployment inputs: the manifest serves --tenant platform while the requested registrations issue tenant:coulomb, and ApiApplication.identity compares them with exact string equality before any object lookup, so those tokens would be denied 403 on every non-health route. flex-auth's tenant:platform is a PDP subject this engine never reads and cannot bridge the two. The values are left as-is on purpose — resolving it needs an owner statement on whether the two name the same layer, and guessing grants cross-tenant access to the approval store. The doc's stale issuer is corrected to the live https://kc.coulomb.social from 06544b0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PM5HnEAhokxdfcPqBNpT7D Assistant: claude-code Assistant-Model: opus Assistant-Process: 715850@bnt-lap001 Assistant-Session: eb557e93-7cb1-45d0-9e57-7d15b3edc60e
2026-09-06 20:35:59 +02:00
| Issuer | the deployed KeyCape issuer (manifest uses `https://kc.coulomb.social`) |
| JWKS | `GET /jwks` on the KeyCape service |
| Scopes | `approval:create`, `approval:read`, `approval:approve`, `approval:revoke`, `approval:supersede`, `approval:consume`, `approval:observe`, `approval:emit` |
Set the approval store tenant to exact tenant:platform Operator decision 5ed3fb35-eca9-413a-82b9-95171ba85bf6 accepts tenant:platform as the platform management, administration and services tenant, with no alias to platform or tenant:coulomb and no implicit cross-tenant grant. This closes the collision recorded in 5c87ba8, where the manifest served --tenant platform while the requested registrations issued tenant:coulomb. The store tenant is now exactly tenant:platform in the manifest, the CLI default, and the Engine default, and the requested client registrations ask for the same spelling. Exact JWT/store equality is retained: no mapping table, no normalisation, no prefix handling. Moving the defaults rather than only the manifest is deliberate. A default of platform under a sanctioned value of tenant:platform is a trap, because a serve that omits --tenant would come up healthy and then refuse every authenticated call -- the exact failure this decision exists to prevent. That default change broke ten tests whose identity fixtures hard-coded platform. This is the hazard flex-auth reported as FLEX-DEC-2026-008: fixtures that all carry one tenant prove nothing about the tenant field. Fixtures are aligned to the exact spelling, and the field is now varied rather than merely present. test_near_miss_tenant_spellings_are_forbidden refuses platform, tenant:coulomb, case variants, whitespace variants and empty against a tenant:platform store; test_exact_sanctioned_tenant_is_admitted pins the other half so a reject-everything bug cannot pass it. 111 tests pass. Also records the credential-independent half of the GLAS-WP-0015 image request: the image builds non-root uid 10001 off the pinned base, carries schema v3 and the new tenant default, migrates and verifies a fresh store to schema_version 3 with integrity ok, and refuses production without a persistent database or authenticated audit delivery. No scan was run -- no scanner is installed here -- and no release digest exists, so T01 and T03 both stay open. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PM5HnEAhokxdfcPqBNpT7D Assistant: claude-code Assistant-Model: opus Assistant-Process: 715850@bnt-lap001 Assistant-Session: eb557e93-7cb1-45d0-9e57-7d15b3edc60e
2026-09-06 22:33:50 +02:00
## Tenant — resolved: exact `tenant:platform`
Name the execute-time digest target; record the tenant collision flex-auth published binding.approval_binding_digest (FLEX-DEC-2026-007) after secrets-engine found that a claim-bearing request's request_digest covers the carried claim, so it can never equal a pdp_digest recorded before that claim existed. A consumer obeying GH-DEC-2026-008 against request_digest would have failed closed permanently on every claim rather than on a bad one. The value recorded at issue was already correct, so no code changes. What was wrong was this repo's description of the comparison target: a reader would reach for request_digest and fail closed forever. The schema and docs/approval-claim.md now name approval_binding_digest as the execute-time target and state that request_digest is never it, while leaving the issue-time description as it stood. Separately, docs/keycape-service-registrations.md now records a live collision in the deployment inputs: the manifest serves --tenant platform while the requested registrations issue tenant:coulomb, and ApiApplication.identity compares them with exact string equality before any object lookup, so those tokens would be denied 403 on every non-health route. flex-auth's tenant:platform is a PDP subject this engine never reads and cannot bridge the two. The values are left as-is on purpose — resolving it needs an owner statement on whether the two name the same layer, and guessing grants cross-tenant access to the approval store. The doc's stale issuer is corrected to the live https://kc.coulomb.social from 06544b0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PM5HnEAhokxdfcPqBNpT7D Assistant: claude-code Assistant-Model: opus Assistant-Process: 715850@bnt-lap001 Assistant-Session: eb557e93-7cb1-45d0-9e57-7d15b3edc60e
2026-09-06 20:35:59 +02:00
Set the approval store tenant to exact tenant:platform Operator decision 5ed3fb35-eca9-413a-82b9-95171ba85bf6 accepts tenant:platform as the platform management, administration and services tenant, with no alias to platform or tenant:coulomb and no implicit cross-tenant grant. This closes the collision recorded in 5c87ba8, where the manifest served --tenant platform while the requested registrations issued tenant:coulomb. The store tenant is now exactly tenant:platform in the manifest, the CLI default, and the Engine default, and the requested client registrations ask for the same spelling. Exact JWT/store equality is retained: no mapping table, no normalisation, no prefix handling. Moving the defaults rather than only the manifest is deliberate. A default of platform under a sanctioned value of tenant:platform is a trap, because a serve that omits --tenant would come up healthy and then refuse every authenticated call -- the exact failure this decision exists to prevent. That default change broke ten tests whose identity fixtures hard-coded platform. This is the hazard flex-auth reported as FLEX-DEC-2026-008: fixtures that all carry one tenant prove nothing about the tenant field. Fixtures are aligned to the exact spelling, and the field is now varied rather than merely present. test_near_miss_tenant_spellings_are_forbidden refuses platform, tenant:coulomb, case variants, whitespace variants and empty against a tenant:platform store; test_exact_sanctioned_tenant_is_admitted pins the other half so a reject-everything bug cannot pass it. 111 tests pass. Also records the credential-independent half of the GLAS-WP-0015 image request: the image builds non-root uid 10001 off the pinned base, carries schema v3 and the new tenant default, migrates and verifies a fresh store to schema_version 3 with integrity ok, and refuses production without a persistent database or authenticated audit delivery. No scan was run -- no scanner is installed here -- and no release digest exists, so T01 and T03 both stay open. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PM5HnEAhokxdfcPqBNpT7D Assistant: claude-code Assistant-Model: opus Assistant-Process: 715850@bnt-lap001 Assistant-Session: eb557e93-7cb1-45d0-9e57-7d15b3edc60e
2026-09-06 22:33:50 +02:00
The operator accepted `tenant:platform` as the platform management,
administration and services tenant (the landlord zone). Decision
`5ed3fb35-eca9-413a-82b9-95171ba85bf6`, recorded in
`glas-harness/docs/platform-tenant-decision.md` and relayed by `glas-harness`
2026-09-06.
**The spelling is the contract.** `ApiApplication.identity` compares the
verified JWT `tenant` claim to the engine's configured store tenant with exact
string equality and raises `Forbidden` before any object lookup
Name the execute-time digest target; record the tenant collision flex-auth published binding.approval_binding_digest (FLEX-DEC-2026-007) after secrets-engine found that a claim-bearing request's request_digest covers the carried claim, so it can never equal a pdp_digest recorded before that claim existed. A consumer obeying GH-DEC-2026-008 against request_digest would have failed closed permanently on every claim rather than on a bad one. The value recorded at issue was already correct, so no code changes. What was wrong was this repo's description of the comparison target: a reader would reach for request_digest and fail closed forever. The schema and docs/approval-claim.md now name approval_binding_digest as the execute-time target and state that request_digest is never it, while leaving the issue-time description as it stood. Separately, docs/keycape-service-registrations.md now records a live collision in the deployment inputs: the manifest serves --tenant platform while the requested registrations issue tenant:coulomb, and ApiApplication.identity compares them with exact string equality before any object lookup, so those tokens would be denied 403 on every non-health route. flex-auth's tenant:platform is a PDP subject this engine never reads and cannot bridge the two. The values are left as-is on purpose — resolving it needs an owner statement on whether the two name the same layer, and guessing grants cross-tenant access to the approval store. The doc's stale issuer is corrected to the live https://kc.coulomb.social from 06544b0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PM5HnEAhokxdfcPqBNpT7D Assistant: claude-code Assistant-Model: opus Assistant-Process: 715850@bnt-lap001 Assistant-Session: eb557e93-7cb1-45d0-9e57-7d15b3edc60e
2026-09-06 20:35:59 +02:00
(`approval_engine/api.py:66`). There is no mapping table, no normalisation, and
Set the approval store tenant to exact tenant:platform Operator decision 5ed3fb35-eca9-413a-82b9-95171ba85bf6 accepts tenant:platform as the platform management, administration and services tenant, with no alias to platform or tenant:coulomb and no implicit cross-tenant grant. This closes the collision recorded in 5c87ba8, where the manifest served --tenant platform while the requested registrations issued tenant:coulomb. The store tenant is now exactly tenant:platform in the manifest, the CLI default, and the Engine default, and the requested client registrations ask for the same spelling. Exact JWT/store equality is retained: no mapping table, no normalisation, no prefix handling. Moving the defaults rather than only the manifest is deliberate. A default of platform under a sanctioned value of tenant:platform is a trap, because a serve that omits --tenant would come up healthy and then refuse every authenticated call -- the exact failure this decision exists to prevent. That default change broke ten tests whose identity fixtures hard-coded platform. This is the hazard flex-auth reported as FLEX-DEC-2026-008: fixtures that all carry one tenant prove nothing about the tenant field. Fixtures are aligned to the exact spelling, and the field is now varied rather than merely present. test_near_miss_tenant_spellings_are_forbidden refuses platform, tenant:coulomb, case variants, whitespace variants and empty against a tenant:platform store; test_exact_sanctioned_tenant_is_admitted pins the other half so a reject-everything bug cannot pass it. 111 tests pass. Also records the credential-independent half of the GLAS-WP-0015 image request: the image builds non-root uid 10001 off the pinned base, carries schema v3 and the new tenant default, migrates and verifies a fresh store to schema_version 3 with integrity ok, and refuses production without a persistent database or authenticated audit delivery. No scan was run -- no scanner is installed here -- and no release digest exists, so T01 and T03 both stay open. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PM5HnEAhokxdfcPqBNpT7D Assistant: claude-code Assistant-Model: opus Assistant-Process: 715850@bnt-lap001 Assistant-Session: eb557e93-7cb1-45d0-9e57-7d15b3edc60e
2026-09-06 22:33:50 +02:00
no prefix handling anywhere in this engine — deliberately. `platform` is **not**
an accepted alias for `tenant:platform`, and neither is `tenant:coulomb`.
Name the execute-time digest target; record the tenant collision flex-auth published binding.approval_binding_digest (FLEX-DEC-2026-007) after secrets-engine found that a claim-bearing request's request_digest covers the carried claim, so it can never equal a pdp_digest recorded before that claim existed. A consumer obeying GH-DEC-2026-008 against request_digest would have failed closed permanently on every claim rather than on a bad one. The value recorded at issue was already correct, so no code changes. What was wrong was this repo's description of the comparison target: a reader would reach for request_digest and fail closed forever. The schema and docs/approval-claim.md now name approval_binding_digest as the execute-time target and state that request_digest is never it, while leaving the issue-time description as it stood. Separately, docs/keycape-service-registrations.md now records a live collision in the deployment inputs: the manifest serves --tenant platform while the requested registrations issue tenant:coulomb, and ApiApplication.identity compares them with exact string equality before any object lookup, so those tokens would be denied 403 on every non-health route. flex-auth's tenant:platform is a PDP subject this engine never reads and cannot bridge the two. The values are left as-is on purpose — resolving it needs an owner statement on whether the two name the same layer, and guessing grants cross-tenant access to the approval store. The doc's stale issuer is corrected to the live https://kc.coulomb.social from 06544b0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PM5HnEAhokxdfcPqBNpT7D Assistant: claude-code Assistant-Model: opus Assistant-Process: 715850@bnt-lap001 Assistant-Session: eb557e93-7cb1-45d0-9e57-7d15b3edc60e
2026-09-06 20:35:59 +02:00
Set the approval store tenant to exact tenant:platform Operator decision 5ed3fb35-eca9-413a-82b9-95171ba85bf6 accepts tenant:platform as the platform management, administration and services tenant, with no alias to platform or tenant:coulomb and no implicit cross-tenant grant. This closes the collision recorded in 5c87ba8, where the manifest served --tenant platform while the requested registrations issued tenant:coulomb. The store tenant is now exactly tenant:platform in the manifest, the CLI default, and the Engine default, and the requested client registrations ask for the same spelling. Exact JWT/store equality is retained: no mapping table, no normalisation, no prefix handling. Moving the defaults rather than only the manifest is deliberate. A default of platform under a sanctioned value of tenant:platform is a trap, because a serve that omits --tenant would come up healthy and then refuse every authenticated call -- the exact failure this decision exists to prevent. That default change broke ten tests whose identity fixtures hard-coded platform. This is the hazard flex-auth reported as FLEX-DEC-2026-008: fixtures that all carry one tenant prove nothing about the tenant field. Fixtures are aligned to the exact spelling, and the field is now varied rather than merely present. test_near_miss_tenant_spellings_are_forbidden refuses platform, tenant:coulomb, case variants, whitespace variants and empty against a tenant:platform store; test_exact_sanctioned_tenant_is_admitted pins the other half so a reject-everything bug cannot pass it. 111 tests pass. Also records the credential-independent half of the GLAS-WP-0015 image request: the image builds non-root uid 10001 off the pinned base, carries schema v3 and the new tenant default, migrates and verifies a fresh store to schema_version 3 with integrity ok, and refuses production without a persistent database or authenticated audit delivery. No scan was run -- no scanner is installed here -- and no release digest exists, so T01 and T03 both stay open. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PM5HnEAhokxdfcPqBNpT7D Assistant: claude-code Assistant-Model: opus Assistant-Process: 715850@bnt-lap001 Assistant-Session: eb557e93-7cb1-45d0-9e57-7d15b3edc60e
2026-09-06 22:33:50 +02:00
Rendered values, all three now exactly `tenant:platform`:
Name the execute-time digest target; record the tenant collision flex-auth published binding.approval_binding_digest (FLEX-DEC-2026-007) after secrets-engine found that a claim-bearing request's request_digest covers the carried claim, so it can never equal a pdp_digest recorded before that claim existed. A consumer obeying GH-DEC-2026-008 against request_digest would have failed closed permanently on every claim rather than on a bad one. The value recorded at issue was already correct, so no code changes. What was wrong was this repo's description of the comparison target: a reader would reach for request_digest and fail closed forever. The schema and docs/approval-claim.md now name approval_binding_digest as the execute-time target and state that request_digest is never it, while leaving the issue-time description as it stood. Separately, docs/keycape-service-registrations.md now records a live collision in the deployment inputs: the manifest serves --tenant platform while the requested registrations issue tenant:coulomb, and ApiApplication.identity compares them with exact string equality before any object lookup, so those tokens would be denied 403 on every non-health route. flex-auth's tenant:platform is a PDP subject this engine never reads and cannot bridge the two. The values are left as-is on purpose — resolving it needs an owner statement on whether the two name the same layer, and guessing grants cross-tenant access to the approval store. The doc's stale issuer is corrected to the live https://kc.coulomb.social from 06544b0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PM5HnEAhokxdfcPqBNpT7D Assistant: claude-code Assistant-Model: opus Assistant-Process: 715850@bnt-lap001 Assistant-Session: eb557e93-7cb1-45d0-9e57-7d15b3edc60e
2026-09-06 20:35:59 +02:00
Set the approval store tenant to exact tenant:platform Operator decision 5ed3fb35-eca9-413a-82b9-95171ba85bf6 accepts tenant:platform as the platform management, administration and services tenant, with no alias to platform or tenant:coulomb and no implicit cross-tenant grant. This closes the collision recorded in 5c87ba8, where the manifest served --tenant platform while the requested registrations issued tenant:coulomb. The store tenant is now exactly tenant:platform in the manifest, the CLI default, and the Engine default, and the requested client registrations ask for the same spelling. Exact JWT/store equality is retained: no mapping table, no normalisation, no prefix handling. Moving the defaults rather than only the manifest is deliberate. A default of platform under a sanctioned value of tenant:platform is a trap, because a serve that omits --tenant would come up healthy and then refuse every authenticated call -- the exact failure this decision exists to prevent. That default change broke ten tests whose identity fixtures hard-coded platform. This is the hazard flex-auth reported as FLEX-DEC-2026-008: fixtures that all carry one tenant prove nothing about the tenant field. Fixtures are aligned to the exact spelling, and the field is now varied rather than merely present. test_near_miss_tenant_spellings_are_forbidden refuses platform, tenant:coulomb, case variants, whitespace variants and empty against a tenant:platform store; test_exact_sanctioned_tenant_is_admitted pins the other half so a reject-everything bug cannot pass it. 111 tests pass. Also records the credential-independent half of the GLAS-WP-0015 image request: the image builds non-root uid 10001 off the pinned base, carries schema v3 and the new tenant default, migrates and verifies a fresh store to schema_version 3 with integrity ok, and refuses production without a persistent database or authenticated audit delivery. No scan was run -- no scanner is installed here -- and no release digest exists, so T01 and T03 both stay open. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PM5HnEAhokxdfcPqBNpT7D Assistant: claude-code Assistant-Model: opus Assistant-Process: 715850@bnt-lap001 Assistant-Session: eb557e93-7cb1-45d0-9e57-7d15b3edc60e
2026-09-06 22:33:50 +02:00
| Value | Where it is set | Content |
Name the execute-time digest target; record the tenant collision flex-auth published binding.approval_binding_digest (FLEX-DEC-2026-007) after secrets-engine found that a claim-bearing request's request_digest covers the carried claim, so it can never equal a pdp_digest recorded before that claim existed. A consumer obeying GH-DEC-2026-008 against request_digest would have failed closed permanently on every claim rather than on a bad one. The value recorded at issue was already correct, so no code changes. What was wrong was this repo's description of the comparison target: a reader would reach for request_digest and fail closed forever. The schema and docs/approval-claim.md now name approval_binding_digest as the execute-time target and state that request_digest is never it, while leaving the issue-time description as it stood. Separately, docs/keycape-service-registrations.md now records a live collision in the deployment inputs: the manifest serves --tenant platform while the requested registrations issue tenant:coulomb, and ApiApplication.identity compares them with exact string equality before any object lookup, so those tokens would be denied 403 on every non-health route. flex-auth's tenant:platform is a PDP subject this engine never reads and cannot bridge the two. The values are left as-is on purpose — resolving it needs an owner statement on whether the two name the same layer, and guessing grants cross-tenant access to the approval store. The doc's stale issuer is corrected to the live https://kc.coulomb.social from 06544b0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PM5HnEAhokxdfcPqBNpT7D Assistant: claude-code Assistant-Model: opus Assistant-Process: 715850@bnt-lap001 Assistant-Session: eb557e93-7cb1-45d0-9e57-7d15b3edc60e
2026-09-06 20:35:59 +02:00
| --- | --- | --- |
Set the approval store tenant to exact tenant:platform Operator decision 5ed3fb35-eca9-413a-82b9-95171ba85bf6 accepts tenant:platform as the platform management, administration and services tenant, with no alias to platform or tenant:coulomb and no implicit cross-tenant grant. This closes the collision recorded in 5c87ba8, where the manifest served --tenant platform while the requested registrations issued tenant:coulomb. The store tenant is now exactly tenant:platform in the manifest, the CLI default, and the Engine default, and the requested client registrations ask for the same spelling. Exact JWT/store equality is retained: no mapping table, no normalisation, no prefix handling. Moving the defaults rather than only the manifest is deliberate. A default of platform under a sanctioned value of tenant:platform is a trap, because a serve that omits --tenant would come up healthy and then refuse every authenticated call -- the exact failure this decision exists to prevent. That default change broke ten tests whose identity fixtures hard-coded platform. This is the hazard flex-auth reported as FLEX-DEC-2026-008: fixtures that all carry one tenant prove nothing about the tenant field. Fixtures are aligned to the exact spelling, and the field is now varied rather than merely present. test_near_miss_tenant_spellings_are_forbidden refuses platform, tenant:coulomb, case variants, whitespace variants and empty against a tenant:platform store; test_exact_sanctioned_tenant_is_admitted pins the other half so a reject-everything bug cannot pass it. 111 tests pass. Also records the credential-independent half of the GLAS-WP-0015 image request: the image builds non-root uid 10001 off the pinned base, carries schema v3 and the new tenant default, migrates and verifies a fresh store to schema_version 3 with integrity ok, and refuses production without a persistent database or authenticated audit delivery. No scan was run -- no scanner is installed here -- and no release digest exists, so T01 and T03 both stay open. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PM5HnEAhokxdfcPqBNpT7D Assistant: claude-code Assistant-Model: opus Assistant-Process: 715850@bnt-lap001 Assistant-Session: eb557e93-7cb1-45d0-9e57-7d15b3edc60e
2026-09-06 22:33:50 +02:00
| Store tenant | `deploy/approval-engine.yaml` `--tenant` | `tenant:platform` |
| Store tenant default | `approval_engine/cli.py` `--tenant`, `Engine(tenant=…)` | `tenant:platform` |
| JWT `tenant` claim | the client registrations below | `tenant:platform` |
| CheckRequest tenant | flex-auth policy subject | `tenant:platform` |
The CLI and `Engine` defaults were moved off `platform` in the same change. A
default that differs from the sanctioned value is a trap: a `serve` invocation
that omits `--tenant` would have come up healthy and then refused every
authenticated call, which is the failure this decision exists to prevent.
The flex-auth CheckRequest tenant now matches by spelling, but note it still
does not participate in this comparison — it is a PDP policy subject this engine
never reads. Alignment there is a property of the decision, not a mechanism
here.
Denial evidence:
- `tests/test_auth.py::test_wrong_tenant_is_forbidden` — a signature-valid token
whose tenant differs from the store tenant is refused `403` without mutation.
- `tests/test_auth.py::test_near_miss_tenant_spellings_are_forbidden` — pins the
decision's "no alias" clause directly: `platform`, `tenant:coulomb`,
`TENANT:PLATFORM`, `tenant:platform ` (trailing space) and `` (empty) are each
refused against a `tenant:platform` store, while the exact spelling is
admitted. Without a test that *varies* the tenant, a suite of fixtures all
carrying the sanctioned value proves nothing about the field.
This resolves the choice of value only. Verification and the remaining admission
gates — KeyCape actually owning these registrations, credential materialization,
and the `T03` rollout evidence — are unchanged and still open.
Name the execute-time digest target; record the tenant collision flex-auth published binding.approval_binding_digest (FLEX-DEC-2026-007) after secrets-engine found that a claim-bearing request's request_digest covers the carried claim, so it can never equal a pdp_digest recorded before that claim existed. A consumer obeying GH-DEC-2026-008 against request_digest would have failed closed permanently on every claim rather than on a bad one. The value recorded at issue was already correct, so no code changes. What was wrong was this repo's description of the comparison target: a reader would reach for request_digest and fail closed forever. The schema and docs/approval-claim.md now name approval_binding_digest as the execute-time target and state that request_digest is never it, while leaving the issue-time description as it stood. Separately, docs/keycape-service-registrations.md now records a live collision in the deployment inputs: the manifest serves --tenant platform while the requested registrations issue tenant:coulomb, and ApiApplication.identity compares them with exact string equality before any object lookup, so those tokens would be denied 403 on every non-health route. flex-auth's tenant:platform is a PDP subject this engine never reads and cannot bridge the two. The values are left as-is on purpose — resolving it needs an owner statement on whether the two name the same layer, and guessing grants cross-tenant access to the approval store. The doc's stale issuer is corrected to the live https://kc.coulomb.social from 06544b0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PM5HnEAhokxdfcPqBNpT7D Assistant: claude-code Assistant-Model: opus Assistant-Process: 715850@bnt-lap001 Assistant-Session: eb557e93-7cb1-45d0-9e57-7d15b3edc60e
2026-09-06 20:35:59 +02:00
Tracked against `APPROVAL-WP-0002-T01`; related `KEY-WP-0013-T02`,
Set the approval store tenant to exact tenant:platform Operator decision 5ed3fb35-eca9-413a-82b9-95171ba85bf6 accepts tenant:platform as the platform management, administration and services tenant, with no alias to platform or tenant:coulomb and no implicit cross-tenant grant. This closes the collision recorded in 5c87ba8, where the manifest served --tenant platform while the requested registrations issued tenant:coulomb. The store tenant is now exactly tenant:platform in the manifest, the CLI default, and the Engine default, and the requested client registrations ask for the same spelling. Exact JWT/store equality is retained: no mapping table, no normalisation, no prefix handling. Moving the defaults rather than only the manifest is deliberate. A default of platform under a sanctioned value of tenant:platform is a trap, because a serve that omits --tenant would come up healthy and then refuse every authenticated call -- the exact failure this decision exists to prevent. That default change broke ten tests whose identity fixtures hard-coded platform. This is the hazard flex-auth reported as FLEX-DEC-2026-008: fixtures that all carry one tenant prove nothing about the tenant field. Fixtures are aligned to the exact spelling, and the field is now varied rather than merely present. test_near_miss_tenant_spellings_are_forbidden refuses platform, tenant:coulomb, case variants, whitespace variants and empty against a tenant:platform store; test_exact_sanctioned_tenant_is_admitted pins the other half so a reject-everything bug cannot pass it. 111 tests pass. Also records the credential-independent half of the GLAS-WP-0015 image request: the image builds non-root uid 10001 off the pinned base, carries schema v3 and the new tenant default, migrates and verifies a fresh store to schema_version 3 with integrity ok, and refuses production without a persistent database or authenticated audit delivery. No scan was run -- no scanner is installed here -- and no release digest exists, so T01 and T03 both stay open. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PM5HnEAhokxdfcPqBNpT7D Assistant: claude-code Assistant-Model: opus Assistant-Process: 715850@bnt-lap001 Assistant-Session: eb557e93-7cb1-45d0-9e57-7d15b3edc60e
2026-09-06 22:33:50 +02:00
`SECRETS-WP-0009-T03`, `GLAS-WP-0015`.
Name the execute-time digest target; record the tenant collision flex-auth published binding.approval_binding_digest (FLEX-DEC-2026-007) after secrets-engine found that a claim-bearing request's request_digest covers the carried claim, so it can never equal a pdp_digest recorded before that claim existed. A consumer obeying GH-DEC-2026-008 against request_digest would have failed closed permanently on every claim rather than on a bad one. The value recorded at issue was already correct, so no code changes. What was wrong was this repo's description of the comparison target: a reader would reach for request_digest and fail closed forever. The schema and docs/approval-claim.md now name approval_binding_digest as the execute-time target and state that request_digest is never it, while leaving the issue-time description as it stood. Separately, docs/keycape-service-registrations.md now records a live collision in the deployment inputs: the manifest serves --tenant platform while the requested registrations issue tenant:coulomb, and ApiApplication.identity compares them with exact string equality before any object lookup, so those tokens would be denied 403 on every non-health route. flex-auth's tenant:platform is a PDP subject this engine never reads and cannot bridge the two. The values are left as-is on purpose — resolving it needs an owner statement on whether the two name the same layer, and guessing grants cross-tenant access to the approval store. The doc's stale issuer is corrected to the live https://kc.coulomb.social from 06544b0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PM5HnEAhokxdfcPqBNpT7D Assistant: claude-code Assistant-Model: opus Assistant-Process: 715850@bnt-lap001 Assistant-Session: eb557e93-7cb1-45d0-9e57-7d15b3edc60e
2026-09-06 20:35:59 +02:00
## Clients
Confidential client secrets stay in OpenBao/operator custody. `secretRef`
names below are placeholders for that custody path.
```yaml
clients:
- clientId: secrets-engine-approval
displayName: secrets-engine PEP consume client
audience: approval-engine
allowedScopes: [approval:read, approval:consume]
grantTypes: [client_credentials]
clientType: confidential
secretRef: env:KEYCAPE_SECRETS_ENGINE_APPROVAL_CLIENT_SECRET
serviceSubject: service:secrets-engine
principal_type: service
Set the approval store tenant to exact tenant:platform Operator decision 5ed3fb35-eca9-413a-82b9-95171ba85bf6 accepts tenant:platform as the platform management, administration and services tenant, with no alias to platform or tenant:coulomb and no implicit cross-tenant grant. This closes the collision recorded in 5c87ba8, where the manifest served --tenant platform while the requested registrations issued tenant:coulomb. The store tenant is now exactly tenant:platform in the manifest, the CLI default, and the Engine default, and the requested client registrations ask for the same spelling. Exact JWT/store equality is retained: no mapping table, no normalisation, no prefix handling. Moving the defaults rather than only the manifest is deliberate. A default of platform under a sanctioned value of tenant:platform is a trap, because a serve that omits --tenant would come up healthy and then refuse every authenticated call -- the exact failure this decision exists to prevent. That default change broke ten tests whose identity fixtures hard-coded platform. This is the hazard flex-auth reported as FLEX-DEC-2026-008: fixtures that all carry one tenant prove nothing about the tenant field. Fixtures are aligned to the exact spelling, and the field is now varied rather than merely present. test_near_miss_tenant_spellings_are_forbidden refuses platform, tenant:coulomb, case variants, whitespace variants and empty against a tenant:platform store; test_exact_sanctioned_tenant_is_admitted pins the other half so a reject-everything bug cannot pass it. 111 tests pass. Also records the credential-independent half of the GLAS-WP-0015 image request: the image builds non-root uid 10001 off the pinned base, carries schema v3 and the new tenant default, migrates and verifies a fresh store to schema_version 3 with integrity ok, and refuses production without a persistent database or authenticated audit delivery. No scan was run -- no scanner is installed here -- and no release digest exists, so T01 and T03 both stay open. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PM5HnEAhokxdfcPqBNpT7D Assistant: claude-code Assistant-Model: opus Assistant-Process: 715850@bnt-lap001 Assistant-Session: eb557e93-7cb1-45d0-9e57-7d15b3edc60e
2026-09-06 22:33:50 +02:00
tenant: tenant:platform
roles: [secrets-engine]
tokenLifetime: 15m
- clientId: approval-engine-operator
displayName: approval-engine lifecycle operator
audience: approval-engine
allowedScopes:
- approval:create
- approval:read
- approval:approve
- approval:revoke
- approval:supersede
- approval:observe
- approval:emit
grantTypes: [client_credentials]
clientType: confidential
secretRef: env:KEYCAPE_APPROVAL_ENGINE_OPERATOR_CLIENT_SECRET
serviceSubject: service:approval-engine-operator
principal_type: service
Set the approval store tenant to exact tenant:platform Operator decision 5ed3fb35-eca9-413a-82b9-95171ba85bf6 accepts tenant:platform as the platform management, administration and services tenant, with no alias to platform or tenant:coulomb and no implicit cross-tenant grant. This closes the collision recorded in 5c87ba8, where the manifest served --tenant platform while the requested registrations issued tenant:coulomb. The store tenant is now exactly tenant:platform in the manifest, the CLI default, and the Engine default, and the requested client registrations ask for the same spelling. Exact JWT/store equality is retained: no mapping table, no normalisation, no prefix handling. Moving the defaults rather than only the manifest is deliberate. A default of platform under a sanctioned value of tenant:platform is a trap, because a serve that omits --tenant would come up healthy and then refuse every authenticated call -- the exact failure this decision exists to prevent. That default change broke ten tests whose identity fixtures hard-coded platform. This is the hazard flex-auth reported as FLEX-DEC-2026-008: fixtures that all carry one tenant prove nothing about the tenant field. Fixtures are aligned to the exact spelling, and the field is now varied rather than merely present. test_near_miss_tenant_spellings_are_forbidden refuses platform, tenant:coulomb, case variants, whitespace variants and empty against a tenant:platform store; test_exact_sanctioned_tenant_is_admitted pins the other half so a reject-everything bug cannot pass it. 111 tests pass. Also records the credential-independent half of the GLAS-WP-0015 image request: the image builds non-root uid 10001 off the pinned base, carries schema v3 and the new tenant default, migrates and verifies a fresh store to schema_version 3 with integrity ok, and refuses production without a persistent database or authenticated audit delivery. No scan was run -- no scanner is installed here -- and no release digest exists, so T01 and T03 both stay open. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PM5HnEAhokxdfcPqBNpT7D Assistant: claude-code Assistant-Model: opus Assistant-Process: 715850@bnt-lap001 Assistant-Session: eb557e93-7cb1-45d0-9e57-7d15b3edc60e
2026-09-06 22:33:50 +02:00
tenant: tenant:platform
roles: [approval-operator]
tokenLifetime: 15m
```
Human approvers use the existing KeyCape human flow with `approval:approve`
only, still with `aud=approval-engine`. They must not receive
`approval:consume`.