Metis · Architecture

The Lesson Artifact — Delivery Contract

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

10sections14 minread1diagram2tables

On this page

The Lesson artifact is the single thing V9's pipeline produces and the single thing the rest of the system consumes: the compiled, cacheable, provenance-stamped output of the Prometheus forge. Everything upstream — the Atlas wonder-resolution, the solve-first grounding, the HTN plan, the constrained writer, the Hephaestus explorable, the seven gates — exists to mint this object, and everything downstream — the player, the personal star-map, the Mnemosyne review loop, the C2PA ledger — reads it. It is defined as a real Zod schema, V9LessonArtifactSchema, in libs/contracts/src/v9/lesson.ts (249 lines), and it is deliberately small and strict: a .strict() object with a top-level .superRefine that enforces referential integrity, plus a isV9LessonPublishable predicate that is true only when all seven gate verdicts pass (lesson.ts:246). This page is the contract-side companion to the architecture set; the monolith hub is ../V9_ARCHITECTURE.md, and the orientation page is ./overview.md.

The defining design fact — the reason the contract is shaped the way it is — is the skeleton/surface split, the same discipline V8 used for fair detective cases. groundTruth is the verified skeleton: grounded claims and kernel-computed numbers that are authoritative and immutable once the lesson clears its gates. narrative and media are the regenerable surface: the prose, the Socratic turns, the visuals — re-skinnable over the same skeleton without re-verifying anything. The contract makes the split machine-checkable by hashing the two halves separately: skeletonHash over groundTruth and surfaceHash over narrative + media, both content-addressed sha256 (libs/v9/prometheus/src/compile.ts:39-45). That one decision is what makes the artifact simultaneously trustworthy (the skeleton is gated truth, never an LLM's invention) and cheap (a popular lesson is gated once and served many times; a different Chiron persona re-skins the surface without touching the verified core). The doc-comment at the top of lesson.ts:1-12 states this in the source itself.

What ships, honestly#

The contract and its compile/cache machinery are real and tested; the pipeline that fills the contract is real but has an honest model boundary; and a few of the monolith's field types are aspirational shorthand that the code collapses to opaque references. This section separates the three.

Implemented and tested (real today). V9LessonArtifactSchema is a genuine strict Zod schema with enforced invariants, exercised by libs/contracts/src/v9/__tests__/v9-contracts.spec.ts — it parses a complete lesson and reports it publishable, rejects a claimRef out of range (spec asserts /out of range/), requires ≥1 explorable (the Definition of Done), and flips isV9LessonPublishable to false when any gate fails. The compile/stamp path (compileLesson, cacheKeyFor, skeletonHashOf, surfaceHashOf, previewProvenanceBundle, the in-memory ledger and cache) is real and deterministic. The Prometheus pipeline composes Stages 0–8 end-to-end into a publishable artifact (libs/v9/prometheus/src/pipeline.ts:111) and its spec proves the delivered artifact, the determinism property (same seed → identical hashes), the blocked path (a wrong number fails G2 and is never cached), and the fail-loud path (no writer wired). The cross-cutting determinism verifier and the lesson-accessibility manifest both operate over a real V9LessonArtifact (libs/v9/cross-cutting/src/determinism.ts:18, accessibility.ts:57).

Provider-gated (fail-loud seams, not stubs). The artifact's surface needs a generation provider. The writer that realizes the skeleton into prose is an injected boundary; with none wired the forge throws LessonWriterNotConfiguredError rather than fabricating (libs/v9/prometheus/src/write.ts:78). The pipeline test supplies a deterministic test writer (no LLM), so the end-to-end run is real but the content is test-authored. Likewise the media block is caller-supplied and defaults to empty visuals (pipeline.ts:213): the artifact carries references to Isis visuals, a Euterpe score, and an explainer film, but V9 does not itself mint those assets here — they are the provider-gated remainder.

Spec-vs-code drift (named honestly). The monolith §6 sketch types the media and persona fields as branded refs (IsisAssetRef, EuterpeRef, ChironPersonaRef, ExplainerFilmRef). The shipped contract collapses all of these to the single opaque V9ReferenceId string (lesson.ts:205, MediaSchema at :100) — they are stable reference ids the artifact does not itself resolve. Two smaller deltas: the monolith shows media.score as required, but the contract makes it optional (score: V9ReferenceIdSchema.optional(), lesson.ts:103); and the contract adds a schemaVersion: 'v9.lesson.1' literal and a richer provenance bundle the sketch omits. None of these are stubs — they are an honest, narrower realization of an aspirational sketch.

The dual-representation caveat (important). V9 ships two lesson objects and two G1–G7 gate schemes. The canonical one is V9LessonArtifact here, gated by the Prometheus gate composer whose G1–G7 match the monolith §5 exactly. The second is a simpler V9Lesson (libs/v9/lesson-gates/src/index.ts:56) gated by the @oshun/v9-lesson-gates suite over the shared platform release-gate service — and that suite assigns different meanings to the same gate ids. Both are real and tested; the section The seven-gate verdict block reconciles them honestly so a reader is never misled about what "G4" means in a given file.

The skeleton/surface split, in the contract#

The split is not a convention — it is the top-level shape of the schema. The verified skeleton is a single groundTruth object; the regenerable surface is narrative + media; and the determinism guarantees ride on hashing those halves independently.

groundTruth — authoritative, immutable post-gate#

V9GroundTruthSchema (lesson.ts:58) holds two arrays:

  • claims: V9GroundedClaim[] (1–256). Each grounded claim (lesson.ts:31) is text (≤2000 chars) bound to ≥1 Sophia pin (pins.min(1)), an epistemicStatus label (established | contested | one-tradition | frontier), and an Aletheia truth verdict (true | false | unverified). The min(1) on pins is the G1 invariant expressed in the type: an ungrounded claim cannot even be constructed. The doc-comment is blunt — "an ungrounded claim blocks."
  • computedValues: V9KernelResult[] (0–256). Each kernel result (lesson.ts:44) is the computed scalar a STEM claim must match: a kernelRef, a value, an optional unit, a relative tolerance (default 1e-6), and a contentHash — the sha256 of the full computed output, the reproducibility binding. This is V9's "solve-first" rule made into data: a node does not store "age of the universe = 13.8 Gyr," it carries the kernel computation that yields it (see primitives.ts:152, V9KernelRef).

Because the skeleton is the only authoritative half, it is the half the determinism verifier compares field-by-field (compareArtifactDeterminism checks groundTruth directly, plus the two hashes, determinism.ts:31).

narrative + media — the regenerable surface#

V9NarrativeSchema (lesson.ts:91) is beats (1–64 prose strings), socraticTurns (each a mentor/learner turn with claimRefs indexing real claims), and misconceptions (each a misconception + grounded correction). The crucial property is that every surface assertion traces back to a skeleton claim: a Socratic turn's claimRefs and a misconception's claimRef are integer indices into groundTruth.claims, and the artifact's .superRefine (lesson.ts:218-242) walks every one of them and rejects any index ≥ nClaims. The surface can be regenerated freely, but it can never assert something the skeleton has not grounded. V9MediaSchema (lesson.ts:100) is visuals[] + optional score + optional film, all opaque V9ReferenceIds.

Anatomy of the artifact#

The full V9LessonArtifactSchema (lesson.ts:190) is the union of the two halves plus the binding, assessment, scheduling, governance, and continuation fields:

Field Type / shape Notes
schemaVersion literal 'v9.lesson.1' Version pin; not in the monolith sketch.
id V9ReferenceId Derived lesson:<cacheKey[0:16]> at compile (compile.ts:167).
seed int Determinism seed — same (inputs, seed) ⇒ same artifact + hashes.
conceptSet V9ConceptId[] (1–64) The Atlas concept set the lesson covers.
learnerProfileClass string (1–120) Cache by profile class, not learner id.
groundTruth { claims[], computedValues[] } The verified skeleton (immutable post-gate).
narrative { beats[], socraticTurns[], misconceptions[] } Regenerable surface; every ref traces to a claim.
mentor V9ReferenceId (// ChironPersonaRef) Opaque persona ref; resolution is elsewhere.
media { visuals[], score?, film? } Opaque asset refs; score/film optional.
explorables V9Explorable[] (1–8) ≥1 required — the DoD (lesson.ts:209).
assessment { retrievalCheck{prompt,claimRef}, masterySignal } Active-recall item + Mnemosyne mastery signal.
schedule { checkpointRef, intervalDays, nextReviewAtIso } Mnemosyne spaced-review schedule.
gates { G1..G7: { pass, evidence[] } } The seven contract verdicts (lesson.ts:149).
provenance V9C2PABundle Hashes + cache key + ledger id + signing time.
thread { nextWonders: V9ConceptId[] } Theia's continuation — the next, better questions.

The embedded V9Explorable (explorable.ts:104) is itself gate-bearing: it declares a successState whose reachable flag is set true only after the target was demonstrably reached (by a kernel result or a headless verifier), and a computed-kernel explorable must be grounded by a kernelRef, not a Sophia pin (explorable.ts:115-124). isV9ExplorableShippable is just successState.reachable. So "ships ≥1 explorable" means "ships ≥1 explorable whose success state was actually demonstrated" — the artifact cannot carry a blind-asserted interactive.

The seven-gate verdict block#

The gates field is a V9Gates object (lesson.ts:149): seven { pass, evidence[] } verdicts, one per gate id G1G7. The composer that fills it (libs/v9/prometheus/src/gates.ts:132, composeGates) maps each gate to a real evaluation, and the mapping is a faithful realization of the monolith §5 table:

Gate Meaning (contract / Prometheus) Source of the verdict
G1 Grounding — every claim has ≥1 Sophia pin Aletheia evaluateGrounding (aletheia.ts:48)
G2 Accuracy — STEM values recomputed & matched Aletheia evaluateAccuracy (recompute vs assert, tol-bounded)
G3 Pedagogy — prereq coverage + misconception + flow band evaluatePedagogy (gates.ts:36)
G4 Completeness — explorable reached its success state Hephaestus evaluateExplorableDoD (gates.ts:133)
G5 Quality — LLM-as-judge (structural default) evaluateQuality over a QualityJudge boundary (gates.ts:88)
G6 Safety — curriculum policy + epistemic labels Aletheia evaluateSafety (aletheia.ts:51)
G7 Provenance — C2PA hashes + ledger entry present evaluateProvenance (gates.ts:105)

Aletheia owns only the three truth-adjacent gates (G1/G2/G6); it is "a gate, not a grounding engine" — a thin policy over Sophia's pins and the Nyx/Kalika kernels — and the deep treatment lives in ./seven-gates-and-aletheia.md. The contract's isV9LessonPublishable is the AND of all seven pass booleans (lesson.ts:246); the pipeline computes blockedGateIds as the gates that failed and returns without an artifact if any did (pipeline.ts:232-237), so a blocked lesson is never compiled, never cached, never delivered.

Honest reconciliation of the two gate schemes. A reader who opens libs/v9/lesson-gates/src/index.ts will find a different G1–G7. That suite gates the simpler V9Lesson over the shared @oshun/content-release-gates service (V9's "no eighth loop" rule — reuse the platform gate engine, don't fork one), and it labels: G1 truth, G2 grounding, G3 teachability, G4 safety, G5 explorable, G6 adaptive-difficulty, G7 retrieval (index.ts:110-172). It is equally real and tested — but its gate ids do not line up with the contract's. The two are not contradictory implementations of one spec; they are two parallel tracks. The V9LessonArtifact.gates block on this page is always the §5-faithful scheme. When you see "G4" in the lesson-gates package it means safety; when you see it in prometheus/gates.ts or this contract it means completeness. The distinction matters and is exactly the kind of drift this page exists to flag.

How an artifact is minted#

The mint happens in compileLesson (compile.ts:152), the last stage (Stage 8) of the forge. It is pure and deterministic: given the verified skeleton, the realized surface, the explorables, the assessment/schedule, and the gate verdicts, it derives the provenance bundle, parses everything through V9LessonArtifactSchema (so a malformed artifact throws before it can be cached), writes an immutable ledger entry, and stores the result under the profile-class cache key.

sequenceDiagram participant Forge as forgeLesson (pipeline.ts) participant GT as buildGroundTruth participant Gate as composeGates participant Compile as compileLesson (compile.ts) participant Cache as LessonCache participant Ledger as GenerationLedger Forge->>GT: resolve + solve-first (Stages 0–1) GT-->>Forge: groundTruth {claims, computedValues} Forge->>Forge: plan → write → explorable → assess (Stages 2–6) Forge->>Gate: Aletheia G1/G2/G6 + Hephaestus G4 + G3/G5/G7 Gate-->>Forge: V9Gates {G1..G7} alt any gate fails Forge-->>Forge: return {blocked:true, blockedGateIds} (no artifact) else all pass Forge->>Compile: skeleton + surface + gates + seed Compile->>Cache: get(cacheKey) alt cache hit Cache-->>Compile: cached artifact (servedFromCache=true) else miss Compile->>Compile: skeletonHash, surfaceHash, cacheKey, parse() Compile->>Ledger: append(immutable entry) Compile->>Cache: set(cacheKey, artifact) end Compile-->>Forge: {artifact, cacheKey, servedFromCache} end

A subtle correctness check closes the loop: after compiling, the pipeline re-asserts the contract's own isV9LessonPublishable against its gate sum and throws if they disagree (pipeline.ts:257-260) — the contract and the composer must never drift apart at runtime.

Determinism and the profile-class cache#

Determinism is both a provenance guarantee and a cost lever, and both ride on three content-addressed digests in compile.ts:

  • cacheKeyFor(conceptSet, profileClass, seed) (compile.ts:31) hashes the sorted concept set, the profile class, and the seed. Sorting makes the key order-independent in concepts, so [a, b] and [b, a] collide on purpose. The id and ledger id are derived from this key (compile.ts:117,167), so the same inputs always name the same lesson.
  • skeletonHashOf(groundTruth) and surfaceHashOf(narrative, media) (compile.ts:39-45) bind the two halves independently — this is what lets a re-skin change surfaceHash while skeletonHash stays fixed.

The cache is keyed by profile class, not learner id (lesson.ts:197): a popular wonder asked by ten thousand learners in the same profile class is forged, gated, and signed once, then served from LessonCache.get (compile.ts:155-163, servedFromCache: true). The pipeline spec proves this directly — the first forge reports servedFromCache: false, the second true, same id.

The guarantee is enforced, not assumed. compareArtifactDeterminism (determinism.ts:18) runs a forge twice and diffs id, seed, the two hashes, cacheKey, and the whole groundTruth; assertDeterministic (determinism.ts:44) throws to block a promotion on any mismatch, and its spec confirms a divergent run lists skeletonHash among the mismatches (cross-cutting.spec.ts:101).

One precise limitation worth stating: skeletonHash covers groundTruth and surfaceHash covers narrative + media, but neither hash covers explorables, assessment, schedule, gates, or thread. The explorable carries its own contentHash inside its provenance (explorable.ts:91), and the assessment/schedule are deterministic functions of the skeleton plus the caller-supplied clock — so the design choice is that the two top-level hashes bind truth and prose+media, while the interactive binds its own determinism separately. The determinism verifier compensates by comparing groundTruth structurally, but it does not diff the explorable trajectory; that is the explorable's own content-hash job.

Provenance: the C2PA bundle and the immutable ledger#

provenance is a V9C2PABundle (lesson.ts:163): skeletonHash, surfaceHash, cacheKey, a ledgerEntryId, per-asset assetManifests, and a signedAtIso timestamp. The signing time is caller-suppliedcompileLesson and forgeLesson both take signedAtIso/nowUnixMs as inputs rather than reading a clock (compile.ts:139, pipeline.ts:58-60), so the forge never fabricates time. The ledgerEntryId is itself derived from the content, not the run (ledger:<cacheKey[0:32]>, compile.ts:117), which is what keeps the whole artifact byte-identical across reruns of the same inputs.

The generation ledger is append-only and immutable by construction: InMemoryGenerationLedger.append silently no-ops if the entry id already exists ("immutable: never overwrite", compile.ts:67-70). It is an interface (GenerationLedger) so a durable backend can replace the in-memory default without touching the compile logic. The pipeline spec asserts the ledger holds an entry under the artifact's provenance.ledgerEntryId after a successful forge (pipeline.spec.ts:131).

The continuation thread#

thread.nextWonders (lesson.ts:178) is Theia's contribution: up to sixteen concept ids naming the next, better questions this lesson opens. It is not decorative — it is how V9 turns a lesson into a thread (Olbers' paradox → the age of the universe → the CMB → the Big Bang). The values come from the real Atlas graph: nextWonders(atlas, from) (libs/v9/theia/src/theia.ts:31) walks the graph's enables → bridges → specializes → related edges in priority order to surface continuations, and surpriseMe (theia.ts:92) ranks unlearned concepts by the fraction of their prerequisites the learner has mastered — "the frontier of what they almost know." The pipeline passes these through as input.nextWonders (pipeline.ts:252); the full director is covered in ./delivery-and-cross-cutting.md.

Edge cases and failure modes#

The contract and the forge are uncommonly explicit about refusing to fabricate. The behaviours below are all real and (where noted) test-pinned:

  • No writer wired ⇒ fail loud. realizeNarrative throws LessonWriterNotConfiguredError (write.ts:78); the pipeline spec asserts the rejection. The surface is never invented.
  • No grounded fact ⇒ refuse. buildGroundTruth throws "no grounded fact node in the concept set; refusing to forge an ungrounded lesson" (ground-truth.ts:86) — a lesson with nothing to ground is blocked at Stage 1.
  • claimRef out of range ⇒ unparseable. The artifact .superRefine rejects any Socratic-turn, misconception, or retrieval-check ref that does not index a real claim (lesson.ts:218-242; spec asserts /out of range/). The writer-side check in realizeNarrative (write.ts:87-93) catches the same class earlier with a louder error.
  • Zero explorables ⇒ unparseable. explorables.min(1) (lesson.ts:209) enforces the DoD; the spec confirms [] throws.
  • Any gate fails ⇒ blocked, never cached. The pipeline returns without an artifact and the spec confirms a G6 denial leaves the cache empty (pipeline.spec.ts:169-173).
  • A wrong number ⇒ G2 blocks. Because STEM values are recomputed and matched within tolerance (accuracy-gate.ts), a drifted literal fails accuracy and the lesson is blocked — the spec drives exactly this path.
  • Determinism mismatch ⇒ promotion blocked. assertDeterministic throws (determinism.ts:44).

Connections#

The artifact is the seam between every V9 subsystem, so it cross-links the whole set: