architecture.md gains a Managed credential custody section: the single
refresh owner, the store-port lock, per-process single flight, typed
revoked and refresh-failed answers, the provider descriptor token fields
and the one token request, the mode x role gates, custody-only
completions, verification layer and follow-ups by id. The spotify finding
now matches the removed spiverse profile; JOYSTICK joins the provider env
lists. implementation-plan and service-contract lose two stale lines.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Soul F4: the architecture configuration list, the Spotiverse restore steps and
.env.example still taught plaintext secret variables. The architecture list now
names only what src/config.ts reads, plain settings apart from secret files,
and drops the names nothing reads (GC_ACCESS_SESSION_SECRET, GC_ACCESS_ENABLED,
GC_ACCESS_INTERNAL_URL, the Discord bot token, the Patreon campaign id and
two TTLs). .env.example comments out the _FILE lines, because an empty
NAME_FILE= is refused at startup.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GC_ACCESS_SIGNING_PRIVATE_KEY_PEM put the signing key's bytes into the
process environment, and so into any binding, unit or plan that carries
environment. The recipe never declared it and GC_ACCESS_SIGNING_PRIVATE_KEY_PATH
already covers the key as a file, which a binding binds through
[workload.secret_files]. The signing key is now always a file.
Idunn secret-files cut, Cut 1 (delete first).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
R1: no GC service calls this route. Its shape (caller-supplied facts handed
straight to issueAccessClaim) is a second entitlement authority even when
gated, so authenticating it would preserve the oracle behind a password.
Deletion-only commit: route, discovery advertisement, mapper, contract type,
positive test, and doc mentions in architecture.md/service-contract.md/
fresh-workspace-handoff.md.
Deployment topology already says same-host apps reach Heimdall over
localhost or private routing and verify claims locally. The vhost never
implemented it: heimdall.gamecult.org.conf is one
`location / { proxy_pass http://heimdall_app; }`, so every route Fastify
registers is on the open web. The boundary existed in this document and
nowhere in the running system.
Proposed: public is the browser-driven OAuth pair plus JWKS; discovery,
/v1/apps, claim issuance and /healthz go internal. The private command
plane is unaffected -- it is a CultNet operation server and never touches
nginx.
JWKS stays public deliberately rather than by omission. No provider
fetches it (every adapter in src/oauth.ts uses client_secret, not
private_key_jwt) and every relying party today is mesh-reachable, so
nothing external needs it. But publishing verification keys is harmless,
and hiding it fails asymmetrically: the first externally-hosted relying
party gets integrated with a standard OIDC library, and a jwks_uri that
resolves only on the mesh fails confusingly at integration time instead of
obviously at design time.
Discovery and /v1/apps move together, since trimming discovery alone
leaves /v1/apps serving the same catalog. That catalog is a product
inventory: per app it emits identityProviders, entitlementSources,
managedConnectionProviders and capabilities, and with the command plane's
discord_role_access and patreon_membership_access record kinds the
monetization model is legible to anyone who fetches it.
Separately recorded: the spotify provider outlived its consumer.
Spotiverse was lost with the E: drive, it is the only profile naming
spotify, and GC_ACCESS_PROVIDER_SPOTIFY_CLIENT_ID/_SECRET are still
configured in /srv/heimdall/env/service.env while /v1/oauth/spotify/start
stays publicly reachable. That path would run a real OAuth handshake with
a real secret and take custody of a user's refresh token for a consumer
that cannot use it. Not a compromise; a live credential and a
token-custody path kept alive for nothing. Retiring it is its own change
and should not ride along with the boundary work.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>