Mapping storage: two forms, notes or one table, pinned per set
1. In 30 seconds
Section titled “1. In 30 seconds”A mapping set (plain: one published crosswalk release, a list of “this concept corresponds to that concept” rows) used to become one note per row. That is right for review and Bases, and wrong for a set with tens of thousands of rows. Crosswalker now offers two storage forms: Notes (unchanged, still the default) and Table (one .mapping-table.tsv file that opens in any spreadsheet). The form is chosen once, when the import set (plain: the collection of files one import owns) is minted, and a refresh keeps it. Switching forms later will be a resumable conversion job, not an edit.
2. The problem, with an example
Section titled “2. The problem, with an example”Take a synthetic mapping set from framework demo-a to framework demo-b with 40,000 rows. In Notes form that is 40,000 crosswalk edge notes, each with about 16 managed fields. Every one is a vault entry to index, sync and back up; on a shared network drive a refresh touches all 40,000. The same set in Table form is one file with 40,000 lines.
The code survey behind the decision found that the note is the unit of storage everywhere: the query database projects one file to one mappings row, the SSSOM and OLIR exporters walk mapping notes, and the stack completion screen counts notes. Ownership is whole-note per import set. So a smaller footprint needed a real second form, not a tweak.
Three options were weighed:
| Option | Files for the example | Keeps per-row detail | Cost |
|---|---|---|---|
| A. Links written onto framework notes | 0 new, but rewrites framework notes | No, or only in the query database | New per-key ownership; refresh churns framework notes; reverses two logged decisions (the edge-as-note model and “a crosswalk column writes nothing into the concept note”) |
| B. One table file per mapping set | 1 | Yes, full fidelity | Loses Obsidian-native views for that set |
| C. One note per source concept, rows grouped | Hundreds to low thousands | Only as parallel arrays, not queryable per row | Partial Bases support |
B was recommended. The owner accepted B and kept today’s form beside it.
3. The owner’s direction (2026-09-29, in substance)
Section titled “3. The owner’s direction (2026-09-29, in substance)”- Portability is the problem. Notes are portable inside Obsidian (Bases, backlinks, graph). A standard table is portable outside it (spreadsheets, SSSOM tools). Neither wins for every user, so ship both.
- Locked per set. The form is fixed when the set is minted and recorded in its provenance, the same way identity schemes are fixed. A refresh keeps it.
- No notes on demand in table form. One source of truth per set: review happens in the table (or in a later plugin view), or the set is converted to notes.
- Lossless both ways. Every managed and user-owned field has a place in both forms.
- Conversion only if resumable and fault tolerant. An interruption at any step must be continuable by rerunning, the source is never deleted before parity is proven, and a half-finished conversion is never silently rolled back.
- Default stays Notes. Table is opt-in.
4. The decision
Section titled “4. The decision”| # | Decision | Why |
|---|---|---|
| 1 | Two forms, notes and table, recorded as _crosswalker.import_set.mapping_form (Tier 1 schema 1.6.0, optional; absent means notes) | Existing sets are never rewritten to gain the field |
| 2 | Pinned at mint, kept on refresh. A refresh through the wrong form is refused with an action (“Convert the set instead”) | Refresh is chosen, never guessed: the plugin never switches form on its own |
| 3 | Default Notes. Opt-in through the Store as choice on the stack review screen and the standalone mapping import, pre-filled from the Default mapping storage setting | UI first; no profile concept exists yet, so a setting is the opt-in |
| 4 | The table is a Crosswalker ledger that is SSSOM-shaped, marked by a crosswalker_format header. predicate_id holds the STRM predicate; sssom_predicate holds the original SKOS predicate. The standards-pure export stays the SSSOM exporter | Keeping STRM in place needs no lossy reverse mapping on read |
| 5 | The file header carries the set’s whole provenance block once | A table set has no notes, so the header is the only record that the set exists |
| 6 | Row identity is a hash of the assertion facts (subject, predicate, object, modifier, mapping set id) plus an occurrence ordinal ordered by assertion facts only. Never a file path | Identity, never path. Review columns are excluded from the ordering so a review edit never renumbers a sibling |
| 7 | Review columns (review_status, reviewer, notes) live in the file and are carried across refresh by row id. Rows the source dropped are removed and counted | A refresh that wiped reviews would be the user-preserve regression in table form |
| 8 | Discovery is by the .mapping-table.tsv suffix, never by folder. A set seen as both notes and a table, or in two tables, is refused by name | The conversion job will lean on this refusal |
5. Trade-offs, stated at the point of choice
Section titled “5. Trade-offs, stated at the point of choice”| Lost in Table form | Why |
|---|---|
| Bases views for that set | Bases reads only Markdown file properties |
| Graph view, backlinks, and the endpoint wikilinks | Obsidian resolves links only inside .md files |
| Per-mapping notes to open, tag or link | None exist |
Gained: one vault entry per set, a file any spreadsheet opens, review columns in the file. When Table is selected the review screen says: “One file that opens in a spreadsheet. These mappings will not appear in Bases views, graph view or backlinks.” The setting description says the same.
6. What shipped and what remains
Section titled “6. What shipped and what remains”| Slice | PR | What it delivered |
|---|---|---|
| 1. Codec and form pin | #21 | Pure table codec with a fixed column order, deterministic output, and lossless note-to-row-to-note conversion; the mapping_form type, reader and validation (unknown values refused, never coerced) |
| 2. Read everywhere | #22 | Provenance in the table header; occurrence-aware row ids; one shared reader used by the query database projector, the SSSOM and OLIR exporters, and import-set discovery; a bad row still counts its set and blocks pruning; schema 1.6.0 and the provenance writer stamp the pin |
| 3. Write and choose | #23 | Table writer with read-back verification; the importer branch covering stack, wizard and every mapping source kind; refresh with review carry-over; set-qualified file names when two sets map the same pair; Store as on the stack and the standalone import; the setting; file counts treat a table as one file |
| 4. Resumable conversion | #24 | Convert a mapping set between notes and a table, in either direction, from Installed stacks; a visible <set-id>.converting.json marker records the phase; the new form is written and verified row by row (identities, assertion facts, every review and user field, tags and titles) before the old form retires to the vault trash; Cancel is available until retirement begins; an interrupted conversion is picked up by a startup notice or the Finish interrupted mapping conversions command |
| 5. Table review view | #25 | A windowed grid for reviewing a table-form mapping set: search, filter and sort, status counts, bulk edit of status or reviewer, and verified debounced saves that reload and reapply pending edits if the file changes underneath; opens from the Review mappings button on a table set in Installed stacks, the Review a mapping table command, or the .mapping-table.tsv file directly through the Open mapping tables in Crosswalker setting |
Remaining:
- Table rows and the identity index (ruled 2026-09-30): table rows are reconciled inside their file by
row_id; the identity index stays markdown-only; import-set discovery is the existence authority for both forms. A#row_idclaim in the index would reach the note rename step, so guard tests pin that the two never meet. - Edge curies and the Tier 1 curie pattern (ruled 2026-09-30): the recorded gap was false, since both edge-curie sanitizers emit a subset of the pattern. The real failure was a source
subject_idorobject_idthat is not a curie, which failed at write time with a schema message; the importer now refuses those rows early, by row number and value, in both forms, and nothing is written. - Inline crosswalk columns on a framework slot stay as notes for now.
Related
Section titled “Related”- Terms: mapping set, import set, crosswalk, terminology
- Architecture: system architecture (Tier 1 storage and the projection layer both forms feed)
- Machine contract: v0.1 schema spec, what is pinned to the set
- Rules applied: import set = ownership, refresh is chosen, re-import is identity reconciliation
- Prior decisions kept intact: evidence-link edge model synthesis, link sets record their producer
- Vocabulary: SSSOM, STRM, SKOS
- Delivery detail:
CHANGELOG.md[Unreleased]