# The Lesson Artifact — Delivery Contract

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](../V9_ARCHITECTURE.md), and the orientation page is
[./overview.md](./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](#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 `V9ReferenceId`s.

## 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 `G1`…`G7`. 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](./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.

```mermaid
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-supplied** —
`compileLesson` 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](./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:

- The forge that fills it:
  [./prometheus-lesson-pipeline.md](./prometheus-lesson-pipeline.md) (Stages
  0–8) — the artifact is its Stage-8 output.
- The gates that must pass first:
  [./seven-gates-and-aletheia.md](./seven-gates-and-aletheia.md) — the deep
  treatment of G1–G7 and the Aletheia thin-policy layer.
- The verified skeleton's inputs:
  [./atlas-knowledge-graph.md](./atlas-knowledge-graph.md) (grounded fact
  nodes + kernel refs) and the reuse story in
  [./six-layer-reuse-stack.md](./six-layer-reuse-stack.md) and
  [./subsystem-map-and-reuse-ledger.md](./subsystem-map-and-reuse-ledger.md).
- The embedded interactives:
  [./hephaestus-explorables.md](./hephaestus-explorables.md) — what makes
  `explorables[*].successState.reachable` true.
- Determinism, cost discipline, accessibility, and the Theia continuation:
  [./delivery-and-cross-cutting.md](./delivery-and-cross-cutting.md).
- Orientation and the honest build status: [./overview.md](./overview.md) and
  the hub [../V9_ARCHITECTURE.md](../V9_ARCHITECTURE.md).
