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

System model — the whole thing, in pictures

Updated

Status last verified: 2026-09-04 — verified against src/ module layout (config/, export/, generation/, import/, render/, settings/, source/, tier2/, types/, ui/, utils/, validation/, views/) and the roadmap’s locked architectural commitments.

Crosswalker takes whatever shape your data ships in — a control spreadsheet, a crosswalk export, a JSON ontology — and turns it into ordinary Markdown notes in your vault. Then it makes those notes answerable. Everything else on this page is detail on that one spine:

In one line each: Sources are your raw records → Ingest applies an ImportRecipe (the rules for turning rows into notes) → Tier 1 is the real copy, plain Markdown on disk → Tier 2 is a throwaway SQLite cache that makes queries fast → Query + output give you live views, snapshots, reports, and exports.

Two ways in — a wizard, or a recipe file

Section titled “Two ways in — a wizard, or a recipe file ”

There are two doors, and you pick whichever fits — you don’t use both. Either door runs the same engine and produces the identical vault.

Most people use Door A (the wizard). Door B exists for automation, bulk runs, and agents. Both run the same ImportRecipe through the same generation engine (UI first, config as escape hatch).

In the formal vocabulary: the ImportRecipe is a Recipe artifact adjacent to generation — its Recipe Schema Contract governs the instruction, and the Tier 1 Schema Contract governs the generated records. The recipe is never stored inside the vault as ontology content.

What’s the real copy, and what’s just a cache

Section titled “What’s the real copy, and what’s just a cache”

The single most important rule: the Markdown in your vault is the real thing. Everything else is either upstream of it or rebuilt from it. Here’s an actual note it produces:

Tier 0 · Sourcethe spreadsheet or export you started from
↓  import  ↓
DE.AE-02.md
title: Adverse events analyzed
function: DE
category: DE.AE
Potentially adverse events are analyzed…
Tier 1 · your vault — plain Markdown, git-trackable, the authoritative copy
↓  project  ↓
Tier 2 · sqlite sidecara query cache — deletable · rebuilds from Tier 1 · never the source
  • Delete the cache and nothing is lost — the sidecar rebuilds from the Markdown. If it’s missing, stale, or corrupt, the projector regenerates it.
  • Nothing authoritative lives only in Tier 2. That’s what makes the vault portable and the plugin optional.
  • The rules are written down, not implied — a Schema Contract governs what a valid note looks like, and a separate one governs what a valid recipe looks like.

Detail: system architecture · representation tiers.

Anything can write the notes — not just this plugin

Section titled “Anything can write the notes — not just this plugin”

The wizard and the engine are conveniences, not gatekeepers. Because the note format itself is the contract, any tool that writes conforming notes is a first-class producer.

Plugin wizard
Headless harnessthis repo’s tools/
External producerPython, dbt, …
Agent / MCP server
→
Tier 1 Schema Contractgoverns emitted
record validity
→Tier 1 vault

This is the schema-as-primitive commitment — the whole reason the system is modular. Deep dive: ETL and import.

This is the engine room. You give it two things and it hands back a finished note — where it lives and what’s written in it:

  • one record — a row from your source, pinned to a stable id. Below it’s a biological species; it could just as easily be a security control, a product, or a city. (Formally: a ConceptIdentity.)
  • your rules — which source field becomes the folder, the filename, the tags, the properties, the links. (Formally: an ImportRecipe.)

The function that does it is render() — one pure function, render(Recipe, ConceptIdentity) → Address, and the only place layout decisions live.

Concept · one source record
common_name:Lion
lineage:Mammalia;Carnivora;Felidae
species:Panthera leo
family:Felidae
status:Vulnerable
habitat:Savanna
Recipe · your rules
folderƒsplit(lineage, ”;”)
↳ split a packed key into parts
file{common_name}.md
taghabitat/{habitat|lower}
notespecies · status
link[[{family}]]
→
render()applies the
recipe
→
Mammalia / Carnivora / Felidae / Lion.md
species:Panthera leo
status:Vulnerable
tags:[habitat/savanna]

family:[[Felidae]]

One row just got used five different ways at once — folder depth from the lineage column, the filename from common_name, a tag from habitat, two properties, and a [[wikilink]] from family. Structure comes from whatever you point at it, not from one magic id column. The five things a rule can produce:

folder file heading tag wikilink

Most rules are just fill-in-the-blank templates — {field} with a small closed filter set ({habitat|lower}, plus slug · tagsafe · fs-safe · truncate). The folder rule above does one thing more: split(lineage, ”;”) is a transformation expression (JSONata) that breaks one packed field into three parts. That’s the everyday case for coded taxonomies — a lineage string, a Dewey number, AC-2, DE.AE-02 — where the hierarchy is hiding inside a single column. Anything past the closed filters (conditionals, lookups, concatenation) escapes the same way.

Splitting a column gives you parts. It says nothing about nesting. You choose whether those parts become folders at all — the same [Mammalia, Carnivora, Felidae] renders four different ways, and you pick the one your team actually works in:

folder · nested dirs
Mammalia/
Carnivora/
Felidae/
Lion.md
tag · flat facets
Lion.md
#clade/mammalia
#clade/carnivora
#clade/felidae
heading · one-file outline
# Mammalia
## Carnivora
### Felidae
Lion
wikilink · graph edges
[[Mammalia]]
→ [[Carnivora]]
→ [[Felidae]]
→ Lion

Folder = browse it in the file explorer. Tag = flat and faceted, so one note can sit under many axes at once. Heading = one reference document with an outline. Wikilink = parent/child as graph edges, no folders at all.

And you don’t have to choose only one: also_emit runs mechanisms in parallel, so an import can be a folder tree and a tag facet at the same time — folders to browse, tags to slice (Ranganathan-style faceted classification). Because the mechanism set is closed at five and render() is the only place (level + mechanism) → address is decided, re-pointing the mechanisms re-shapes the whole vault without touching the engine.

Where this is written down: terminology (what Concept, Recipe, Address mean) · recipe.schema.json → $defs.layout_entry (the machine contract — mechanism, template, ordered layout) · v0.1 schema spec (the same, in prose) · hierarchy primitives (the 5 mechanisms) · conventions (template + filter naming rules) · mapping into Obsidian (source → vault) · target-structure synthesis (why render() is the single coupling point).

The readers — one per shape your data arrives in

Section titled “The readers — one per shape your data arrives in ”

A reader is a front-end to render(), never a second engine — and the same readers back the wizard’s parsers (one reader, two doors). Between them they produce the only two things a vault holds: concepts (the notes) and relationships (the links between them):

Source shapeWhat a reader doesProduces
Flat table — rows in a CSV or spreadsheetone row → one conceptconcept notes
Nested document — records inside a tree (JSON, at an iterator path)one record → one conceptconcept notes
Mapping table — one source id against many target idseach pair → one typed edgerelationship (crosswalk) notes

The shapes are general. The current named implementations in tools/ — generate-fixtures plus the crosswalk-from-* extractors — happen to target the compliance launch corpus; see the ingestion dev log for those specifics and the readiness board for status.

A vault is just notes and the typed links between them — a graph, in plain Markdown. You query that graph with a small closed set of operations, then draw the result in whatever shape answers the question:

Concepts
+ edges
the graph
→8 primitivesfilter · traverse
aggregate · diff …
→view shapestable · pivot
graph · timeline
→
views & reportstables · graphs
exports
  • Query primitives — the closed 8-verb set every view is composed from.
  • View shapes — how the same data draws as a table, pivot, graph, etc. (the “four dials”: join × shape × layout × view).

A Bases-backed answer is a live view over current data. A saved point-in-time result is a materialized snapshot. Neither changes which copy is authoritative — the Markdown still is.

What an answer means is domain-specific — that’s the application layer, not the system model. The same primitives produce a coverage matrix, a gap list, a dependency graph, or a standard-format export, depending on the question you ask. Example (compliance launch use case): they compose into the Control / Assurance / Risk lenses — a data-morphology move (traverse → summarize → reshape → draw). A different domain frames its own.

The same power from the UI or a recipe file

Section titled “The same power from the UI or a recipe file”

Every step of the active Shape workflow is reachable from both surfaces by design; later lifecycle capabilities still have named parity gaps (UI parity audit):

UI surface (Obsidian)wizard · query commands · crosswalk import · reset
both reach…
Recipe / CLI (escape hatch)ImportRecipe JSON · tools · automation
→
same engine
+ schema

The recipe file is the portable path for automation, agents, and power users. The UI edits that same file rather than keeping a second, private configuration model — which is why anything you can click, you can also script. Principle: UI first, config as escape hatch.

Crosswalker’s vocabulary describes four different dimensions. Keeping them separate stops a Recipe, a Tier, a layer, and a lifecycle capability being read as the same kind of thing.

DimensionQuestion answeredExamples
Artifact roleWhat kind of governed thing is this?Schema Contract (the rules); Knowledge Set (the content); Recipe (the instructions); later Execution Record and Package Manifest
Representation tierWhere does knowledge live, and which copy wins?Tier 0 source; Tier 1 canonical Markdown; Tier 2 derived SQLite cache
Logical layerWhich part of the system is doing the work?Import, storage, projection, query, export, audit
Lifecycle capabilityWhat is the user actually trying to do?Acquire, Shape, Connect, Understand, Share, Maintain

Tier 1 is therefore a representation and authority tier, not the root artifact. The ImportRecipe sits beside generation: its Recipe Schema Contract governs the instruction, the Tier 1 Schema Contract governs the generated records.

This page is the hub; each concept goes as deep as you want: