# The Atlas of Reality and Wonder Resolution

The Atlas is V9's unified knowledge spine — the single
`ConceptNode`/`ConceptEdge` graph that makes _"start from a wonder, pull the
thread"_ possible — and **wonder resolution** is the function that turns a
learner's free-text question (_"why is the night sky dark?"_) into the minimal,
prerequisite-ordered set of concepts a lesson should teach right now. It is the
first thing that runs when anyone enters V9: the experience layer's front door
(`buildWonderFrontDoor`), the Prometheus forge (`forgeLesson` stage 0), and
Theia's thread continuation all stand on it. The Atlas is also the only one of
V9's three net-new structures (Atlas graph, Hephaestus explorable, Lesson
artifact) that is a _graph_ — and the design doc calls it "the single largest
net-new build" (`graph-unifier.ts` header).

What keeps it buildable rather than aspirational is the same decision that runs
through all of V9: **compose the real engines, reinvent nothing.** The Atlas
owns no retrieval algorithm and no graph-reasoning algorithm. Free-text matching
is the real Sophia BM25 index (`@sophia/semantic-search`); prerequisite closure,
topological sort, and gap analysis are the real Mnemosyne `KnowledgeGraph`
(`@mnemosyne/core`). The package's own `package.json` declares exactly three
runtime dependencies — `@mnemosyne/core`, `@oshun/contracts`,
`@sophia/semantic-search` — and the six implementation modules under
`libs/v9/atlas/src/` are the connective tissue that maps three source dialects
into one content-addressed store and reads the answers back out of those
engines. This page is the deep companion to the section
[overview](./overview.md); the hub for the set is
[../V9_features.md](../V9_features.md).

## What ships, honestly

The Atlas package is **real and tested.** All six implementation modules
(`atlas-store.ts`, `source-adapters.ts`, `graph-unifier.ts`,
`grounded-leaves.ts`, `wonder-resolution.ts`, `grounding-validator.ts`) are
implemented and exercised by **three spec files holding 21 test cases**
(`atlas-graph.spec.ts` — 11, `wonder-resolution.spec.ts` — 5,
`grounded-leaves.spec.ts` — 5). The tests run the real substrates, not mocks:
`resolveWonder` actually builds a Sophia BM25 index and queries the Mnemosyne
knowledge graph, and `grounded-leaves.spec.ts` actually imports
`ageTodayGyr(PLANCK_2018_COSMOLOGY)` from `@kalika/cosmology` and asserts the
bound value lands in `13.6 < age < 14.0 Gyr` before declaring the node grounded.

Two things are **honest seams, not stubs.** The dense reranker
(`WonderEmbedder`) is the Nous model boundary: it is an _optional injected_
interface, and when it is absent `resolveWonder` runs **BM25-only** — it does
not fabricate embeddings. A wonder that matches nothing throws
`WonderUnresolvedError` rather than inventing a concept; the front-door
view-model catches that throw and reports `unresolved: true` so the UI asks for
a rephrase instead of hallucinating.

Three claims in the originating design doc are **spec-ahead-of-code, and this
page corrects them** rather than restating them:

- The monolith says _"**Theia** decides whether to answer directly, scaffold, or
  open a thread"_ (`V9_features.md` §1). The **signal** for that decision is
  computed and surfaced — `buildWonderFrontDoor` returns `prerequisiteCount`
  (described in code as "the scaffolding depth") and the full `conceptSet` — but
  there is **no single shipped three-way `answer | scaffold | thread` decision
  function**. The data a director would branch on exists; the explicit branch is
  a view-model signal, not a gate.
- `V9KernelSchema` admits four kernels (`nyx`, `kalika`, `nisaba`, `demeter`),
  but `grounded-leaves.ts` ships binders for only **three** (`kalikaKernelLeaf`,
  `nyxEphemerisLeaf`, `nisabaSourceLeaf`), and the Aletheia kernel registry
  (`kernel-evaluators.ts`) wires **no `demeter:` ref** today. A Demeter-computed
  leaf is admissible by contract but unbuilt.
- The Atlas is a **runtime structure assembled from injected source graphs**,
  not a shipped reality-corpus. The adapters, the unifier, the leaf binders, and
  the invariants are all real; the actual concept population is supplied by the
  caller (and exercised by the tests with small fixtures). There is no large
  persisted "map of reality" dataset checked into this package.

## The concept graph: nodes, edges, and two invariants

The vocabulary lives in `libs/contracts/src/v9/concept-graph.ts` and
`primitives.ts`. A `V9ConceptNode` is
`{ id, label, aka, wonderAxis, discipline, kind, groundingPins, kernelRefs, epistemicStatus, safetyPolicyRef? }`,
and the whole point of the type is that two structural rules are baked into
`V9ConceptNodeSchema` via `.superRefine`, so a node that breaks them **fails to
parse**:

- **Invariant 1 — grounding.** A `fact` node must carry ≥1 `V9SophiaPin` (a real
  source id + locator + a `credibility` in `[0,1]`). _No ungrounded fact exists
  in the graph._
- **Invariant 2 — computation.** A STEM `fact`/`principle`/`procedure` node must
  carry a `kernelRef` instead of a stored number. The STEM set is decided by
  `v9NodeRequiresKernel(discipline, kind)`, which is true exactly when the
  discipline is in `V9_STEM_DISCIPLINES`
  (`astronomy, physics, mathematics, chemistry, earth-science`) and the kind is
  one of the three value-bearing kinds. The node holds a callable ref like
  `kalika:cosmology#ageTodayGyr`, not the literal `13.8 Gyr`, so the value is
  _reproduced_ at gate time and can never silently drift.

The same two checks are also exposed as a **non-throwing** function,
`checkV9ConceptNodeInvariants(node)`, which returns every violation as a
structured `{ nodeId, invariant, message }`. That is the build-validator path:
the parsing form fails fast at the door, the aggregating form lets the Atlas
build report _all_ violations across the whole graph at once.

Identity is **content-addressed**. `V9ConceptIdSchema` accepts either a stable
namespaced slug (`nyx:olbers-paradox`, `kalika:age-of-the-universe`) or a 64-hex
content hash (`cn:<sha256>`), enforced by the regex
`^(cn:[a-f0-9]{64}|[a-z][a-z0-9-]*:[a-z0-9][a-z0-9._-]*)$`. The slug form is
what a single source mints; the `cn:` form is what the unifier mints when it
collapses the same concept seen across multiple sources (below). Edges
(`V9ConceptEdgeSchema`) carry a `type` from the nine-value
`V9ConceptEdgeTypeSchema` —
`prerequisite, related, part_of, generalizes, specializes, enables, conflicts, complements, bridges`
— and a `weight` in `[0,1]`; `bridges` is the cross-axis thread V9 adds
(cosmology ↔ deep-time), and self-loops (`from === to`) are rejected by the edge
schema's own `superRefine`.

## Building the Atlas: three dialects into one spine

V9 unifies three existing graphs, each of which speaks its own dialect, so
`source-adapters.ts` provides one pure transform per source into the shared
`V9ConceptNode`/`V9ConceptEdge` vocabulary:

- **`fromMetisConceptGraph`** (`metis:` namespace) maps the Metis concept-graph
  dataclass shape. Its `topic/skill/fact/procedure/principle` types already
  match V9 kinds, and — crucially — it **grounds fact nodes from their
  `evidence_citation_ids`**, turning each citation into a `V9SophiaPin` at a
  caller-supplied default credibility (0.7). This is the seed of Invariant 1.
- **`fromSophiaKnowledgeGraph`** (`sophia:` namespace) maps the Sophia KG's
  entities (figures, texts, traditions) to V9 **topics** — organizational nodes,
  not atomic facts, so they need no grounding pin — and translates relation
  types (`PART_OF → part_of`, `OPPOSITE_OF → conflicts`,
  `DERIVED_FROM → specializes`, else `related`).
- **`fromMnemosynePrereqGraph`** (`mnemosyne:` namespace) maps the real
  `@mnemosyne/core` `KGNode`/`KGEdge` types and supplies the prerequisite
  skeleton.

`graph-unifier.ts`'s `unifyAtlas` then does the merge that is "the single
largest net-new build." It groups every draft node by `atlasSlug(label)` (a
lowercase, NFKD-normalized, hyphenated key), mints a canonical id
`cn:<sha256(labelKey)>`, and collapses the group into **one** node: it takes the
**most specific kind**
(`KIND_PRIORITY = principle > procedure > fact > skill > topic`), unions and
de-dupes the grounding pins (by `pinId`) and kernel refs (by `ref`), folds
sibling labels into `aka`, and carries any `safetyPolicyRef` forward. The
honesty rule is explicit in the header and verified by a test: a merged node
that **still** violates an invariant (e.g. an ungrounded `fact` that no source
could ground) is **not coerced** — it is reported in `skipped` with the parse
error's reason and left out of the store, "a logged truncation, not a fabricated
grounding." Edges are then remapped onto the canonical ids, de-duped by
`(from, to, type)`, and any edge that touches a skipped or unknown endpoint — or
that became a self-loop after the merge — is counted in `droppedEdgeCount`. The
whole operation returns an `AtlasUnificationResult` with the raw vs merged node
counts so a build can see exactly how much collapsing happened.

The merged nodes land in **`AtlasStore`** (`atlas-store.ts`), and the store is
where the invariants become a runtime gate: `addNode` runs
`V9ConceptNodeSchema.parse(node)` before inserting, so an ungrounded fact or a
kernel-less STEM node is rejected _at the door_; `addEdge` refuses an edge whose
endpoints are not already nodes. The store deliberately **does not reimplement
prerequisite reasoning** — its `toMnemosyneGraph(learnerMastery?)` method
projects every node and edge into a fresh `@mnemosyne/core` `KnowledgeGraph`
(mapping `prerequisite → prerequisite`, `part_of → part_of`, everything else →
`related_to`, and annotating each node with its mastery), and `prerequisitesOf`
simply reads `getPrerequisites` back off that projection. This is the reuse
seam: closure, topo-sort, and gap analysis are the Mnemosyne engine's, run over
an Atlas projection.

Finally, `grounded-leaves.ts` is how the **real domain kernels become Atlas
nodes**. `kalikaKernelLeaf`, `nyxEphemerisLeaf`, and `nisabaSourceLeaf` each
build-and-validate a node whose `kernelRef.ref` points at an in-repo callable
(`kalika:cosmology#ageTodayGyr`, `nyx:ephemeris#sun.position`) — the node holds
the _reference_, never the value, which Aletheia recomputes at G2. The
build-time enforcement is `grounding-validator.ts`: `validateConceptNodes`
counts fact/STEM/grounded-fact nodes and collects violations, and
`assertAtlasGrounded(atlas)` **throws** on any violation so a build pipeline
blocks rather than warns. (Because the store already enforces at insert, this
gate is belt-and-suspenders: a validly-built Atlas always passes it.)

## Resolving a wonder

`resolveWonder(wonder, atlas, options)` in `wonder-resolution.ts` is the
function the rest of V9 calls. It runs four steps:

```mermaid
flowchart TD
    W["Wonder (free text)"] --> IDX["Build BM25 index over Atlas nodes<br/>doc text = label + aka + discipline + wonderAxis"]
    IDX --> BM["BM25 search<br/>(limit = max(topK·4, 16))"]
    BM --> HY{"WonderEmbedder<br/>injected?"}
    HY -->|no| CAND["Ranked candidates (BM25 score)"]
    HY -->|yes Nous| BLEND["Dense rerank over ALL nodes<br/>score = (1-w)·BM25 + w·cosine, w=0.5"]
    BLEND --> CAND
    CAND -->|empty| ERR["throw WonderUnresolvedError"]
    CAND --> TGT["target = top candidate"]
    TGT --> PROJ["Project Atlas → Mnemosyne KnowledgeGraph<br/>annotate learner mastery"]
    PROJ --> GAP["identifyKnowledgeGaps(mastery, target, masteryFloor)"]
    GAP --> TOPO["order gaps by topologicalSort (prereq-first)"]
    TOPO --> SET["conceptSet = [...orderedGaps, target]"]
```

1. **Lexical retrieval (Sophia BM25).** A fresh `createBM25LexicalIndex()` is
   loaded with one document per Atlas node, where the indexed text is
   `label + aka + discipline + wonderAxis`. The query is the raw wonder; the
   index returns up to `max(topK·4, 16)` hits scored by the real BM25 engine
   (`k1 = 1.2`, `b = 0.75`, IDF with length normalization over an inverted
   index).
2. **Optional hybrid rerank (the Nous boundary).** If an `embedder` is supplied,
   `resolveWonder` embeds `[wonder, ...allNodeTexts]` in one call and re-scores
   **every** node as `(1 − denseWeight)·bm25 + denseWeight·cosine`
   (`denseWeight` default `0.5`). This is what lets a wonder with _zero lexical
   overlap_ — _"the beginning of everything"_ — still reach the Big Bang node,
   which a pure BM25 path would miss; the `wonder-resolution.spec.ts` test
   proves exactly that with a toy embedder. Without an embedder, ranking is BM25
   alone. (Blending a saturating BM25 score with a cosine in `[0,1]` is a
   deliberate, documented approximation, not a learned fusion.)
3. **Mastery-filtered prerequisite frontier (Mnemosyne).** The target's
   transitive prerequisites are computed on the Mnemosyne projection, then
   `identifyKnowledgeGaps(mastery, target, masteryFloor)` drops any prerequisite
   the learner already holds at or above the floor (`'intermediate'` by default;
   the Mnemosyne ladder is
   `novice → beginner → intermediate → advanced → expert → master`). What
   remains is the real frontier — only what this learner still needs.
4. **The minimal teaching set.** `orderByPrerequisite` runs Mnemosyne's
   `topologicalSort` (Kahn's algorithm, deterministically tie-broken) and
   filters it to the gap set, then appends the target:
   `conceptSet = [...orderedGaps, target.id]`. The output is prerequisite-first,
   target-last — cognitive-load discipline, encoded.

**Worked example — _"why is the night sky dark?"_ (Olbers' paradox).** With an
Atlas of three nodes (`How light travels`, `The universe has a finite age`,
`Olbers' paradox`) and two prerequisite edges into Olbers, BM25 picks `a:olbers`
as the target. A learner who knows nothing gets the full frontier
`[a:finite-age, a:light]` and a `conceptSet` of length 3 ending in `a:olbers`. A
learner already `advanced` on `a:light` gets the frontier trimmed to
`[a:finite-age]` and a two-element teaching set `[a:finite-age, a:olbers]`.
These are the literal assertions in `wonder-resolution.spec.ts`.

## The front door, and the threads it opens

`buildWonderFrontDoor` (`libs/v9/experience/src/wonder-front-door.ts`) is the
tested view-model the "ask a wonder" screen binds to. It wraps `resolveWonder`
in a `try/catch`: on success it returns the disambiguated `resolved` candidate,
the ranked `candidates`, the `prerequisiteCount` ("the scaffolding depth"), the
`conceptSet`, and a few `suggestedWonders`; on a thrown `WonderUnresolvedError`
it returns a fully-formed `unresolved: true` state with empty fields, so the UI
degrades to "try rephrasing" rather than crashing or faking a concept.

The two "surprise me" mechanisms are worth distinguishing, because they are
different code with different intent. The front door's `suggestFrontier` is a
**cheap adjacency walk**: a concept that some edge leads _to_ from inside the
teaching set, but that is not itself in the set — the immediate next thing.
Theia owns the richer one: `surpriseMe` (in `libs/v9/theia/src/theia.ts`) ranks
unexplored concepts by **readiness** — the fraction of a concept's prerequisites
the learner has already mastered — surfacing "the frontier of what they almost
know." Theia also owns thread continuation (`nextWonders` walks the Atlas edges
in the priority order `enables → bridges → specializes → related`) and the
emotional arc; those are documented in
[threads-and-mastery-loop.md](./threads-and-mastery-loop.md).

The `conceptSet` does not stop at the front door. It is the seam into the lesson
forge: `V9LessonArtifact` (`libs/contracts/src/v9/lesson.ts`) carries
`conceptSet: z.array(V9ConceptIdSchema).min(1).max(64)` as part of its cache-key
digest, and the Prometheus forge's stage 0 _is_ `resolveWonder`. So the same
resolution that decides what to show on the front door also seeds what gets
forged, gated, and cached — see
[prometheus-lesson-forge.md](./prometheus-lesson-forge.md).

## Edge cases, failure modes, and configuration

The Atlas's correctness story is mostly about what it **refuses** to do:

- **Empty Atlas / no match ⇒ fail loud.** `resolveWonder` throws
  `WonderUnresolvedError` if the store has no nodes or nothing scores above
  zero. It never returns a "best guess" concept.
- **No lexical overlap ⇒ needs the Nous seam.** A wonder phrased entirely
  outside a concept's vocabulary will not be found by BM25 alone; bridging it is
  the injected `embedder`'s job, and the system is honest that the seam is
  required rather than approximating semantics with a stand-in.
- **Cyclic prerequisite graph ⇒ graceful fallback.** `topologicalSort` throws on
  a cycle (the Mnemosyne engine refuses to invent an order);
  `orderByPrerequisite` catches that and falls back to the given order rather
  than crashing the resolution.
- **Ungrounded merges ⇒ skipped, not coerced.** As above, a merged node that
  cannot satisfy an invariant is dropped into `skipped`; the unifier never
  back-fills a fake pin or kernel to force it through.
- **Dangling / self-loop edges ⇒ dropped, counted.** Both the adapters and the
  unifier drop edges with a missing endpoint or `from === to`, and the unifier
  surfaces the count in `droppedEdgeCount` so truncation is visible.
- **The `demeter` gap is admitted, not hidden.** The contract permits a Demeter
  kernel, but no binder or evaluator exists yet — a STEM leaf needing Demeter
  would have to wait on that wiring, which the registry will reject loudly (an
  unknown kernel ref is a G2 accuracy-gate failure) rather than silently
  passing.

**Configuration** is the `WonderResolutionOptions` bag: `learnerMastery` (the
`ReadonlyMap<V9ConceptId, MasteryLevel>` that drives gap filtering), `topK`
(default 5), `masteryFloor` (default `'intermediate'`), `embedder` (the Nous
boundary), and `denseWeight` (default 0.5, clamped to `[0,1]`). One
implementation detail worth knowing: because `V9ConceptId` and Mnemosyne's
`KGNodeId` are both opaque string ids, the store performs a **structural cast**
between them when it projects into the engine. That cast is sound here precisely
because the Atlas's content-addressed ids are stable and unique across sources —
the same property the unifier exists to guarantee.

## How it connects

The Atlas is the substrate every other V9 subsystem reads from. Aletheia
recomputes the values its `kernelRef`s point at (G1/G2/G6); Hephaestus and
Prometheus consume the `conceptSet`; Theia walks its edges for threads and
"surprise me"; the mastery loop feeds learner `MasteryLevel`s back in to
re-scope the next wonder. For the surrounding mechanics:

- [overview.md](./overview.md) — the product and the five nested primitives
- [subsystem-map-and-gates.md](./subsystem-map-and-gates.md) — the ownership
  ledger and the seven-gate mechanics
- [prometheus-lesson-forge.md](./prometheus-lesson-forge.md) — the nine forge
  stages (stage 0 is `resolveWonder`)
- [chiron-and-hephaestus.md](./chiron-and-hephaestus.md) — the teacher and the
  explorable workshop
- [threads-and-mastery-loop.md](./threads-and-mastery-loop.md) — Theia threads,
  the emotional arc, and FSRS mastery feeding back into wonder-scoping
- [braid-commons-and-films.md](./braid-commons-and-films.md) — the science↔human
  braid, Agora, and explainer films
- [governance-and-boundaries.md](./governance-and-boundaries.md) — entitlements,
  the anti-metric, determinism, and the non-goals
- Hub: [../V9_features.md](../V9_features.md)
