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

Mapping into Obsidian — from external data to vault primitives

Updated

Crosswalker’s front half is a morph: take disparate external data — a NIST control spreadsheet, an OLIR crosswalk, a JSON ontology — and turn it into Obsidian’s own native structures: folders, notes, headings, tags, frontmatter properties, and [[wikilinks]]. Nothing proprietary — just a vault you’d be happy to open without the plugin installed.

This page covers that front side: from a source row to a note on disk. The hard part isn’t writing files — it’s doing the morph systematically and modularly, so the same source can land in different shapes, the rules are reusable across frameworks, and re-imports are safe. To make that possible we built a named convention at each step.

Everything maps onto these six native Obsidian structures. The first four are the structural primitives (the four Obsidian primitives covers them in depth — and why each is the right or wrong carrier for a given ontology); file (the leaf note) and frontmatter (properties) are the other two building blocks.

PrimitiveCarriesExample
Folderpath-based, single-parent hierarchyFrameworks/NIST-800-53/AC/
File (.md)one note per concept (the leaf)AC-2.md
Headingintra-file hierarchy## Account Management
Tagpolyhierarchy, location-independent#framework/nist/ac
Frontmattertyped properties (Bases-queryable)control_id: AC-2
Wikilinkthe graph — typed relationships[[AC-2]], mapped_to: "[[ISO-A.9.2.1]]"

The wikilink row above is the one that needs unpacking, because a link can mean two quite different things and Crosswalker keeps them apart:

  • Concept ↔ concept — a crosswalk: “these two concepts correspond.” NIST 800-53/AC-2 ↔ ISO 27001/A.9.2.1. Both ends are ontology concepts, so this is the link type every import produces whenever you map one framework or taxonomy onto another.
  • Your document → a concept — an evidence link: “this note demonstrates that concept, at such-and-such status.” This is one common use, not the assumed one — it’s what GRC teams reach for (a policy note pointing at the control it satisfies), and the same shape serves any “artefact attests to concept” pattern: a paper citing a method, a dataset backing a claim, a service implementing a spec. If your ontology work never involves attesting to anything, you’ll only ever use the first kind.

Both have committed architectures. Side by side:

Both are typed links — the umbrella term. Crosswalks land through the reserved wikilink / graph_edges grammar (see what’s wired vs. reserved below); evidence links are authored by you, in your own notes, and never overwritten by a re-import.

The conventions — source row to note on disk

Section titled “The conventions — source row to note on disk”

Each step of the morph, the convention that controls it, and where it’s specified. (This is the front half of the conventional systems; the contract is where it hands off to the rest of the pipeline.)

StepThe conventionIn one lineSpecified in
Parse — source → structured datatransformation primitivesa closed ~40-primitive catalog (slugify, split, regex, tree-from-flat, joins, type-coerce) that turns messy input into structured dataetl-and-import
Morph — structured data → vault shapethe recipe grammara closed 5-mechanism grammar — folder | file | heading | tag | wikilink × ordered layout × also_emit × graph_edges — saying which Obsidian primitive carries whatCh 22 deliverable · synthesis
Address — concept → place + linksrender(Recipe, ConceptIdentity) → Address + template filters (source: src/render/template.ts FILTERS)one pure, deterministic function turns a concept + recipe into its path, heading anchor, wikilink target, tags, and frontmatter — the single coupling pointimport-engine-design
IdentityCURIE / ConceptIdentityevery concept has one stable id (nist-800-53:AC-2) independent of where it’s storedterminology
Contract — what lands on diskTier 1 schema (schema-as-primitive)the canonical Markdown+frontmatter spec — plus managed/user_preserve ownership, the _crosswalker provenance block, and STRM/SSSOM for crosswalk edgesv0.1 schema spec · the reframe

The grammar is fully specified; only part is wired in v0.1. The line matters — it’s where the front-side rough edges live.

Conventionv0.1Status
folder / file / heading mechanisms✅ wired—
template filters (source: src/render/template.ts FILTERS)✅ wired—
managed / user_preserve frontmatter✅ wired—
tag / wikilink as layout mechanisms📐 schema-reservedv0.2
graph_edges (concept ↔ concept links)📐 schema-reservedv0.2
body templates (note body recipe-driven)⛔ not yetlater
identity normalization (AC-01 vs AC-1, reserved chars)🟡 named, undecidedfront-end work
front-end ETL (raw xlsx → rows)🛠️ hand-wired per sourcetooling/doc gap

So the “morph onto wikilinks” you’d expect for crosswalk navigation is the reserved wikilink / graph_edges part of the grammar — designed, not yet built. (It’s why crosswalk-edge CURIEs don’t yet click through to their concept notes.)

Status last verified: 2026-10-31, scoped to standalone mapping filenames and Run again. Checked against src/import/sssom-importer.ts, src/render/template.ts, src/mappings/mapping-recipe.ts, the stack guards, spec/recipe.schema.json and synthetic unit tests. This naming change has not yet been visually verified in Obsidian.

New standalone mapping sets use names such as T1059.001 · M1036 (a1b2c3d4).md. The endpoint IDs stay readable, and a short hash of the assertion’s identity distinguishes otherwise equal names. The filename does not convey direction or predicate; read the note’s properties for those facts. Each endpoint stem is bounded to 60 UTF-16 code units by the existing truncate(60) filter (source: src/import/sssom-importer.ts READABLE_SSSOM_FILE_TEMPLATE). Very long destination folders can still approach Windows path-length limits.

A recipe (saved instructions for layout and content) defines the name. Anyone authoring or generating a recipe can change its naming template and compose the same filters over any available source field. The default removes the first ID prefix, limits its length, makes a link-safe name, supplies unnamed for an empty stem, and adds a stable short hash. The general filters name-safe, default(text) and hash(N) are documented in the schema spec. They do not change existing fs-safe, truncate, or slug outputs.

Existing sets keep their names and retained original recipe. There is no automatic rename or migration. A new readable set also keeps its chosen template through refresh and supported notes/table conversion; converting a newly imported standalone table into notes uses that table’s retained recipe. This default does not change child mapping sets emitted by a framework recipe.

Stack Run again accepts a known original recipe in either notes or table form. Table headers use the normalized recipe hash, and saved stack facts may use its raw equivalent. Only the pair from the same known naming template agrees; unknown hashes, ambiguous history and legacy/readable cross-pairs still refuse refresh. Framework recipe guards are unchanged.

Status last verified: 2026-10-31 — this section checked against src/mappings/mapping-recipe.ts, src/mappings/mapping-conversion-recipe.ts, src/mappings/mapping-conversion.ts, the conversion preparation in src/generation/generation-engine.ts, the release allocator in src/mappings/mapping-set.ts and synthetic engine tests. Native UI conversion and refresh were verified on synthetic sets in real Obsidian on 2026-10-31.

A recipe (the saved instructions for layout and managed content) belongs to the mapping set, not to the currently bundled defaults. New direct-table imports retain their complete normalized original. Stamped standalone refresh and supported note/table conversions select that exact original and preserve its recorded effective hash plus complete-document digest. These are distinct values: a valid older runtime hash can precede normalization, but only an exact supported producer/full-document comparison proves that exception. The complete-original preservation boundary follows portable recipe fidelity.

Conversion edits only a temporary clone to transport recorded review/user fields; it does not file that clone as a new original. The current supported transport is one mapping file per row, with layout, title, properties and managed body reproducible from the recorded table inputs. Source shaping, unavailable template inputs, enrichment and other unproven dependencies refuse conversion before a marker is written. User body prose that a table cannot carry also blocks conversion instead of disappearing.

Before writing its marker, conversion prepares the complete output with the same rendering used for execution, including each mapping note and release record destination. A custom layout that gives two assertions the same normalized address refuses before creating either output. Incompatible destinations and duplicate release records also refuse rather than being overwritten or cleaned up.

Resume compares existing output against that source-derived preparation, including reviews, reviewer, carried user fields, tags, body and release metadata. Unchanged files retain their bytes, and missing expected output may be completed. An edited target stops the conversion without changing its phase back to writing. Safe current-text checks protect updates, with source and target checks repeated at write and phase boundaries; this is not a whole-vault atomic transaction.

During an interrupted conversion, the source selects the recipe until the verified target enters retirement; partially retired or missing source artifacts never choose a different revision. An active conversion blocks ordinary refresh. Corrupt or unreadable partial artifacts may independently block recovery, but never authorize automatic overwrite/deletion as repair. Finish or recover the existing conversion rather than starting another refresh.

Missing or corrupt originals, conflicting assertion revisions and wholly unstamped history fail closed. Restore the exact original from backup or import into a new set; no recipe-repair UI or automatic migration is implied. Child producer compatibility and wholly unstamped history remain outside this slice; stamped legacy standalone recipes have synthetic byte-preserving refresh coverage. See the delivery boundary in the changelog and the mapping storage design.