From 17096e85b10cf73cef3d033889afe8bf6b3738ae Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sun, 26 Apr 2026 11:41:27 +0100 Subject: [PATCH] Rename repo to Heimdall --- AGENTS.md | 64 ++++---- README.md | 21 ++- docs/app-profiles/repixelizer.md | 17 +- docs/app-profiles/streampixels.md | 11 +- docs/architecture.md | 154 ++++++++++++------ docs/implementation-plan.md | 94 +++++++---- notes/fresh-workspace-handoff.md | 41 +++-- state/branches.json | 4 +- state/evidence.jsonl | 1 + state/map.yaml | 58 ++++--- ...tion.py => heimdall_prepare_compaction.py} | 8 +- ...cult_access_state.py => heimdall_state.py} | 2 +- 12 files changed, 296 insertions(+), 179 deletions(-) rename tools/{gamecult_access_prepare_compaction.py => heimdall_prepare_compaction.py} (97%) rename tools/{gamecult_access_state.py => heimdall_state.py} (98%) diff --git a/AGENTS.md b/AGENTS.md index e12bc8b..fd1d502 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,18 +1,19 @@ -# GameCult Access Instructions +# Heimdall Instructions ## Project Purpose -GameCult Access exists to hold the shared auth and access-control architecture -for GameCult-hosted experiments without letting that shared layer metastasize -into a giant mixed-data pit. +Heimdall exists to hold the shared auth authority design for GameCult-hosted +experiments without letting that shared layer metastasize into a giant +mixed-data pit. -The planned reusable backbone is: +The planned shared authority owns: - provider OAuth and linking -- signed local sessions +- signed session / claim issuance - entitlement refresh - capability evaluation - app-facing access claims +- audit and grant surfaces The planned non-shared-by-default layer is: @@ -21,6 +22,7 @@ The planned non-shared-by-default layer is: - creator data - queue/job payloads - product-specific state +- app-local resource ownership checks ## Canonical State @@ -38,35 +40,35 @@ The planned non-shared-by-default layer is: ## Important Paths -- Project root: `E:\Projects\gamecult-access` -- Shared architecture: `E:\Projects\gamecult-access\docs\architecture.md` -- Implementation plan: `E:\Projects\gamecult-access\docs\implementation-plan.md` -- Repixelizer profile: `E:\Projects\gamecult-access\docs\app-profiles\repixelizer.md` -- StreamPixels profile: `E:\Projects\gamecult-access\docs\app-profiles\streampixels.md` -- Handoff summary: `E:\Projects\gamecult-access\notes\fresh-workspace-handoff.md` -- State CLI: `E:\Projects\gamecult-access\tools\gamecult_access_state.py` -- Pre-compaction helper: `E:\Projects\gamecult-access\tools\gamecult_access_prepare_compaction.py` +- Project root: `E:\Projects\Heimdall` +- Shared architecture: `E:\Projects\Heimdall\docs\architecture.md` +- Implementation plan: `E:\Projects\Heimdall\docs\implementation-plan.md` +- Repixelizer profile: `E:\Projects\Heimdall\docs\app-profiles\repixelizer.md` +- StreamPixels profile: `E:\Projects\Heimdall\docs\app-profiles\streampixels.md` +- Handoff summary: `E:\Projects\Heimdall\notes\fresh-workspace-handoff.md` +- State CLI: `E:\Projects\Heimdall\tools\heimdall_state.py` +- Pre-compaction helper: `E:\Projects\Heimdall\tools\heimdall_prepare_compaction.py` ## Useful Commands This repo does not carry a dedicated virtualenv yet. Use any working Python -3.11+ interpreter. On this workstation, `E:\Projects\repixelizer\.venv\Scripts\python.exe` -is known-good. +3.11+ interpreter. On this workstation, +`E:\Projects\repixelizer\.venv\Scripts\python.exe` is known-good. Preferred here: ```powershell -E:\Projects\repixelizer\.venv\Scripts\python.exe .\tools\gamecult_access_state.py status -E:\Projects\repixelizer\.venv\Scripts\python.exe .\tools\gamecult_access_state.py add-evidence --type design --status accepted --note "..." -E:\Projects\repixelizer\.venv\Scripts\python.exe .\tools\gamecult_access_prepare_compaction.py +E:\Projects\repixelizer\.venv\Scripts\python.exe .\tools\heimdall_state.py status +E:\Projects\repixelizer\.venv\Scripts\python.exe .\tools\heimdall_state.py add-evidence --type design --status accepted --note "..." +E:\Projects\repixelizer\.venv\Scripts\python.exe .\tools\heimdall_prepare_compaction.py ``` Fallback if a normal `python` command exists: ```powershell -python .\tools\gamecult_access_state.py status -python .\tools\gamecult_access_state.py add-evidence --type design --status accepted --note "..." -python .\tools\gamecult_access_prepare_compaction.py +python .\tools\heimdall_state.py status +python .\tools\heimdall_state.py add-evidence --type design --status accepted --note "..." +python .\tools\heimdall_prepare_compaction.py ``` ## Session Bootstrap And Re-entry Protocol @@ -79,21 +81,21 @@ On fresh session load: - `docs/architecture.md` - `docs/implementation-plan.md` 2. run: - - `python .\tools\gamecult_access_state.py status` + - `python .\tools\heimdall_state.py status` - `git status --short --branch` - `git log --oneline -5` 3. restate the current next action from persisted state before editing After compaction or suspicious continuity loss: -1. rerun `gamecult_access_state.py status` +1. rerun `heimdall_state.py status` 2. reread `state/map.yaml` and `notes/fresh-workspace-handoff.md` 3. treat the persisted next action as authoritative unless fresh evidence contradicts it When the user says to prepare for imminent compaction: -1. run `tools/gamecult_access_prepare_compaction.py` +1. run `tools/heimdall_prepare_compaction.py` 2. update only the state that actually changed 3. rerun the helper 4. commit the completed persistence pass unless the work is deliberately @@ -102,10 +104,14 @@ When the user says to prepare for imminent compaction: ## Operating Discipline - Before substantial edits, restate the current mechanism and intended change. -- Keep the shared backbone separate from app-domain data unless there is a very - deliberate, source-grounded reason to share something. -- Prefer embedded shared-package mode before inventing a dedicated auth service. -- Do not let app profiles turn into duplicate copies of the shared architecture. +- Keep identity authority central and app-domain data local unless there is a + very deliberate, source-grounded reason to share something. +- Prefer the Yggdrasil-hosted Heimdall service over duplicated per-app provider + stacks. +- Do not require a Heimdall round-trip for every guarded app route; host apps + should verify signed claims locally and keep resource ownership checks local. +- Do not let app profiles turn into duplicate copies of the shared + architecture. - If the diff grows while the boundary gets blurrier, stop and simplify. - Before handoff or compaction, sync `state/map.yaml`, refresh `notes/fresh-workspace-handoff.md`, add distilled evidence when future belief diff --git a/README.md b/README.md index c3e66db..b6bfaf8 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,20 @@ -# GameCult Access +# Heimdall -GameCult Access is the planned shared auth and access-control backbone for -GameCult-hosted experiments. +Heimdall is the planned shared auth authority for GameCult-hosted experiments. The point is painfully simple: - stop rebuilding Discord/Patreon login glue in every app -- keep signed local sessions, linked identities, and capability checks reusable +- keep provider OAuth, linked identities, grants, entitlements, and session + issuance in one place +- let app runtimes verify signed claims locally so "shared auth" does not + become "everything phones home for every request" - keep app-domain data separate so "shared auth" does not become "shared swamp" ## Current Shape -This repo is planning and extraction work, not a landed auth service yet. +This repo is still planning and service-contract work, not a landed auth +service yet. Canonical docs: @@ -25,7 +28,9 @@ Canonical docs: ## Working Loop 1. Rehydrate from canonical state. -2. Keep the shared backbone separate from app-domain data. -3. Prefer embedded shared-package mode before inventing a central auth service. -4. Move only one organ at a time. +2. Keep identity authority central and app-domain data local. +3. Prefer a Yggdrasil-hosted shared service over duplicated per-app provider + stacks. +4. Let app backends verify signed claims locally instead of begging Heimdall on + every guarded route. 5. Commit finished passes before the notes start breeding. diff --git a/docs/app-profiles/repixelizer.md b/docs/app-profiles/repixelizer.md index 19e22db..6f62a53 100644 --- a/docs/app-profiles/repixelizer.md +++ b/docs/app-profiles/repixelizer.md @@ -2,7 +2,8 @@ ## What this file is -This file binds Repixelizer onto the shared design in `docs/architecture.md`. +This file binds Repixelizer onto the shared Heimdall design in +`docs/architecture.md`. It is future design, not a claim that the hosted demo already has auth. @@ -89,8 +90,20 @@ Important invariant: - job ownership resolves from the local session/account, not from Discord or Patreon ids +Heimdall should be involved in: + +- login / link entry +- session and claim issuance +- entitlement refresh + +Repixelizer should still do locally: + +- claim verification on guarded routes +- ownership checks on job reads, events, heartbeat, and cancel +- queue/runtime behavior + ## Config mapping - keep runtime/queue/limit env vars under `REPIXELIZER_*` -- move shared access machinery under `GC_ACCESS_*` +- move Heimdall integration under `GC_ACCESS_*` - keep per-app role/tier/capability bindings small and app-scoped diff --git a/docs/app-profiles/streampixels.md b/docs/app-profiles/streampixels.md index f440609..23eb040 100644 --- a/docs/app-profiles/streampixels.md +++ b/docs/app-profiles/streampixels.md @@ -2,7 +2,7 @@ ## What this file is -This file records how StreamPixels should fit onto the shared access backbone in +This file records how StreamPixels should fit onto Heimdall in `docs/architecture.md` without turning streamer-audience data into shared auth sludge. @@ -27,6 +27,13 @@ That is already compatible with the shared backbone idea. - creator/operator capability evaluation - grant/invite primitives where they fit +In the service-first Heimdall version, the most likely reusable slice is: + +- provider OAuth authority +- identity-linking authority +- entitlement refresh +- shared claim semantics + ## What StreamPixels should keep separate - viewer audience profiles @@ -37,7 +44,7 @@ That is already compatible with the shared backbone idea. In plain language: -- the same backbone can authenticate and authorize the control plane +- Heimdall can authenticate and authorize the control plane - StreamPixels audience data stays StreamPixels data ## Capability direction diff --git a/docs/architecture.md b/docs/architecture.md index c2fb602..936ab76 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,13 +1,16 @@ -# GameCult Hosted Access Architecture +# Heimdall Architecture ## What this file is -This file is the shared architecture note for GameCult-hosted experiments that -need the same basic promise: +This file is the shared architecture note for Heimdall, the planned auth +authority service for GameCult-hosted experiments. + +Its basic promise is: - let people sign in with Discord and/or Patreon -- decide access from GameCult/community entitlements -- keep app runtimes tied to local sessions and local accounts, not provider ids +- decide access from GameCult/community entitlements and local grants +- issue signed local claims that hosted apps can verify without trusting raw + provider tokens It is not a map of landed auth code yet. @@ -19,30 +22,32 @@ If every experiment rebuilds: - identity linking - entitlement refresh - session cookies -- queue/job ownership +- grant handling - "Discord role OR Patreon tier" logic then we get five near-identical auth pits and deserve what happens next. -The reusable move is to build one GameCult hosted-access layer and let each app +The reusable move is to build one Heimdall authority service and let each app bind its own policy and runtime seams onto it. ## Design goals - one reusable local account/session model across hosted experiments -- provider login and linking owned by one shared access layer +- provider login and linking owned by one shared authority - per-app capability policies instead of hard-coded one-off gate checks -- host runtimes consume signed local session claims, not raw provider tokens +- host runtimes consume signed local claims, not raw provider tokens +- routine route auth stays local to the host app after claim verification - keep app-domain data separate unless there is a deliberate reason to share it -- start as an embedded shared package before pretending we need a central auth - service +- fit the current GameCult topology: Heimdall on Yggdrasil behind nginx, with + hosted apps either on the same host or verifying over normal HTTPS/JWKS ## Non-goals - becoming a general public identity provider - building multi-tenant SaaS auth machinery - inventing global billing analytics because we got bored -- forcing cross-app single sign-on on day one before a second app exists +- forcing app-domain data into a shared auth store +- requiring every guarded route in every app to call Heimdall synchronously ## Core concepts @@ -61,6 +66,8 @@ bind its own policy and runtime seams onto it. policy rules - `Session` signed local app/browser session; the browser gets this, not provider tokens +- `AccessClaim` + signed app-facing claim set derived from the account, grants, and app policy - `AuditEvent` durable record for login, link, unlink, refresh, denial, and admin overrides @@ -69,20 +76,20 @@ bind its own policy and runtime seams onto it. ```mermaid flowchart TD A["Browser / app web UI"] --> B["Hosted experiment web surface"] - B --> C["GameCult access layer"] + B -->|start auth / link| C["Heimdall"] C --> D["Discord OAuth + guild role checks"] C --> E["Patreon OAuth + campaign/tier checks"] - C --> F["Local account/session/grant store"] - C --> G["Signed access claims"] - G --> H["Hosted experiment runtime"] + C --> F["Local auth/control-plane store"] + C --> G["Signed sessions / claims"] + B -->|verify claims locally| H["Hosted experiment runtime"] ``` The important split: -- the access layer owns provider OAuth, identity linking, entitlement refresh, - and signed local sessions -- the host experiment owns its workload, queue, editor, render path, or other - product-specific machinery +- Heimdall owns provider OAuth, identity linking, entitlement refresh, signed + sessions, claims, grants, and audit surfaces +- the host experiment owns its workload, queue, editor, render path, viewer + profile model, creator model, or other product-specific machinery Do not smear provider logic directly through every host runtime. @@ -94,10 +101,11 @@ pour their user data into one communal bucket. The reusable thing is: - provider OAuth plumbing -- signed local session mechanics +- signed session and claim mechanics - linked-identity primitives - entitlement refresh - capability evaluation +- local grant and audit surfaces The non-reusable-by-default thing is: @@ -109,12 +117,12 @@ The non-reusable-by-default thing is: Recommended default: -- shared auth/access **code** +- shared auth/access **authority** - separate app databases or at least separate app-owned schemas/tables for domain data -- if a shared access store exists at all, keep it limited to auth/control-plane - truth such as identities, grants, entitlement snapshots, sessions, and audit - events +- if a shared Heimdall store exists at all, keep it limited to auth/control + plane truth such as identities, grants, entitlement snapshots, sessions, + claims metadata, and audit events Do not let "same auth backbone" quietly mutate into "same user-data swamp." @@ -182,8 +190,8 @@ Important invariants: - one provider identity belongs to one local account only - provider tokens stay server-side only - session cookies represent local sessions, not provider trust directly -- app-specific capability checks read local claims, not Discord or Patreon on - every route +- app-specific capability checks read signed local claims, not Discord or + Patreon on every route ## Provider adapters @@ -203,7 +211,7 @@ Shared provider/gate adapters should be boring and reusable: ## Policy model -The reusable core should not hard-code one app's access rules. It should +The shared authority should not hard-code one app's access rules. It should evaluate capabilities against an app profile. Suggested app-profile shape: @@ -227,18 +235,41 @@ queue_submit = app_access admin_access = grant.operator || grant.admin_access ``` +## Request path discipline + +This is where shared authority stays useful instead of becoming a nuisance. + +Heimdall should be in the loop for: + +- OAuth start and callback flows +- explicit identity linking and unlinking +- session issuance and refresh +- entitlement refresh +- grant administration +- occasional explicit introspection or revocation checks + +Host apps should stay in the loop for: + +- routine route authorization after claim verification +- resource ownership checks +- domain-specific permission decisions that depend on app-local data + +Normal guarded routes should **not** need to round-trip to Heimdall every time. + ## Auth and linking flow The generic flow should be the same across hosted experiments: 1. user starts OAuth from the host app UI -2. shared access layer signs OAuth `state` -3. provider callback resolves the provider identity -4. shared layer finds or creates the local account -5. if the user was already signed in locally, the identity may link onto the +2. host app redirects to Heimdall or calls Heimdall to begin the flow +3. Heimdall signs OAuth `state` +4. provider callback resolves the provider identity through Heimdall +5. Heimdall finds or creates the local account +6. if the user was already signed in locally, the identity may link onto the existing account instead of creating a new one -6. shared layer refreshes entitlements and capability claims -7. shared layer issues or updates the local session cookie +7. Heimdall refreshes entitlements and capability claims +8. Heimdall issues or updates the local session / claim artifacts consumed by + the host app The browser should never be the custodian of provider tokens. @@ -248,6 +279,7 @@ Every hosted experiment should integrate through the same seams: - define an `app_slug` - define the app capability profile +- verify Heimdall-issued claims locally - mark which routes are public, authenticated, or capability-gated - attach `account_id`, `session_id`, and `access_revision` to owned resources such as jobs, drafts, uploads, or queue entries @@ -260,50 +292,64 @@ If an app has a queue or job system: - active-job cancellation policy is app-specific, but the recommended first cut is still "let the active job finish, block new work" +## Deployment topology + +Current intended first deployment shape: + +- Heimdall runs on `yggdrasil.gamecult.org` +- Heimdall binds a localhost port behind nginx +- public auth hostname is expected to become `heimdall.gamecult.org` +- same-host app stacks such as StreamPixels and Repixelizer call Heimdall over + localhost or normal private routing when they need auth-authority work done +- app backends verify signed claims locally for routine guarded requests + +This matches the host pattern already used by other GameCult app workloads. + ## Deployment modes -### Mode 1: Embedded shared package +### Mode 1: Same-host shared access service Recommended first cut. -- one shared `gamecult_access` package/module -- each hosted experiment embeds it -- each app keeps its own runtime and route integration -- sessions may still be app-local +- one Heimdall service on Yggdrasil +- each hosted experiment keeps its own runtime and route integration - each app keeps its own domain-data store +- apps verify signed claims locally after issuance Why this is the right first cut: -- reusable without forcing a separate always-on dependency -- small blast radius -- easy to evolve while only one or two apps use it +- fits the current host topology +- avoids cross-runtime shared-library nonsense +- keeps OAuth and provider sludge in one place +- keeps routine guarded requests local inside the app -### Mode 2: Same-host shared access service +### Mode 2: Multi-host shared access service -Use this when multiple apps on the same server want one shared access store and -session issuer, but full centralization still feels premature. +Use this when multiple app stacks across hosts still want one shared auth +authority, but the same claim-verification discipline continues. -### Mode 3: Dedicated GameCult access service +### Mode 3: Wider cross-app session platform -Only do this when multiple independently deployed apps genuinely need: +Only do this when independently deployed apps genuinely need: - shared cross-app sessions - central grant administration - one callback origin for all providers -- enough auth churn that duplicated embedded deployments become the bigger pain +- enough auth churn that Heimdall is clearly a long-lived platform surface Do not build this first because it sounds important. -Even in this mode, the dedicated service should own auth/control-plane data, not -absorb every app's audience or product data. +Even in this mode, Heimdall should own auth/control-plane data, not absorb +every app's audience or product data. ## Configuration split Suggested generic env surface: - `GC_ACCESS_ENABLED=1` +- `GC_ACCESS_BASE_URL=https://heimdall.gamecult.org` +- `GC_ACCESS_INTERNAL_URL=http://127.0.0.1:4100` - `GC_ACCESS_SESSION_SECRET=...` -- `GC_ACCESS_BASE_URL=https://access.gamecult.org` or host-app local equivalent - `GC_ACCESS_DISCORD_CLIENT_ID=...` - `GC_ACCESS_DISCORD_CLIENT_SECRET=...` - `GC_ACCESS_DISCORD_BOT_TOKEN=...` @@ -321,10 +367,12 @@ env namespaces. The correct reusable seam is: -- shared provider/session/entitlement/capability machinery +- one shared identity authority service +- local verification of signed claims by each host app - thin per-app access profiles and runtime bindings The incorrect reusable seam is: -- cloning one app’s auth blob into every future experiment +- cloning one app's auth blob into every future experiment - or turning shared auth into shared domain-data soup +- or forcing every guarded app route to make a Heimdall network hop diff --git a/docs/implementation-plan.md b/docs/implementation-plan.md index 7904a15..18ff84c 100644 --- a/docs/implementation-plan.md +++ b/docs/implementation-plan.md @@ -1,8 +1,8 @@ -# GameCult Access Implementation Plan +# Heimdall Implementation Plan ## What this file is -This file is the forward plan and active hypothesis ledger for GameCult Access. +This file is the forward plan and active hypothesis ledger for Heimdall. It is not the canonical shared architecture note. That lives in `docs/architecture.md`. @@ -16,86 +16,112 @@ There is no landed auth machine here yet. What exists right now is: -- the extracted shared architecture note +- the shared architecture note - app-binding notes for Repixelizer and StreamPixels -- explicit state/doctrine scaffolding so the plan does not immediately turn into - soup +- explicit state/doctrine scaffolding so the plan does not immediately turn + into soup ## Primary objective -Build a reusable `gamecult_access` layer that can: +Build Heimdall as a shared auth authority service that can: - own provider OAuth and identity linking -- issue signed local sessions +- issue signed sessions and app-facing claims - refresh Discord and Patreon entitlements - evaluate per-app capability rules -- stay separate from app-domain data +- expose a clean contract to app backends without owning their domain data ## Current priorities -### 1. Lock the shared boundary +### 1. Lock the service boundary Goals: -- keep the shared layer scoped to auth/control-plane machinery +- keep Heimdall scoped to auth/control-plane machinery - keep app-domain data in app-owned stores - keep app profiles thin and explicit -### 2. Prefer embedded-package mode first +### 2. Define the contract before the service grows teeth Goals: -- define the reusable package/module seam -- avoid a dedicated shared service until a second real app proves the need -- keep initial blast radius small +- define signed claim/session shapes +- define OAuth start/callback/link surfaces +- define grant and entitlement persistence boundaries +- define how app backends verify claims locally -### 3. Make Repixelizer the first binding +### 3. Make Repixelizer the first auth-blank consumer Goals: -- gate hosted access and queue submission through local sessions +- gate hosted access and queue submission through Heimdall-issued claims - attach queue ownership to `account_id` and `session_id` - preserve the existing hosted GUI and queue runtime -### 4. Keep StreamPixels separate where it matters +### 4. Migrate StreamPixels without flattening its good local seams Goals: -- let StreamPixels reuse auth/control-plane machinery +- move provider/linking authority toward Heimdall - keep viewer profiles, creator memberships, connector creds, and audience data in StreamPixels +- keep creator/operator route authorization local to StreamPixels after claim + verification + +### 5. Capture the Yggdrasil deployment shape early + +Goals: + +- treat Heimdall as a localhost service behind nginx on Yggdrasil +- support same-host callers first +- avoid per-request network chatter for routine app authorization ## Proposed phases -### Phase 1: Shared package scaffold +### Phase 1: Contract and schema scaffold -- create package/module boundaries for: - - provider adapters - - signed session issuance/verification - - linked identity persistence interfaces - - entitlement refresh/evaluation - - capability rule evaluation +- define: + - signed claim/session payloads + - JWKS or equivalent verification surface + - OAuth `state` and callback contracts + - grant / entitlement / audit record shapes + - app profile / capability rule inputs and outputs -### Phase 2: Repixelizer binding +### Phase 2: Heimdall service skeleton -- integrate the shared layer into the hosted demo +- create the first real service layout for: + - provider OAuth start/callback routes + - account / linked-identity persistence + - session issuance and refresh + - capability evaluation + - audit logging + +### Phase 3: Repixelizer binding + +- integrate Heimdall into the hosted demo - gate `POST /api/jobs` - gate per-job read/cancel/event routes by local session ownership -### Phase 3: StreamPixels control-plane fit check +### Phase 4: StreamPixels authority migration -- map the shared access layer onto StreamPixels creator/operator auth seams -- explicitly reject audience-data centralization +- replace provider/linking authority paths with Heimdall calls where they truly + belong +- keep viewer/session expansion, creator memberships, and operator route + enforcement local where they are app-domain concerns -### Phase 4: Shared-store decision +### Phase 5: Ops and deploy -- only after two real bindings exist, decide whether auth/control-plane state - should stay embedded or move into a shared service/store +- define nginx/systemd/env/runbook surfaces for Heimdall on Yggdrasil +- keep callers and state stores localhost-first where practical ## Guardrails - do not let auth reuse become data mixing -- do not build a central service first just because it sounds grand +- do not turn Heimdall into a mandatory round-trip for every guarded app route +- do not fantasize about one shared cross-runtime embedded library; the apps can + share contracts without sharing a runtime - do not duplicate the shared architecture note into app profiles +- do not rip out app-local ownership and authorization logic that still belongs + in the app - keep one clear hypothesis per pass - commit completed work before the planning notes start breeding diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index 5c798ba..317eb1e 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -1,6 +1,6 @@ # Fresh Workspace Handoff -This is the re-entry packet for `E:\Projects\gamecult-access`. +This is the re-entry packet for `E:\Projects\Heimdall`. It is intentionally short. Historical proof belongs in git history and the distilled `state/evidence.jsonl` ledger; the shared architecture belongs in @@ -13,7 +13,7 @@ From the repo root: ```powershell # This repo has no dedicated venv yet. Use any working Python 3.11+. -E:\Projects\repixelizer\.venv\Scripts\python.exe .\tools\gamecult_access_state.py status +E:\Projects\repixelizer\.venv\Scripts\python.exe .\tools\heimdall_state.py status Get-Content '.\state\map.yaml' Get-Content '.\notes\fresh-workspace-handoff.md' Get-Content '.\docs\architecture.md' @@ -27,23 +27,29 @@ Do not trust this file for the exact live HEAD. Always check git. ## Current Orientation -- this repo is planning and extraction work, not a landed auth service yet +- this repo is still planning and service-contract work, not a landed auth + service yet - the canonical shared architecture lives in `docs/architecture.md` - app profiles live under `docs/app-profiles/` - the key boundary is explicit: - - shared auth/control-plane mechanics are reusable + - Heimdall owns OAuth, linking, grants, entitlement refresh, and signed claim + issuance - app-domain data stays app-owned by default -- Repixelizer is the first binding target -- StreamPixels is the boundary case that proves backbone reuse must not imply - audience-data centralization -- the recommended first implementation mode is an embedded shared package, not a - dedicated auth service + - host apps verify signed claims locally for routine auth instead of calling + Heimdall on every request +- Repixelizer is the first auth-blank binding target +- StreamPixels is the migration target with useful existing auth seams that + should not be flattened into mush +- the intended first deployment shape is a Heimdall service on Yggdrasil behind + nginx, not an embedded cross-runtime shared library fantasy ## Critical Doctrine - Persistent state is the agent's mind. - Cut stale architecture notes as ruthlessly as stale code. - Do not mistake "shared auth" for "shared database." +- Do not mistake "shared auth service" for "every guarded route must call the + mothership." - If compaction hits before boundary findings are persisted, that work is gone. Re-gather it instead of pretending continuity happened. @@ -51,16 +57,17 @@ Do not trust this file for the exact live HEAD. Always check git. Do not continue implementation automatically from a rehydrate-only request. -If the user asks to continue, the current next move is to define the embedded -`gamecult_access` package seam: +If the user asks to continue, the current next move is to define the first +Heimdall service contract and skeleton: -- storage interfaces -- provider adapter interfaces -- signed-session claims -- capability rule evaluation boundaries +- OAuth start/callback surfaces +- signed session / claim shape +- claim verification contract for app backends +- entitlement/grant persistence boundaries +- Repixelizer and StreamPixels integration seams -without central-service theatrics and without smearing app-domain data into the -shared layer. +without dragging app-domain data into the shared layer and without forcing +per-request auth round-trips. ## Immediate Re-entry Instruction diff --git a/state/branches.json b/state/branches.json index 2785196..ea9d340 100644 --- a/state/branches.json +++ b/state/branches.json @@ -2,7 +2,7 @@ "branches": [ { "id": "main", - "hypothesis": "A shared GameCult access backbone can be reused across hosted experiments if OAuth/session/capability machinery is centralized while app-domain data stays app-owned.", + "hypothesis": "A shared Heimdall authority service on Yggdrasil can be reused across hosted experiments if OAuth, sessions, grants, and capability machinery are centralized while app-domain data and routine route authorization stay app-local.", "status": "active", "artifacts": [ "state/map.yaml", @@ -11,7 +11,7 @@ "docs/architecture.md", "docs/implementation-plan.md" ], - "notes": "Current mainline. Repixelizer is the first binding target; StreamPixels is the boundary stress test." + "notes": "Current mainline. Repixelizer is the first auth-blank binding target; StreamPixels is the migration and boundary stress test." } ] } diff --git a/state/evidence.jsonl b/state/evidence.jsonl index d4cd442..1cd3b42 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -1,3 +1,4 @@ {"ts":"2026-04-26T00:00:00Z","type":"design","status":"accepted","note":"The shared hosted-access plan was extracted from Repixelizer into its own repo so the backbone can evolve separately from app-specific notes."} {"ts":"2026-04-26T00:00:00Z","type":"design","status":"accepted","note":"The recommended first implementation mode is an embedded shared gamecult_access package, not a dedicated auth service."} {"ts":"2026-04-26T00:00:00Z","type":"boundary","status":"accepted","note":"StreamPixels can reuse the same auth/control-plane backbone, but streamer-audience data, creator memberships, connector credentials, and runtime state should remain StreamPixels-owned."} +{"ts":"2026-04-26T06:00:00Z","type":"design","status":"accepted","note":"The cross-runtime split and current Yggdrasil topology changed the first implementation shape from an embedded shared package to Heimdall: a shared auth authority service whose signed claims are verified locally by each app."} diff --git a/state/map.yaml b/state/map.yaml index 68d7482..0114be8 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -1,23 +1,24 @@ project: - name: GameCult Access + name: Heimdall status: active - phase: architecture_extraction + phase: service_topology_alignment premise: > - Build one reusable GameCult-hosted access backbone for OAuth, linked - identities, local sessions, and capability checks without mixing in - app-domain audience or product data. + Build Heimdall as one reusable GameCult auth authority service for OAuth, + linked identities, local sessions, grants, and capability claims without + mixing in app-domain audience or product data. objective: primary: > - Create a reusable shared access layer that Repixelizer, StreamPixels, and - future GameCult-hosted experiments can bind onto. + Create a shared auth authority service that Repixelizer, StreamPixels, and + future GameCult-hosted experiments can consume. current: > - Lock the shared architecture, extraction path, and app-boundary rules before - implementation starts breeding myths. + Lock the Heimdall service boundary, Yggdrasil deployment shape, and app + integration rules before implementation starts breeding myths. constraints: - - Shared auth/access code is reusable; app-domain data is separate by default. - - Prefer an embedded shared package before a dedicated auth service. + - Heimdall owns auth/control-plane authority; app-domain data stays app-owned by default. + - Prefer a same-host Heimdall service on Yggdrasil over duplicated per-app provider stacks. + - Host apps verify signed claims locally for routine guarded routes. - App profiles stay thin; the shared architecture note owns the main model. - StreamPixels audience and creator data must not be absorbed into a shared auth store. @@ -25,10 +26,10 @@ invariants: - The map is canonical and slow-changing. - Scratch is temporary and disposable. - Evidence is durable and distilled; routine activity logs belong in git history and commit messages. - - The shared access layer owns OAuth, local sessions, entitlement refresh, capability evaluation, and audit surfaces. + - Heimdall owns OAuth, local sessions, grants, entitlement refresh, capability evaluation, and audit surfaces. - App-domain data stays with the host app unless there is a deliberate, source-grounded reason to share it. - - Embedded shared-package mode is the default first implementation strategy. - - StreamPixels can reuse control-plane auth mechanics without centralizing streamer-audience data. + - Host apps verify signed claims locally and keep resource ownership checks local. + - StreamPixels can reuse shared auth authority without centralizing streamer-audience data. - Persistent state cruft is the same failure class as architecture cruft. data_flow: @@ -37,45 +38,48 @@ data_flow: - shared architecture note - app profile bindings - current app/runtime seams in Repixelizer and StreamPixels + - Yggdrasil host topology and nginx/service shape process: - rehydrate from canonical state - choose one bounded architecture or implementation slice - - keep shared code and app-domain data boundaries explicit + - keep auth authority and app-domain data boundaries explicit + - keep routine route auth local after claim verification - record only belief-changing evidence - commit or backtrack cut: - reject shared-data creep - reject duplicated auth blobs per app - - reject central-service theatrics before two real bindings justify them + - reject per-request auth round-trip chatter active_subgoals: - Keep the shared architecture explicit and separate from app profiles. - Preserve the default boundary that app-domain data remains app-owned. - - Make Repixelizer the first binding and StreamPixels the boundary stress test. - - Define the extraction seam for an embedded `gamecult_access` package. + - Align the plan to Heimdall as a Yggdrasil-hosted authority service. + - Define the claim-verification contract that Repixelizer and StreamPixels will consume locally. accepted_design: - `docs/architecture.md` is the canonical shared architecture note. - `docs/app-profiles/*.md` are thin app bindings, not duplicate brains. - - The reusable backbone owns provider OAuth, identity linking, signed local sessions, entitlement refresh, capability evaluation, and audit events. + - Heimdall owns provider OAuth, identity linking, signed local sessions, grants, entitlement refresh, capability evaluation, and audit events. - Host apps own their domain data, runtime state, and product-specific resources. - - A future shared access store, if it exists, should hold auth/control-plane truth only. - - Repixelizer is the first hosted-app binding. - - StreamPixels is the boundary case that proves backbone reuse must not imply audience-data centralization. + - Host apps verify signed claims locally for routine route authorization. + - A shared Heimdall store, if it exists, should hold auth/control-plane truth only. + - Repixelizer is the first auth-blank hosted-app binding. + - StreamPixels is the migration case that proves shared authority must not imply audience-data centralization. landed_baseline: - - Shared architecture extracted into its own repo. + - Shared architecture extracted into its own repo and renamed to Heimdall. - Repixelizer and StreamPixels app-profile notes created. - Repo-local state, handoff, and compaction scaffolding created. rejected_paths: - name: per_app_auth_copy_paste reason: duplicates the same provider/session sludge across every experiment - - name: central_auth_service_first - reason: adds deployment weight before two real bindings justify it + - name: per_request_auth_round_trip + reason: turns shared auth into a noisy bottleneck instead of local claim verification - name: shared_user_data_swamp reason: reusing auth mechanics does not justify centralizing app-domain audience or product data current_status: - summary: The shared hosted-access planning work has been extracted into its own repo with a canonical architecture note, a forward implementation plan, and thin app profiles for Repixelizer and StreamPixels. The key boundary is explicit: reusable auth/control-plane code is shared, app-domain data remains app-owned by default. The recommended first implementation mode is an embedded shared package rather than a dedicated auth service. - next_action: Start the first implementation-oriented pass by defining the embedded `gamecult_access` package seam: storage interfaces, provider adapter interfaces, signed-session claims, and capability rule evaluation boundaries, while keeping Repixelizer as the first binding target. + summary: The shared hosted-access planning work now lives in Heimdall, a service-first auth authority repo with a Yggdrasil-hosted topology in mind. The key boundary is explicit: Heimdall owns OAuth, linking, grants, entitlement refresh, and signed claims; app-domain data remains app-owned; routine route authorization stays local after claim verification. + next_action: Start the first implementation-oriented pass by defining Heimdall's service contract and skeleton: OAuth start/callback surfaces, signed session and claim payloads, claim verification material for app backends, and the first Repixelizer and StreamPixels integration seams. diff --git a/tools/gamecult_access_prepare_compaction.py b/tools/heimdall_prepare_compaction.py similarity index 97% rename from tools/gamecult_access_prepare_compaction.py rename to tools/heimdall_prepare_compaction.py index b262ea1..c273586 100644 --- a/tools/gamecult_access_prepare_compaction.py +++ b/tools/heimdall_prepare_compaction.py @@ -199,10 +199,10 @@ def add_handoff_checks(findings: list[Finding]) -> None: def add_agents_checks(findings: list[Finding]) -> None: text = read_text(AGENTS_PATH) - if "gamecult_access_prepare_compaction.py" in text: + if "heimdall_prepare_compaction.py" in text: findings.append(Finding("ok", "AGENTS.md tells agents to use the compaction helper")) else: - findings.append(Finding("error", "AGENTS.md does not mention tools/gamecult_access_prepare_compaction.py")) + findings.append(Finding("error", "AGENTS.md does not mention tools/heimdall_prepare_compaction.py")) if "prepare for imminent compaction" in text.lower(): findings.append(Finding("ok", "AGENTS.md names the imminent-compaction trigger")) else: @@ -232,7 +232,7 @@ def render_report(findings: list[Finding], status: str, log: str, evidence: list } lines = [ - "GameCult Access pre-compaction persistence check", + "Heimdall pre-compaction persistence check", f"Workspace: {ROOT}", f"Findings: {counts['ok']} ok, {counts['warn']} warn, {counts['error']} error", "", @@ -280,7 +280,7 @@ def render_report(findings: list[Finding], status: str, log: str, evidence: list def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser( - description="Audit GameCult Access persistent state before imminent compaction." + description="Audit Heimdall persistent state before imminent compaction." ) parser.add_argument( "--strict", diff --git a/tools/gamecult_access_state.py b/tools/heimdall_state.py similarity index 98% rename from tools/gamecult_access_state.py rename to tools/heimdall_state.py index 1cd8933..d4aa50a 100644 --- a/tools/gamecult_access_state.py +++ b/tools/heimdall_state.py @@ -131,7 +131,7 @@ def cmd_close_branch(args: argparse.Namespace) -> int: def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser( - description="Inspect and update GameCult Access persistent state files." + description="Inspect and update Heimdall persistent state files." ) subparsers = parser.add_subparsers(dest="command", required=True)