# Atlas — The Knowledge Spine (Graph, Invariants, Wonder Resolution)

Atlas is V9's single largest net-new build: the unified knowledge graph that
makes "start from a wonder, pull the thread" possible. A learner types a
free-text question — _"why is the night sky dark?"_, _"how old is the
universe?"_ — and Atlas turns it into a concrete, prerequisite-ordered set of
concepts a grounded lesson can teach _now_. It is the input to Stage 0 of the
[Prometheus lesson pipeline](./prometheus-lesson-pipeline.md), the substrate the
Theia wonder-director walks to find the next, better question, and the map the
consumer front door binds to. Crucially, Atlas is **assembled, not authored**:
it unifies three knowledge graphs that already exist in the monorepo — the Metis
concept graph
(`services/metis/src/metis/services/source_concept_graph_helpers.py`), the
Sophia knowledge graph (`libs/sophia/knowledge-graph`, 15 entity types and 27
relationship types), and the Mnemosyne prerequisite graphs
(`libs/mnemosyne/core`) — and binds the domain kernels (Nyx, Kalika, Nisaba) as
_grounded leaves_. The whole engine is the package `@oshun/v9-atlas`
(`libs/v9/atlas/src`, ten source files), whose own module doc-comment states the
discipline plainly: it "composes the real engines (Sophia BM25, Mnemosyne
KnowledgeGraph) — no new graph store, retrieval, or psychometrics"
(`libs/v9/atlas/src/index.ts:1`).

Why is it built this way — content-addressed nodes, two hard invariants, and a
build-time gate? Because in V9 a lesson is a _reproducible, provenance-stamped
artifact_, and that promise can only be as honest as the knowledge it draws
from. Atlas refuses to hold an unsourced fact or a hand-typed physical constant:
every fact node must carry a vetted Sophia source pin, and every STEM value node
must carry a _reference to the computation that yields it_ rather than the
number itself, so the value is reproducible and current instead of a literal
that silently drifts (`libs/contracts/src/v9/concept-graph.ts:66`). Those two
rules are not lint suggestions — they are baked into the Zod schema, enforced
again at store insert, and aggregated by a build gate that _blocks_ on any
violation. This page is the deep companion to the architecture set; the section
hub is [overview.md](./overview.md) and the monolith is
[../V9_ARCHITECTURE.md](../V9_ARCHITECTURE.md).

## What ships, honestly

**Implemented and tested (real today).** The `@oshun/v9-atlas` engine is real
and green: 21 tests pass across `atlas-graph.spec.ts` (11),
`wonder-resolution.spec.ts` (5), and `grounded-leaves.spec.ts` (5). The pieces
are genuine compositions, not reimplementations. The `AtlasStore` enforces both
invariants at insert by parsing every node through `V9ConceptNodeSchema`
(`libs/v9/atlas/src/atlas-store.ts:61`) and **delegates all prerequisite
reasoning** — closure, topological sort, gap analysis — to the real
`@mnemosyne/core` `KnowledgeGraph` (`libs/v9/atlas/src/atlas-store.ts:113`).
Wonder resolution runs real Sophia BM25 (`createBM25LexicalIndex`,
`libs/sophia/semantic-search/src/bm25/bm25.ts:522`) and the real Mnemosyne
`identifyKnowledgeGaps` (`libs/mnemosyne/core/src/knowledge-graph.ts:432`). The
grounded-leaf binders point at real in-repo kernels —
`kalika:cosmology#ageTodayGyr` resolves through the genuine `@kalika/cosmology`
Friedmann solver, and the `grounded-leaves` test asserts the bound value really
is ≈ 13.8 Gyr (`13.6 < age < 14.0`), i.e. it checks _domain correctness_, not
just shape. The graph-unifier, source adapters, and the build-time grounding
validator are all present with real assertions. Atlas is consumed for real by
[Prometheus](./prometheus-lesson-pipeline.md)
(`libs/v9/prometheus/src/ground-truth.ts:43`), Theia
(`libs/v9/theia/src/theia.ts`), and the experience view-models
(`libs/v9/experience/src/wonder-front-door.ts:43`).

**Spec-only / not yet wired to live data.** The honest limit is upstream of the
engine: **nothing in the repo populates a production Atlas from the three live
graphs.** A search for callers of `unifyAtlas` or the `from*` source adapters
outside `libs/v9/atlas/src` returns _only_ the package's own tests and the
consumer packages' test fixtures — there is no seed job, ETL, or runtime that
drives the unifier over real Metis/Sophia/Mnemosyne exports and persists the
result. So Atlas today is a **real, tested assembly engine over an in-memory
store**, not a deployed, populated knowledge spine. This matches
[the gap analysis](../V9_GAP_ANALYSIS.md) §2.1 exactly: "the pieces exist;
unification + grounding-pin coverage is the work." The `AtlasStore` is in-memory
only (`Map`-backed); no persistence/serialization layer ships.

**Two honest spec corrections.** (1) The monolith's `ConceptNode` sketch writes
`kernelRefs: { nyx?, kalika?, ... }` — an object keyed by kernel
(`V9_ARCHITECTURE.md:88`). The shipped contract is an **array** of
`V9KernelRef { kernel, ref, params?, unit? }`
(`libs/contracts/src/v9/primitives.ts:152`), so a node can carry several kernel
bindings of the same kernel. (2) The Sophia adapter is intentionally **lossy**
on Sophia's rich relation vocabulary: of the 27 `RelationType` values, only
three are special-cased (`PART_OF → part_of`, `OPPOSITE_OF → conflicts`,
`DERIVED_FROM → specializes`); the remaining 24 collapse to a single lateral
`related` edge (`libs/v9/atlas/src/source-adapters.ts:150`). Treat the "27
relationship types" in the monolith as the _source's_ expressiveness, not what
survives unification.

## The node and edge contract

The typed vocabulary lives in `libs/contracts/src/v9/` and is re-exported flat
and under the `V9Contracts` namespace (`libs/contracts/src/v9/index.ts`). A node
is a `V9ConceptNode` (`libs/contracts/src/v9/concept-graph.ts:66`); its fields:

| Field             | Type / enum                                                                    | Notes                                                                         |
| ----------------- | ------------------------------------------------------------------------------ | ----------------------------------------------------------------------------- |
| `id`              | `V9ConceptId`                                                                  | Content-hash `cn:<64-hex>` **or** namespaced slug `<ns>:<slug>` (see grammar) |
| `label`, `aka[]`  | string (1–300), ≤32 aliases                                                    | `aka` carries wonder-match synonyms ("dark night sky", "Olbers' paradox")     |
| `wonderAxis`      | `cosmos \| laws \| mind \| meaning \| deep-time \| living-world \| made-world` | The 7 axes a wonder can enter on (`primitives.ts:42`)                         |
| `discipline`      | core-six ∪ 12 extended (18 total)                                              | Metis taxonomy placement (`primitives.ts:58`)                                 |
| `kind`            | `topic \| skill \| fact \| procedure \| principle`                             | Drives both invariants                                                        |
| `groundingPins[]` | `V9SophiaPin[]` (≤64)                                                          | `{ pinId, sourceId, sourceTitle?, locator?, credibility∈[0,1], quote? }`      |
| `kernelRefs[]`    | `V9KernelRef[]` (≤16)                                                          | `{ kernel: nyx\|kalika\|nisaba\|demeter, ref, params?, unit? }`               |
| `epistemicStatus` | `established \| contested \| one-tradition \| frontier`                        | Required on every claim (feeds the G6 gate)                                   |
| `safetyPolicyRef` | optional ref                                                                   | A Metis per-discipline curriculum-safety policy id                            |

The schema is `.strict()`, so unknown keys are rejected. The **id grammar** is
the load-bearing detail (`libs/contracts/src/v9/primitives.ts:17`): the regex
`^(cn:[a-f0-9]{64}|[a-z][a-z0-9-]*:[a-z0-9][a-z0-9._-]*)$` admits exactly two
forms — a content hash minted by the unifier, or a stable namespaced slug minted
by an adapter or leaf binder (`metis:olbers-paradox`,
`kalika:age-of-the-universe`, `nisaba:the-republic`). That is what lets the same
concept resolve identically no matter which source graph first named it.

An edge is a `V9ConceptEdge { from, to, type, weight∈[0,1] }`
(`libs/contracts/src/v9/concept-graph.ts:102`). The nine edge types unify the
three sources' relations plus one V9 addition:
`prerequisite, related, part_of, generalizes, specializes, enables, conflicts, complements, bridges`
— where **`bridges`** is the cross-axis thread V9 adds (e.g. cosmology ↔
deep-time), the structural hook the Theia director uses to walk from one wonder
to the next. The edge schema `superRefine` forbids self-loops.

## The two invariants — what makes Atlas a map of reality

The difference between Atlas and "a bag of facts" is two structural rules, both
defined once on the contract and re-checked everywhere a node can enter the
graph.

**Invariant 1 — every fact node carries ≥1 grounding pin.** A `kind: 'fact'`
node with `groundingPins.length === 0` fails to parse with the message
`Invariant 1 violated: fact node "<id>" has no grounding pin`
(`concept-graph.ts:67`). No ungrounded fact may exist.

**Invariant 2 — every STEM value node carries a kernelRef.** A node whose
discipline is in the STEM set _and_ whose kind is `fact | principle | procedure`
must carry ≥1 `kernelRef`, or it fails with
`Invariant 2 violated: STEM <kind> node ... has no kernelRef (its value must be computed, not asserted)`
(`concept-graph.ts:74`). The STEM set is exactly five disciplines —
`astronomy, physics, mathematics, chemistry, earth-science`
(`V9_STEM_DISCIPLINES`, `primitives.ts:93`) — and the gate function
`v9NodeRequiresKernel(discipline, kind)` (`concept-graph.ts:36`) is the single
source of truth for "is this node subject to Invariant 2." Topics and skills are
_organizational_ and exempt; a humanities fact is subject to Invariant 1 but not
2 (its value is textual, grounded by a pin, not computed).

These are enforced at three layers, deliberately redundant:

1. **At parse** — `V9ConceptNodeSchema.superRefine` (`concept-graph.ts:66`)
   throws.
2. **At insert** — `AtlasStore.addNode` re-parses through the same schema before
   storing (`atlas-store.ts:61`), so a node can _only_ exist in a store if it is
   already valid. The unifier-test "rejects an ungrounded fact node at insert"
   pins this.
3. **At build** — a non-throwing aggregator,
   `checkV9ConceptNodeInvariants(node)` (`concept-graph.ts:133`), returns every
   violation across a whole candidate set so the build gate can report and block
   (see the grounding gate below).

The reason for the redundancy is the unifier: when several source drafts
collapse into one node, the _merged_ node might still violate an invariant (e.g.
an ungrounded fact that no source supplied a citation for). Rather than silently
inventing a pin, the merged node is re-validated at insert and, if it fails, is
**reported as skipped, not coerced** — a logged truncation, never a fabricated
grounding (`graph-unifier.ts:9`).

## Assembling the spine: source adapters and the unifier

Each source graph "speaks its own dialect," so an adapter maps it into the
shared `V9ConceptNode`/`V9ConceptEdge` vocabulary
(`libs/v9/atlas/src/source-adapters.ts`):

- **Metis** (`fromMetisConceptGraph`, `source-adapters.ts:87`) mirrors the
  `ConceptGraphNode` dataclass shape from the Python helper
  (`source_concept_graph_helpers.py`): topic/skill/fact/procedure/principle
  kinds already match V9, and fact nodes are **grounded from their
  `evidence_citation_ids`** — each citation becomes a `V9SophiaPin` at a
  caller-supplied default credibility (0.7 unless overridden). This is the seed
  of Invariant-1 coverage. The test "Metis adapter grounds fact nodes from
  evidence citations" verifies two citations become two pins at the configured
  credibility.
- **Sophia** (`fromSophiaKnowledgeGraph`, `source-adapters.ts:163`) consumes a
  **duck-typed** `SophiaGraphStoreLike` (`getAllNodes`/`getAllEdges`), not the
  concrete `@sophia/knowledge-graph` `GraphStore` class — a structural seam that
  keeps Atlas from hard-coupling to Sophia's internals. Sophia entities
  (figures, texts, traditions) are organizational `topic` nodes with **no**
  grounding pin (they are not atomic facts), and the relation mapping is the
  lossy one noted above.
- **Mnemosyne** (`fromMnemosynePrereqGraph`, `source-adapters.ts:212`) maps real
  `@mnemosyne/core` `KGNode`/`KGEdge`: it supplies the prerequisite skeleton,
  mapping `prerequisite`/`part_of` through and skills to `skill` kind.

All three adapters are pure transforms — they build _draft_ nodes; they never
enforce invariants themselves (that is the store's job), and `mapEdges` drops
dangling/self-loop edges at the source.

The **unifier** (`unifyAtlas`, `graph-unifier.ts:96`) is the largest single
piece of net-new logic. It works in three passes:

1. **Group by content-addressed label key.** Every draft's `label` is slugged
   (`atlasSlug`: lowercased, NFKD-normalized, non-alphanumerics → hyphens, ≤100
   chars, `source-adapters.ts:49`) and the slug is hashed to the canonical id
   `cn:<sha256(slug)>` (`graph-unifier.ts:68`). Two drafts that name the same
   thing — _"Photosynthesis"_ from Metis and from Mnemosyne — slug identically
   and therefore land in the same group, **independent of namespace**.
2. **Merge each group into one node.** Grounding pins and kernel refs are
   **unioned and de-duplicated** (by `pinId` / `ref`); aliases absorb the
   _other_ drafts' labels so cross-source naming becomes searchable `aka`; and
   the kind is resolved to the **most specific** present via the priority
   `principle > procedure > fact > skill > topic` (`KIND_PRIORITY`,
   `graph-unifier.ts:30`) — so a `topic` from one source and a `fact` from
   another collapse to a grounded `fact`. The merged node is added to the store,
   which re-validates it; a failure goes to `skipped` with its reason.
3. **Remap, dedupe, and prune edges.** Every per-source id is remapped to its
   canonical id; edges whose endpoints were skipped/unknown, became self-loops
   after the merge, or duplicate an existing `(from,to,type)` are dropped and
   counted in `droppedEdgeCount`.

The result is an `AtlasUnificationResult` reporting `rawNodeCount`,
`mergedNodeCount`, the `skipped` list, and `droppedEdgeCount` — so a build can
see exactly how much collapsed and what was honestly left out. The unifier tests
pin each behaviour: same-concept collapse to one `cn:` node, an ungrounded fact
surfacing as `skipped` (not coerced), and correct edge remapping.

## Grounded leaves — references, not values

The fourth input is domain data bound as leaves (`grounded-leaves.ts`). The key
idea, and the reason Invariant 2 exists, is that a STEM node holds **a reference
to the computation, never the number**. `kalikaKernelLeaf` mints a node carrying
`kernelRef.ref = 'kalika:cosmology#ageTodayGyr'` (`grounded-leaves.ts:70`);
`nyxEphemerisLeaf` binds `nyx:ephemeris#sun.position` (`:106`);
`nisabaSourceLeaf` binds a humanities fact to a vetted source pin with no kernel
(`:139`). The leaf holds the _ref string_; Atlas itself does **not** execute
kernels.

The actual recomputation happens later, at G2, in
[Aletheia](./seven-gates-and-aletheia.md). Its kernel registry
(`libs/v9/aletheia/src/kernel-evaluators.ts:33`) is the binding from a ref to a
real in-repo computation —
`kalika:cosmology#ageTodayGyr → ageTodayGyr(PLANCK_2018_COSMOLOGY)`,
`nyx:constants#speedOfLight → SPEED_OF_LIGHT` (299 792 458 m/s,
`libs/nyx/constants/src/physical.ts:18`). The registry currently knows **six**
refs. This is a deliberate fail-loud seam: when Prometheus builds ground truth
it calls the evaluator (`ground-truth.ts:65`), and if a node references a ref
the registry can't compute, the Aletheia accuracy gate emits
`cannot recompute ...: unknown kernel ref "<ref>"` and blocks — it never passes
an unverifiable number. So the leaf binders can name refs ahead of the evaluator
supporting them; the cost is an honest G2 failure, not a silently-skipped check.

## Wonder resolution

Resolution (`resolveWonder`, `libs/v9/atlas/src/wonder-resolution.ts:86`) turns
a free-text string into the minimal, prerequisite-ordered concept set to teach
now.

```mermaid
flowchart TD
  W["free-text wonder<br/>'why is the night sky dark?'"] --> BM["BM25 lexical index<br/>over node label+aka+axis"]
  ATL[("AtlasStore<br/>ConceptNodes")] --> BM
  BM --> CAND["ranked candidates (top-K)"]
  EMB["optional dense reranker<br/>(Nous WonderEmbedder)"] -. "linear blend" .-> CAND
  CAND -->|"top-1"| TGT["target ConceptNode"]
  CAND -->|"none score > 0"| ERR[["WonderUnresolvedError"]]
  TGT --> PROJ["project Atlas → Mnemosyne<br/>KnowledgeGraph"]
  ATL --> PROJ
  MAST["learner mastery map"] --> PROJ
  PROJ --> GAP["identifyKnowledgeGaps()<br/>unmastered prerequisites"]
  GAP --> TOPO["topologicalSort()<br/>prerequisite order"]
  TOPO --> SET["conceptSet:<br/>gaps first, target last"]
```

Step by step:

1. **Lexical recall (always).** A fresh BM25 index is built over each node's
   text — `label + aka + discipline + wonderAxis` (`nodeText`,
   `wonder-resolution.ts:63`) — and searched for the wonder. BM25 is the Sophia
   engine, not a reimplementation.
2. **Optional dense rerank (the Nous boundary).** `WonderEmbedder` is an
   injected interface `embed(texts) → vectors` (`wonder-resolution.ts:21`). When
   present, every node is scored
   `(1 − denseWeight)·bm25 + denseWeight·cosine(wonder, node)` over **all**
   nodes, so a wonder with no lexical overlap ("the beginning of everything")
   can still reach the right concept. Absent ⇒ BM25-only. _Honest
   characterization:_ the fusion is a **weighted linear blend of the raw BM25
   score and the dense cosine** — not a rank-normalized fusion (RRF); the
   shipped tests exercise the pure-lexical end (no embedder) and the pure-dense
   end (`denseWeight: 1`), where the toy embedder steers "the beginning of
   everything" to the finite-age node.
3. **Disambiguation.** The top-ranked candidate is the `target`; all ranked
   candidates are returned so the front door can show alternatives. If nothing
   scores above zero (or the Atlas is empty), it **fails loud** with
   `WonderUnresolvedError` rather than inventing a concept
   (`wonder-resolution.ts:56`).
4. **Mastery-scoped prerequisite frontier.** Atlas is projected onto a Mnemosyne
   `KnowledgeGraph` annotated with the learner's mastery (`toMnemosyneGraph`,
   `atlas-store.ts:113`), and
   `identifyKnowledgeGaps(mastery, target, masteryFloor)` returns the unmastered
   prerequisites (default floor `intermediate`). Only `prerequisite`/`part_of`
   Atlas edges become prerequisite/structural KG edges; everything else is a
   lateral `related_to`, so the closure follows true dependencies, not mere
   relatedness (`toKgRelation`, `atlas-store.ts:31`).
5. **Minimal teaching set.** The gaps are ordered by the graph's topological
   sort and the target is appended, giving
   `conceptSet = [...orderedGaps, target]` — prerequisite-first, target last. A
   cyclic prerequisite graph degrades gracefully: `topologicalSort` throws, and
   resolution falls back to the given order rather than failing the whole query
   (`orderByPrerequisite`, `wonder-resolution.ts:153`).

**Worked example (from the test suite).** Build an Atlas with _How light
travels_, _The universe has a finite age_, and _Olbers' paradox_, with the first
two as prerequisites of the third. `resolveWonder('why is the night sky dark?')`
resolves to `a:olbers` (BM25 over the aliases), and with a learner who knows
nothing the `conceptSet` is the full chain `[finite-age, light, olbers]`, target
last (`wonder-resolution.spec.ts:55`). Give the learner `advanced` mastery of
`a:light` and the frontier drops to `[finite-age]`; the teaching set becomes
`[finite-age, olbers]` (`wonder-resolution.spec.ts:64`). This is the
cognitive-load discipline the product calls for, computed — not guessed.

## The build-time grounding gate

The validator (`libs/v9/atlas/src/grounding-validator.ts`) turns the per-node
invariant checks into a graph-wide verdict. `validateConceptNodes(nodes)` walks
a candidate set and returns an
`AtlasGroundingReport { ok, totalNodes, factNodes, stemNodes, groundedFactNodes, violations[] }`
(`:28`); `validateAtlasGrounding` runs it over a built store; and
`assertAtlasGrounded(atlas)` **throws** on any violation, listing each offending
node id and message (`:57`). The intent is a CI gate —
`Atlas grounding invariant violated by N node(s): ...` — so an ungrounded node
can never reach a shipped lesson. Because the store already rejects bad nodes at
insert, a built store should always pass; the validator's real value is over a
_candidate_ set before it enters the store (e.g. an offline coverage audit that
counts `groundedFactNodes / factNodes`).

## How Atlas connects to its neighbours

Atlas is a spine precisely because so much hangs off it:

- **Prometheus, Stage 0 → Stage 1.** Wonder resolution feeds the concept set
  into `buildGroundTruth` (`libs/v9/prometheus/src/ground-truth.ts:43`), which
  reads each node back out of the store: grounded fact nodes become
  `V9GroundedClaim`s carrying their pins; STEM kernel nodes become _recomputed_
  `V9KernelResult`s via the Aletheia evaluator. If the concept set has no
  grounded fact node, Prometheus **refuses to forge an ungrounded lesson**
  (`ground-truth.ts:86`). See
  [the Prometheus pipeline](./prometheus-lesson-pipeline.md).
- **Theia, the wonder-director.** `nextWonders` walks the outgoing
  `enables → bridges → specializes → related` edges to surface "the next, better
  question," and `surpriseMe` ranks unlearned concepts by the fraction of their
  prerequisites the learner has already mastered — both read straight off the
  Atlas store and its Mnemosyne projection (`libs/v9/theia/src/theia.ts:31,92`).
- **The experience layer.** `buildWonderFrontDoor` wraps `resolveWonder` into
  the tested view-model the "ask a wonder" screen binds to, including the
  scaffolding depth (`prerequisiteCount`) and frontier "surprise me" suggestions
  (`libs/v9/experience/src/wonder-front-door.ts:43`); the personal Atlas
  star-map reads the same graph. See
  [delivery and cross-cutting](./delivery-and-cross-cutting.md).

The reuse ledger for all of this — which `@sophia`, `@mnemosyne`, `@kalika`, and
`@nyx` surfaces Atlas leans on, and where the seams are — is catalogued in
[the subsystem map and reuse ledger](./subsystem-map-and-reuse-ledger.md) and
[the six-layer reuse stack](./six-layer-reuse-stack.md).

## Failure modes and edge cases

- **Empty Atlas / no match** → `WonderUnresolvedError` (fail loud, never
  invent).
- **Ungrounded merged node** → reported in `skipped` with its invariant reason
  and left out of the store; the edges touching it are dropped and counted.
- **Cyclic prerequisite graph** → `topologicalSort` throws inside
  `orderByPrerequisite`, which catches and falls back to the input order so
  resolution still returns a set.
- **Unknown kernel ref** → Atlas accepts the leaf (the ref is a valid string),
  but the downstream Aletheia G2 gate fails loud at recompute time. The binding
  is honest about absence rather than fabricating a value.
- **Edge to a missing endpoint** → `AtlasStore.addEdge` throws
  `edge endpoint "<id>" is not a node in the Atlas` (`atlas-store.ts:70`).
- **Performance shape (documented, not a bug).** `resolveWonder` rebuilds the
  BM25 index and the Mnemosyne projection from `allNodes()` on every call
  (`wonder-resolution.ts:96`, `atlas-store.ts:113`). That is O(N) per query and
  fine at the current in-memory scale; it is a known cost to revisit when a
  persisted, large Atlas ships (see the spec-only note above).

## Related

- [Architecture overview](./overview.md) and the monolith hub
  [../V9_ARCHITECTURE.md](../V9_ARCHITECTURE.md)
- [Subsystem map and reuse ledger](./subsystem-map-and-reuse-ledger.md) and
  [the six-layer reuse stack](./six-layer-reuse-stack.md) — where Sophia,
  Mnemosyne, Kalika, and Nyx plug in
- [Prometheus lesson pipeline](./prometheus-lesson-pipeline.md) — Atlas is Stage
  0
- [Seven gates and Aletheia](./seven-gates-and-aletheia.md) — the kernel
  registry and the G1/G2 grounding/accuracy gates
- [Hephaestus explorables](./hephaestus-explorables.md) — concept nodes bound to
  manipulable kernels
- [The lesson artifact](./lesson-artifact.md) and
  [delivery and cross-cutting](./delivery-and-cross-cutting.md) — what the
  resolved concept set ultimately becomes
