Metis · Architecture

Web-First Delivery & Cross-Cutting Concerns

A focused page within the Metis Architecture documentation. The full map and every sibling page live in the Architecture hub.

9sections16 minread1diagram

On this page

This page covers the bottom of V9's six-layer stack — the delivery layer (L6) where a forged lesson actually reaches a learner — together with the cross-cutting concerns that wrap every lesson regardless of which subsystem produced it: governance, determinism, cost discipline, localization, accessibility, identity, and entitlements. The throughline is a single scoping decision that the rest of the version is built around: V9's P1 experience is web-first and deliberately does not depend on the Bellona Unreal cook path. That is not an accident or a shortcut — it is the choice that lets V9 ship without inheriting the portfolio's single biggest blocker (the headless-engine content pipeline), because the two heaviest "wow" surfaces are already web-native and the one surface that needs the engine is the one V9 sequences to P2/P3. The concrete code surface for this layer is three focused packages — @oshun/v9-experience (the consumer view-models), @oshun/v9-governance (the AgentRun envelope, the seven-gate confirmation, the entitlement graph), and @oshun/v9-cross-cutting (determinism, cost, accessibility, champion-challenger gating, the anti-metric guard, localization routing, LMS interop) — plus the embodied-delivery fallback in @oshun/v9-chiron.

The reason it is shaped this way is the same reason the whole architecture is shaped the way it is: V9 is an integration version, so the "delivery and cross-cutting" story is overwhelmingly one of consuming the shared platform plane rather than rebuilding it. Every forge runs as an AgentRun under the V1 Agentic AI Studio envelope; entitlements come from the single Oshun account graph; localization reuses the Metis lecture-package dubbing; accessibility reuses the Metis captioning floor. What V9 adds is the thin connective tissue that binds a V9LessonArtifact to those shared services — and, importantly, a set of runtime refusals (an ungated lesson cannot be played, a banned engagement metric cannot be set as an optimization target, a non-deterministic forge cannot be promoted) that make the discipline checkable rather than aspirational. This page is the deep companion to the brief "Delivery (L6) and cross-cutting" section of the architecture overview; the section hub is ../V9_ARCHITECTURE.md.

What ships, honestly#

The three packages this page documents are real and tested: 35 passing tests across libs/v9/experience/src/experience.spec.ts (8), cross-cutting.spec.ts (17), and governance.spec.ts (10), all green when run directly with vitest. The view-models (buildWonderFrontDoor, buildLessonPlayer, buildPersonalAtlasMap, checkBillingAccess), the governance registration and confirmations (registerV9AgentFamily, confirmSevenGateSuite, confirmConsumesSharedLoop, checkLessonAccess), and the cross-cutting machinery (compareArtifactDeterminism, CostLedger, the a11y manifest, the two-proportion z-test, the anti-metric guard, the LMS-interop emitters, planLocalization) are all present, exercised, and back the claims in the monolith's §8/§9.

Three honest caveats separate that real core from the more ambitious framing in the monolith:

  • The rendered consumer application is not shipped. @oshun/v9-experience ships the tested data and logic the screens bind to, not the screens. The apps/metis/{web,mobile} targets exist but are "currently instructor/admin- shaped"; the consumer-first rebuild is, by the architecture's own admission, "the largest net-new build" and is open. Treat the front door, lesson player, and star-map as view-models with passing tests, not as a deployed product.
  • One substrate claim in the monolith is inaccurate, and one is over-stated. §8 says "Nyx ships a WebGL/Three.js renderer." The Nyx renderer is real but it is WebGPU/WebGL (@nyx/renderer-core, dev-typed against @webgpu/types) — there is no Three.js dependency or import … from 'three' anywhere in libs/nyx/. Separately, §8 says "Kalika ships a wasm-bindgen build … computed physics runs in the browser." A wasm-bindgen build does exist, but it is the CAS (computer-algebra) engine (libs/kalika/cas-engine/crates/kalika-cas-wasm, built via wasm-pack for nodejs and web targets in libs/kalika/cas-engine/scripts/build-wasm.sh). The kernels the V9 explorables actually call — @kalika/symplectic and @kalika/cosmology — are pure TypeScript with no WASM crate, and the V9 explorable libs run them in-process (Node/TS), not through a browser-WASM build. The computation is real; the in-browser WASM packaging is the planned delivery, not what these libs exercise today.
  • Localization and LMS interop are P3 backlog surfaces. The routing planner and the seven standards emitters are real, spec-conformant code with tests, but they are the plan and the artifacts; the dubbing/translation themselves are an injected Metis/Nous boundary, and institutional rollout is sequenced to P3.

Where something is provider-gated, it is a fail-loud injected seam, not a fake: the Nous local-inference tier in localization, the embodied Chiron voice/ avatar runtime, and the no-eighth-loop Iris confirmation all refuse rather than fabricate (an unavailable tier is disclosed and downgraded; an incomplete platform consolidation files a gap). Honest "planned/gated" beats fake "shipped."

The deliberate non-dependency on Bellona#

The architecture's load-bearing delivery decision is negative: V9 P1 does not take a dependency on the Bellona headless Unreal cook path. The justification is that the surfaces a learner would most want to be "immersive" are already computable in the browser:

  • The Nyx live cosmos is a real WebGPU/WebGL renderer plus a TS client (@nyx/client), so the sky runs client-side.
  • The Kalika physics the lessons teach is computed by deterministic kernels (the Friedmann solver in @kalika/cosmology, the velocity-Verlet integrator in @kalika/symplectic), which the Hephaestus explorables drive directly.

Only the game-bridge explorables — learn-by-playing inside the V2–V8 game substrates — need the engine cook path, and that work is shared with V8 and sequenced as a P2/P3 motion. This is why, of the three explorable kinds in the contract, only computed-kernel has a shipped builder: the web-native kinds are P1 and the engine-bridged kind is deferred. The embodied teacher (Chiron) does not need Bellona either — it runs through Psyche's real-time avatar/voice runtime (the libs/psyche/avatar-* libraries the Metis live-voice tutoring service provisions), and degrades gracefully when that runtime is unavailable (see Embodied delivery below). The net effect is that V9's heaviest experiences are either pure-client (Nyx), pure-deterministic-compute (Kalika), or a managed fallback (Chiron) — none of which block on the engine pipeline. The deeper accounting of which surface reuses which substrate is in ./six-layer-reuse-stack.md and ./subsystem-map-and-reuse-ledger.md.

The experience layer (L6): tested view-models#

@oshun/v9-experience is four pure functions, each projecting a domain object into exactly the data one consumer screen needs. They are deliberately not React/UI — they are the testable seam between the forge and the renderer.

The ask-a-wonder front door. buildWonderFrontDoor (libs/v9/experience/src/wonder-front-door.ts:43) takes a free-text wonder and an AtlasStore, calls the real resolveWonder (libs/v9/atlas/src/wonder-resolution.ts:86, Sophia BM25 + optional Nous dense rerank + Mnemosyne mastery-scoped prerequisite frontier), and returns the disambiguated concept, the ranked candidates, the scaffolding depth (prerequisiteCount), the prerequisite-first conceptSet a lesson would teach, and a few "surprise me" frontier suggestions. The entry point is a single wonder, not a course catalog — that is the whole product thesis. The frontier suggestions come from suggestFrontier (line 80), which walks the Atlas edges for concepts that build on something in the teaching set (inSet.has(edge.from) && !inSet.has(edge.to)) — the next thread a learner almost knows. Crucially, an unresolvable wonder does not throw at the UI: the resolveWonder call is wrapped in a try/catch (line 66) that converts a WonderUnresolvedError into a calm unresolved: true state with a null resolution, so the screen can show "we couldn't find that yet" rather than an error boundary.

The lesson player. buildLessonPlayer (libs/v9/experience/src/lesson-player.ts:61) projects a gated artifact into the four panels the player shows: the grounded narrative (beats + Socratic turns + addressed misconceptions), the "show your sources" transparency panel (every claim with its Sophia pins, credibility, and epistemic label), the explorable canvas (each kernel's params and measured reachable success state), and the active-recall retrieval checkpoint. Its first line is a refusal: if (!isV9LessonPublishable(artifact)) throw new UngatedLessonError(artifact.id) — the player cannot be built for a lesson whose seven gates did not all pass (isV9LessonPublishable, libs/contracts/src/v9/lesson.ts:246). The transparency panel is not a UI nicety bolted on; it is a direct projection of groundTruth.claims, so what the learner sees as "sources" is the same gated skeleton the forge verified. The contract anatomy is in ./lesson-artifact.md.

The personal Atlas star-map. buildPersonalAtlasMap (libs/v9/experience/src/mastery-map.ts:40) renders "what you know / what's next" as a literal sky: a mastered concept is bright (brightness = the Mnemosyne mastery ordinal over the six Dreyfus levels, so master = 1.0), an unexplored one is dark (brightness 0), and a frontier star is a dark concept whose every prerequisite is already mastered — the inviting next step (line 54, computed from atlas.prerequisitesOf(node.id).every(isMastered) with the mastered floor pinned at intermediate, line 32). It is the visual dual of the front door's scaffolding depth, and it is tested against a two-node atlas where mastering the basics lights up the dependent concept as the single frontier star.

The honest framing again: these four functions are tested data, and the screens that bind to them are the open consumer build. See the broader picture in ./overview.md.

Identity, entitlements, and billing#

Access to a lesson is a deny-by-default decision over one Oshun account's age/region attributes plus its subscription tier and grants. checkLessonAccess (libs/v9/governance/src/entitlements.ts:52) evaluates a V9LearnerProfile against a LessonAccessRequest and returns a discriminated AccessVerdict. The precedence is deliberate and tested: age-gating and region-blocks win over the paywall. A requiresAdult lesson is refused for a non-adult with a age-gate CTA (line 56) before any premium check; a blockedRegions hit returns a region-block CTA (line 63); only then does the premium branch consider tier and grants (line 70). This ordering means a minor is never offered an upgrade path to buy their way past an age gate.

The cross-product story is real here, not hand-wavy. CROSS_PRODUCT_GRANTS (entitlements.ts:91) is a concrete map from a portfolio achievement to the specific premium lesson it unlocks — v2-racing:podium-finishapplied-physics-vehicle-dynamics, meditation:7-day-streakneuroscience-of-attention, v8-detective:case-solvedlogic-and-deduction — and applyCrossProductGrant (line 98) returns a profile carrying the new grant, which then satisfies checkLessonAccess with via: 'grant'. The governance test walks exactly this path: a racing podium finish grants, and then unlocks, the physics lesson.

@oshun/v9-experience layers one more thing on top: the free-tier quota. checkBillingAccess (libs/v9/experience/src/billing-gating.ts:36) first calls checkLessonAccess; if entitlement denies (age/region/premium), that denial wins unchanged (line 43); otherwise, only for a free learner on a free lesson, it applies DEFAULT_FREE_TIER_QUOTA (5 lessons per period, line 21) and returns a cta: 'upgrade' paywall when the quota is exhausted, or the verdict plus a quotaRemaining count when it is not. The separation of concerns is clean: the entitlement graph owns eligibility, the experience layer owns metering.

flowchart TD A[Learner opens a lesson] --> B{requiresAdult and not adult?} B -- yes --> AG[deny · cta=age-gate] B -- no --> C{region blocked?} C -- yes --> RB[deny · cta=region-block] C -- no --> D{premium lesson?} D -- yes --> E{tier=plus or holds grant?} E -- no --> UP[deny · cta=upgrade] E -- yes --> OK1[allow · via=tier or grant] D -- no --> F{free tier and free lesson?} F -- yes --> G{quota remaining?} G -- no --> UP2[deny · cta=upgrade · quotaExhausted] G -- yes --> OK2[allow · via=free · quotaRemaining] F -- no --> OK3[allow · via=free] OK1 --> H{embodied tier available?} OK2 --> H OK3 --> H H -- avatar --> LA[live_avatar] H -- voice only --> LV[live_voice · disclose downgrade] H -- neither --> TX[text floor · disclose downgrade]

Governance: every forge is a governed AgentRun#

V9 does not run ungoverned. registerV9AgentFamily (libs/v9/governance/src/agent-family.ts:73) builds, deterministically, the registration that places V9 under the V1 Agentic AI Studio envelope. Its DAG is projected straight from the real Prometheus stage graph — stages 0, 1, 2, 3, 5, 6, 7, 8 (resolve, ground/solve-first, plan, write, build explorable, assess & schedule, gate, compile & cache) — each node depending on the previous one so the chain is visible, and each tagged with whether it requires an evidence artifact (STAGE_TITLES, line 55). The envelope carries budget inheritance (per-stage caps that must sum to ≤ the inherited ceiling, validated by validateBudgetInheritance, line 111), kill switches at family and provider scope with declared fallback behaviors (pause, text_only), and a replay manifest that names the determinism fields (cacheKey, skeletonHash, ledgerEntryId). The registration also asserts consumesSharedLoop: true.

That last flag is enforced, not decorative. confirmConsumesSharedLoop (libs/v9/governance/src/iris-loop.ts:41) checks that V9 declares it consumes the shared Iris loop and owns zero tool-call loops of its own — and, if the platform Iris v2 is not yet the single agentic substrate, it files a Phase-A2 gap (finding F2, line 49) rather than papering over the platform's incomplete consolidation. This is the fail-loud pattern at the governance layer: V9 honestly reports "my side adds no loop, but the platform unification is still open" instead of claiming a clean bill of health. Likewise, confirmSevenGateSuite (libs/v9/governance/src/gate-suite.ts:27) attests that the seven content-release gates run on the shared ReleaseGateService, not a bespoke per-product checker — its ok is literally gates.length === 7 over the real buildV9LessonGates. The gate semantics themselves (and a real naming divergence between two G1–G7 framings) are in ./seven-gates-and-aletheia.md.

Cross-cutting concerns#

Determinism#

Same (inputs, seed) ⇒ same lesson and same hashes; a mismatch blocks promotion. compareArtifactDeterminism (libs/v9/cross-cutting/src/determinism.ts:18) compares two artifacts that should be byte-identical across the fields that must be stable — id, seed, the skeletonHash/surfaceHash/cacheKey provenance triple, and the whole groundTruth skeleton — and returns the list of mismatched fields. verifyForgeDeterminism (line 36) runs a forge twice and diffs the results; assertDeterministic (line 44) throws on any mismatch. This is both a provenance guarantee (a lesson is reproducible) and a cost lever (a deterministic lesson is gated once per profile class and served many times), which is why determinism and cost are documented together. The Prometheus compile already derives the content-addressed hashes; this module is the verifier that proves reproducibility.

Cost discipline#

CostLedger (libs/v9/cross-cutting/src/cost-discipline.ts:20) enforces per-stage spend ceilings with no silent overspend: spend(stage, amount) (line 28) returns false — it does not absorb or clamp — when a charge would exceed either the stage cap or the inherited ceiling. Critically, any coverage truncation is logged explicitly via logTruncation (line 49): if the forge has to drop candidates or shorten coverage to stay under budget, that is recorded with a stage, reason, and dropped-unit count, so no cap is ever silent. validateStageBudget (line 59) checks that per-stage caps sum within the ceiling — the same budget- inheritance invariant the governance registration enforces, applied at run time.

Accessibility#

Accessibility is structural and gated, not optional. buildLessonA11yManifest (libs/v9/cross-cutting/src/accessibility.ts:26) constructs, from the artifact, a caption cue per narrative beat and per Socratic turn, a full text transcript of the narrative, and alt text for every explorable surface (e.g. "Interactive computed-kernel explorable: Expansion. lookback grows with z."). checkLessonAccessibility (line 57) then fails the a11y gate if any surface is uncaptioned, the transcript is empty, or any explorable is undescribed — a lesson does not silently ship inaccessible. This reuses the Metis lecture-package captioning as its base, specialized for the lesson artifact's beat/turn/explorable shape.

Champion-challenger ramp gating#

New prompt/persona/explorable variants ramp only when they are statistically better — no vibes-based promotion. twoProportionZTest (libs/v9/cross-cutting/src/champion-challenger.ts:32) runs a pooled two-proportion z-test over gold outcomes (the normal CDF via the Abramowitz & Stegun 7.1.26 erf approximation, line 21), and evaluateRamp (line 59) promotes a challenger only when the one-sided p-value is below α (default 0.05) and the lift is positive, behind a minimum-trials guard (default 100 per variant). The tests pin the behavior at both ends: 9/10 vs 5/10 does not promote (insufficient trials), while 720/1000 vs 600/1000 does (significant lift). Human review decisions are the gold set this gate consumes.

The anti-metric guard#

V9's north star is durable understanding, explicitly not time-on-app, and the code refuses at the type/runtime level to let an engagement metric become the optimization target. assertNorthStarTarget (libs/v9/cross-cutting/src/anti-metric.ts:35) throws a BannedOptimizationTargetError for any of the banned engagement traps (time-on-app, session-length, daily-active-minutes, watch-time, …, line 15, matched case-insensitively). The metric V9 does instrument, computeDurableUnderstanding (line 61), is the fraction of taught concepts a learner still holds at ≥ the intermediate floor after a retention delay — measured by retained recall (week-4 mastery), not by minutes spent. The test confirms the semantics: of four concepts retained at advanced/novice/null/ expert, exactly two clear the floor, for a score of 0.5. A product that optimizes session length has already lost the plot, and this module makes that a compile/ runtime fact rather than a slogan.

Localization and LMS interop (P3)#

planLocalization (libs/v9/cross-cutting/src/localization.ts:43) routes each BCP-47 target language to the Nous local-inference tier when Nous can serve it on-device (lower cost, on-device privacy, offline equity) and to the cloud tier otherwise, returning a per-language routing plan with local/cloud counts. The plan and routing are real; the dubbing and translation themselves are the injected Metis/Nous pipeline boundary. The institutional-interop module (libs/v9/cross-cutting/src/lms-interop.ts) emits seven real, spec-conformant ed-tech artifacts from a gated lesson: a SCORM 1.2 imsmanifest.xml (line 16), a QTI 3.0 assessment item wrapping the retrieval checkpoint (line 37), an xAPI (Tin Can) statement (line 49), an IMS Caliper event, a OneRoster line item, LTI 1.3 resource-link claims, and an Open Badges 3.0 verifiable credential (line 116). Both surfaces are P3 backlog — the artifacts are tested, the rollout is sequenced.

Embodied delivery and graceful degradation#

The embodied teacher does not fail hard when its richest runtime is missing. resolveDelivery (libs/v9/chiron/src/delivery.ts:45) walks the fallback order live_avatar → live_voice → text (line 13) from the requested tier down to the first one the session's capabilities support, with text as the guaranteed floor, and — this is the honesty rule — every downgrade is disclosed (a learner is never silently handed a lesser experience). The avatar/voice runtimes are the Psyche libraries (libs/psyche/avatar-core, …); when they are unavailable or over their latency/fidelity budget, the resolver records the reason (avatar-runtime-unavailable, voice-runtime-unavailable) and the disclosure string the UI surfaces. This is the same fail-loud-not-fake discipline that runs through the whole layer.

Failure modes and edge cases#

The delivery and cross-cutting layer is built to refuse rather than fabricate, and each refusal is specific and located:

  • Ungated lesson reaches the playerUngatedLessonError (lesson-player.ts:62); the player cannot be constructed unless all seven gate verdicts pass.
  • Unresolvable wonder at the front door → the front door catches WonderUnresolvedError and returns unresolved: true (wonder-front-door.ts:66) — it degrades, it does not throw at the UI.
  • Age/region vs paywall conflict → age-gate and region-block always precede the premium check (entitlements.ts:56/:63), so a minor is never offered an upgrade past an age gate.
  • Free-tier quota exhaustedcta: 'upgrade' with quotaExhausted: true (billing-gating.ts:48); entitlement denials still win over the quota.
  • Non-deterministic forgeassertDeterministic throws with the diverging field names (determinism.ts:44); promotion blocks.
  • Budget overspendCostLedger.spend returns false (cost-discipline.ts:28); any truncation is logged, never silent.
  • Inaccessible surfacecheckLessonAccessibility returns ok: false with the specific missing caption/transcript/alt-text (accessibility.ts:57).
  • Banned optimization targetBannedOptimizationTargetError (anti-metric.ts:35).
  • Platform Iris not yet unified → a Phase-A2 gap is filed, not hidden (iris-loop.ts:49).
  • Embodied runtime missing → fall back down the chain to the text floor and disclose the downgrade (delivery.ts:55).