No open questions and no blocking questions; implementation binding present, verifier binding present, and expected-outcome oracle present.
The Design Review links a Spec's relations both ways and gives keyed Pack questions a separate carrier-line link, but it offers no built-in filtering of a Pack or navigation from arbitrary prose mentions to their entries. The Spec Studio serves a person who iterates on a design in the repository and a pull request reviewer who holds only a link. A linked-notes view lets a person follow relations, filter members by rung and design columns, open entries by address and see each pass's edits arrive. It is a projection of its own, not the Studio's local half: the Studio's page set serves a reader who holds a link, the vault serves a person who iterates in a notes app, and each has its own verb; the verb that writes every projection root writes the vault too.
Obsidian already does that for linked Markdown notes with properties: backlinks, a graph view, search, and filterable tables it calls Bases. The first prototype, in explorations/obsidian-vault/, wrote notes from dumped graph and reader values, with relations and checked mentions as wikilinks, entry addresses as block links, and Bases for Pack members and standing queries. The second prototype checked the vault this Pack designs on the Protocol's corpus and the first adopter's: links that resolve, Bases whose views select what their recipes select, two identical renders, and a person's notes that survive. Its measurements remain in reviews/assets/plan46-prototypes/vault-measurements.md; git holds the replaced prototype.
The principle is a linked-notes view regenerated from the one graph; Obsidian and its Bases are the representation chosen first. Like every projection, the vault states no truth, and two runs over one graph write the same bytes, so no note names a commit or a time. Intent is still written only in the carriers. One thing sets the vault apart from the other projections: a person keeps it open and writes notes of their own in it. So the command owns one folder of the vault and a list of what it wrote there, and synchronizes that folder instead of rewriting a root wholesale; the vault's owned-folder decision, spec:decisions.vault-owned-folder, says why. For the same reason the vault never lies where the shared build removes files, and a run refuses rather than guess whose a file is.
The default vault sits under generated/, which discovery skips and an adopter usually keeps out of git. A person who wants to keep the intent notes they write in the vault points --out at a directory of their own.
Intent
actor
A person in the repository who designs a capability in passes while an agent edits the carriers.
problem
The Markdown Design Review offers no built-in Pack filters or navigation from arbitrary prose mentions to their entries, so a person iterating on a design still reads carrier files alongside it.
outcome
Generate an Obsidian vault from the one graph, in which a person reads, navigates and filters the corpus and follows each pass's edits, while the carriers stay the only place intent is written.
risks
A notes app invites editing, and an edit to a generated note never reaches the carrier.
Behavior
The world writes a probe corpus of one Pack and its member Specs, runs the vault command with the vault under the root's generated/ directory unless a point names another, and reads the vault from disk; a second run reads the same graph. The world leaves a stopped run's files by running the command with its first rename failing, which leaves the file list recording both contents and a whole temporary beside its target, and a failed run's files by running it while the member's carrier is one the extractor refuses, then repairing that carrier. A fresh vault is one the command writes into an empty directory from the same graph.
The vault is a projection of its own, written only through its synchronization, which sdp vault and the verb that writes every projection root run, and no part of the Studio's page set.
sdp vault [root | --root PATH] [--exclude PATH]... [--out DIR] [--watch] validates the graph as sdp validate does and then synchronizes the vault at DIR, by default generated/obsidian-vault/ under the root; the root is given as an argument or by --root, never both, and defaults to the working directory, each exclusion is a root-relative path prefix as for the publishers, and --out resolves against the working directory; a root given both ways, --root or --out given twice or without a value, and any other option refuse the run.
A vault directory inside the root must lie under a directory discovery skips or under an excluded prefix, so no file in the vault is ever read as a carrier or a source; any other vault directory inside the root refuses the run before the build, writing and removing nothing.
A vault directory that is, lies inside or holds a path the shared build writes or removes, as spec:consumers.obsidian-vault#design.buildOwnedPaths lists, refuses the run before the build, writing and removing nothing, so no build ever removes a file in the vault.
The command writes only the generated folder sdp/ inside the vault; Obsidian's settings, a person's intent notes and every other file of the vault lie outside it and are never written, moved or removed.
Before the build and before any cleanup, a run inspects sdp/ and every entry under it, names that begin with a dot included, and refuses when sdp/ or an entry under it is a symbolic link or neither a file nor a directory, when the file list is not well formed, when a file other than the file list, the command's own temporaries and a .DS_Store file is one the list does not name, or when a listed file other than a .base file has bytes matching no content the list records for it; a refused run writes nothing, removes nothing, names each such path and exits 1. A .DS_Store file under sdp/ is ignored. A listed .base file whose bytes a person changed is kept, even when it leaves the generated set; the run warns with its path and the words delete it to regenerate, keeps its recorded content in the list, and goes on. Without a file list, every file under sdp/ other than .DS_Store is one the list does not name.
A run that passes the guard first removes each file the list names that the new set no longer holds, except a changed .base file; then it writes each generated file whose bytes differ from the file on disk through a temporary sibling renamed into place, except a changed .base file, leaves every file whose bytes already match untouched, removes every directory under sdp/ left empty, and writes the file list last.
Before it changes any file, a run records in the file list both the content each path holds and the content it will hold, so a run stopped at any point leaves every generated file other than a kept, changed .base file matching the list, and the next run recognizes each, and each whole temporary, as its own and completes the synchronization; a temporary cut short while it was written matches no recorded content, so the guard refuses it as unknown and the person deletes it.
The vault is not one of the five projection roots the shared build rewrites: the build's invalidation never removes it, and the repository's projection suite does not certify it. The verb that writes every projection root also synchronizes the vault, at the directory sdp vault would use for the same root unless that verb names another, through the same directory checks and guard, before it publishes any root; a refusal there leaves the vault untouched and fails that run. sdp vault enters the shared build, so it removes the five projection roots as sdp validate does.
Without kept, changed .base files, two runs over one graph write byte-identical files under sdp/, the file list included, and no file there holds a time, a commit, an absolute path or a host name.
Every wikilink, embed and block link a generated note writes resolves to a file the run wrote or kept and, with a block, to a block id that file carries; no note links outside the vault, a carrier or source file is named by its root-relative path in a code span, and text in a code span or a fenced block, where Obsidian reads no link, is no link.
Findings never withhold the vault: a run over a graph with validation findings still synchronizes and returns the validation exit code. A finding whose subject is a Spec or a Pack with a note shows on that note; every other finding shows on the index with its severity, its validator, its file and line when known and its message; and the index counts every finding by severity.
A run whose extraction fails keeps every generated file but the index and the file list: it rewrites the index to open with a warning naming the failure, through the steps spec:consumers.obsidian-vault#rule.interruptedRun and spec:consumers.obsidian-vault#rule.synchronize state, so the file list ends recording the warning index, and exits 1. The next run that succeeds finds the index matching the list and writes it whole again; with no earlier run, the failed run leaves only that index and the file list under sdp/.
Given an extraction root holding a Pack and its member Specs, among them {member: "spec:probe.member"}
Given a vault whose generated folder holds {generated: "the files an earlier run wrote"}
Given the vault holds {extra: "a generated note a person changed"}
Given the member {change: "stays in the corpus"}
When the vault command runs at that root
Then the run exits {exitCode: 1}
Then the run refuses the path {refusedPath: "sdp/specs/probe/spec.probe.member.md"} as {refusal: "changed"}
Then every file the person wrote is byte-identical: {personFilesKept: true}
Given
an extraction root holding a Pack and its member Specs, among them {member: "spec:probe.member"}
a vault whose generated folder holds {generated: "the files an earlier run wrote"}
the vault holds {extra: "a generated note a person changed"}
the member {change: "stays in the corpus"}
When
the vault command runs at that root
Then
the run exits {exitCode: 1}
the run refuses the path {refusedPath: "sdp/specs/probe/spec.probe.member.md"} as {refusal: "changed"}
every file the person wrote is byte-identical: {personFilesKept: true}
Given an extraction root holding a Pack and its member Specs, among them {member: "spec:probe.member"}
Given a vault whose generated folder holds {generated: "the files an earlier run wrote"}
Given the vault holds {extra: "an intent note and Obsidian settings outside the generated folder"}
Given the member {change: "has a carrier the extractor refuses"}
When the vault command runs at that root
Then the run exits {exitCode: 1}
Then the member's note is present: {memberNote: true}
Then the index note warns that the last regeneration failed: {indexWarns: true}
Then every generated file matches a content the file list records for it: {listMatches: true}
Then every file the person wrote is byte-identical: {personFilesKept: true}
Given
an extraction root holding a Pack and its member Specs, among them {member: "spec:probe.member"}
a vault whose generated folder holds {generated: "the files an earlier run wrote"}
the vault holds {extra: "an intent note and Obsidian settings outside the generated folder"}
the member {change: "has a carrier the extractor refuses"}
When
the vault command runs at that root
Then
the run exits {exitCode: 1}
the member's note is present: {memberNote: true}
the index note warns that the last regeneration failed: {indexWarns: true}
every generated file matches a content the file list records for it: {listMatches: true}
every file the person wrote is byte-identical: {personFilesKept: true}
Given an extraction root holding a Pack and its member Specs, among them {member: "spec:probe.member"}
Given a vault whose generated folder holds {generated: "the files a run whose extraction failed left"}
Given the vault holds {extra: "an intent note and Obsidian settings outside the generated folder"}
Given the member {change: "stays in the corpus"}
When the vault command runs at that root
Then the run exits {exitCode: 0}
Then the index note warns that the last regeneration failed: {indexWarns: false}
Then the generated folder is byte-identical to a fresh vault's, the file list included: {matchesFresh: true}
Then every file the person wrote is byte-identical: {personFilesKept: true}
Given
an extraction root holding a Pack and its member Specs, among them {member: "spec:probe.member"}
a vault whose generated folder holds {generated: "the files a run whose extraction failed left"}
the vault holds {extra: "an intent note and Obsidian settings outside the generated folder"}
the member {change: "stays in the corpus"}
When
the vault command runs at that root
Then
the run exits {exitCode: 0}
the index note warns that the last regeneration failed: {indexWarns: false}
the generated folder is byte-identical to a fresh vault's, the file list included: {matchesFresh: true}
every file the person wrote is byte-identical: {personFilesKept: true}
Given an extraction root holding a Pack and its member Specs, among them {member: "spec:probe.member"}
Given a vault whose generated folder holds {generated: "nothing"}
Given the vault holds {extra: "no other file"}
Given the member {change: "stays in the corpus"}
When the vault command runs at that root
Then the run exits {exitCode: 0}
Then a second run over the same graph leaves every generated file byte-identical: {byteIdentical: true}
Then the member's note is present: {memberNote: true}
Then every link a generated note writes resolves inside the vault: {linksResolve: true}
Given
an extraction root holding a Pack and its member Specs, among them {member: "spec:probe.member"}
a vault whose generated folder holds {generated: "nothing"}
the vault holds {extra: "no other file"}
the member {change: "stays in the corpus"}
When
the vault command runs at that root
Then
the run exits {exitCode: 0}
a second run over the same graph leaves every generated file byte-identical: {byteIdentical: true}
the member's note is present: {memberNote: true}
every link a generated note writes resolves inside the vault: {linksResolve: true}
Given an extraction root holding a Pack and its member Specs, among them {member: "spec:probe.member"}
Given a vault whose generated folder holds {generated: "the files an earlier run wrote"}
Given the vault holds {extra: "a file a person wrote inside the generated folder under a temporary's name"}
Given the member {change: "stays in the corpus"}
When the vault command runs at that root
Then the run exits {exitCode: 1}
Then the run refuses the path {refusedPath: "sdp/specs/probe/.spec.probe.member.md.sdp-tmp"} as {refusal: "unknown"}
Then every file the person wrote is byte-identical: {personFilesKept: true}
Given
an extraction root holding a Pack and its member Specs, among them {member: "spec:probe.member"}
a vault whose generated folder holds {generated: "the files an earlier run wrote"}
the vault holds {extra: "a file a person wrote inside the generated folder under a temporary's name"}
the member {change: "stays in the corpus"}
When
the vault command runs at that root
Then
the run exits {exitCode: 1}
the run refuses the path {refusedPath: "sdp/specs/probe/.spec.probe.member.md.sdp-tmp"} as {refusal: "unknown"}
every file the person wrote is byte-identical: {personFilesKept: true}
Given an extraction root holding a Pack and its member Specs, among them {member: "spec:probe.member"}
Given a vault whose generated folder holds {generated: "the files a run stopped at its first rename left"}
Given the vault holds {extra: "an intent note and Obsidian settings outside the generated folder"}
Given the member {change: "changes its title"}
When the vault command runs at that root
Then the run exits {exitCode: 0}
Then the generated folder is byte-identical to a fresh vault's, the file list included: {matchesFresh: true}
Then every file the person wrote is byte-identical: {personFilesKept: true}
Given
an extraction root holding a Pack and its member Specs, among them {member: "spec:probe.member"}
a vault whose generated folder holds {generated: "the files a run stopped at its first rename left"}
the vault holds {extra: "an intent note and Obsidian settings outside the generated folder"}
the member {change: "changes its title"}
When
the vault command runs at that root
Then
the run exits {exitCode: 0}
the generated folder is byte-identical to a fresh vault's, the file list included: {matchesFresh: true}
every file the person wrote is byte-identical: {personFilesKept: true}
Given an extraction root holding a Pack and its member Specs, among them {member: "spec:probe.member"}
Given a vault whose generated folder holds {generated: "the files an earlier run wrote"}
Given the vault holds {extra: "an intent note and Obsidian settings outside the generated folder"}
Given the member {change: "leaves the corpus"}
When the vault command runs at that root
Then the run exits {exitCode: 0}
Then the member's note is present: {memberNote: false}
Then every file the person wrote is byte-identical: {personFilesKept: true}
Then every link a generated note writes resolves inside the vault: {linksResolve: true}
Given
an extraction root holding a Pack and its member Specs, among them {member: "spec:probe.member"}
a vault whose generated folder holds {generated: "the files an earlier run wrote"}
the vault holds {extra: "an intent note and Obsidian settings outside the generated folder"}
the member {change: "leaves the corpus"}
When
the vault command runs at that root
Then
the run exits {exitCode: 0}
the member's note is present: {memberNote: false}
every file the person wrote is byte-identical: {personFilesKept: true}
every link a generated note writes resolves inside the vault: {linksResolve: true}
Given an extraction root holding a Pack and its member Specs, among them {member: "spec:probe.member"}
Given a vault whose generated folder holds {generated: "the files an earlier run wrote"}
Given the vault holds {extra: "a note a person wrote inside the generated folder"}
Given the member {change: "leaves the corpus"}
When the vault command runs at that root
Then the run exits {exitCode: 1}
Then the run refuses the path {refusedPath: "sdp/specs/probe/mine.md"} as {refusal: "unknown"}
Then the member's note is present: {memberNote: true}
Then every file the person wrote is byte-identical: {personFilesKept: true}
Given
an extraction root holding a Pack and its member Specs, among them {member: "spec:probe.member"}
a vault whose generated folder holds {generated: "the files an earlier run wrote"}
the vault holds {extra: "a note a person wrote inside the generated folder"}
the member {change: "leaves the corpus"}
When
the vault command runs at that root
Then
the run exits {exitCode: 1}
the run refuses the path {refusedPath: "sdp/specs/probe/mine.md"} as {refusal: "unknown"}
the member's note is present: {memberNote: true}
every file the person wrote is byte-identical: {personFilesKept: true}
Given an extraction root holding a Pack and its member Specs, among them {member: "spec:probe.member"}
Given the vault command names the vault directory {out: "generated/design-review"}
Given a vault whose generated folder holds {generated: "nothing"}
Given the vault holds {extra: "an intent note and Obsidian settings outside the generated folder"}
When the vault command runs at that root
Then the run exits {exitCode: 1}
Then the run refuses the vault directory as {placement: "overlapping a path the build owns"}
Then every file the person wrote is byte-identical: {personFilesKept: true}
Given
an extraction root holding a Pack and its member Specs, among them {member: "spec:probe.member"}
the vault command names the vault directory {out: "generated/design-review"}
a vault whose generated folder holds {generated: "nothing"}
the vault holds {extra: "an intent note and Obsidian settings outside the generated folder"}
When
the vault command runs at that root
Then
the run exits {exitCode: 1}
the run refuses the vault directory as {placement: "overlapping a path the build owns"}
every file the person wrote is byte-identical: {personFilesKept: true}
Given an extraction root holding a Pack and its member Specs, among them {member:string}
Given the vault command names the vault directory {out:string}
Given a vault whose generated folder holds {generated:"nothing"|"the files an earlier run wrote"|"the files a run stopped at its first rename left"|"the files a run whose extraction failed left"}
Given the vault holds {extra:"no other file"|"an intent note and Obsidian settings outside the generated folder"|"a note a person wrote inside the generated folder"|"a file a person wrote inside the generated folder under a temporary's name"|"a generated note a person changed"}
Given the member {change:"stays in the corpus"|"leaves the corpus"|"changes its title"|"has a carrier the extractor refuses"}
When the vault command runs at that root
Then the run exits {exitCode:number}
Then the run refuses the path {refusedPath:string} as {refusal:"unknown"|"changed"}
Then the run refuses the vault directory as {placement:"overlapping a path the build owns"|"inside the extraction"}
Then the index note warns that the last regeneration failed: {indexWarns:boolean}
Then every generated file matches a content the file list records for it: {listMatches:boolean}
Then the generated folder is byte-identical to a fresh vault's, the file list included: {matchesFresh:boolean}
Then a second run over the same graph leaves every generated file byte-identical: {byteIdentical:boolean}
Then the member's note is present: {memberNote:boolean}
Then every file the person wrote is byte-identical: {personFilesKept:boolean}
Then every link a generated note writes resolves inside the vault: {linksResolve:boolean}
the generated folder sdp/ holds index.md, specs.base, specs/<family>/<note>.md, packs/<note>.md beside packs/<note>.base, code/<namespace>/<note>.md, terms/<note>.md and the file list .sdp-vault.json; <family> is the first segment of a Spec's path, <namespace> a code unit's or anchor's namespace, and <note> the note name spec:consumers.obsidian-vault.notes#design.noteName states.
{ "schema": "sdp-vault/1", "files": { "<path under sdp/>": ["<sha256 hex of the file's bytes>"] } } in sdp/.sdp-vault.json, paths sorted in code-unit order, JSON with two-space indentation and a closing newline; a path lists one content after a completed run and at most two while a run is under way, the content it held first.
a file list is well formed when it parses to the fileList shape, every path is relative to sdp/, /-separated, with no empty, . or .. segment, and names neither the file list nor a temporary, every content is 64 lowercase hex digits, every path lists one or two contents, and the file's bytes equal the serialization spec:consumers.obsidian-vault#design.fileList states of what it parses to, so a repeated or reordered path is not well formed.
a file is written as .<name>.sdp-tmp in its own directory and renamed over <name>; a temporary a run finds is the command's own only when its bytes match a content the file list records for <name>, and the run removes it after the guard passes; the file list's own temporary is the command's own only when it is itself a well-formed file list; any other file of a temporary's name is one the list does not name.
under the root, generated/graph.json, generated/contracts/, generated/registrars.json, generated/imports.json, generated/design-review/, generated/census/, generated/mermaid/, generated/gherkin/ and generated/spec-studio/, each with its .tmp sibling, and every path the shared build comes to write or remove.
one stderr line per refused path, with <path> relative to the vault: sdp vault: <path> is not a file this command wrote (unknown), sdp vault: <path> has changed since this command wrote it (changed), sdp vault: <path> is a link or a special file, which this command never writes (not a file) or sdp vault: sdp/.sdp-vault.json is not a file list this command wrote (malformed); then sdp vault: nothing was written or removed; keep what you wrote outside sdp/, then delete each refused path, or delete sdp/ whole.
sdp vault: the vault <out> lies inside the root and would be read as source; choose a directory under generated/, under an excluded prefix, or outside the root.
sdp vault: the vault <out> overlaps <path>, which every build rewrites or removes; choose another directory, such as generated/obsidian-vault/, with <path> the first overlapping path in the order spec:consumers.obsidian-vault#design.buildOwnedPaths lists them.
0 when the vault is synchronized over a graph with no validation error; the validation exit code when it is synchronized over one with errors; 1 on a refused option, a refused vault directory, a guard refusal, a case collision, a failed extraction or a write that fails.
sdp/index.md opens with > [!info] Generated by `sdp vault` from the graph. Every file under `sdp/` is regenerated, except a `.base` file you changed: write your own notes outside it, and intent notes in `intent/`. , then # Spec graph, the finding counts by severity, ## Findings without a note with one line per finding whose subject has no Spec or Pack note, in report order, the corpus Base embedded, and a link to each Pack note in id order.
- <severity> · <validator id> · <file>:<line> · <message>, leaving out the file and line when the finding names none and the line when it names only a file, with the message escaped as spec:consumers.obsidian-vault.notes#rule.textEscaped states.
after a failed extraction, sdp/index.md opens with > [!warning] The last regeneration failed, and the notes show the graph of the last one that succeeded. followed by one line per extraction error, its root-relative file and its message.
Verification
Verifier bindings
spec:consumers.obsidian-vault.changed-note-refused · A generated note a person changed refuses the regeneration and keeps the edit · declared · example enabled · specs/consumers/obsidian-vault.changed-note-refused.sdp.md
External run evidence from test/vault-examples.test.ts
spec:consumers.obsidian-vault.failed-extraction-recovered · The run after a failed extraction passes the guard and restores the index · declared · example enabled · specs/consumers/obsidian-vault.failed-extraction-recovered.sdp.md
spec:consumers.obsidian-vault.fresh-vault · A fresh vault is written the same way twice and every link resolves · declared · example enabled · specs/consumers/obsidian-vault.fresh-vault.sdp.md
spec:consumers.obsidian-vault.hidden-file-refused · A person's file under a temporary's name refuses the regeneration · declared · example enabled · specs/consumers/obsidian-vault.hidden-file-refused.sdp.md
spec:consumers.obsidian-vault.person-notes-survive · A regeneration removes a note that left the graph and keeps a person's notes · declared · example enabled · specs/consumers/obsidian-vault.person-notes-survive.sdp.md
spec:consumers.obsidian-vault.unknown-note-refused · A note a person wrote inside the generated folder refuses the regeneration · declared · example enabled · specs/consumers/obsidian-vault.unknown-note-refused.sdp.md
spec:consumers.obsidian-vault.vault-in-build-path-refused · A vault directory the build removes refuses the run and keeps a person's notes · declared · example enabled · specs/consumers/obsidian-vault.vault-in-build-path-refused.sdp.md
Example space
member · string · declared by an extraction root holding a Pack and its member Specs, among them {member}
out · string · declared by the vault command names the vault directory {out}
generated · "nothing" | "the files an earlier run wrote" | "the files a run stopped at its first rename left" | "the files a run whose extraction failed left" · declared by a vault whose generated folder holds {generated}
extra · "no other file" | "an intent note and Obsidian settings outside the generated folder" | "a note a person wrote inside the generated folder" | "a file a person wrote inside the generated folder under a temporary's name" | "a generated note a person changed" · declared by the vault holds {extra}
change · "stays in the corpus" | "leaves the corpus" | "changes its title" | "has a carrier the extractor refuses" · declared by the member {change}
an extraction root holding a Pack and its member Specs, among them {member:string}
the vault command names the vault directory {out:string}
a vault whose generated folder holds {generated:"nothing"|"the files an earlier run wrote"|"the files a run stopped at its first rename left"|"the files a run whose extraction failed left"}
the vault holds {extra:"no other file"|"an intent note and Obsidian settings outside the generated folder"|"a note a person wrote inside the generated folder"|"a file a person wrote inside the generated folder under a temporary's name"|"a generated note a person changed"}
the member {change:"stays in the corpus"|"leaves the corpus"|"changes its title"|"has a carrier the extractor refuses"}
When
the vault command runs at that root
Then
the run exits {exitCode:number}
the run refuses the path {refusedPath:string} as {refusal:"unknown"|"changed"}
the run refuses the vault directory as {placement:"overlapping a path the build owns"|"inside the extraction"}
the index note warns that the last regeneration failed: {indexWarns:boolean}
every generated file matches a content the file list records for it: {listMatches:boolean}
the generated folder is byte-identical to a fresh vault's, the file list included: {matchesFresh:boolean}
a second run over the same graph leaves every generated file byte-identical: {byteIdentical:boolean}
the member's note is present: {memberNote:boolean}
every file the person wrote is byte-identical: {personFilesKept:boolean}
every link a generated note writes resolves inside the vault: {linksResolve:boolean}
Composing needs the script.
Design view
Requirement drawing
Requirement drawing for spec:consumers.obsidian-vault
Shown as text: this drawing exceeds the height budget of 2000 pixels after folding.
The diagram as text
spec:consumers.obsidian-vault: An Obsidian vault lets a person iterate on designs from the one graph · declared Spec
spec:consumers.obsidian-vault.bases: A Pack's members and the standing queries are tables over the reader's values · declared Spec
spec:consumers.obsidian-vault.changed-note-refused: A generated note a person changed refuses the regeneration and keeps the edit · declared Spec
spec:consumers.obsidian-vault.failed-extraction-keeps-notes: A failed extraction keeps the notes and says so on the index · declared Spec
spec:consumers.obsidian-vault.failed-extraction-recovered: The run after a failed extraction passes the guard and restores the index · declared Spec
spec:consumers.obsidian-vault.fresh-vault: A fresh vault is written the same way twice and every link resolves · declared Spec
spec:consumers.obsidian-vault.hidden-file-refused: A person's file under a temporary's name refuses the regeneration · declared Spec
spec:consumers.obsidian-vault.interrupted-run-recovered: A run after a stopped synchronization finishes it · declared Spec
spec:consumers.obsidian-vault.iteration: A design pass is followed and answered from the vault · declared Spec
spec:consumers.obsidian-vault.notes: Every Spec, Pack, code unit and keyed term is a note, and every relation is a link · declared Spec
spec:consumers.obsidian-vault.person-notes-survive: A regeneration removes a note that left the graph and keeps a person's notes · declared Spec
spec:consumers.obsidian-vault.unknown-note-refused: A note a person wrote inside the generated folder refuses the regeneration · declared Spec
spec:consumers.obsidian-vault.vault-in-build-path-refused: A vault directory the build removes refuses the run and keeps a person's notes · declared Spec
spec:decisions.vault-owned-folder: The vault's generator owns one folder and synchronizes it · declared Spec
spec:consumers.obsidian-vault.bases.views-match-sources: Each standing view selects what its source selects · declared Spec
spec:consumers.obsidian-vault.iteration.watch-rewrites-changed-notes: A carrier edit under watch rewrites only the notes it changes · declared Spec
spec:consumers.obsidian-vault.iteration.watch-source-anchor: An anchor added in a source file under watch rewrites the Spec's note · declared Spec
spec:consumers.obsidian-vault.notes.case-collision-refused: Two Specs whose notes differ only in case refuse the run · declared Spec
spec:consumers.obsidian-vault.notes.entry-address-link: An entry address in prose opens the entry's block in its Spec's note · declared Spec
spec:consumers.obsidian-vault.notes.term-address-link: A keyed Model term gets a note, and its address in prose opens the term's block · declared Spec
spec:consumers.obsidian-vault.notes.unresolved-mention-stays: A mention of an entry that does not exist stays as written · declared Spec
spec:consumers.projections-model: Projections fan out from one graph without becoming truth stores · declared Spec
impl:protocol.vault-bases: serializes standing query predicates over reader properties as Obsidian Bases · anchored code · src/projections/vault/bases.ts:43
impl:protocol.vault-command: validates after vault preflight and retains prior notes behind a failed-extraction index · anchored code · src/cli/vault-command.ts:191
impl:protocol.vault-notes: renders reader contexts, declared relations and keyed terms as linked notes · anchored code · src/projections/vault/notes.ts:45
impl:protocol.vault-sections: renders sections in graph order with keyed entry block ids · anchored code · src/projections/vault/sections.ts:10
impl:protocol.vault-sync: guards every owned entry and records transitional hashes before atomic file replacement · anchored code · src/projections/vault/sync.ts:98
impl:protocol.vault-watch: reuses the coalescing watcher with source suffixes and a synchronous vault cycle · anchored code · src/cli/vault-command.ts:219
spec:decisions.vault-owned-folder refines spec:consumers.obsidian-vault · declared — settles which files the vault's generator owns and how it regenerates them
Re-specifying the shipped Design Review, census, Mermaid, or Gherkin projection is refused. These four projections are the ruled read surfaces over the one graph. A later decision Spec that supersedes this one replaces the ruling whole; a decision Spec that refines this one narrows it for one projection or one rendering and leaves the rest frozen. Either passes the ADR three-part test.
The vault's generator owns one folder of the vault, sdp/, and keeps there a file list naming every file it wrote with a hash of its content. A .DS_Store file under sdp/ is ignored. A listed .base file a person changed is kept, even when it leaves the generated set, with its recorded content unchanged in the list; the run warns with its path and the words delete it to regenerate and goes on. For every other file, a run refuses, writing and removing nothing, while that folder holds a file the list does not name or one whose content the list does not record. Otherwise it synchronizes the folder: it writes the files whose content changed, leaves the others untouched, and removes the files it wrote that the graph no longer holds. The vault is not one of the projection roots the shared build rewrites: no build removes it, a vault directory where the build writes or removes is refused, and only the vault's synchronization writes it, whether sdp vault or the verb that writes every projection root runs it.
spec:consumers.obsidian-vault decidedBy spec:decisions.shipped-projections-frozen · declared — the vault is a new root, since the four shipped projections stay frozen
spec:consumers.obsidian-vault decidedBy spec:decisions.vault-owned-folder · declared — the generator owns one folder of the vault and synchronizes it instead of rewriting a root
spec:consumers.obsidian-vault dependsOn spec:consumers.reader · declared — the generator reads the graph through the reader's joined values
spec:consumers.obsidian-vault dependsOn spec:consumers.wholesale-view-rewrite · declared — the shared build's invalidation removes the five projection roots and never the vault
spec:consumers.obsidian-vault refines spec:consumers.projections-model · declared — a projection for a person who iterates on designs in a notes app
spec:consumers.vault-on-ramp dependsOn spec:consumers.obsidian-vault · declared — the skill teaches the verb, where the vault may lie and the guard's refusals
spec:decisions.vault-owned-folder refines spec:consumers.obsidian-vault · declared — settles which files the vault's generator owns and how it regenerates them
impl:protocol.vault-command · validates after vault preflight and retains prior notes behind a failed-extraction index · src/cli/vault-command.ts:191 · role publisher · component component:protocol.cli · layer edge · context protocol
impl:protocol.vault-notes · renders reader contexts, declared relations and keyed terms as linked notes · src/projections/vault/notes.ts:45 · role projection · component component:protocol.projections · layer edge · context protocol
impl:protocol.vault-sync · guards every owned entry and records transitional hashes before atomic file replacement · src/projections/vault/sync.ts:98 · role publisher · component component:protocol.projections · layer edge · context protocol
Design reference: none
Verifier binding: present
spec:consumers.obsidian-vault.changed-note-refused · A generated note a person changed refuses the regeneration and keeps the edit · declared · specs/consumers/obsidian-vault.changed-note-refused.sdp.md · example enabled
spec:consumers.obsidian-vault.failed-extraction-recovered · The run after a failed extraction passes the guard and restores the index · declared · specs/consumers/obsidian-vault.failed-extraction-recovered.sdp.md · example enabled
spec:consumers.obsidian-vault.fresh-vault · A fresh vault is written the same way twice and every link resolves · declared · specs/consumers/obsidian-vault.fresh-vault.sdp.md · example enabled
spec:consumers.obsidian-vault.hidden-file-refused · A person's file under a temporary's name refuses the regeneration · declared · specs/consumers/obsidian-vault.hidden-file-refused.sdp.md · example enabled
spec:consumers.obsidian-vault.person-notes-survive · A regeneration removes a note that left the graph and keeps a person's notes · declared · specs/consumers/obsidian-vault.person-notes-survive.sdp.md · example enabled
spec:consumers.obsidian-vault.unknown-note-refused · A note a person wrote inside the generated folder refuses the regeneration · declared · specs/consumers/obsidian-vault.unknown-note-refused.sdp.md · example enabled
spec:consumers.obsidian-vault.vault-in-build-path-refused · A vault directory the build removes refuses the run and keeps a person's notes · declared · specs/consumers/obsidian-vault.vault-in-build-path-refused.sdp.md · example enabled