Add read-only Heimdall Eve witness advertisement
This commit is contained in:
parent
76fd3153a7
commit
5a06957a0d
8 changed files with 391 additions and 3 deletions
|
|
@ -23,7 +23,7 @@ Landed right now:
|
|||
- file-backed signing-key loading/bootstrap so restart does not vaporize the
|
||||
service identity when you configure a real key path
|
||||
- signed OAuth start-state scaffolding for Discord, Patreon, GitHub, Twitch,
|
||||
and YouTube
|
||||
YouTube, and Spotify
|
||||
- durable auth/control-plane storage with in-memory and Postgres backends
|
||||
- account, linked-identity, session, grant, entitlement snapshot, and audit
|
||||
persistence
|
||||
|
|
@ -33,15 +33,18 @@ Landed right now:
|
|||
- AES-256-GCM sealing for managed provider tokens at rest
|
||||
- a one-time browser completion exchange kept as fallback for opener
|
||||
`postMessage` handoff
|
||||
- app profile surfaces for Repixelizer and StreamPixels
|
||||
- app profile surfaces for Repixelizer, StreamPixels, and Spotiverse
|
||||
- signed claim issuance for app-local verification experiments
|
||||
- a local verifier helper in `src/verifier.ts` so app backends can validate
|
||||
Heimdall JWTs without phoning home on every guarded request
|
||||
- a read-only Odin/Eve provider advertisement export that names redacted
|
||||
CultCache witness document shapes and paths without touching live secrets
|
||||
|
||||
Still not landed:
|
||||
|
||||
- end-to-end app integrations in consumer repos
|
||||
- refresh/revocation flows and admin surfaces
|
||||
- runtime CultCache `.cc` witness writing or CultMesh publication
|
||||
|
||||
Canonical docs:
|
||||
|
||||
|
|
@ -51,6 +54,7 @@ Canonical docs:
|
|||
- `docs/verse-service-contract.md`
|
||||
- `docs/app-profiles/repixelizer.md`
|
||||
- `docs/app-profiles/streampixels.md`
|
||||
- `docs/app-profiles/spotiverse.md`
|
||||
- `state/map.yaml`
|
||||
- `notes/fresh-workspace-handoff.md`
|
||||
|
||||
|
|
@ -61,6 +65,7 @@ pnpm install
|
|||
pnpm dev
|
||||
pnpm test
|
||||
pnpm build
|
||||
pnpm export:provider-advertisement
|
||||
```
|
||||
|
||||
## Working Loop
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ Current app slugs:
|
|||
|
||||
- `repixelizer`
|
||||
- `streampixels`
|
||||
- `spotiverse`
|
||||
|
||||
Profile fields:
|
||||
|
||||
|
|
@ -597,6 +598,42 @@ The following are not landed yet:
|
|||
- revocation flows
|
||||
- admin/grant management surfaces
|
||||
- GitHub callback runtime
|
||||
- runtime CultCache `.cc` witness writing or CultMesh publication
|
||||
|
||||
## Odin/Eve provider advertisement
|
||||
|
||||
Purpose:
|
||||
|
||||
- advertise Heimdall as a read-only Eve provider surface for Odin discovery
|
||||
- name the redacted CultCache witness document schemas and future `.cc` paths
|
||||
- avoid touching provider secrets, token custody, store migrations, or runtime
|
||||
mutation paths
|
||||
|
||||
Command:
|
||||
|
||||
```powershell
|
||||
pnpm export:provider-advertisement
|
||||
```
|
||||
|
||||
Current output shape:
|
||||
|
||||
- `schemaVersion`: `gamecult.eve.provider_advertisement.v1`
|
||||
- `providerId`: `heimdall`
|
||||
- `status`: `read_only_witness_planned`
|
||||
- provider catalog entries for Discord, Patreon, GitHub, Twitch, YouTube, and
|
||||
Spotify
|
||||
- app profile projections for Repixelizer, StreamPixels, and Spotiverse
|
||||
- redacted witness descriptors for accounts, linked identities, grants,
|
||||
sessions, entitlement snapshots, auth completions, audit events, app profiles,
|
||||
and managed credential projections
|
||||
|
||||
Important behavior:
|
||||
|
||||
- this is a projection/export command, not a writer
|
||||
- advertised witness paths are future CultCache targets, not proof that a live
|
||||
`.cc` exporter is already running
|
||||
- exported descriptors forbid raw provider tokens, encrypted token blobs,
|
||||
completion codes, callback secrets, and app-domain state
|
||||
|
||||
## Next implementation move
|
||||
|
||||
|
|
|
|||
|
|
@ -99,3 +99,44 @@ refreshing an entitlement snapshot. Heimdall accepts or denies those actions.
|
|||
The invariant: Heimdall owns shared auth truth, not app-domain truth. CultCache
|
||||
and CultMesh make Heimdall inspectable; they do not leak secrets or move product
|
||||
state into the auth authority.
|
||||
|
||||
## Read-Only Witness First Cut
|
||||
|
||||
The current repo cut defines the read-only witness/ad surface without wiring a
|
||||
runtime writer or migrating live state.
|
||||
|
||||
Authority map:
|
||||
|
||||
- Owner: Heimdall remains the only owner of auth/control-plane mutation.
|
||||
- Inputs: static provider catalog, static app profiles, and the redacted witness
|
||||
descriptor table in `src/verse-witness.ts`.
|
||||
- Outputs: `gamecult.eve.provider_advertisement.v1` JSON printed by
|
||||
`pnpm export:provider-advertisement`.
|
||||
- Derived state: advertised CultCache paths and schema IDs are witness/export
|
||||
targets. They do not prove that a live `.cc` writer exists yet.
|
||||
- Forbidden writers: Odin probes, Eve renderers, dashboards, and this export
|
||||
command cannot mutate accounts, linked identities, grants, sessions, tokens,
|
||||
completions, entitlements, audit events, or app profiles.
|
||||
- Shared paths: future CultCache export, CultMesh publication, and Eve lowering
|
||||
should reuse the same document IDs and redaction doctrine.
|
||||
- Deletion line: if a future writer needs app-domain fields or provider tokens
|
||||
to make the surface useful, the design is wrong; keep those fields out or move
|
||||
the concern back to the owning app/service.
|
||||
|
||||
The advertised witness descriptors currently name these redacted `.cc` targets:
|
||||
|
||||
- `heimdall.account.v0` at `cultcache/heimdall/accounts/{accountId}.cc`
|
||||
- `heimdall.linked_identity.v0` at
|
||||
`cultcache/heimdall/linked-identities/{provider}/{providerUserId}.cc`
|
||||
- `heimdall.capability_grant.v0` at
|
||||
`cultcache/heimdall/grants/{grantId}.cc`
|
||||
- `heimdall.session.v0` at `cultcache/heimdall/sessions/{sessionId}.cc`
|
||||
- `heimdall.entitlement_snapshot.v0` at
|
||||
`cultcache/heimdall/entitlements/{accountId}/{provider}/{scope}.cc`
|
||||
- `heimdall.auth_completion.v0` at
|
||||
`cultcache/heimdall/auth-completions/{completionCodeHash}.cc`
|
||||
- `heimdall.audit_event.v0` at `cultcache/heimdall/audit/{eventId}.cc`
|
||||
- `heimdall.app_profile.v0` at
|
||||
`cultcache/heimdall/app-profiles/{appSlug}.cc`
|
||||
- `heimdall.managed_credential_projection.v0` at
|
||||
`cultcache/heimdall/managed-credentials/{appSlug}/{accountId}/{provider}.cc`
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@
|
|||
"dev": "tsx watch src/index.ts",
|
||||
"start": "node dist/index.js",
|
||||
"test": "vitest run --config vitest.config.ts",
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit",
|
||||
"export:provider-advertisement": "tsx src/export-provider-advertisement.ts --pretty"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fastify/cors": "^11.2.0",
|
||||
|
|
|
|||
23
src/export-provider-advertisement.ts
Normal file
23
src/export-provider-advertisement.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { buildHeimdallProviderAdvertisement } from "./verse-witness.js";
|
||||
|
||||
function readUpdatedAt(args: string[]): string {
|
||||
const index = args.indexOf("--updated-at");
|
||||
const value = index >= 0 ? args[index + 1] : undefined;
|
||||
|
||||
if (!value) {
|
||||
return new Date().toISOString();
|
||||
}
|
||||
|
||||
const parsed = new Date(value);
|
||||
if (Number.isNaN(parsed.getTime())) {
|
||||
throw new Error(`Invalid --updated-at value: ${value}`);
|
||||
}
|
||||
|
||||
return parsed.toISOString();
|
||||
}
|
||||
|
||||
const pretty = process.argv.includes("--pretty");
|
||||
const updatedAt = readUpdatedAt(process.argv.slice(2));
|
||||
const advertisement = buildHeimdallProviderAdvertisement({ updatedAt });
|
||||
|
||||
process.stdout.write(`${JSON.stringify(advertisement, null, pretty ? 2 : 0)}\n`);
|
||||
222
src/verse-witness.ts
Normal file
222
src/verse-witness.ts
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
import { appProfiles, type CapabilityDefinition } from "./app-profiles.js";
|
||||
import { appSlugs, providers, type AppSlug, type Provider } from "./contracts.js";
|
||||
import { providerCatalog, type ProviderRole } from "./providers.js";
|
||||
|
||||
export const heimdallWitnessSchemaIds = [
|
||||
"heimdall.account.v0",
|
||||
"heimdall.linked_identity.v0",
|
||||
"heimdall.capability_grant.v0",
|
||||
"heimdall.session.v0",
|
||||
"heimdall.entitlement_snapshot.v0",
|
||||
"heimdall.auth_completion.v0",
|
||||
"heimdall.audit_event.v0",
|
||||
"heimdall.app_profile.v0",
|
||||
"heimdall.managed_credential_projection.v0",
|
||||
] as const;
|
||||
|
||||
export type HeimdallWitnessSchemaId = (typeof heimdallWitnessSchemaIds)[number];
|
||||
|
||||
export interface HeimdallWitnessDocumentDescriptor {
|
||||
schemaId: HeimdallWitnessSchemaId;
|
||||
documentType: HeimdallWitnessSchemaId;
|
||||
cultCachePath: string;
|
||||
redaction: string;
|
||||
ownedTruth: string;
|
||||
forbiddenFields: string[];
|
||||
}
|
||||
|
||||
export const heimdallWitnessDocuments: readonly HeimdallWitnessDocumentDescriptor[] = [
|
||||
{
|
||||
schemaId: "heimdall.account.v0",
|
||||
documentType: "heimdall.account.v0",
|
||||
cultCachePath: "cultcache/heimdall/accounts/{accountId}.cc",
|
||||
redaction: "Email is optional and should be omitted unless an operator-safe identity hint is needed.",
|
||||
ownedTruth: "Local GameCult account record independent of upstream providers.",
|
||||
forbiddenFields: ["providerAccessToken", "providerRefreshToken"],
|
||||
},
|
||||
{
|
||||
schemaId: "heimdall.linked_identity.v0",
|
||||
documentType: "heimdall.linked_identity.v0",
|
||||
cultCachePath: "cultcache/heimdall/linked-identities/{provider}/{providerUserId}.cc",
|
||||
redaction: "Token ciphertext and raw provider profile stay out; export only custody status and selected identity labels.",
|
||||
ownedTruth: "External provider identity linked to exactly one Heimdall account.",
|
||||
forbiddenFields: ["accessTokenEncrypted", "refreshTokenEncrypted", "profileJson"],
|
||||
},
|
||||
{
|
||||
schemaId: "heimdall.capability_grant.v0",
|
||||
documentType: "heimdall.capability_grant.v0",
|
||||
cultCachePath: "cultcache/heimdall/grants/{grantId}.cc",
|
||||
redaction: "Operator notes must be reviewed before export and kept free of secrets or private app data.",
|
||||
ownedTruth: "Manual or system-issued shared capability grant.",
|
||||
forbiddenFields: ["providerAccessToken", "providerRefreshToken", "appDomainPayload"],
|
||||
},
|
||||
{
|
||||
schemaId: "heimdall.session.v0",
|
||||
documentType: "heimdall.session.v0",
|
||||
cultCachePath: "cultcache/heimdall/sessions/{sessionId}.cc",
|
||||
redaction: "Signed tokens are never exported; claims are reduced to audience, revision, expiry, facts, and capabilities.",
|
||||
ownedTruth: "Local app/browser session issued by Heimdall.",
|
||||
forbiddenFields: ["accessToken", "refreshToken", "claimsJson"],
|
||||
},
|
||||
{
|
||||
schemaId: "heimdall.entitlement_snapshot.v0",
|
||||
documentType: "heimdall.entitlement_snapshot.v0",
|
||||
cultCachePath: "cultcache/heimdall/entitlements/{accountId}/{provider}/{scope}.cc",
|
||||
redaction: "Raw provider responses stay out; export reason codes and compact summaries only.",
|
||||
ownedTruth: "Cached provider entitlement evaluation result.",
|
||||
forbiddenFields: ["rawProviderResponse", "providerAccessToken", "providerRefreshToken"],
|
||||
},
|
||||
{
|
||||
schemaId: "heimdall.auth_completion.v0",
|
||||
documentType: "heimdall.auth_completion.v0",
|
||||
cultCachePath: "cultcache/heimdall/auth-completions/{completionCodeHash}.cc",
|
||||
redaction: "Completion codes are hashed or omitted; payload is redacted to status, provider, app, mode, and expiry.",
|
||||
ownedTruth: "One-time OAuth browser/backend handoff completion state.",
|
||||
forbiddenFields: ["code", "accessToken", "refreshToken", "callbackSecret"],
|
||||
},
|
||||
{
|
||||
schemaId: "heimdall.audit_event.v0",
|
||||
documentType: "heimdall.audit_event.v0",
|
||||
cultCachePath: "cultcache/heimdall/audit/{eventId}.cc",
|
||||
redaction: "Audit payloads must pass token, code, secret, callback, and private-profile redaction before export.",
|
||||
ownedTruth: "Durable auth/control-plane event history.",
|
||||
forbiddenFields: ["accessToken", "refreshToken", "authorizationCode", "clientSecret"],
|
||||
},
|
||||
{
|
||||
schemaId: "heimdall.app_profile.v0",
|
||||
documentType: "heimdall.app_profile.v0",
|
||||
cultCachePath: "cultcache/heimdall/app-profiles/{appSlug}.cc",
|
||||
redaction: "Profile exports contain policy shape and capability names, not app-local state.",
|
||||
ownedTruth: "Per-app auth policy profile owned by Heimdall.",
|
||||
forbiddenFields: ["appSharedSecret", "appDomainState"],
|
||||
},
|
||||
{
|
||||
schemaId: "heimdall.managed_credential_projection.v0",
|
||||
documentType: "heimdall.managed_credential_projection.v0",
|
||||
cultCachePath: "cultcache/heimdall/managed-credentials/{appSlug}/{accountId}/{provider}.cc",
|
||||
redaction: "Expose custody status, provider subject, scopes, and expiry only; never provider tokens.",
|
||||
ownedTruth: "Read-only projection that a managed provider credential exists in Heimdall custody.",
|
||||
forbiddenFields: ["accessToken", "refreshToken", "accessTokenEncrypted", "refreshTokenEncrypted"],
|
||||
},
|
||||
] as const;
|
||||
|
||||
export interface HeimdallAdvertisedProvider {
|
||||
key: Provider;
|
||||
displayName: string;
|
||||
roles: ProviderRole[];
|
||||
defaultScopes: string[];
|
||||
authorizationEndpoint: string;
|
||||
configuredByEnv: {
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface HeimdallAdvertisedAppProfile {
|
||||
slug: AppSlug;
|
||||
displayName: string;
|
||||
profileVersion: string;
|
||||
identityProviders: Provider[];
|
||||
entitlementSources: Provider[];
|
||||
managedConnectionProviders: Provider[];
|
||||
capabilities: CapabilityDefinition[];
|
||||
}
|
||||
|
||||
export interface EveProviderAdvertisementDocument {
|
||||
schemaVersion: "gamecult.eve.provider_advertisement.v1";
|
||||
providerId: "heimdall";
|
||||
verseId: "gamecult";
|
||||
title: "Heimdall";
|
||||
description: string;
|
||||
version: string;
|
||||
status: "read_only_witness_planned";
|
||||
updatedAt: string;
|
||||
provider: {
|
||||
id: "heimdall";
|
||||
title: "Heimdall";
|
||||
description: string;
|
||||
capabilities: string[];
|
||||
usesCultMesh: boolean;
|
||||
transport: string;
|
||||
};
|
||||
controlSurface: {
|
||||
primary: string;
|
||||
controls: {
|
||||
mode: "read-only";
|
||||
writes: false;
|
||||
reason: string;
|
||||
};
|
||||
};
|
||||
endpoints: string[];
|
||||
providers: HeimdallAdvertisedProvider[];
|
||||
appProfiles: HeimdallAdvertisedAppProfile[];
|
||||
documents: HeimdallWitnessDocumentDescriptor[];
|
||||
}
|
||||
|
||||
export function buildHeimdallProviderAdvertisement(options: { updatedAt: string }): EveProviderAdvertisementDocument {
|
||||
return {
|
||||
schemaVersion: "gamecult.eve.provider_advertisement.v1",
|
||||
providerId: "heimdall",
|
||||
verseId: "gamecult",
|
||||
title: "Heimdall",
|
||||
description: "Shared GameCult auth/control-plane authority with redacted CultCache witness projections.",
|
||||
version: "read-only-witness-v0",
|
||||
status: "read_only_witness_planned",
|
||||
updatedAt: options.updatedAt,
|
||||
provider: {
|
||||
id: "heimdall",
|
||||
title: "Heimdall",
|
||||
description: "Publishes provider/app auth surfaces and planned redacted witness document paths for Odin and Eve.",
|
||||
capabilities: [
|
||||
"shared-auth",
|
||||
"provider-oauth",
|
||||
"linked-identities",
|
||||
"capability-grants",
|
||||
"session-claims",
|
||||
"managed-credential-custody",
|
||||
"redacted-cultcache-witness",
|
||||
],
|
||||
usesCultMesh: true,
|
||||
transport: "CultMesh provider advertisement; first cut is a read-only fixture/export.",
|
||||
},
|
||||
controlSurface: {
|
||||
primary: "cultmesh://gamecult/heimdall/provider-advertisement",
|
||||
controls: {
|
||||
mode: "read-only",
|
||||
writes: false,
|
||||
reason: "This export advertises witness shapes only. Runtime auth mutation remains behind Heimdall APIs.",
|
||||
},
|
||||
},
|
||||
endpoints: [
|
||||
"cultmesh://gamecult/heimdall/provider-advertisement",
|
||||
"cultmesh://gamecult/heimdall/witness",
|
||||
],
|
||||
providers: providers.map((provider) => {
|
||||
const descriptor = providerCatalog[provider];
|
||||
return {
|
||||
key: descriptor.key,
|
||||
displayName: descriptor.displayName,
|
||||
roles: descriptor.roles,
|
||||
defaultScopes: descriptor.defaultScopes,
|
||||
authorizationEndpoint: descriptor.authorizationEndpoint,
|
||||
configuredByEnv: {
|
||||
clientId: descriptor.clientIdEnv,
|
||||
clientSecret: descriptor.clientSecretEnv,
|
||||
},
|
||||
};
|
||||
}),
|
||||
appProfiles: appSlugs.map((appSlug) => {
|
||||
const profile = appProfiles[appSlug];
|
||||
return {
|
||||
slug: profile.slug,
|
||||
displayName: profile.displayName,
|
||||
profileVersion: profile.profileVersion,
|
||||
identityProviders: profile.identityProviders,
|
||||
entitlementSources: profile.entitlementSources,
|
||||
managedConnectionProviders: profile.managedConnectionProviders,
|
||||
capabilities: profile.capabilities,
|
||||
};
|
||||
}),
|
||||
documents: [...heimdallWitnessDocuments],
|
||||
};
|
||||
}
|
||||
|
|
@ -32,6 +32,7 @@ invariants:
|
|||
- Host apps verify signed claims locally and keep resource ownership checks local.
|
||||
- StreamPixels can reuse shared auth authority without centralizing streamer-audience data.
|
||||
- Spotiverse consumes Spotify as a Heimdall-managed credential; Spotiverse does not own Spotify refresh-token custody.
|
||||
- Heimdall's first Odin/Eve provider advertisement is read-only: it names redacted CultCache witness document shapes and paths, but does not write `.cc` state or mutate auth truth.
|
||||
- Persistent state cruft is the same failure class as architecture cruft.
|
||||
|
||||
data_flow:
|
||||
|
|
@ -93,6 +94,7 @@ landed_baseline:
|
|||
- Heimdall now has generic Twitch and YouTube OAuth callback runtimes plus an app-authenticated managed-credential resolve endpoint for app-owned connector runtimes.
|
||||
- StreamPixels now starts viewer and creator Twitch/YouTube OAuth through Heimdall, redeems Heimdall completion codes server-side, writes its own viewer sessions and creator connector bindings, and leaves provider refresh-token custody in Heimdall.
|
||||
- Spotiverse now has a Heimdall app profile and Spotify provider runtime for managed credential custody.
|
||||
- A read-only `gamecult.eve.provider_advertisement.v1` export now advertises Heimdall provider/app surfaces plus redacted CultCache witness document paths for Odin/Eve discovery.
|
||||
|
||||
rejected_paths:
|
||||
- name: per_app_auth_copy_paste
|
||||
|
|
|
|||
57
tests/verse-witness.test.ts
Normal file
57
tests/verse-witness.test.ts
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
buildHeimdallProviderAdvertisement,
|
||||
heimdallWitnessDocuments,
|
||||
heimdallWitnessSchemaIds,
|
||||
} from "../src/verse-witness.js";
|
||||
|
||||
describe("Heimdall Verse witness advertisement", () => {
|
||||
it("advertises the Odin/Eve provider surface and redacted witness documents", () => {
|
||||
const advertisement = buildHeimdallProviderAdvertisement({
|
||||
updatedAt: "2026-06-03T12:00:00.000Z",
|
||||
});
|
||||
|
||||
expect(advertisement).toMatchObject({
|
||||
schemaVersion: "gamecult.eve.provider_advertisement.v1",
|
||||
providerId: "heimdall",
|
||||
verseId: "gamecult",
|
||||
status: "read_only_witness_planned",
|
||||
updatedAt: "2026-06-03T12:00:00.000Z",
|
||||
controlSurface: {
|
||||
controls: {
|
||||
mode: "read-only",
|
||||
writes: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(advertisement.providers.map((provider) => provider.key)).toEqual([
|
||||
"discord",
|
||||
"patreon",
|
||||
"github",
|
||||
"twitch",
|
||||
"youtube",
|
||||
"spotify",
|
||||
]);
|
||||
expect(advertisement.appProfiles.map((profile) => profile.slug)).toEqual([
|
||||
"repixelizer",
|
||||
"streampixels",
|
||||
"spotiverse",
|
||||
]);
|
||||
expect(advertisement.documents.map((document) => document.schemaId)).toEqual([...heimdallWitnessSchemaIds]);
|
||||
});
|
||||
|
||||
it("keeps token custody fields forbidden in witness descriptors", () => {
|
||||
expect(heimdallWitnessDocuments).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
schemaId: "heimdall.linked_identity.v0",
|
||||
forbiddenFields: expect.arrayContaining(["accessTokenEncrypted", "refreshTokenEncrypted"]),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
schemaId: "heimdall.managed_credential_projection.v0",
|
||||
forbiddenFields: expect.arrayContaining(["accessToken", "refreshToken"]),
|
||||
}),
|
||||
])
|
||||
);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue