railiance-platform replied on all three open threads. Recording what they add rather than what we already knew. T02: they reached the same reading of the verifier receipt independently and add human_client_consume_denied: true for both clients, confirm the verifier ran twice (activation and post-rollout) at generation 38 with the signing key unchanged, and state T02 can close against that receipt. They also correct our framing, rightly: the packet says client-side retrieval is unadmitted, which stays true, but the attended operator path is not a client-side read and never required one. This task had been treating those as the same constraint. T04: the live approval clients mean this repo now holds a committed receipt that looks like rotation evidence and is not. Both owners independently state the same two gaps -- no real predecessor rotation, no observed wall-clock expiry. Recorded in T04 rather than only in T02, because verify-client's predecessor rejection is now implemented and unproven, which is a different state from missing or done, and T04 is where that distinction belongs. Answered their open question on CCR-2026-0020, which has no named presenting actor. As issuer: the registration carries both approval:create and approval:approve, so one presenter can create an entry and approve it. That is a separation-of-duties property of the holder, not a defect in the token -- KeyCape issues the grants approval-engine asked for. Recorded both shapes KeyCape can support, and that who holds it is approval-engine's decision and the doctrine question gate-house's, not ours. Also narrowed the operations note: the deployed config has not been written since the activation, so the inspected state is the state that will boot. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016uV8zoCKpA1WRAxsKRYbdH Assistant: claude-code Assistant-Model: opus Assistant-Process: 1182213@bnt-lap001 Assistant-Session: 966597b9-ae61-46a4-8b9e-1594ab3ec4ad
474 lines
27 KiB
Markdown
474 lines
27 KiB
Markdown
---
|
|
id: KEY-WP-0013
|
|
type: workplan
|
|
title: "Approval-engine resource audience and client registrations"
|
|
domain: infotech
|
|
repo: key-cape
|
|
status: blocked
|
|
owner: codex
|
|
topic_slug: approval-engine-resource-audience
|
|
created: "2026-09-05"
|
|
updated: "2026-09-09"
|
|
state_hub_workstream_id: "6e815d88-b0e3-5ce0-be5d-13ab15917f7f"
|
|
---
|
|
|
|
Source: approval-engine inbox request 5583e896-52f2-45bd-895f-02f227b7e836,
|
|
reviewed against its local registration and caller-authentication contracts.
|
|
|
|
## Implement static resource audiences
|
|
|
|
```task
|
|
id: KEY-WP-0013-T01
|
|
status: done
|
|
priority: high
|
|
state_hub_task_id: "a92432a2-4e92-5b90-be2b-d82b784bf8f0"
|
|
```
|
|
|
|
Added optional static audience configuration for access tokens in both grants;
|
|
ID tokens retain the relying-party audience. Added human access-token scope.
|
|
Published bounded approval service fragments and the human registration contract.
|
|
Regression tests cover the default audience, request override resistance, JWKS
|
|
signature validation, ID-token separation and service registration scope isolation.
|
|
Browser requests and token exchanges now enforce the client scope allow-list,
|
|
including grants removed after authorization.
|
|
|
|
## Provision and prove live registrations
|
|
|
|
```task
|
|
id: KEY-WP-0013-T02
|
|
status: done
|
|
priority: high
|
|
state_hub_task_id: "607897c5-bad9-55e5-86df-7802f592d6e8"
|
|
```
|
|
|
|
Needs deployment-owned custody for both new service secret references and the
|
|
compatible image/configuration rollout. The upstream issuer is now verified and
|
|
pinned by NetKingdom (2026-09-09). Deploy the implementation and service registrations together,
|
|
then prove live JWKS verification and denied excess scopes without logging values.
|
|
Local signature proof is not live rollout evidence. See docs/approval-engine-auth-contract.md.
|
|
The separate human UI callback gate is retained in T05; a bearer-only resource
|
|
server does not own a redirect and its absence does not block these service clients.
|
|
|
|
|
|
2026-09-05 follow-up: read-only deployment metadata shows the current image is
|
|
forgejo.coulomb.social/coulomb/key-cape:main-153258b and only the Qonto service
|
|
secret environment reference is present. The two approval clients are not
|
|
materialized through deployment environment references. Published a concrete
|
|
non-secret admission packet at docs/approval-engine-provisioning-request.yaml.
|
|
Custody routing has no exact admitted lane for these two clients. `warden plan`
|
|
returned founder_required but matched an unrelated generic database lane; that
|
|
mismatch is not authority to provision. Human callback clarification is pending.
|
|
No secrets were read or production resources changed.
|
|
|
|
2026-09-08: the two blocking requests were sent and their receipts are readable
|
|
back via `GET /messages/?from_agent=key-cape`.
|
|
|
|
- `278a3ebe-b529-49f6-bd1a-e3ebcf318260` -> railiance-platform. Custody admission
|
|
for both clients, quoting `docs/approval-engine-provisioning-request.yaml`
|
|
verbatim: exact OpenBao paths and `client_secret` field, the two Kubernetes
|
|
delivery references, the two `KEYCAPE_*_CLIENT_SECRET` environment names,
|
|
`tenant:platform` per decision `5ed3fb35-eca9-413a-82b9-95171ba85bf6`, and the
|
|
15m lifetime. It asks for four things explicitly: confirm/correct the paths and
|
|
fields, confirm the delivery references, name the authority for the attended
|
|
first provision, and agree a rollout window in which registrations and the
|
|
KeyCape version that reads them deploy together. It restates that the
|
|
`warden plan` `founder_required` result matched an unrelated generic database
|
|
lane and is not being treated as authorization.
|
|
- `43fbd997-6e22-4859-8433-58b784c2df6c` -> approval-engine. Requests the two
|
|
missing human-registration inputs — the exact `client_id` and the full callback
|
|
URI — noting redirects are matched exactly and a near-miss fails closed at
|
|
`/authorize`. Restates the human client shape (public, S256 PKCE,
|
|
`audience: approval-engine`, `allowedScopes: [openid, approval:approve]`,
|
|
`mfaRequired: true`, no consume grant) and asks approval-engine to confirm it
|
|
validates the access token, not the ID token, against the deployed `/jwks`.
|
|
|
|
Deployment state is unchanged from the 2026-09-05 observation: neither approval
|
|
client is materialized. Task remains `wait` — the two admissions are owned by
|
|
railiance-platform and approval-engine respectively, and live provisioning proof
|
|
cannot begin until both land. Still no secret read and no production resource
|
|
changed.
|
|
|
|
2026-09-08, second pass. Custody and the human callback are still owed, but one
|
|
part of this task was ours all along and was not built: the task requires proving
|
|
"live JWKS verification and denied excess scopes without logging values", and
|
|
there was no way to do that except by hand, against production, at the moment
|
|
custody lands — which is the worst time to be improvising a check.
|
|
|
|
Shipped `keycape verify-client` (`src/internal/authclient/verify.go`, documented
|
|
in `docs/native-authentication.md`). Per registration it verifies discovery
|
|
origin, the `client_credentials` exchange and its RS256 signature against the
|
|
deployed JWKS, exact `sub`/`tenant`/`roles`/`principal_type`, that every
|
|
`-deny-scope` is refused, and that the token carries no scope that was not
|
|
requested — the last being a gap the caller commands do not cover, since they
|
|
prove requested scopes were granted and not that nothing extra came back.
|
|
Failures name the claim, never the observed value, and nothing is written to
|
|
disk, so it is safe to run against production. The exact invocation for each of
|
|
the two clients is now recorded in the `verification:` block of
|
|
`docs/approval-engine-provisioning-request.yaml`, so admission hands back a
|
|
command rather than a description.
|
|
|
|
Tests: `src/internal/authclient/verify_test.go` covers the passing case, an
|
|
over-broad registration, a live predecessor secret, an identical "rotation" and
|
|
output non-disclosure. Verified with teeth — neutering `mustFail` makes the suite
|
|
fail rather than pass silently.
|
|
|
|
Task stays `wait`. What is owed from elsewhere is unchanged and unreduced: the
|
|
two secret values through an admitted custody path, and the exact human
|
|
`client_id` and callback URI. Nothing here provisions anything.
|
|
|
|
### 2026-09-08 attended upstream issuer proof — T02 return
|
|
|
|
The user explicitly admitted the prepared temporary probe. At 21:44:44 UTC its
|
|
actual Authelia authorization-code exchange verified the signed issuer
|
|
**`https://auth.coulomb.social`**, including signature, keycape audience, validity
|
|
window and browser-bound nonce. The pinned Job exited 0. No token or user claim
|
|
was retained, and no downstream credential was issued.
|
|
|
|
All five temporary resources and the Pod were removed with scoped cleanup;
|
|
KeyCape Deployment and config Secret metadata remained unchanged. Receipt:
|
|
`docs/evidence/2026-09-08-upstream-issuer-live-proof.json`.
|
|
|
|
This closes the actual-token issuer observation gate. T02 remains wait for the
|
|
configuration owner to ensure `authelia.issuer` is pinned to the verified HTTPS
|
|
value, named CCR-2026-0017/0018 reviews, custody delivery and compatible rollout.
|
|
The diagnostic did not prove downstream KeyCape MFA/application login and did
|
|
not activate custody. Do not repeat discovery or request another observation
|
|
unless the provider/configuration or proof context changes.
|
|
|
|
|
|
2026-09-09 configuration-owner return: NetKingdom's exercised, revision-guarded
|
|
issuer-only helper inserted `authelia.issuer: https://auth.coulomb.social` into
|
|
`sso/keycape-config`; resourceVersion `51346058` -> `58713343`. Independent
|
|
readback matches. Every other configuration byte and Secret data entry, including
|
|
the private key, was preserved. Thirteen tests passed. Receipt:
|
|
`docs/evidence/2026-09-09-upstream-issuer-pin.json`.
|
|
|
|
The stored issuer configuration prerequisite is closed. No process restart,
|
|
image rollout, custody activation or downstream login proof occurred. T02 stays
|
|
wait for the named CCR reviews, attended custody and compatible rollout. The
|
|
single-instance replacement and existing-human-login checks remain mandatory.
|
|
|
|
2026-09-09 completion: both named CCR reviews are recorded. Verifier-side custody,
|
|
ESO delivery and the compatible pinned image/config are now live. Both service
|
|
clients passed the pinned verifier plus independent live signature and exact
|
|
claim/lifetime checks; wrong-secret and excess-scope requests were rejected.
|
|
The existing OpenBao human login passed before and after replacement; each
|
|
attended session self-revoked. Receipt: `docs/evidence/2026-09-09-keycape-verifier-admission.json`.
|
|
|
|
The deployment owner exercised compatible rollback and version-preserving resume.
|
|
The final helper checks containerd imageID against the pinned manifest and uses
|
|
the existing 30-second future-iat bound without extending token expiry. The
|
|
pinned native verifier runs in the contained owner process because workload
|
|
network policy excludes public issuer HTTPS. No network grant was broadened.
|
|
|
|
Live residual ownership remains KEY-WP-0013-T05 for the approval UI callback/MFA;
|
|
RPF-WP-0035-T05 for separate client-side delivery; AUDIT-WP-0009-T09 for audit
|
|
custody; APPROVAL-WP-0002 and SECRETS-WP-0009-T03 for end-to-end claim/consume,
|
|
expiry/revocation and native delivery. Natural JWT expiry and real predecessor
|
|
rotation were not exercised by initial provisioning.
|
|
|
|
## Reconcile tenant vocabularies across approval layers
|
|
|
|
```task
|
|
id: KEY-WP-0013-T03
|
|
status: done
|
|
priority: high
|
|
state_hub_task_id: "e98da2eb-1f05-5c7f-bc84-711fa38b8c4a"
|
|
```
|
|
|
|
Source: glas-harness inbox message 356f6977-d361-4e3b-83ab-b2c7f4759286
|
|
(GLAS-WP-0015), which asks for the exact store tenant, JWT tenant, CheckRequest
|
|
tenant, any permitted mapping and wrong-tenant denial evidence.
|
|
|
|
Published `docs/tenant-claim-contract.md`. The KeyCape-owned JWT tenant for all
|
|
four reviewed service registrations is `tenant:coulomb`, bound at registration
|
|
and required by config validation. Approval store `platform` and policy
|
|
`tenant:platform` are owned by approval-engine and flex-auth; KeyCape performs no
|
|
normalization or aliasing, so exact comparison does not match today. No mapping
|
|
was invented and no live registration or policy subject was changed — the two
|
|
admissible resolutions are recorded for the owning parties to decide.
|
|
|
|
Added `src/internal/server/oidc/tenant_test.go`: request-supplied `tenant` and
|
|
`tenant_hint` cannot alter the claim; two registrations never carry each other's
|
|
tenant (the wrong-tenant denial basis); human tokens default to `tenant:coulomb`
|
|
rather than an empty claim. Local issuance proof only, not live-rollout evidence.
|
|
|
|
## Align approval registrations to tenant:platform
|
|
|
|
```task
|
|
id: KEY-WP-0013-T04
|
|
status: done
|
|
priority: high
|
|
state_hub_task_id: "d93e21cc-a422-5e78-981f-9c00f3161375"
|
|
```
|
|
|
|
Source: glas-harness inbox message f487c63a-dd7e-4ad8-9188-eec9fbea59c6,
|
|
operator decision 5ed3fb35-eca9-413a-82b9-95171ba85bf6 ("Use tenant:platform for
|
|
the Glas approval dependency chain", verified resolved in the hub). This closed
|
|
resolution 2 of the two options recorded in KEY-WP-0013-T03.
|
|
|
|
Changed the `tenant` field on exactly `secrets-engine-approval` and
|
|
`approval-engine-operator` in `config/service-clients.example.yaml` and the two
|
|
matching entries in `docs/approval-engine-provisioning-request.yaml` to
|
|
`tenant:platform`, each annotated with the decision id. `codex-railiance-platform`,
|
|
`secrets-engine-openbao` and the human directory default keep `tenant:coulomb`.
|
|
No audience, scope, subject, role, lifetime or MFA change; no cross-tenant grant.
|
|
|
|
Evidence: `TestApprovalClientIssuesExactPlatformTenantAndRejectsAliases` (exact
|
|
`tenant:platform` with `aud=approval-engine` even when the caller requests
|
|
`platform`, `tenant:coulomb` or `TENANT:PLATFORM`),
|
|
`TestUnrelatedServiceClientKeepsCoulombTenant`, and
|
|
`TestServiceRegistrationTenantsAreExactPerDecision`, which pins every reviewed
|
|
client's tenant against the real fixture so a reintroduced alias fails the build.
|
|
Full `go test ./...` and `go vet ./...` pass. Choice only — live provisioning and
|
|
the other admission gates remain with KEY-WP-0013-T02.
|
|
|
|
2026-09-09 observation, read-only. Custody has been admitted since the
|
|
2026-09-08 record and the deployment now carries the pieces T02 was waiting for:
|
|
`sso/keycape-config` registers `secrets-engine-approval` and
|
|
`approval-engine-operator` alongside the existing clients; the secrets
|
|
`keycape-secrets-engine-approval-client` and
|
|
`keycape-approval-engine-operator-client` exist in the namespace; both
|
|
`KEYCAPE_SECRETS_ENGINE_APPROVAL_CLIENT_SECRET` and
|
|
`KEYCAPE_APPROVAL_ENGINE_OPERATOR_CLIENT_SECRET` are wired into the pod; and the
|
|
image is pinned by digest to
|
|
`sha256:7ff54c54e63ee172ae9e6e7fd2da96e427352f712343d74626ee6fe0f6f82611`, the
|
|
candidate T06 published.
|
|
|
|
What that does and does not establish. The live issuer passes every conformance
|
|
check that needs no credential — it advertises itself, offers the profile
|
|
authorization surface, publishes usable RS256 keys and advertises no excluded
|
|
grant (`src/tests/conformance`, run against `https://kc.coulomb.social`). It does
|
|
**not** establish the claim contract: subject, tenant, roles, scopes, lifetime
|
|
and excess-scope denial per client are exactly what `keycape verify-client`
|
|
exists to prove, and that needs the client secret in the environment.
|
|
|
|
Deliberately not done here. Reading either secret to run the verifier is
|
|
client-side retrieval, which the provisioning packet records as **not admitted**
|
|
(`custody_return.scope`: verifier-side copies only). So the remaining proof is an
|
|
attended operator action, not something to take by reading the secret. T02 stays
|
|
`wait` on that run, not on custody.
|
|
|
|
**Correction, same day.** The refusal above was right; the conclusion drawn from
|
|
it was not. The attended run had already happened, on 2026-09-09T00:10Z, and its
|
|
receipt is committed in this repository at
|
|
`docs/evidence/2026-09-09-keycape-verifier-admission.json`. Per client — both
|
|
`secrets-engine-approval` and `approval-engine-operator` —
|
|
`live_jwks_signature_verified: true`, `exact_claims_verified: true`,
|
|
`excess_scope_denied: true`, `wrong_secret_denied: true`,
|
|
`lifetime_seconds: 900`, `maximum_future_iat_seconds: 30`,
|
|
`pinned_artifact_verifier_passed: true`, `verifier_location: "attended owner
|
|
process"`. `credential_values_emitted: false` and
|
|
`client_side_read_admitted: false` throughout, so it was run the admitted way.
|
|
The claim contract is established and T02's `status: done` is correct; it does
|
|
not revert to `wait`.
|
|
|
|
The reasoning error is worth naming because it is the third of its kind here in
|
|
two days: "not admitted **for me**" was read as "not done **by anyone**", without
|
|
checking `docs/evidence/`. The preceding paragraph's read-only observations are
|
|
sound and its restraint about not reading the secret is exactly right — only the
|
|
final sentence was wrong, and it is retained above rather than deleted so the
|
|
correction has something to point at.
|
|
|
|
What the receipt itself declines to claim, which is the honest residual and
|
|
matches this task's earlier record: `real_predecessor_rotation_tested: false`,
|
|
`observed_wall_clock_expiry: false`, and under `limits`,
|
|
`actual_predecessor_rotation_observed: false` and
|
|
`wall_clock_jwt_expiry_observed: false`. Natural expiry and a real predecessor
|
|
rotation were not exercised by initial provisioning and are not claimed by
|
|
anyone. Predecessor rejection is implemented and unit-tested in
|
|
`keycape verify-client`; it has not been run against a real rotation, which is
|
|
KEY-WP-0014-T04's territory and still has no admitted execution authority.
|
|
|
|
**Independently confirmed by the custody owner, 2026-09-09** (railiance-platform
|
|
`ae6ff4bc`, `570e6746`). They reached the same reading from their own side and
|
|
add one datum this record did not carry: `human_client_consume_denied: true` for
|
|
both clients, alongside the signature, exact-claim, lifetime, excess-scope and
|
|
wrong-secret results. They confirm the verifier ran twice — once during
|
|
activation and again after rollout — at deployment generation 38, single ready
|
|
replica, signing key unchanged, no credential values emitted, and state that T02
|
|
can be closed against that receipt rather than against a run KeyCape cannot make.
|
|
|
|
They also correct one thing in our framing, and they are right: the packet says
|
|
*client-side retrieval* is unadmitted, which remains true, but the attended
|
|
operator path is not a client-side read and never required one. The two are
|
|
distinct and this task had been treating them as the same constraint.
|
|
|
|
**Do not cite this receipt as rotation evidence.** Both owners now state the same
|
|
two gaps independently: `real_predecessor_rotation_tested: false` and
|
|
`observed_wall_clock_expiry: false`. Neither is a T02 acceptance criterion, and
|
|
neither is closed by anything recorded here — see KEY-WP-0014-T04.
|
|
|
|
**Client-side retrieval is prepared but still unadmitted.** CCR-2026-0019
|
|
(secrets-engine reader) and CCR-2026-0020 (approval-engine-operator reader) are
|
|
`in_flight` under RPF-WP-0035-T06. 0020 has no named presenting actor in any
|
|
owner source, and railiance-platform left it undetermined rather than guessing —
|
|
correctly. KeyCape's view, asked for as the issuer of the identity and recorded
|
|
in `docs/approval-engine-provisioning-request.yaml`, is that the registration as
|
|
issued carries both `approval:create` and `approval:approve`, so a single
|
|
presenter can create and then approve the same entry. That is a
|
|
separation-of-duties question about who may hold the credential, not a defect in
|
|
the token: KeyCape issues the grants approval-engine requested. The decision is
|
|
approval-engine's and the doctrine question is gate-house's.
|
|
|
|
## Assign and register the human approver browser client
|
|
|
|
```task
|
|
id: KEY-WP-0013-T05
|
|
status: wait
|
|
priority: high
|
|
assignee: the-custodian
|
|
blocking_reason: "Client ID and deployed callback pending INFD-WP-0001-T07 from informed-decision. The tenant blocker is resolved in code."
|
|
state_hub_task_id: "9a782909-91db-59fa-aae7-83766f4fbb0d"
|
|
```
|
|
|
|
Own the residual human registration separately from service-client T02. Reuse
|
|
an existing accepted UI if available; do not invent a callback on approval-engine.
|
|
Require public S256 PKCE, exact redirect, MFA, approval:approve without consume,
|
|
and a real human access-token proof. No service credential substitutes for this.
|
|
HFACT-WP-0001-T03 consumes the acceptance where a human approval is required.
|
|
|
|
2026-09-09. The owner exists: `informed-decision` (hub repo cf4c7da8) claims the
|
|
approver surface and owns the two strings. approval-engine was right that a
|
|
bearer-only resource server never owned them. Both wrote in asking to be told
|
|
early about problems with the registration shape rather than at handover, so
|
|
their requested shape was checked against the source rather than agreed on
|
|
paper. Three results.
|
|
|
|
**Scope gap — accepted, theirs was right.** `[openid, approval:approve]` cannot
|
|
render a decision: `GET /v1/approvals/{id}` and `/claim` both need
|
|
`approval:read`, so the surface could submit an entry it was never able to
|
|
display. Published `[openid, approval:read, approval:approve]` in
|
|
`docs/approval-engine-auth-contract.md` and the provisioning packet. Reading
|
|
through a service identity was the alternative and is worse: it weakens the
|
|
evidence-of-what-this-person-saw claim the component exists to make.
|
|
`approval:consume` stays excluded.
|
|
|
|
**Assurance shape — published, and a defect fixed.** Both asked for a documented
|
|
shape; KeyCape already emitted one, so it is written down rather than
|
|
renegotiated: `level` (aal1|aal2, ~acr), `methods` (~amr), `mfa`, `source`, `at`.
|
|
Writing it down surfaced that `at` was the token **mint** time, not the
|
|
authentication time. Those differ by hours whenever a browser session is reused,
|
|
and approval-engine persists this object verbatim as the only downstream record
|
|
that MFA happened — so a stored approval could have evidenced MFA at a moment the
|
|
person proved nothing. `PKCESession.AuthTime` now carries the original login
|
|
instant through session reuse, with mint time as the fallback for sessions
|
|
predating the field. Covered by `src/internal/server/oidc/assurance_time_test.go`.
|
|
|
|
**Blocker found before anyone built on it — a human token cannot carry
|
|
`tenant:platform`.** `effectiveTenant` resolves the human tenant from the
|
|
directory user, not the client registration, and no adapter populates
|
|
`domain.User.Tenant`, so every human token defaults to `tenant:coulomb`. The
|
|
per-client `tenant` field that carries `tenant:platform` on the two service
|
|
clients is read only on the `client_credentials` path. approval-engine compares
|
|
tenant by exact string equality and pins near-miss spellings as refused, so an
|
|
approver token issued today would be rejected — and it would present as a failed
|
|
approval, not as a registration defect.
|
|
|
|
Two resolutions, sent to both owners (`67132ddc`, `a6a070a1`, pointer
|
|
`42d435e8`) rather than chosen here, because the choice decides what a human's
|
|
tenant *means*: (1) directory-sourced, approvers carry `tenant:platform` on their
|
|
LLDAP record — keeps tenant a property of the person, needs a directory attribute
|
|
and an owner, and changes those users everywhere; (2) registration-bound and
|
|
fail-closed, symmetric with the service clients and with decision
|
|
`5ed3fb35-eca9-413a-82b9-95171ba85bf6` — defensible only because registrations
|
|
are static and deployment-owned, since dynamic registration is excluded by
|
|
design. KeyCape leans to (2) and deliberately implemented neither: (1) is a
|
|
directory-ownership question and (2) writes a cross-tenant capability into the
|
|
issuer.
|
|
|
|
**Tenant blocker resolved in code, 2026-09-09.** On review the choice did not
|
|
have to be made to unblock: the two resolutions differ in where a human's tenant
|
|
*comes from*, and an implementation exists that is correct under either. A client
|
|
registration may now declare a `tenant`, and `humanTenant()` resolves it by four
|
|
rules — no declaration keeps the directory answer unchanged; a declared zone
|
|
applies when the directory has placed the user nowhere; agreement passes; and a
|
|
declared zone that **conflicts** with a directory assignment refuses issuance
|
|
(`403`, `error_type: tenant_binding`) rather than relabelling the user.
|
|
|
|
That refusal is the whole design. A registration can bind a zone for unplaced
|
|
users and can never move a placed one, so option (2) is available now without
|
|
writing a general cross-tenant override into the issuer, and if the owners prefer
|
|
option (1) the same code stops supplying the zone and starts enforcing agreement
|
|
with it — no second migration and no window where a stale registration silently
|
|
wins. It is safe only because registrations are static and deployment-owned;
|
|
`docs/tenant-claim-contract.md` records that this rule must be revisited if
|
|
dynamic client registration is ever admitted.
|
|
|
|
Evidence: `src/internal/server/oidc/human_tenant_test.go`, four cases including
|
|
the relabel refusal. Verified with teeth — neutering the conflict check makes
|
|
`TestHumanTenantRefusesToRelabelAPlacedUser` fail rather than pass silently.
|
|
`docs/tenant-claim-contract.md` carries the resolution table,
|
|
`docs/approval-engine-auth-contract.md` states the requirement on the approver
|
|
client, and the provisioning packet now declares `tenant: tenant:platform`.
|
|
|
|
Task stays `wait` on one thing only: `client_id` and callback URI from
|
|
INFD-WP-0001-T07, once informed-decision has a deployed origin. The owners were
|
|
asked which resolution they prefer and have not answered; that answer is no
|
|
longer blocking, and this implementation is compatible with either.
|
|
|
|
## Make negative rollout evidence discriminate actual issuer refusal
|
|
|
|
```task
|
|
id: KEY-WP-0013-T06
|
|
status: done
|
|
priority: high
|
|
assignee: the-custodian
|
|
state_hub_task_id: "65132e1d-10db-5915-a664-83beaf30365a"
|
|
```
|
|
|
|
Critical-path review found `mustFail` accepted any Exchange error: timeout,
|
|
HTTP 5xx, invalid token or JWKS failure could falsely prove excess-scope denial
|
|
or predecessor rejection after a successful positive exchange. Preserve a bounded
|
|
typed provider refusal, require the exact token endpoint/status/error/feature,
|
|
and prove the checker rejects unrelated failures without exposing provider bodies.
|
|
Publish the verified image candidate and return its immutable digest to T02.
|
|
|
|
Platform's 2026-09-08 return is RPF-WP-0035-T05 and proposed CCR-2026-0017/0018.
|
|
The custody field is `CLIENT_SECRET`; Kubernetes key `client-secret` and both env
|
|
names remain as requested. These records cover verifier-side delivery only.
|
|
Owner approval, client-side retrieval and the actual upstream ID-token issuer
|
|
proof remain distinct gates. Public discovery currently advertises
|
|
`https://auth.coulomb.social`; that alone is not the signed-token observation.
|
|
|
|
T06 completion: full Go suite and vet passed; published code `dcebd46` and pulled the image by digest `sha256:7ff54c54e63ee172ae9e6e7fd2da96e427352f712343d74626ee6fe0f6f82611`. Container `keycape verify-client --help` confirms the command is present. `docs/approval-clients-rollout.md` and its proposed deployment patch record configuration/issuer, first-provision, single-instance rollout, acceptance and rollback. This closes verifier preparation only; T02 and T05 retain the explicit live dependencies.
|
|
|
|
|
|
## Prepare a bounded upstream issuer observation before client rollout
|
|
|
|
```task
|
|
id: KEY-WP-0013-T07
|
|
status: done
|
|
priority: high
|
|
assignee: the-custodian
|
|
state_hub_task_id: "62bc6d8e-dbb2-57b6-a30b-472ffd06b497"
|
|
```
|
|
|
|
2026-09-08: T02's actual upstream signed-token issuer gate had no executable
|
|
observation path. A downstream OpenBao/KeyCape login cannot return the upstream
|
|
Authelia token; it remains inside the adapter. Prepare `probe-upstream-issuer`
|
|
and an exact-state temporary route so one attended flow can be verified before
|
|
changing the normal KeyCape Deployment or its configuration.
|
|
|
|
Acceptance: signed issuer/audience/time/nonce proof without token or user-claim
|
|
output; browser/state binding and one-shot refusal; finite issuer allowlist;
|
|
ten-minute bound; no downstream credential; current Traefik route isolation;
|
|
immutable image; reviewed temporary Job/config projection, network policies and
|
|
owner-reference cleanup. Normal callbacks and the production issuer stay on
|
|
the existing Deployment. The probe mounts only `config.yaml`, not `key.pem`,
|
|
and has no Kubernetes API token. That file contains existing credential data;
|
|
its use by the temporary diagnostic needs deployment-owner admission.
|
|
|
|
Source tests, image and deployment packet close this preparation task. The
|
|
attended live receipt and issuer pin remain in T02, alongside the named CCR
|
|
reviews and custody/rollout acceptance. Preparing the diagnostic does not
|
|
approve CCR-2026-0017/0018 or close any live factory gate.
|
|
|
|
T07 preparation complete: source `6f33abd`, immutable image
|
|
`sha256:0c85ed377cae7ae6ca5b5c56b1a52930e706b3cb78009747e42f551e869a22e4`.
|
|
Complete Go race suite and vet passed; five renderer/collector checks, seven
|
|
real Traefik 3.6.7 route cases and Railiance server dry-run passed. The packaged
|
|
command ran as UID 65534 with no network and a read-only filesystem and produced
|
|
the expected bounded idle-timeout receipt. See `docs/upstream-issuer-proof.md`
|
|
and `docs/evidence/upstream-issuer-probe.json`. T02 retains the live proof and
|
|
configuration-owner admission; no production resource or custody value changed.
|