Commit graph Heimdall/docs/service-contract.md
Author SHA1 Message Date
Meta Vens
778eabaedc Docs: map managed credential custody, the callback mint and the open follow-ups
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>
2026-10-19 15:49:34 +02:00
Meta Vens
a32be41878 Token answers: a default lifetime replaces an unknown expiry; a malformed refresh_token is typed
A token answer without a usable expires_in (absent, non-numeric, a string that
is not a positive integer) now takes the descriptor's
defaultAccessTokenLifetimeSeconds (3600 for every provider), so no issued
token has a NULL expiry that dueForRefresh would never refresh. A 2xx whose
refresh_token is present but not a string is the typed
provider_body_not_token_set (502 managed_credential_refresh_failed, tokens
kept); an empty string stays a missing token, so a rotating provider still
fails closed. The expiry rule is now pinned on memory and both live Postgres
rigs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 13:24:21 +02:00
Meta Vens
b15bf33b21 Refresh: one attempt per identity per process; waiters share its outcome
Callers that arrive while a refresh of one linked identity is in flight in
this process join it and receive its success or failure, instead of queuing
on the row lock and presenting the same refresh token again behind a failed
holder. The entry lives as long as the attempt, so a later request retries.
The store lock stays the cross-process guard; the residual risk (one
presentation per process per burst) is written into the service contract.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 12:34:39 +02:00
Meta Vens
d989860163 Refresh classification: revoke only on a 400 invalid_grant; any other answer keeps the tokens
A grant verdict is read from a 400 body by exact code, never from a 401, a
5xx or a 2xx. A 2xx that is not an object with a non-empty string
access_token and no error is refused as provider_body_not_token_set, so a
malformed success keeps the tokens and surfaces as the typed 502 for every
provider, rotating or not. A refresh answer without expires_in stores the
expiry as unknown rather than keeping the already-due one; an unknown expiry
is never due, so the next resolve does not refresh again.

Pins Soul's three surviving mutants: substring code match, the dropped
expiry rule, and the snapshot refresh token on a non-rotating provider.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 12:34:17 +02:00
Meta Vens
3eaa9ef265 Declare Joystick in the Idunn recipe and document the locked refresh
Recipe optional_environment gains the Joystick client id and secret file;
service-contract documents the row lock, rotation fail-closed and the 410/502
answers; the StreamPixels profile names Heimdall as sole holder of provider
refresh tokens (F5).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 11:15:42 +02:00
Meta Vens
83d2f48256 Docs: secrets are _FILE names; drop the unread Spotiverse secret
Soul S4: the Bifrost handoff notes, the Bifrost profile and the service
contract still named plaintext secret variables; each now names the _FILE
credential path. .env.example and the Spotiverse profile taught
GC_ACCESS_APP_SPOTIVERSE_SHARED_SECRET_FILE, which nothing reads: spotiverse
is not in appSlugs. Both lines are removed rather than adding the slug.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 14:40:21 +02:00
Meta Vens
d2d0778847 Delete the inline signing-key PEM input
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>
2026-09-30 12:26:56 +02:00
Meta Vens
827cdee761 Tests/docs: promote Soul's two it.fails, update the tripwire and fixtures
Both caller-identity-soul.test.ts holes are now closed and promoted
from it.fails to a plain it (both green): the caller-chosen
completionCode is never redeemable, and an arbitrary returnTo origin
is refused. The AppCaller-literal tripwire's expected forger list moves
from ["private-command-plane.ts"] to [] now that the private plane
constructs its caller through callerFromOpenedEnvelope instead of the
literal; the "refuses to mint on refresh" test's wrongApp caller is
built the same way, since a raw { appSlug } literal no longer
satisfies the now-branded AppCaller type.

access-plugin.test.ts's ghostlight completion-code assertion is
updated: `completion.code` is minted independently of the attempt
handle now, so the test documents that the code differs from the
handle instead of asserting they match.

app-registry.test.ts drops validateAppRegistration coverage (deleted)
and seeds registered-app fixtures through InMemoryStore.seedRegisteredApp
instead of the deleted store.registerApp.

docs/service-contract.md documents the new 400 return_to_not_allowed
failure mode. state/evidence.jsonl records this pass's decisions.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 14:23:04 +02:00
Meta Vens
a7d1d2abbf Correct service-contract.md: entitlement policy trust is uniform, not per-slug
The doc still described the pre-cut world (Repixelizer named as the one app
allowed to send policy). Documents the actual failure mode: 401
app_auth_required from resolveAppCaller for any app, any handoff kind.
2026-09-08 13:53:09 +02:00
Meta Vens
326f613ffe Delete the global GC_ACCESS_APP_SHARED_SECRET fallback (R6, fork 2)
Per-app identity with a global fallback made repixelizer and streampixels one
identity in practice. Each app now needs its own
GC_ACCESS_APP_<SLUG>_SHARED_SECRET; operator mints distinct values on
Yggdrasil in the same deploy window. Doc note in service-contract.md corrected
to stop advertising the fallback.
2026-09-08 13:53:09 +02:00
Meta Vens
a999d48b6d Delete claims/issue: zero callers, arbitrary-facts oracle bypassing entitlement evaluation
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.
2026-09-08 13:53:09 +02:00
Meta Vens
9065dfa43b Bind Ghostlight logout to exact session custody 2026-08-22 01:53:21 +02:00
Meta Vens
19e5109cf4 Add Eve access plugin and private auth plane 2026-08-22 00:44:57 +02:00
Meta Vens
5a06957a0d Add read-only Heimdall Eve witness advertisement 2026-06-03 03:21:31 +01:00
Meta Vens
e2a2f20693 Add Spotiverse Spotify credential custody 2026-06-02 16:25:38 +01:00
Meta Vens
ee8c012c85 Refresh Repixelizer app claims through Heimdall 2026-05-20 13:43:53 +01:00
Meta Vens
f3e1f9c292 Refresh managed provider credentials on resolve 2026-04-29 17:24:44 +01:00
Meta Vens
f22db66d64 Add StreamPixels managed auth seams 2026-04-29 15:00:55 +01:00
Meta Vens
5ef94bb725 Refresh Patreon launch state 2026-04-29 14:39:11 +01:00
Meta Vens
5043a5fc9a Accept caller-owned entitlement policy 2026-04-29 13:22:01 +01:00
Meta Vens
c8658ddd91 Allow Inner Sanctum Repixelizer access 2026-04-29 13:07:44 +01:00
Meta Vens
ccc713b911 Clarify Repixelizer Discord role policy 2026-04-29 11:54:01 +01:00
Meta Vens
0332e0c966 Refresh Heimdall launch state 2026-04-29 11:51:29 +01:00
Meta Vens
f8eec3558f Prefer direct backend auth handoff 2026-04-26 17:00:57 +01:00
Meta Vens
6795439ac7 Harden Heimdall custody and verifier seams 2026-04-26 16:23:29 +01:00
Meta Vens
8f173e937a Land one-time browser auth completion flow 2026-04-26 15:23:04 +01:00
Meta Vens
12ce1f45ae Normalize Heimdall claim fact vocabulary 2026-04-26 14:58:58 +01:00
Meta Vens
cb0041343a Land first stateful Discord auth slice 2026-04-26 12:50:07 +01:00
Meta Vens
58a12a2835 Land Heimdall service skeleton 2026-04-26 12:25:11 +01:00