Every AI generation in V1 — a curated devotional image, a narrated contemplative arc, an instrumental ambient bed, a creator's raw ComfyUI graph, an operator's RunPod dashboard — is gated by a single, deterministic tier resolver before a pixel or sample is produced. This page documents the canonical tier taxonomy, the 28 named generation surfaces and their per-tier allowlists, the enforcement spine a request crosses (entitlements → Lilith → Isis → provider → provenance → audit), and the real, provider-gated execution substrate behind it. It serves product, platform, and operator readers who need to know exactly what a given audience can do and where the fail-closed boundaries sit. It lives in the generation-pipeline area alongside Agentic AI Studio and Living Scenes, and rests on the Isis — Generation Control Substrate.
Product scope:
V1/features.md§ Generation Audience Tiers and Surface Boundaries.
The canonical tier taxonomy#
V1 has four generation audience tiers. The single source of truth is the
GenerationTier union in libs/isis/entitlements/src/generation-tier.ts (lines
23–28), frozen as the GENERATION_TIERS array:
type GenerationTier =
| 'operator-admin'
| 'aaa-creator'
| 'curated-creator'
| 'contemplative';
These four code-level identifiers are the canonical taxonomy. Earlier prose
(V1/features.md–3230) named the tiers Customer, Curated-Creator,
AAA-Creator, and Operator and claimed all four were "used verbatim" — but
two of those do not match the implementation. The mapping below reconciles
the product-facing labels with the enforced code identifiers:
| Product-facing label | Enforced GenerationTier |
Who it is |
|---|---|---|
| Customer | contemplative |
The default consumer of finished, curated products — no raw generation surfaces. |
| Curated-Creator | curated-creator |
A creator working only inside curated workflow classes — no raw model picker, no Civitai. |
| AAA-Creator | aaa-creator |
A power creator with the full graph editor, Civitai browser, LoRA training, model merging, 3D. |
| Operator | operator-admin |
Platform staff: full surface set plus admin-only review queues, RunPod, audit, node registry. |
Free-versus-paid is a subscription distinction within the contemplative
(Customer) tier, not a fifth tier. The resolver knows nothing about billing; it
reads entitlement tags only.
How a tier is resolved#
resolveGenerationTier(entitlement) (line 134) is the deterministic resolver
that BFF, Admin, and Studio middleware all consult before rendering any
generation surface. It takes a GenerationEntitlement —
{ id, tenantId, userId, persona, tags } — and returns a ResolvedTier of
{ entitlement, tier, surfaceAllowlist }. Resolution is a strict, ordered
precedence over the normalized tag set (resolveTierFromTags, line 143):
- tag
operator-adminpresent →operator-admin; - else tag
aaa-creator→aaa-creator; - else tag
curated-creator→curated-creator; - else →
contemplative(the deny-by-default floor).
Tags are trimmed, empties dropped, and a non-array input degrades safely to an
empty set (normalizeEntitlementTags), so a malformed entitlement falls to
contemplative rather than silently escalating. This is the why behind the
ordering: the most privileged tier wins only on an explicit positive tag, and
the absence of any recognized tag always lands a user on the smallest surface
set.
The 28 generation surfaces#
The tier vocabulary is not abstract — it resolves to a frozen catalog of 28
named GenerationSurface values (GENERATION_SURFACE_VALUES, lines 35–68,
exported frozen as GENERATION_SURFACES). Each surface is a concrete UI or
capability that the resolver either grants or denies. The canonical list,
grouped by the tier band that introduces it:
| Band | Surfaces |
|---|---|
| Contemplative (curated) | curated-image, curated-living-scene, curated-voice-clip |
| Curated-creator | curated-workflow-pick, curated-asset-search |
| AAA-creator | graph-editor, civitai-search, civitai-lora-hash-picker, lora-trainer, model-merger, model-comparison, runpod-region-selector, gpu-worker, multi-gpu-orchestration, gaussian-splatting, auto-rigging, topaz, rife, animatediff, voice-cloning-tool, music-generation, 3d-generation |
| Operator-admin (admin-only) | runpod-dashboard, intake-review-queue, lora-training-queue, output-gallery-admin, comfyui-node-registry, audit-trail |
Per-tier allowlists#
TIER_ALLOWLISTS (lines 87–126) is a frozen
Record<GenerationTier, GenerationSurface[]> that binds each tier to exactly
the surfaces it may reach. The bands are cumulative: aaa-creator inherits the
contemplative and curated-creator surfaces and adds the heavy creative tooling;
operator-admin is the full GENERATION_SURFACES set (so it includes every
admin-only review and infrastructure surface). The exact grants:
| Tier | Surface count | Surfaces granted |
|---|---|---|
contemplative |
3 | curated-image, curated-living-scene, curated-voice-clip |
curated-creator |
5 | the 3 contemplative surfaces + curated-workflow-pick, curated-asset-search |
aaa-creator |
22 | the 5 curated surfaces + all AAA-creator surfaces (graph editor through 3d-generation) |
operator-admin |
28 | all of GENERATION_SURFACES (everything, including the 6 admin-only surfaces) |
The contemplative floor is deliberately narrow: a Customer can only ever touch
finished, curated products — never a raw model picker, a graph editor, or
Civitai.
Surface access is deny-by-default#
checkSurfaceAccess({ entitlement, surface }) (line 180) is the guard every
surface lookup calls. It resolves the tier, then returns a
SurfaceAccessVerdict:
{ verdict: 'allow', tier }when the surface is on the resolved allowlist;{ verdict: 'deny', tier, action: { kind: '404-hard-block' } }otherwise.
The verdict type also allows a softer { kind: '404-with-aaa-cta' } action, but
checkSurfaceAccess itself never issues it — CTA handling is reserved for
product-shell boundary guards that can prove the caller is already entitled to
the surface but reached it from the wrong host app. Surface access in isolation
is strictly deny-by-default: an unknown or unentitled surface yields a plain
404, not an upsell.
Where each tier actually lives#
| Tier | Product shell | Capability cap |
|---|---|---|
contemplative |
Living Offerings (Arete), Contemplative Arcs (Tara), Grounded Explainers (Veritas), Sky Briefings (Nyx), Lesson Visualizers (Metis) | Tone-gated; no raw graph editor; provenance always on. |
curated-creator |
apps/oshun/web/src/app/studio/generation + studio/generation-gallery |
Curated workflow classes only; no raw model picker, no Civitai access. |
aaa-creator |
apps/yemaya/studio-web + apps/yemaya/studio-desktop (Yemaya Studio) |
Full graph editor, Civitai browser, LoRA training, model merging, 3D. |
operator-admin |
apps/oshun/admin/ with @oshun/generation-control-isis |
Workflow graph editor, intake review queues, RunPod power-user surfaces. |
The contemplative ↔ Yemaya AAA Studio boundary#
The legacy customer-facing tree under apps/oshun/web/src/app/studio/isis/* is
now fail-closed. libs/isis/entitlements/src/studio-boundary.ts exports
STUDIO_ISIS_ALLOWED_ROUTE_SEGMENTS as an empty frozen array — so
isAaaOnlyRoute() treats every non-empty legacy isis segment as AAA-only. The
approved customer-facing generation surfaces live under /studio/generation/*
and /studio/generation-gallery, not under the legacy provider-machinery tree.
This corrects a stale claim: earlier docs said AAA-tier studio/isis/* routes
return "disclosure + Yemaya signup gate." In reality, because the allowlist is
empty, those legacy segments hard-block to 404 for a contemplative user.
resolveStudioBoundary({ routeSegment, entitlement }) only emits the
render-yemaya-cta outcome (disclosure copy + /aaa-upgrade link) when the
caller is already an aaa-creator or operator-admin who landed on the wrong
shell; everyone else gets hard-block-404. The AAA_ONLY_STUDIO_ROUTES catalog
(e.g. 3d-generation, lora-training, model-merging, music-generation,
multi-gpu-orchestration, comfyui-nodes, text-to-speech) is retained for
documentation, tests, and operator diagnostics — not as a live allowlist.
A Yemaya output can flow back into the contemplative product through the
editorial pipeline. YemayaPromotionRequest (studio-boundary.ts:101) posts to
/api/isis/output-gallery/promote with a proposedCardKind restricted to
curated-image | curated-living-scene | curated-voice-clip | curated-workflow-pick;
validatePromotionRequest rejects missing output ids, tenants, authors, notes,
or a non-curated card kind before forwarding to the §16 editorial lifecycle.
Generation request flow#
A generation request crosses the same enforcement spine regardless of tier:
entitlements gate the surface, Lilith gates the tone and crisis frame, Isis
dispatches against a versioned workflow template, the provider executes
sandboxed, and a ProvenanceBundle + watermark + audit record are produced
before the response returns. The provider node is intentionally drawn with the
hosted adapters that the live customer-facing executors actually call
(Stability, ElevenLabs, Suno, fal.ai), not the operator substrate
(RunPod/ComfyUI) — see
Why the provider node is hosted, not ComfyUI.
The BFF generation executors#
The concrete request handlers live in apps/oshun/bff/src/generation/. Each is
a real executor paired with a *-provider-env.ts resolver and a test:
- image (
image-executor.ts) — text-to-image; - narration (
narration-executor.ts) — TTS; - music + music-enqueue (
music-executor.ts,music-enqueue-executor.ts) — batch ambient/instrumental music; - video (
video-executor.ts) — text-to-video and image-to-video; - caption-dub (
caption-dub-executor.ts) — captioning + dubbing; - accessibility-pass (
accessibility-pass-executor.ts); - explainer (
explainer-executor.ts); - sky-briefing + sky-briefing-enqueue (
sky-briefing-executor.ts); - curated-generation (
curated-generation-executor.ts); - realtime-music (
realtime-music-route.ts) — the live streaming path.
Every executor routes its output through a release measurement
(provider-measurement.ts → buildReleaseMeasurement) that
evaluateGenerationRelease (release-gate.ts:116) grades into one of three
GenerationReleaseStatus values — complete, needs_review, or blocked. The
gate is fail-closed: a result with no measurement, a malformed measurement, or a
missing governance signal returns blocked with a specific reason (e.g.
governance_measurement_absent, governance_measurement_invalid). Two output
classifiers gate content before release: image-safety-classifier.ts scans
image URLs and text-safety-classifier.ts scans spoken/explainer text. When a
scanner is absent or its scan throws, the safety score is null and the gate
blocks — an unscanned output is never released ungoverned.
The live providers (env-gated, fail-closed by design)#
Every live provider call is environment-gated and fail-closed. With no
credential the resolver returns null, the executor never receives a generate
function, and the job fails closed with provider_not_configured — never a
fabricated result. Per their own source comments, the HTTP paths to each hosted
provider are "exercised at deploy time with a live key," not in default CI; the
env→provider wiring, request/result adaptation, and measurement emission are
unit-tested.
| Output kind | Live provider | Adapter symbol & path | Required env |
|---|---|---|---|
| Image | Stability SD3.5 (hosted) | StabilityProvider — @isis/ai-providers/providers/image-generation |
OSHUN_STABILITY_API_KEY (or STABILITY_API_KEY) |
| Voice / TTS | ElevenLabs | createElevenLabsProvider — @psyche/voice-synthesis |
OSHUN_ELEVENLABS_API_KEY and OSHUN_ELEVENLABS_VOICE_ID |
| Batch music | Suno | SunoProvider — @isis/audio-generation/generation |
OSHUN_SUNO_API_KEY |
| Realtime music | Google Magenta RealTime (MRT2) | MagentaRtProvider over @euterpe/providers / @euterpe/realtime-gen |
OSHUN_MRT2_ENABLED=true + on-device runtime loads |
| Video | fal.ai-hosted LTX-Video | LTXProvider — @isis/ai-providers/providers/video-generation |
fal.ai key |
The real env-var contract#
These are the deploy-time toggles that flip a surface from fail-closed to live:
- Image —
OSHUN_STABILITY_API_KEYis required;OSHUN_STABILITY_MODELoverrides the defaultsd3.5-large. Accepted aspect ratios are1:1,16:9,9:16,4:3,3:4,21:9,9:21,3:2,2:3; anything else is coerced to1:1(image-provider-env.ts). - Voice — both
OSHUN_ELEVENLABS_API_KEYandOSHUN_ELEVENLABS_VOICE_IDare required; either missing returnsnull. Narration uses a fixed, deploy-configured, platform-licensed voice — not a per-request clone — so thecloned-voice-usedhuman-review trigger does not apply (narration-provider-env.ts). - Batch music —
OSHUN_SUNO_API_KEYis required;OSHUN_SUNO_MODEL_VERSIONselectsv3 | v3.5 | v4 | v5(default model from the provider);OSHUN_SUNO_BASE_URLis an optional override. Curated music is instrumental (no lyrics/vocals), and there is no in-repo audio-content scanner, so its release measurement carriessafetyScanScore: nulland blocks at the audio safety floor until a deploy-bound audio scanner, watermarker, and C2PA signer are wired (music-provider-env.ts). - Realtime music — gated by
OSHUN_MRT2_ENABLED=trueand a successfulruntime.load(). The runtime is the official-engine Python sidecar whenOSHUN_MRT2_SIDECARis set (OSHUN_MRT2_PYTHON,OSHUN_MRT2_TIERof230m|2.4b), otherwise the native (Rust) on-device engine. If no inference backend or weights are present,load()reportsnot_configured, the resolver returnsnull, and the realtime route serves a503— never a fake stream (realtime-music-provider-env.ts).
Realtime music is a distinct generation mode#
The batch pipeline is admit → enqueue → worker → { id, url? }. Realtime music
(MRT2) is fundamentally different: a long-lived, frame-level interactive session
where control flows in and audio frames flow out for the life of a socket
(realtime-music-route.ts). buildSource drives a GenerationSession one
block at a time — 48 kHz stereo PCM, an ~8-token-per-frame budget, a ~2.667 ms
frame budget — pushing each frame to an audioSink that the route forwards on
the WS generation-audio channel. Each tick layers the current live
conditioning (text/MIDI/audio-ref updates from the control channel) over the
request's initial prompt. The loop ends the moment the client disconnects
(isActive() returns false) or the engine stops producing audio — terminating
the stream. This streaming mode is omitted from the prose music section in
features.md.
Why the provider node is hosted, not ComfyUI#
ComfyUI on RunPod is the operator substrate — the heavy GPU execution
environment AAA creators and operators drive through graph-editor,
gpu-worker, multi-gpu-orchestration, and runpod-dashboard. But the live,
customer-facing executors call hosted HTTP providers directly: Stability for
image, ElevenLabs for voice, Suno for batch music, fal.ai's LTX-Video for video.
A mermaid label of "ComfyUI · ElevenLabs · Suno" is therefore incomplete for the
customer path — image goes to StabilityProvider, video to LTXProvider, and
ComfyUI/RunPod sit behind the AAA-creator surfaces, not the default executor
flow.
The provider adapter library#
The full adapter catalog lives in libs/isis/ai-providers/src/providers/, one
subdirectory per capability: animation, civitai, comfy, comfy-cloud,
controlnet, conversational-ai, florence, image-generation, instantid,
ip-adapter, live-preview-streaming, llm, model-registry,
multi-gpu-orchestration, music-generation, three-d, tts, usage,
video-generation, video-processing, and workflow-versioning. The audio
provider set lives separately in libs/isis/audio-generation/src/generation/:
suno-provider.ts, udio-provider.ts, self-hosted.ts, sfx-provider.ts,
music-generator.ts, plus audio-analysis.ts.
Two accuracy notes on the music provider list. First, earlier docs described the
music abstraction as "MusicGen, Suno, Udio, Stable-Audio, custom-on-Comfy"; the
real files are suno-provider.ts, udio-provider.ts, self-hosted.ts,
sfx-provider.ts, and music-generator.ts. There is no provider file named
for MusicGen or Stable-Audio, and the realtime Magenta RT path is omitted from
that list. Second, although deps§9 lists both Suno and Udio as V1-used, only
Suno is wired into the BFF batch music executor (SunoProvider,
OSHUN_SUNO_API_KEY, OSHUN_SUNO_MODEL_VERSION); udio-provider.ts exists as
an adapter but is not customer-path-wired. The docs imply a Suno/Udio parity
that the BFF wiring does not have.
The @isis/music-generation library ("provider abstraction, workflow classes,
watermark + provenance" — §24.8) enforces per-class MIME guardrails in
guardrails.ts: each workflow class declares an allowedMimeTypes list drawn
from audio/wav, audio/mpeg, audio/ogg, and audio/flac, and a request
whose mimeType is not in its class list is rejected before generation.
The output gallery#
@isis/output-gallery ("filters, lineage, branch/replay, compare grid, bulk
actions" — §24.10) is the unified review surface for generated outputs. Its real
typed contracts (libs/isis/output-gallery/src/) are richer than the prose
describes:
- Compare grid (
compare-grid.ts) —buildCompareGridproduces a typedCompareGridLayoutof2-up | 4-up | n-up(capped at 16 slots,MAX_N_UP_SLOTS). Each diff pair carries aDiffMetrictyped per asset class:pixel-delta(mean absolute delta) for image,frame-deltafor video,audio-rms-delta(RMS delta in dB) andwaveform-delta(correlation coefficient) for audio, andmesh-vertex-delta(Hausdorff distance) for 3D. Mixing incompatible kinds throwsCompareGridErrorwith codemixed-kinds. - Branch / replay-with-tweak (
branch-replay.ts) — branching is not free-form.validateBranchDeltachecks every override in aBranchDeltaagainst the workflow class'sParameterAllowedRange[]: an unknown parameter throwsunknown-parameter, a number outside[min, max]or an enum value not inenumOptionsthrowsout-of-range, and a type mismatch throwswrong-type.planBranchthen assembles aBranchRunRequest(parent output id, validated inputs, workflow class, estimated cost) — so a derived run can only ever stay inside the parent class's bounded parameter space. - Bulk actions (
bulk-actions.ts) —BulkAction/BulkActionInvocationdrive multi-output operations.
The AAA-tier execution scheduler: @oshun/render-farm#
The AAA-creator tier's heavy GPU work is scheduled by @oshun/render-farm
(libs/oshun/render-farm), a real lib imported by apps/yemaya/studio-web (its
vite.config.ts, vitest.config.ts, and tsconfig.json all alias
@oshun/render-farm to the lib source, and package.json declares the
workspace:* dependency). It provides the render-farm scheduling primitives the
tier docs gesture at but never name: priority queues, worker-node capability and
GPU-requirement matching, dependency execution, preemption, checkpoint/resume,
cloud-burst, cost estimation, and dashboard snapshots. Its public surface (from
src/index.ts) includes RenderJob, RenderJobSubmission, RenderAssignment,
RenderCheckpoint, RenderCloudBurstPlan/RenderCloudBurstDecision/
RenderCloudBurstProvider, RenderCostEstimate, RenderGpuCapability/
RenderGpuRequirement, PreemptionDecision, RenderQuotaBreach/
RenderQuotaEvaluation, RenderDashboardSnapshot, and the
RenderFarmScheduler (via createRenderFarmScheduler).
The autonomous creative orchestrator: @oshun/creative-orchestrator#
The contemplative/curated "send-to-editorial" and autonomous generation
pipelines depend on @oshun/creative-orchestrator
(libs/oshun/creative-orchestrator), a real, BFF-wired lib (consumed by
apps/oshun/bff/src/agentic/ and libs/yemaya/orchestration + agents). It
turns a brief into produced content through three real stages, all built on the
shared @oshun/ai/agent-loop primitives (runStructuredOutput, runReflexion)
and fail-loud throughout — never fabricating a plan or artifact when no provider
is configured:
- Plan —
decomposeBriefruns a real LLM planner that emits a schema-validated, acyclicCreativePlanDAG (validated againstCREATIVE_PLAN_SCHEMA; cycle detection viadetectCycle/validateDagStructure). - Route + govern —
routePlan/CreativeOrchestratordispatch plan nodes to aGeneratorRegistryofDomainGenerators, gated by aGovernanceGate(BudgetGovernanceGate, withALLOW_ALL_GATEfor tests). - Critique → revise —
reviseArtifactruns a bounded generate → critique → revise (Reflexion) loop around each artifact, scored by anArtifactCritic(createContentEvalCritic,createMetricCritic, orcreateLlmJudgeCritic).
It also exposes createYemayaAgentGenerator (wiring Yemaya's specialized agents
in) and createMetisNarrator. This is the connective tissue behind the agentic
studio; see Agentic AI Studio for the full picture.
What is real vs. provider-gated vs. aspirational#
To match the docs' candor:
- Real, in-repo: the deterministic tier resolver (
resolveGenerationTier,TIER_ALLOWLISTS,checkSurfaceAccess) with 4 tiers and 28 surfaces; the studio boundary fail-closed allowlist; output-gallery lineage, branch-replay, compare-grid, and bulk-actions contracts; the music-generation guardrails; the BFF executors, release gate, and safety classifiers; the provider adapter library;@oshun/render-farm;@oshun/creative-orchestrator. - Provider-gated (real wiring, live call exercised only at deploy): every
hosted provider call — image (Stability), voice (ElevenLabs), batch music
(Suno), video (fal.ai LTX), realtime music (MRT2). With no key the resolver
returns
nulland the job fails closed (provider_not_configured). - Spec-only / unverified at runtime here: LoRA training, model merging,
gaussian-splatting, and 3D pipelines exist as data models and surfaces
(
@isis/lora-training-surface— "LoRA training queue + model merging + tuning rehearsal + quality scoring + lineage," §24.5;@isis/runpod-surface— "endpoint registry, dashboard, cost/quota, queue inspector," §24.4), but live training execution was not verified. The AAA Yemaya Studio graph editor exists asapps/yemaya/studio-{web,desktop}(studio-web importing@oshun/render-farm), but a rendered AAA graph editor was not confirmed here. Udio is library-present but not customer-path-wired.