Point hosted access docs at gamecult-access

This commit is contained in:
Meta Vens 2026-04-26 05:18:49 +01:00
commit 9d08b9d98a
5 changed files with 43 additions and 566 deletions

View file

@ -21,11 +21,15 @@ diagnostics good enough to catch when we are lying to ourselves.
- Treat `docs/implementation-plan.md` as the forward plan.
- Treat `docs/lean-optimizer-algorithm-map.md` as the source-grounded live
algorithm map.
- Treat `docs/gamecult-hosted-access-architecture.md` as the future shared
access architecture for GameCult-hosted experiments, not as proof that auth
machinery already exists here already.
- Treat `docs/repixelizer-hosted-access-profile.md` as the Repixelizer-specific
binding onto that future shared access architecture.
- Treat `E:\Projects\gamecult-access\docs\architecture.md` as the canonical
future shared access architecture for GameCult-hosted experiments, not as
proof that auth machinery already exists here already.
- Treat `E:\Projects\gamecult-access\docs\app-profiles\repixelizer.md` as the
canonical Repixelizer-specific binding onto that future shared access
architecture.
- Treat local `docs/gamecult-hosted-access-architecture.md` and
`docs/repixelizer-hosted-access-profile.md` as redirect stubs, not the real
design notes.
- Update `state/map.yaml` when project understanding changes.
- Add evidence after meaningful research, implementation, verification, or
rejected paths, but keep it distilled. Routine "I just did this" proof belongs
@ -37,8 +41,8 @@ diagnostics good enough to catch when we are lying to ourselves.
- Project root: `E:\Projects\repixelizer`
- Implementation plan: `E:\Projects\repixelizer\docs\implementation-plan.md`
- Algorithm map: `E:\Projects\repixelizer\docs\lean-optimizer-algorithm-map.md`
- Shared hosted access architecture: `E:\Projects\repixelizer\docs\gamecult-hosted-access-architecture.md`
- Repixelizer hosted access profile: `E:\Projects\repixelizer\docs\repixelizer-hosted-access-profile.md`
- Shared hosted access architecture: `E:\Projects\gamecult-access\docs\architecture.md`
- Repixelizer hosted access profile: `E:\Projects\gamecult-access\docs\app-profiles\repixelizer.md`
- Handoff summary: `E:\Projects\repixelizer\notes\fresh-workspace-handoff.md`
- State CLI: `E:\Projects\repixelizer\tools\repixelizer_state.py`
- Pre-compaction helper: `E:\Projects\repixelizer\tools\repixelizer_prepare_compaction.py`

View file

@ -1,399 +1,20 @@
# GameCult Hosted Access Architecture
## What this file is
This shared design moved.
This file is a future shared architecture note for GameCult-hosted experiments
that need the same basic promise:
Canonical location:
- 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
- `E:\Projects\gamecult-access\docs\architecture.md`
It is not a map of currently landed auth code in this repo.
Repixelizer-specific binding:
## Problem
- `E:\Projects\gamecult-access\docs\app-profiles\repixelizer.md`
Repixelizer is probably not the last hosted experiment that wants this shape.
This local file remains only as a redirect stub so old notes and links do not
pretend the shared access plan still lives inside Repixelizer.
If every experiment re-implements:
Current repo truth is unchanged:
- provider OAuth
- identity linking
- entitlement refresh
- session cookies
- queue/job ownership
- "Discord role OR Patreon tier" logic
then we will end up with five damp little auth pits that are all almost the
same and all annoying in slightly different ways.
The reusable move is to build one GameCult hosted-access layer 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
- per-app capability policies instead of hard-coded one-off gate checks
- host runtimes consume signed local session claims, not raw provider tokens
- keep app-domain data separate unless there is a deliberate reason to share it
- reusable enough as a shared package first, with room to graduate into a
shared service later if that pain actually materializes
## 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
## Core concepts
- `Account`
one GameCult-local person record independent of external providers
- `LinkedIdentity`
one external provider identity attached to exactly one local account
- `EntitlementSnapshot`
cached result from a provider or grant source saying whether some claim is
currently true
- `CapabilityGrant`
a reusable local grant such as `global_member`, `app_access`, or
`admin_access`, scoped either globally or to one app
- `AppAccessProfile`
the per-app binding that names capabilities, allowed provider sources, and
policy rules
- `Session`
signed local app/browser session; the browser gets this, not provider tokens
- `AuditEvent`
durable record for login, link, unlink, refresh, denial, and admin overrides
## System shape
```mermaid
flowchart TD
A["Browser / app web UI"] --> B["Hosted experiment web surface"]
B --> C["GameCult access layer"]
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"]
```
The important split:
- the access layer owns provider OAuth, identity linking, entitlement refresh,
and signed local sessions
- the host experiment owns its actual workload, queue, editor, render path, or
whatever weird machine it is
Do not smear provider logic directly through every host runtime.
## Backbone reuse is not data merging
Using the same auth backbone does **not** mean all hosted experiments should
pour their user data into one communal bucket.
The reusable thing is:
- provider OAuth plumbing
- signed local session mechanics
- linked-identity primitives
- entitlement refresh
- capability evaluation
The non-reusable-by-default thing is:
- app-domain user data
- creator data
- audience data
- queue/job payloads
- inventories, scenes, outputs, or other product state
Recommended default:
- shared auth/access **code**
- 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, and audit events
Do not let "same auth backbone" quietly mutate into "same user-data swamp."
## Reusable data model
Suggested durable core:
- `accounts`
- `id`
- `created_at`
- `last_seen_at`
- `display_name`
- `primary_email` nullable
- `linked_identities`
- `id`
- `account_id`
- `provider`
- `provider_user_id`
- `username`
- `access_token_encrypted`
- `refresh_token_encrypted`
- `token_expires_at`
- `profile_json`
- unique on `(provider, provider_user_id)`
- `entitlement_snapshots`
- `id`
- `account_id`
- `provider`
- `scope`
- `evaluated_at`
- `is_allowed`
- `reason_code`
- `reason_detail`
- `raw_summary_json`
- `capability_grants`
- `id`
- `account_id`
- `scope_type` enum: `global`, `app`
- `scope_id` nullable app slug
- `capability`
- `source` enum: `manual`, `invite`, `migration`, `operator`
- `status`
- `expires_at` nullable
- `note`
- `sessions`
- `id`
- `account_id`
- `app_slug`
- `created_at`
- `last_seen_at`
- `expires_at`
- `claims_json`
- `access_revision`
- `audit_events`
- `id`
- `account_id` nullable
- `session_id` nullable
- `app_slug` nullable
- `event_type`
- `event_payload_json`
- `created_at`
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
## Provider adapters
Shared provider/gate adapters should be boring and reusable:
- `DiscordRoleEntitlementProvider`
- login via Discord OAuth
- entitlement check via guild membership and role ids, ideally through the
GameCult bot token server-side
- `PatreonMembershipEntitlementProvider`
- login via Patreon OAuth
- entitlement check via campaign membership and tier ids
- `ManualGrantProvider`
- local override for staff, migrations, experiments, or emergency access
- `InviteGrantProvider`
- reusable invite-issued app or role grants where that pattern makes sense
This is the good kind of bureaucracy: one place to keep the provider sludge.
## Policy model
The reusable core should not hard-code "Repixelizer access". It should evaluate
capabilities against an app profile.
Suggested app-profile shape:
```text
AppAccessProfile {
appSlug: string
displayName: string
capabilities: string[]
policyRules: CapabilityRule[]
publicRoutes: string[]
explanationStrings: map[string]string
}
```
Example rule style:
```text
app_access = discord.allowed_role || patreon.allowed_tier || grant.global_member || grant.app_access
queue_submit = app_access
admin_access = grant.operator || grant.admin_access
```
This gives reuse in the right place:
- the shared layer knows how to link identities, refresh entitlements, and
evaluate capabilities
- each experiment only defines which capabilities it wants and what qualifies
## 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
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
The browser should never be the custodian of provider tokens. It is a goblin
with JavaScript.
## App integration contract
Every hosted experiment should integrate through the same seams:
- define an `app_slug`
- define the app capability profile
- mark which routes are:
- public
- authenticated
- capability-gated
- attach `account_id`, `session_id`, and `access_revision` to owned resources
such as jobs, drafts, uploads, or queue entries
- require resource-owner checks on private read/update/delete paths
If an app has a queue or job system:
- queue entries belong to local account/session ids, not provider ids
- entitlement refresh should block new submissions after access loss
- active-job cancellation policy is app-specific, but the recommended first cut
is still "let the active job finish, block new work"
## Deployment modes
This is where we avoid building the cathedral first.
### Mode 1: Embedded shared package
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
- each app keeps its own domain-data store
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
### Mode 2: Same-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.
### Mode 3: Dedicated GameCult access service
Only do this when multiple 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
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. If it starts trying to become the
warehouse for StreamPixels viewer profiles, Repixelizer jobs, and whatever comes
next, we have built a larger and more expensive mistake.
## Configuration split
Reusable shared config should stop pretending each app needs its own provider
stack.
Suggested generic env surface:
- `GC_ACCESS_ENABLED=1`
- `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=...`
- `GC_ACCESS_DISCORD_GUILD_ID=...`
- `GC_ACCESS_PATREON_CLIENT_ID=...`
- `GC_ACCESS_PATREON_CLIENT_SECRET=...`
- `GC_ACCESS_PATREON_CAMPAIGN_ID=...`
- `GC_ACCESS_ENTITLEMENT_CACHE_TTL_SECONDS=900`
- `GC_ACCESS_PROVIDER_FAILURE_GRACE_SECONDS=3600`
Per-app binding should live in code or profile config, not in a hundred cloned
provider env names:
- app slug
- hostnames
- allowed Discord role ids
- allowed Patreon tier ids
- capability rules
- public queue-summary policy, if any
## Recommended extraction path
1. Keep the repixelizer-specific note as an app profile, not the whole world.
2. Extract the shared ideas into a reusable `gamecult_access` package or sibling
repo once the first real implementation starts.
3. Move provider OAuth, signed session handling, identity linking, entitlement
refresh, and capability evaluation into that shared layer.
4. Leave app-specific route gating and runtime ownership fields in the host app.
5. Only consider a dedicated shared access service after a second hosted
experiment proves the need.
## StreamPixels boundary example
StreamPixels is the obvious case where reuse must not become data mixing.
What StreamPixels can likely reuse:
- signed local session mechanics
- linked-identity and OAuth-state patterns
- creator/operator capability evaluation
- provider adapter structure
- grant/invite primitives where they fit
What StreamPixels should keep separate:
- viewer audience profiles
- creator memberships tied to streamer spaces
- creator connector credentials
- overlay/runtime state
- audience-facing app data in general
In plain terms:
- the same backbone can authenticate and authorize the control plane
- StreamPixels audience data should remain StreamPixels data
If a future shared access store exists, it should not become the canonical home
for streamer-audience identity and product data. That stays with StreamPixels.
## Audit Result
Yes, this can be made reusable.
The correct reusable seam is:
- shared provider/session/entitlement/capability machinery
- thin per-app access profiles and runtime bindings
The incorrect reusable seam is:
- cloning a Repixelizer-specific auth blob into every future experiment and
changing a few strings with a kitchen knife.
- the hosted demo here still only has queue and limit machinery
- no Discord login, Patreon login, linked identities, local sessions, or access
gates are landed in `E:\Projects\repixelizer`

View file

@ -2,5 +2,5 @@ This file moved.
Use:
- `docs/gamecult-hosted-access-architecture.md` for the reusable shared design
- `docs/repixelizer-hosted-access-profile.md` for the Repixelizer-specific binding
- `E:\Projects\gamecult-access\docs\architecture.md` for the reusable shared design
- `E:\Projects\gamecult-access\docs\app-profiles\repixelizer.md` for the Repixelizer-specific binding

View file

@ -1,172 +1,22 @@
# Repixelizer Hosted Access Profile
## What this file is
This app-binding note moved.
This file binds Repixelizer onto the shared future design in
`docs/gamecult-hosted-access-architecture.md`.
Canonical location:
It is still future design, not a claim that the hosted demo already has auth.
- `E:\Projects\gamecult-access\docs\app-profiles\repixelizer.md`
## Current repo boundary
Shared architecture note:
Current live hosted-demo behavior is still just:
- `E:\Projects\gamecult-access\docs\architecture.md`
- hosted-demo runtime limits and UI flags in `src/repixelizer/gui.py`
- the local single-process queue in `src/repixelizer/gui.py`
- frontend runtime-config consumption in `frontend/src/app.ts`
This local file remains only as a redirect stub so Repixelizer does not keep a
second copy of the same future auth plan fermenting in a corner.
No Discord login, Patreon login, linked identities, local sessions, or access
gates are landed yet.
Current repo truth is unchanged:
## App identity
- `app_slug`: `repixelizer`
- primary hostname: `repixelizer.gamecult.org`
- runtime shape: one hosted GUI plus one queue-backed worker loop inside the app
## Capabilities
Repixelizer does not need a baroque permission lattice.
The likely first-cut capabilities are:
- `app_access`
- may load the protected hosted GUI
- `queue_submit`
- may create a repixelizer job
- `job_read_own`
- may read own job state, event stream, and final output
- `job_cancel_own`
- may cancel own queued or running job
- `admin_access`
- may inspect operational or grant/admin surfaces later
For the first cut:
```text
queue_submit = app_access
job_read_own = app_access + resource ownership check
job_cancel_own = app_access + resource ownership check
```
## Access policy
Repixelizer should inherit the generic GameCult access pattern:
```text
app_access =
discord.allowed_role
|| patreon.allowed_tier
|| grant.global_member
|| grant.app_access
queue_submit = app_access
admin_access =
grant.operator
|| grant.admin_access
```
Likely Discord-allowed examples:
- Patreon supporter role
- founder role
- moderator role
- explicit alpha-tester role
Likely Patreon-allowed examples:
- any paid GameCult membership
- or one configured subset of tiers if the experiment needs a stricter gate
## Repixelizer runtime binding
When this lands, the shared access layer should integrate with the current queue
shape rather than replace it.
Suggested queue/job additions:
- `jobs.account_id`
- `jobs.session_id`
- `jobs.access_revision`
- `jobs.entitlement_checked_at`
Route policy:
- public:
- `/api/health`
- `/api/config`
- public or capability-gated by policy choice:
- `/api/queue`
- requires `queue_submit`:
- `POST /api/jobs`
- requires ownership:
- `GET /api/jobs/{job_id}`
- `GET /api/jobs/{job_id}/events`
- `POST /api/jobs/{job_id}/heartbeat`
- `DELETE /api/jobs/{job_id}`
Important invariant:
- job ownership must resolve from the local session/account, not from raw
Discord or Patreon identifiers
## UI binding
Once access exists, the hosted landing flow should become:
- anonymous visitor sees:
- `Log in with Discord`
- `Log in with Patreon`
- signed-in but denied visitor sees:
- plain-English denial reason
- what qualifies
- link-the-other-provider affordance
- allowed visitor sees:
- the normal hosted GUI and queue flow
The queue/progress UI itself should stay as close as possible to the current
runtime. The auth layer should gate entry, not rewrite the machine for fun.
## Config mapping
Repixelizer-specific auth config should not grow a separate provider jungle.
Keep:
- existing runtime/queue/limit env vars under `REPIXELIZER_*`
Add shared access config under `GC_ACCESS_*`.
Repixelizer-specific app-profile config can live under either:
- code constants, or
- a small profile/config surface such as:
- `GC_ACCESS_REPIXELIZER_ALLOWED_DISCORD_ROLE_IDS=...`
- `GC_ACCESS_REPIXELIZER_ALLOWED_PATREON_TIER_IDS=...`
- `GC_ACCESS_REPIXELIZER_PUBLIC_QUEUE_SUMMARY=1`
The exact encoding can be decided later. The important thing is not cloning the
provider credentials into a second app-specific namespace.
## Recommended implementation order
1. Extract or embed the shared `gamecult_access` layer described in
`docs/gamecult-hosted-access-architecture.md`.
2. Gate `POST /api/jobs` plus per-job read/cancel/event routes through local
sessions and ownership checks.
3. Add login/link/denied-state UI around the existing hosted GUI shell.
4. Decide whether `/api/queue` stays public summary or moves behind `app_access`.
5. Only after a second hosted experiment exists, decide whether the shared
access layer should leave embedded-package mode and become a shared service.
## Invariants
- Repixelizer should consume the shared access layer; it should not become the
template that future experiments copy by hand.
- The queue remains owned by local session/account ids.
- Provider tokens stay server-side only.
- Capability checks belong at route boundaries and resource ownership seams, not
scattered through queue internals.
- Auth should gate the hosted demo, not mutate the solver model or pretend to be
the product.
- hosted-demo runtime limits and UI flags still live in `E:\Projects\repixelizer\src\repixelizer\gui.py`
- the queue is still the local single-process queue in that same module
- frontend runtime-config consumption still lives in `E:\Projects\repixelizer\frontend\src\app.ts`
- no Discord login, Patreon login, linked identities, local sessions, or access
gates are landed here yet

View file

@ -33,8 +33,8 @@ Do not trust this file for the exact live HEAD. Always check git.
- The current tracked weakness is the widened dark contour near the badge sword tip on tapered shapes.
- `source_structure` exists because lattice-only `source_fidelity` could call visibly better outputs worse.
- The old tray optimizer is dead and should stay dead unless the entire machine map changes for a real reason.
- `docs/gamecult-hosted-access-architecture.md` is the future shared design for reusable GameCult-hosted access across experiments.
- `docs/repixelizer-hosted-access-profile.md` is the Repixelizer-specific binding onto that future shared access layer.
- `E:\Projects\gamecult-access\docs\architecture.md` is the future shared design for reusable GameCult-hosted access across experiments.
- `E:\Projects\gamecult-access\docs\app-profiles\repixelizer.md` is the Repixelizer-specific binding onto that future shared access layer.
- current hosted-demo truth is still just the queue and limit machinery in `src/repixelizer/gui.py` plus the frontend runtime-config plumbing.
## Critical Doctrine
@ -92,8 +92,10 @@ Rules now in force:
- this handoff is a compact re-entry packet.
- `docs/implementation-plan.md` is the forward plan.
- `docs/lean-optimizer-algorithm-map.md` is the source-grounded control-flow map.
- `docs/gamecult-hosted-access-architecture.md` is future shared auth/access design, not live implementation truth.
- `docs/repixelizer-hosted-access-profile.md` is the app-specific future binding, also not live implementation truth.
- `E:\Projects\gamecult-access\docs\architecture.md` is future shared auth/access design, not live implementation truth.
- `E:\Projects\gamecult-access\docs\app-profiles\repixelizer.md` is the app-specific future binding, also not live implementation truth.
- local `docs/gamecult-hosted-access-architecture.md` and
`docs/repixelizer-hosted-access-profile.md` are redirect stubs.
Do not let any one note become all of those things. That is how the tower grows
sideways and starts calling itself architecture.