Skip to main content
Version: 0.3.0

History

The narrative record of Vault-LD, release by release — what changed, why it changed, and what it means for implementers. This file is written by humans and their agents (see AGENTS.md): substantive work adds its rationale under ## [Unreleased], and the section is titled with the version when that release ships.

The mechanical per-PR ledger — every merged change, one line each, with links — is generated by release-please: attached to each GitHub release and accumulated in .github/CHANGELOG.md. Neither is ever written by hand.

0.3.0 — 2026-07-07

Process

  • Automated releases via release-please — PR titles are now conventional commits (enforced in CI, squash-merged so the title is the commit), and release-please maintains a running release PR whose merge cuts the version, tag, and GitHub release. A bespoke label-driven release workflow was prototyped first and discarded before ever landing: it kept accumulating edge cases the standard tool already handles (concurrent-merge races, batch bumping, re-run idempotency), and owning release machinery isn't this project's job. The record split three ways: the machine ledger in .github/CHANGELOG.md and on each GitHub release, the human/agent narrative here in HISTORY.md, and AGENTS.md instructing agents to record their rationale so the repo's reasoning stays queryable.
  • Versioned documentation site — the spec is now published as a static site (docs/, Docusaurus, deployed to GitHub Pages on every push to main). Docusaurus was chosen over MkDocs/Jekyll because the site is meant to grow beyond rendered markdown — media, custom theming, React components — and it is the established standard for that shape of OSS docs. The pages are assembled from the repo's root markdown at build time (docs/assemble.mjs) so there is no second copy to drift, and every page embeds schema.org JSON-LD — a linked-data spec should itself be discoverable as linked data. Released versions are frozen snapshots cut at release time (npm run snapshot -- X.Y.Z, part of the release ritual in CONTRIBUTING.md): the site root always serves the latest release, older releases stay in the version dropdown, and main's live state is browsable as "Next (unreleased)" — so a reader can never mistake unreleased spec text for a published version.

Security

The reference tools now treat both a cloned vault and ingested RDF as untrusted input throughout (see the new SECURITY.md for the trust model). All findings from the branch security review are fixed and exploit-tested; the roundtrip is unchanged.

  • Contained file writes — a vld:path placement hint from ingested RDF can no longer escape the vault. Absolute paths, .. traversal, and hints that resolve outside the vault root (including via symlinks) are refused, closing an arbitrary-file-write path in rdf_to_vault.py.
  • No network I/O on ingestrdf_to_vault.py disables outbound requests before parsing, so hostile RDF can no longer make rdflib fetch remote @context/imported documents (SSRF). A remote reference now fails with a clear error instead.
  • Contained context reads — a @context string reference in a vault can no longer read files outside the vault; absolute and .. references are refused (vault_to_rdf.py), matching the existing remote-refusal.
  • Bounded frontmatter parsing — YAML frontmatter is parsed with an alias-refusing SafeLoader variant (blocks "billion laughs" alias-expansion DoS) and capped at 1 MiB per note; malformed frontmatter skips the note rather than aborting the whole sweep.
  • Symlink-safe vault sweeps — both tools skip .md files that are symlinks or resolve outside the vault root.
  • Pinned dependenciesscripts/requirements.txt pins exact versions (rdflib==7.6.0, PyYAML==6.0.3); the ingest tool's safety depends on the resolved parser behaviour.
  • Repository hygiene — added .github/workflows/ci.yml that rejects tracked bytecode/venv and runs the roundtrip as a regression guard; removed stray local __pycache__ artifacts.
  • Security regression suite — new scripts/test_security.py (make test, stdlib unittest only) pins every SECURITY.md guarantee as an executable test and runs in CI, so a pull request cannot silently weaken a patch.
  • No frontmatter injection — coined YAML keys (and all generated scalars) are quoted unless they provably re-parse to themselves, so a hostile predicate IRI whose localname contains a newline or : can no longer inject frontmatter lines into generated notes (rdf_to_vault.py).
  • Hardened file-stem sanitisation — stems derived from IRI localnames now also strip control characters and leading dots (./../hidden files), and every note write is re-checked against the vault root as a final containment guard (rdf_to_vault.py).
  • Bounded context and note readscontext.jsonld documents are capped at 4 MiB and parse failures warn instead of crashing (a malformed root context is a hard error and is never overwritten); the 1 MiB frontmatter cap now binds before a note is read into memory, and undecodable notes are skipped instead of aborting the sweep (vault_to_rdf.py, rdf_to_vault.py).
  • Network kill-switch everywheredisable_network moved to vault_to_rdf.py and is now also applied by compare_builds.py, so no reference tool can be made to fetch a remote document while parsing RDF.
  • CI hardening — the workflow token is read-only (permissions: contents: read), checkout no longer persists credentials, and actions are pinned to full commit SHAs (actions/checkout v4.3.1, actions/setup-python v5.6.0) instead of mutable tags.
  • --unsafe-allow-network ingest flag — an explicit opt-out of the network kill-switch for RDF whose remote references (public @context documents, e.g. schema.org's) you have verified as trustworthy. It prints a warning banner and pauses five seconds before proceeding so the run can be cancelled with Ctrl-C; cancellation exits before anything is fetched or written. The fail-closed default is unchanged.

[0.2.0] — 2026-07-03

Specification

Identity (§4.5)

  • One minting rule for every note: the IRI is the governing @base + the file name alone, without .md. Folders never enter any IRI — Recipes/ is shelving, not naming. Moving a note never re-mints its identity; only renaming does.
  • The governing context is formalised as the nearest context.jsonld at or above the note (the scoped-base assembly rule, §4.2). Only the vault-root context is mandatory; per-ontology, per-vocabulary, and nested data-folder contexts are optional refinements that govern their subtrees.
  • An explicit id MUST be a full absolute IRI (http(s)://…), used verbatim. It disambiguates same-named files (which otherwise mint the same IRI and merge), pins an identity across renames, and can place a subject in any namespace. Relative values are non-conforming.
  • Non-IRI-safe file names (spaces, most commonly) are percent-encoded on minting and percent-decoded on ingest, so Red Lentil Soup.md round-trips without a pin.

Placement roundtrip (§5.4, §5.5)

  • vld:path "<path>.md" — Vault-LD's own string-valued property, the sole term of the vld: namespace (https://github.com/The-Knowledge-Graph-Guys/vault-ld#) — carries the true, context-relative location of every note the graph could not otherwise reconstruct — every pinned note, every instance not sitting directly in its governing context's folder, and every schema note away from the flat placement of §5.1. The triple exists only on the RDF side: it materialises on export and dissolves back into file placement on ingest, never appearing in frontmatter. File placement is now part of roundtrip fidelity (§5.6).
  • Emitting the placement triples is required only of a roundtrip-face export; an export produced purely for querying — a read-only artifact — omits them (§5.4 step 7).

Hierarchy (§5.2)

  • Frontmatter is the only carrier of subClassOf / subPropertyOf / broader / topConceptOf; a tool MUST NOT derive hierarchy from folder nesting. Nesting a schema file is purely organisational.
  • skos:topConceptOf joins the frontmatter contract, with the concept-vs-scheme distinction (broader links concept to concept, never to the scheme) made explicit.
  • Canonical, reconstructable schema placement is the flat form of §5.1 (classes in Classes/, properties in Properties/, concepts at the vocabulary top level). Folder organisation beyond that is deliberately a tool's own affair, outside the spec — any layout round-trips via vld:path.

Keywords and the context (§4.2, §4.3)

  • Keyword aliasing: the context maps type@type and id@id (JSON-LD 1.1 §4.1.7), so notes read as plain YAML keys with no quoting. Conforming tools honour declared aliases on input and write them on output.
  • Host-tool keys (tags, aliases, cssclasses) are recognised as editor affordances: never emitted as triples, never warned about, and promotable to first-class terms by mapping them in the context.
  • Context composition hardened: array contexts compose by reference with left-to-right override; a conflicting redefinition of an established term or prefix warns (an identical re-declaration stays silent); the scoped-@base rule is framed as Vault-LD's assembly rule above stock JSON-LD, with the interoperability consequence (generic processors ignore @base in referenced contexts) stated plainly.
  • Full link grammar: paths disambiguate, aliases are display-only, fragments resolve to the note. Generation and resolution are two halves of one contract — ambiguous names are emitted path-qualified and resolved right-aligned on segment boundaries; dangling links are flagged, never dropped.

Conformance and compatibility (§6, Appendix B)

  • Conformance checklist expanded to cover name-only minting, absolute id, frontmatter-only hierarchy, vld:path handling, and body preservation; conforming exporter / ingester / roundtrip tool defined.
  • Appendix B: lifting an OKF bundle — a compatibility profile turning a Google Open Knowledge Format bundle into a conforming vault without modifying a single bundle file.

Tools

  • scripts/rdf_to_vault.py — the reference ingester (RDF → vault), closing the roundtrip: one note per subject, in-place updates that preserve bodies and host keys, context synthesis and term coining for unmapped predicates, vld:path consumption, and percent-decoded file names. Opt-in --nest lays fresh schema files out along the declared hierarchy — a folder-management convenience, not spec behaviour.
  • Tooling moved to scripts/, with a root Makefile (make roundtrip = export → rehydrate → rebuild → graph-isomorphic compare), scripts/compare_builds.py, and scripts/requirements.txt.
  • --data-ns on both tools is now an explicit override for the vault-root base only; nested data-folder contexts still govern their subtrees.
  • Exporter defaults to query-only output — a lean, read-only .ttl with no placement bookkeeping; --source opts into the roundtrip face that emits the vld:path triples (used by make roundtrip).
  • Makefile venv paths work on Windows (venv/Scripts/) as well as Unix (venv/bin/); added a make query target for the default lean export.
  • Exporter warnings sharpened: same-IRI merges suggest an explicit absolute id, ambiguous names suggest path-qualified links, non-absolute id values are flagged and recovered.

Project

  • Open-source governance: Apache-2.0 LICENSE, CONTRIBUTING.md (spec changes start as issues), CODE_OF_CONDUCT.md, issue and PR templates.

[0.1.0] — 2026-07-02

Initial release of the specification and the reference exporter.

  • The format: a directory of Markdown notes read as an RDF graph — each note's YAML frontmatter, interpreted as YAML-LD through one shared, external context.jsonld, is that note's triples; the body is documentation for human and machine readers, deliberately not converted to RDF.
  • Resource-per-file: one note = one subject. Two layers, one mechanism: the schema layer under Ontologies/ and Vocabularies/ (classes, properties, concept schemes, concepts) and the instance layer everywhere else, linked by @type wiki links.
  • Wiki links are the edges: [[Target]] is simultaneously the object IRI and a clickable, bidirectional link in the host tool (Obsidian, Notion, any editor).
  • Identity: a note's IRI is its file name resolved against its namespace's @base — ontologies and vocabularies declare scoped bases in their own composed contexts, instances resolve against the data base; an explicit @id pins a stable IRI.
  • Hierarchy lives in the frontmatter: subClassOf / subPropertyOf / broader as wiki links; the Classes/ folder is flat and folders carry no formal meaning.
  • The roundtrip: export (vault → Turtle, split into schema.ttl and data.ttl) and ingest (RDF → vault) specified as inverses, with neither serialization privileged as source of truth; unmapped constructs are flagged, never silently dropped.
  • Reference exporter vault_to_rdf.py and the bundled example vault (Culinary ontology, DifficultyLevels vocabulary, recipe instances).
  • Conformance criteria (§6), relationship to existing work (§7), and a minimal copyable example bundle (Appendix A).