# How content projects into the canonical model
Projecting content into a canonical model is not a metaphor. It names a
specific pipeline, a specific identity scheme, and a specific set of edges. This
unit walks all three, using Cogitave's own Core as the worked example, from its
[Core architecture spec](../../../../core/docs/architecture.md).
## The node it becomes
Content lands as one of the model's typed nodes. In Cogitave's Core, for
instance, that is a `Doc` (a Diataxis-typed docs page), an `Article`, a
`Standard`, an `ADR`, or - for this very platform - a `Unit`, `Module`, or
`LearningPath`. Each type has its own JSON Schema, but all share one base schema
and one identity rule, so a standard and a learning unit are queried the exact
same way.
## Two identifiers, two jobs
Every node carries two orthogonal identifiers. The **`uid`** is identity: an
immutable, dotted, globally unique name in one flat namespace across the whole
estate (Cogitave's own read `cogitave..`, e.g.
`cogitave.core.architecture`), so a URL can change under it without breaking a
single link. The **`contentHash`** is version: a content-addressed digest of the
canonicalized payload, the same Merkle-DAG idea Git and IPFS use - a changed
leaf changes only the hashes on the path back to the root, and two byte-identical
nodes are stored once. (Vector embeddings are deliberately excluded from
`contentHash`, so swapping an embedding model never looks like a content edit.)
## The edges that link it in
A node alone is inert; a **closed set of edge types** is what makes it part of
the graph. Cogitave's Core fixes **eleven** of them, and content-authoring hits
four most: `xref` (a citation, `` or `@uid`, traversed both ways),
`partOf` (composition - a `Unit` is `partOf` a `Module`, which is `partOf` a
`LearningPath`; this module's own five units are wired this way), `appliesTo` (a
`Standard` governs a `Repo`), and `teachesSkill` (a `Unit` teaches a `Skill`).
The vocabulary is closed on purpose: a fixed set of labels keeps traversal and
faceting stable as the estate grows.
## The pipeline: ACQUIRE to PUBLISH
A source blob becomes a node through a fixed pipeline; Cogitave's Core runs
**ACQUIRE → PARSE → ENRICH → INDEX → EMIT → PUBLISH**. Each acquired blob is
hashed; an unchanged blob short-circuits, and
a changed one recomputes its node's `contentHash` and marks only the
*transitively affected* nodes dirty via the `xref`/`include` edge DAG - the same
on-demand, memoized recompute discipline as `salsa` and Adapton. Schema
validation and broken-xref/link checks are a **blocking** gate: if it doesn't
validate, it doesn't publish. On a green merge, three materializations rebuild
as pure functions of the graph - a UID-keyed catalog, a graph store for
traversal, and a hybrid search index - swapped in atomically behind a new
content root.
## Served identically to humans and agents
Once published, every node is an MCP **resource** - in Cogitave's Core, addressed
as `cogitave://{type}/{id}` (for example `cogitave://doc/cogitave.core.architecture`).
`docs_fetch` returns the node's `uid`, `type`, `title`, rendered content, and its
`contentHash` and `lastModified` - the same [MCP interface](../../../../core/docs/mcp-interface.md)
a human's search box calls underneath. `describe_schema` lets an agent read the
closed vocabulary before it queries, and `resources/subscribe` plus
`notifications/resources/updated` mean a client learns about a changed node
instead of polling for it.
> [!TIP]
> "How does a doc become a queryable node" now has a precise answer: it is
> hashed, typed, given a stable UID, linked in through the closed edge set, and
> served through `docs_fetch`/`resources/read` - the same path whether the
> caller is a person's browser or an agent's MCP client.