v0.1.4 shipped — Junction notes + crosswalk edges
What shipped
Section titled “What shipped”Milestone v0.1.4 — Junction notes + crosswalk edges. Status flipped to ✅ in the milestone hub.
After v0.1.4, the plugin produces all three Tier 1 shapes — concept notes, junction notes (evidence links), crosswalk edges (cross-ontology mappings) — through one unified pipeline. The same render() function, the same also_emit.frontmatter.managed template engine, the same provenance writer, the same merge semantics. The discriminator that selects which Tier 1 shape to produce is a single field on the recipe layout: kind.
| Surface | Delivered |
|---|---|
spec/recipe.schema.json | layout_entry.kind: concept | junction-note | crosswalk-edge (default concept). Backwards-compatible — existing recipes (no kind field) continue to validate and produce concept notes. |
src/render/index.ts | New Tier1Kind type. render() walks the layout for non-default kinds and emits frontmatter.kind discriminator. Last non-default wins (recipe authors typically declare on the leaf entry). |
src/generation/generation-engine.ts | New generateFromRecipe() entry point. Bypasses v0.1.0 column-role legacy logic. Calls render() per row → composes frontmatter + tags + aliases + provenance → validates pre-write against spec/tier1.schema.json → merges with existing user-edited frontmatter on replace mode → writes to vault. |
src/main.ts | plugin.runImportFromRecipe(parsedData, recipe, options) exposed alongside the existing runImport. E2E tests + future native-recipe commands use this handle. |
| Pre-write validation | validateTier1Frontmatter is called on every row’s rendered frontmatter. With strictValidation: true (default), invalid frontmatter blocks the write and surfaces in result.errors[]. STRM predicate enforcement is the load-bearing case — bad predicate_id values are rejected with AJV’s structured error before any file I/O happens. |
recipes/starter/nist-csf-to-800-53-crosswalk.json | Maps NIST CSF subcategories → NIST 800-53 controls via STRM predicates + SSSOM-shaped envelope (match_type, mapping_justification, mapping_provider). |
recipes/starter/iso27001-to-800-53-crosswalk.json | Maps ISO 27001 controls → NIST 800-53 controls. Identical recipe shape to the CSF crosswalk — proves recipe genericity across framework pairs. |
recipes/starter/evidence-junction-notes.json | Bulk-import evidence-link junction notes from a CSV. Generic over any concept ontology + any evidence path. |
recipes/starter/README.md | Inventory of starters + the multi-framework recipe-pattern story (how to author a new recipe for any framework pair by copying + swapping ontology slugs). |
tools/fixtures/synthetic/csf-800-53-crosswalk.csv | Synthetic 5-row crosswalk fixture for tests + dev. |
Multi-framework support — the user’s question
Section titled “Multi-framework support — the user’s question”A reviewer asked mid-implementation: “are we accounting for all of the existing frameworks?”
Yes — the v0.1.4 design is generic over framework pairs. The recipe shape doesn’t bake in any particular ontology. subject_id and object_id are CURIEs (spec/tier1.schema.json $defs/curie), and CURIE prefixes can be any registered ontology slug. The starter recipe library demonstrates this concretely: CSF→800-53 and ISO→800-53 have byte-identical layout shape; only the source ontology slug + tag path + folder name differ.
| Framework | Concept-note import (kind=concept, default) | Crosswalk-edge import (kind=crosswalk-edge) | Junction-note import (kind=junction-note) |
|---|---|---|---|
| NIST 800-53 r5 | ✅ wizard or native recipe | ✅ via crosswalk recipe | ✅ via junction recipe (evidence on 800-53 controls) |
| NIST CSF 2.0 | ✅ same | ✅ same shape, swap ontology slug | ✅ same |
| MITRE ATT&CK | ✅ same | ✅ | ✅ (evidence on techniques) |
| CIS Controls v8 | ✅ same | ✅ | ✅ |
| ISO 27001:2022 | ✅ same | ✅ shipped as starter | ✅ |
| CRI Profile / FFIEC / COBIT | ✅ same | ✅ | ✅ |
| Any future framework | ✅ same | ✅ | ✅ |
The recipes/starter/README.md page documents the swap-ontology-slug-and-folder-path pattern explicitly so users can produce their own framework-pair recipe in minutes. v0.2’s wizard milestone will surface this as an authoring UX (no JSON editing required); v0.1.4’s surface is the recipe JSON itself.
| Suite | Count | Detail |
|---|---|---|
| Jest unit tests | 108 (was 85, +23) | New: tests/render-kinds.test.ts (5 tests for kind dispatch); tests/validation-strm-enum.test.ts (18 tests for STRM enforcement + recipe-schema kind field) |
| WebDriver E2E | 33 across 7 spec files (was 28 across 6) | New: tests/e2e/crosswalks.spec.ts (5 tests for crosswalk-edge + junction-note import via runImportFromRecipe, including STRM rejection + user_preserve survival) |
| Total | 141 passing | All green before commit. |
The new crosswalks.spec.ts is the v0.1.4 success-criterion gate. It does:
- Imports 3 valid crosswalk-edge rows via
runImportFromRecipe(parsedData, crosswalkRecipe, options)→ verifies all three files appear atCrosswalks/v0-1-4-test/cw-{subject}-{object}.md - Reads first crosswalk’s frontmatter via
metadataCache→ verifieskind: 'crosswalk-edge',predicate_id: 'is_equivalent_to', spec-conformant_crosswalkerblock - Imports 1 row with
predicate_id: 'totally_invalid_predicate'→ verifies write was BLOCKED (result.success === false,result.errorscontains a Tier 1 validation error mentioning predicate_id, no file created) - Imports 2 junction-note rows via
runImportFromRecipe→ verifieskind: 'junction-note', predicate field is open-string (not enum-constrained per Tier 1 schema) - Adds
review_status+creator_id+notesto a crosswalk file viaprocessFrontMatter→ re-imports → verifies user keys survived, managed keys still recipe-driven
Notable design decisions
Section titled “Notable design decisions”-
kind defaults to
conceptfor backwards compat. Existing v0.1.0 saved configs continue to flow throughlegacyConfigToRecipe()→ render() with no kind set → produces concept notes exactly as before. Zero migration cost. Per Ch 22 §10.7 four-phase migration plan. -
Native-recipe path lives alongside legacy column-role path.
runImport(legacy) andrunImportFromRecipe(Ch 22 native) are both first-class entry points. The wizard UI authors v0.1.0 column-role configs (still works, still ships); native recipes are JSON files + therunImportFromRecipeAPI. v0.2 will refactor the wizard to author native recipes directly; until then, both paths are supported. -
Pre-write validation is the v0.1.4 STRM enforcement mechanism. The recipe schema’s
predicate_idis just a string template (renders to whatever the source CSV has). The Tier 1 schema’scrosswalk_edge_frontmatter.predicate_idis the closed STRM enum. CallingvalidateTier1Frontmatteron every rendered row pre-write is what catches bad predicates. Strict mode (default true) blocks the write; non-strict mode logs and continues. This is also the v0.1.3 task §Validation hook that wasn’t fully completed — now wired in v0.1.4 where it matters most. -
Numeric-typed managed values are deferred.
match_confidenceistype: numberin the spec but render() produces strings. Rather than adding type coercion in v0.1.4, the starter recipes markmatch_confidenceasuser_preserve(so reviewers can edit it) and omit it frommanaged. A future milestone adds{template, type: "number"}shape to managed values. -
Junction-note
predicateis open-string. Per Tier 1 schema, junction-note’s predicate field istype: string, minLength: 1— no enum constraint. Recipe authors pick from their workflow’s vocabulary (covers,evidences,partially_covers,attests,reviews, etc.). STRM enforcement is for crosswalk-edge predicates only, where the set-theoretic semantics are load-bearing. -
Three starter recipes ship from day one. CSF→800-53, ISO→800-53, evidence-junctions. Demonstrating multi-framework genericity by example beats documenting it abstractly. The
recipes/starter/README.mdmakes the “swap-the-slug” pattern explicit.
How v0.1.4 plugs into the system
Section titled “How v0.1.4 plugs into the system”Interfaces this milestone introduces / changes
Section titled “Interfaces this milestone introduces / changes”| Interface | Status |
|---|---|
layout_entry.kind field in spec/recipe.schema.json | ✅ Added; default concept; 3 enum values |
Tier1Kind type in src/render/index.ts | ✅ Exported |
Kind dispatch logic in render() | ✅ Live; non-default kind → address.frontmatter.kind |
RecipeImportOptions interface in generation-engine.ts | ✅ Exported |
generateFromRecipe() function | ✅ Exported; new entry point for native Ch 22 recipes |
plugin.runImportFromRecipe() | ✅ Live; primarily for E2E + future commands |
Pre-write Tier 1 validation in generateFromRecipe() | ✅ Live; STRM predicate enforcement |
recipes/starter/ directory + 3 recipes + README | ✅ Shipped |
What did NOT change in this milestone
Section titled “What did NOT change in this milestone”- Wizard UI — still authors v0.1.0 column-role configs that flow through
legacyConfigToRecipe(). Wizard refactor to author native Ch 22 recipes is v0.2. - Existing concept-note imports — backwards-compatible. No saved-config migration required. v0.1.0 → v0.1.4 is a non-breaking surface change.
- Tier 1 schema — already had the discriminator on
kind(concept-note via if-not-junction-or-crosswalk; junction-note + crosswalk-edge via discriminated branches). Unchanged in v0.1.4. - Validator wiring —
initValidator()already compiles both schemas; v0.1.4 just callsvalidateTier1Frontmatterfrom a new place (the new generation path). - Body templates — body content is still a minimal
# {title}line in native-recipe path. Body templating viaalso_emit.bodyor similar is a future milestone.
Memory rules followed this session
Section titled “Memory rules followed this session”- ✅ Always test thoroughly — milestone gate is the new
crosswalks.spec.tsE2E that exercises real file I/O for both new kinds + STRM rejection - ✅ No personal data in commits/logs (sweep before push)
- ✅ No AI co-author attribution in commits
- ✅ Brevity preference — terse table-heavy delivery log; system-design diagram replaces narrative architecture description
- ✅ General-ontology positioning — recipe + log read in any domain (CURIE-shaped CURIEs, predicate vocabulary, evidence-link primitives all generic)
What this unblocks for v0.1.5 (sqlite-wasm projector)
Section titled “What this unblocks for v0.1.5 (sqlite-wasm projector)”- All three Tier 1 shapes are now produced by the engine. The Tier 2 projector indexes:
- concept-note → primary entity table (CURIE + attributes)
- junction-note → evidence-coverage join table (subject_concept × evidence_doc + coverage)
- crosswalk-edge → FK-rich edge table (subject_id ←→ object_id with STRM predicate as edge type)
- Junction-note + crosswalk-edge frontmatter shapes are SQL-projection-friendly (flat, type-stable, predicate enums map cleanly to enum-typed columns).
- The validation gate guarantees Tier 2 doesn’t have to defensively handle malformed crosswalk predicates — STRM enforcement upstream means Tier 2 can rely on the closed enum.
Related
Section titled “Related”- Milestone v0.1.4 page
- v0.1.3 delivery log — preceding milestone (engine integration)
- Ch 22 synthesis (target-structure grammar) — recipe grammar source of truth
- Ch 07 evidence-link edge model — junction-note 13-field schema origin
- STRM registry page — predicate vocabulary
- SSSOM registry page — envelope shape
recipes/starter/README.md— multi-framework recipe-pattern storyspec/tier1.schema.jsondiscriminator — concept-note / junction-note / crosswalk-edge dispatchspec/recipe.schema.jsonlayout_entry.kind — recipe-side discriminator
Next milestone
Section titled “Next milestone”v0.1.5 — Tier 2 sqlite-wasm sidecar projector — projects all three Tier 1 shapes into a .crosswalker.sqlite sidecar with sqlite-vec for vector queries. Per-milestone E2E spec is tests/e2e/sidecar.spec.ts — must verify project-from-vault, recover-on-delete, and full FK integrity for crosswalk-edge mappings.