Skip to content
🚧 Early alpha — building the foundation. See the roadmap →

Primitives and vocabulary — the adversarial review, in plain language

Created Updated

Four decisions (P7, P12, P13, P14) about what Crosswalker calls its own building blocks were held back from ratification on 2026-08-19 and sent to two independent adversarial reviews, on the grounds that the primitives layer is load-bearing and a wrong call there is expensive to unwind.

All four recommendations turned out to be wrong in some way. Holding them back was the right call. This page explains each one without jargon, with the trade-offs made explicit.

P12 — “we said five building blocks but really there are nine”

Section titled “P12 — “we said five building blocks but really there are nine””

There are two different questions you can ask about an import, and they have different answers. The confusion is that both answers got called “primitives.”

Question 1: where does the note go? A row in a spreadsheet has to become a note somewhere. The ways to place and address it:

WayWhat it means
folderthe note goes in a folder named from this column
filethis column becomes the note’s filename
headingthis becomes a heading inside a parent note
tagreserved, not built yet
wikilinkreserved, not built yet

That is five, and it is a closed list on purpose. It is what makes “given a recipe and a concept, compute its address” a single, predictable step.

Question 2: what can I send a column to? Once a note exists, a column’s value can also fill in things that are not locations at all:

DestinationIs it a location?
folder, name, headingyes — these are questions 1’s mechanisms
tag, property, aliasno — these are labels and fields on the note
linkno — this is a relationship to another note
bodyno — this is text inside the note
noteno — this decides whether a separate note gets made at all

That is nine. The workbench shows nine because nine is the honest answer to question 2.

These are not competing counts of one list — they are different axes. The architecture already says so, in the commitment itself: the grammar is five layout mechanisms x ordered layout x also_emit x graph_edges. That is a product of dimensions, not a flat inventory. Asking “so how many primitives are there?” is like asking how many cells a spreadsheet has without saying whether you mean rows or columns. The honest answer names the axis first.

That the public claim of “five” is false because nine exist, and the docs should be restated on a nine-primitive palette.

The claim is false, and it is a category error. Verified directly against the machine contract, not the docs:

  • spec/recipe.schema.json defines the mechanism list as a closed set of exactly five.
  • The generated TypeScript has exactly five variants.
  • The renderer handles those five and rejects anything else.
  • The schema file says, in its own text, that body is “not a sixth layout mechanism.”

And two of the “nine” are not new things at all:

  • name is file. Same mechanism, different label in the UI.
  • link is not wikilink. One emits a relationship; the other is a reserved addressing mechanism. Different jobs.
  • note cannot even be written into a portable recipe — it blocks serialization today.

Only three of the nine are layout mechanisms. The rest are content, and content was always a declared, separate dimension of the design.

The real defect is different, and it is real: the docs assert twelve different counts in different places (3, 4, 5, 6, 6, 9, 9, 8, 7, 13, ~40, and “12 senses”), almost none of which say what they are counting. That is what makes the docs feel contradictory — not a broken contract.

OptionCostRiskWhat you get
Defend five, scope every countOne docs pass. No code, no schema changeNone to codePublic claim becomes true and legible. Every count says what it counts
Declare nine canonicalEither rewrite the schema so nine is actually true (large, breaks anyone already emitting recipes) or publish docs that contradict the codeHighNothing identified
DeferFree todayReaders keep hitting contradictory numbersNothing

Keep five, and fix the counts by saying what each one counts: five layout mechanisms (three built, two reserved), nine workbench destinations, four hierarchy carriers, eight query verbs, nine graph-change operations.


P13 — the word “primitive” means five different things

Section titled “P13 — the word “primitive” means five different things”

Yes — this is mostly a communication problem, and the intuitive fix is the right one. But one part of it is not just words, and that distinction is the whole decision.

The word “primitive” is currently used for: layout mechanisms, workbench destinations, query verbs, view settings, and change operations. A reader meeting the bare word cannot tell which is meant.

HalfWhat it isRisk to change
Words in the docsProse saying “primitive” where it should say “layout mechanism” or “query verb”None. Reversible, no code
Names inside saved filesThe query.primitives field written into saved recipes, and TablePrimitives in the schemaReal. Renaming a field breaks recipes people already saved, and needs a version migration

The proposal bundled both halves and assumed they could ride one schema version bump. That plan does not work, because it names the wrong file — TablePrimitives lives in the recipe schema, not the preset schema. There are actually three independent renames, on three different compatibility clocks, one of which breaks saved recipes.

Also worth knowing: the proposal is titled “give it exactly one meaning,” but what it actually delivers is qualified names — several meanings, each clearly labelled. That is the correct fix. The title oversells it.

The “breaks saved recipes” concern assumed there are saved recipes in the wild. There are not. The project is pre-alpha, unannounced, and has no external users, which the owner confirmed on 2026-08-19. Nothing outside this repository consumes the schema URI or the field names.

That collapses the entire compatibility argument. What is left to update is inside the repo and mechanical:

What changesScope
Prose in the docsA sweep
query.primitives field name and the $defs names in the recipe schema33 files across src/, spec/, tests/, recipes/ reference the term
Bundled recipes10 import recipes plus 7 query recipes; regenerated, not hand-edited
Fixturesbun run fixtures regenerates them
OptionCostRiskWhat you get
Full sweep now: words and field namesOne coordinated pass over 33 files plus a fixtures regenLow — no external consumers exist, and the fixture-drift gate catches missesThe names finally match what the things actually are, with no migration debt carried forward
Words only, defer field namesA docs sweepNoneDocs read correctly while the file format keeps the misleading names
Defer everythingFreeThe word stays ambiguousNothing

Do the full sweep now, field names included. The window where renaming is free closes the moment someone saves a recipe outside this repo. Renaming now costs one coordinated pass; renaming later costs a versioned migration and a compatibility shim forever.


P7 — cutting the internal vocabulary from 16 names to 7

Section titled “P7 — cutting the internal vocabulary from 16 names to 7”

What “pruning” actually means in practice

Section titled “What “pruning” actually means in practice”

This is the question that was never answered, and without it the decision is undecidable. Pruning a noun does not delete anything from the product, and changes no code. It changes one thing only:

A kept nounA cut noun
Has a glossary entry with a definitionHas no glossary entry
Is used deliberately and means the same thing everywhereIs just ordinary English; you can still write the words
Is a candidate to become a real type or schema in codeIs not
A reader is expected to learn it before the docs make senseA reader never has to look it up

So the real question per term is: is this a thing with its own boundary that a reader must learn, or is it a handy phrase? Handy phrases are free. Defined terms cost the reader.

That reframes the instinct “if these are just useful terms to describe things, I do not see the problem” as exactly right, with one caveat: a capitalized, defined term implies a thing with a boundary. When the boundary is not real, readers waste effort hunting for a distinction that does not exist. That is the cost, and it is the only cost.

Grouped by the job it performs, rather than by whether it survives:

The jobTermsVerdict
Decides whether something is valid — the rulebook a file is checked againstSchema ContractKeep
Claims something is true about the world — content someone asserts and can be held toKnowledge Set (a body of asserted content, e.g. NIST CSF 2.0), MappingSet (one published crosswalk release), MappingAssertion (one row inside it)Keep all three
Specifies what should happen — instructions, before anything runsRecipe (the reusable instructions), Recipe binding (those instructions pinned to one exact input)Keep both
Records what did happen — evidence after a runExecution RecordKeep (planned contract)
Moves bytes to someone elsePackage (the resolved release), Package Manifest (the signed list of what is inside), Carrier (the zip or folder it travels in)Keep 2, cut Carrier
Derived views over data you already haveLive view (computed on read), Materialized snapshot (computed once and stored), Projection (the mapping from notes into the query database)Keep all three, qualified
Reusable starting points — a shipped default you copy and adaptRecipe templateKeep (not a root artifact)
Not separate things at allRecipe specification, Recipe executionCut both

12 keep, 1 defer, 3 cut = 16. Package is the defer: it is probably real, but its boundary against Package Manifest is not settled until v0.1.8 defines both.

The two that are not separate things, and the one that is

Section titled “The two that are not separate things, and the one that is”
  • Recipe specification vs Recipe — the same thing. “Specification” only means the Recipe written out in canonical form. There is no second artifact. Cut.
  • Recipe execution — an event, not something you can hold. The durable facts about a run belong to Execution Record. Cut.
  • Recipe template — genuinely different, and genuinely used. A template is a shipped starting point you copy; a specification is the finished thing in canonical form. The template one is backed by real code. Keep as a defined term, not as a root artifact.

And Carrier: the “vague” read is correct. It means “the zip or folder the bytes travel in.” That deserves one sentence of warning — signing a folder name is not the same as signing its contents — but it does not need to be a defined term. Cut the capital C, keep the sentence.

Why the proposed seven were the wrong seven

Section titled “Why the proposed seven were the wrong seven”

The proposal kept Projection, Live view, and Materialized snapshot — all derived views, and the authority page itself says Projection “does not gain independent semantic authority.” Meanwhile it cut Execution Record and Package Manifest, the two primary artifacts of v0.1.8, the next milestone. That is backwards: it drops the things with authority and keeps the things without it.

It also cut Knowledge Set, the only term separating “this file is valid” from “NIST actually says this.” Those are different claims, and merging them lets “passes validation” quietly start to read as “is authoritative.”

OptionCostRiskWhat you get
Selective prune (12 keep, 1 defer, 3 cut)A modest docs passLowThree genuinely empty terms stop costing readers; nothing the next milestone needs is lost
Prune to 7Re-introduce two terms next milestone across docs, schemas, types, UI labels, and testsAudit and replay code may encode the wrong boundary permanentlyA smaller number, for its own sake
Defer all pruningFreeThree empty terms keep costing readersPrune once later against real contracts

Selective prune, on the rule above: cut the terms that name no boundary, keep the ones that do.

P14 — splitting the glossary into two pages

Section titled “P14 — splitting the glossary into two pages”

The terminology page is hard to read. The proposal assumed the cause is length and prescribed splitting it into a short reader glossary plus a deep internals reference.

The cause is not length. Measured:

  • Current page: 221 lines. Proposed reader page: about 180 lines. That is 19% shorter — not a transformation.
  • The split would require migrating 215 inbound links across 79 files, 89 of them in historical logs that should not be rewritten at all.
  • It would create a third competing vocabulary authority, alongside the existing UI lexicon — exactly the collision problem P13 exists to remove.

What actually makes the page hard to trust is that five of its claims are stale, including two that contradict settled commitments (it still discusses a database engine that was rejected, and still presents a renamed concept under its old name).

A third option neither the card nor the review proposed

Section titled “A third option neither the card nor the review proposed”

The owner suggested it on 2026-08-19, and it is better than both: organize one page by audience, and link the deep internals out to the pages that own them.

The split proposal assumed you must choose between “one page that serves nobody” and “two pages that serve two audiences.” That is a false choice. Audience separation is a structural problem, solvable with sections; it does not require a second URL.

ShapeReader finds their terms?Link migrationVocabulary authorities
One page, undifferentiated (today)No — reader terms buried among internals—2 (terminology + UI lexicon)
One page, grouped by audienceYes — reader section first, internals belowNone2
Two pagesYes215 links3

The “link internals out” half matters as much as the grouping. Today the glossary duplicates definitions that live on concept pages, which is why it is both long and prone to drift — two copies, one of which nobody updates. Replacing those with a one-line gloss plus a link to the owning page shortens the page and removes the drift source. That is the mechanism the split proposal was reaching for without naming.

OptionCostRiskWhat you get
One page, grouped by audience, internals linked outFix five stale rows, regroup by audience, replace duplicated definitions with gloss + linkNone — URL and anchors unchangedReaders find their terms; the drift source is removed; no second authority
Trim in place, keep current orderFix five stale rowsNoneCorrectness, but reader terms stay buried
Split into two pages215 link migrations, anchor-compatibility work, a third vocabulary authorityTwo pages to keep in sync; still stale unless content is fixed anywayA 19% shorter first page
DeferFreeStale claims keep misleading readersNothing

One page, grouped by audience, internals linked out to their owning concept pages. Revisit a split only if the corrected page is still unwieldy after v0.1.8, by which point the vocabulary will have stopped moving.


DecisionOriginal recommendationAfter review
P12 primitives countRestate on nineKeep five; scope every count. The “exceeded” claim is false
P13 the word “primitive”Full sweep, one schema bumpFull sweep now, field names included. The migration premise was wrong, but so was the caution: there are no saved recipes to break
P7 vocabulary 16 → 7Prune to sevenSelective prune: 12 keep, 1 defer, 3 cut. The seven cut next milestone’s primary artifacts
P14 split the glossarySplit after P7/P12/P13One page grouped by audience, internals linked out. Length was never the problem