Rename repo to Heimdall

This commit is contained in:
Meta Vens 2026-04-26 11:41:27 +01:00
commit 17096e85b1
12 changed files with 296 additions and 179 deletions

View file

@ -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

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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."
}
]
}

View file

@ -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."}

View file

@ -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.

View file

@ -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",

View file

@ -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)