---
title: Vault Edges (Wikilink Conventions + Typed Edge Vocabulary)
type: reference
tags: [vault, edges, wikilinks, schema, v1.6, EDGE-01, EDGE-02, EDGE-03]
status: locked
locked_at: 2026-05-29
edges:
  broader:
    - "[[v1.6-graph-primitives]]"
  see-also:
    - "[[vault-atoms]]"
    - "[[vault-trails]]"
    - "[[vault-mochs]]"
---

# Vault Edges (Wikilink Conventions)

The vault graph is a derived artifact: source files in `.planning/`, `agents/`,
`workflows/`, `skills/`, `tests/`, and `references/` carry zero `[[wikilinks]]`,
and `bin/lib/vault-wikilink.js::rewriteProseSpan` injects them at sync time.
This means the autolinker's forward-rewrite contract IS the edge contract
(Forms 1-3 below). Authors who want a wikilink the autolinker doesn't emit
must type it explicitly.

Authoritative implementations: `bin/lib/vault-wikilink.js` (rewriter),
`bin/lib/vault-sync.js::detectOrphans` (orphan trigger).

## Forms the autolinker handles

Three forms generate wikilinks automatically. Forms 1-3 carry semantic
cross-references — a Phase or REQ-ID mentioned in prose IS that referent,
not merely a path-string match.

```
Form 1: Phase N-NN  → wikilinks to Phase NN's vault page
                      (e.g., "Phase 48-02" → [[...|48-02]])
Form 2: Phase N     → wikilinks to phase README.md
                      (e.g., "Phase 47" → [[...|Phase 47]])
Form 3: REQ-ID      → wikilinks to REQUIREMENTS.md#anchor
                      (Set-guarded; unknown IDs pass through unchanged)
```

These run inside `rewriteProseSpan`'s prose-only segments. Frontmatter,
fenced code blocks, and protected `[[...]]` are never re-rewritten — the
`do-no-harm` contract preserves authorial intent and prevents double-rewrite
on idempotent re-sync.

## Explicit wikilinks (author-typed)

Phase 47 FOUND-01 dropped Forms 4 (`gmd-*` agent shortlinks) and 5
(workflow basenames) from `rewriteProseSpan`. They generated phantom
edges — path-string matches mistaken for semantic references — that
dominated the graph view. Pre-Phase-47, ~993 files contained
`[[...execute-plan...]]` wikilinks, almost entirely from the autolinker
wrapping the literal `execute-plan` substring whenever it appeared in
a file path.

Authors who genuinely want a backlink to an agent or workflow now type
the wikilink explicitly:

```
Bad (autolinker-emitted, would-be-Form-4):  the gmd-roadmapper agent
Good (explicit author intent):              [[gmd-roadmapper]] orchestrates ...

Bad (autolinker-emitted, would-be-Form-5):  see workflows/execute-plan.md
Good (explicit author intent):              the [[execute-plan]] workflow defines ...
```

The `[[...]]` syntax in source files survives the rewriter unchanged
(protected segment contract). The same applies to MOC-style explicit
links and any author-typed cross-reference the autolinker doesn't know
how to derive.

## What counts as a real edge

A real edge expresses **semantic intent** — the author claims target X
is materially relevant to the surrounding prose. A path-string mention
does NOT.

Empirical validation of locked principle **P3 — over-fitting in this
codebase = visible graph noise**: Phase 47 FOUND-01 dropped Forms 4+5
and ran `vault-sync --retrofit --all` to strip already-injected phantom
edges from the synced vault. Result: the `execute-plan` node's backlink
count dropped from **993 files to 2** — a **99.80% reduction**. The 2
remaining are intentional literal-text mentions inside fenced code
blocks (protected from the rewriter), not autolinker output.

The autolinker now stops over-fitting at Forms 1-3. Future rewriter
additions that re-introduce path-string matching (e.g., a hypothetical
"Form 6: workflow slug suffix") would re-create the phantom-edge class
P3 forbids — propose those as a milestone-level decision, not a
plan-author's discretion.

## When orphan-detection fires

A vault note is an **orphan** when it cannot be traced back to a live
source file in the repo. Phase 48 FOUND-02 added a stderr-warn detector
inside `vault-sync` (`detectOrphans` helper at `bin/lib/vault-sync.js`,
fired post-`writeRetrievalIndex` / pre-stdout-emission). Three reason
classes are flagged:

```
[VAULT-ORPHAN] N orphan note(s) found — run
              `gmd-tools vault-clean-orphans --dry-run` to inspect
[VAULT-ORPHAN]   <relPath> (reason: source-missing, synced_from=<path>)
[VAULT-ORPHAN]   <relPath> (reason: no-frontmatter)
[VAULT-ORPHAN]   <relPath> (reason: no-synced-from)
```

Detector contract: **read-only**. Exit code unchanged on orphans
(advisory; fail-open per `vault-sync`'s established discipline).
Cleanup is gated through a separate human-confirmable command:

```
node bin/gmd-tools.cjs vault-clean-orphans --dry-run    # list candidates
node bin/gmd-tools.cjs vault-clean-orphans --confirm    # delete after explicit
                                                          confirmation
```

This split (advisory detector + opt-in cleanup) prevents accidental
deletion of in-progress work or transient state files that haven't yet
been committed to the source tree.

## Closed Vocabulary (EDGE-01 / Phase 71)

v1.6-B introduces a typed-edge layer on top of the Forms 1-3 autolinker
substrate. Authors express semantic relationships in YAML frontmatter via
an `edges:` block; one key per verb; a list of `[[wikilink]]` targets per
verb. The vocabulary is a **closed 10-verb set** LOCKED from DEEP-03
synthesis at `.planning/research/v1.6-deep-dive/SUMMARY.md` `## Typed
Edges`. The JSDoc back-reference in `bin/lib/vault-frontmatter.js`
`EDGE_VOCABULARY` constant cites this doc as the authoritative source.

```yaml
edges:
  supersedes: [[Phase 47-01 FOUND-01]]
  contradicts: [[Phase 46-03 forms-4-5]]
  see-also: [[gmd-vault-sync]]
```

The 10 verbs and their one-line semantic gloss:

| Verb           | Gloss                                                              |
| -------------- | ------------------------------------------------------------------ |
| `supersedes`   | This note replaces / obsoletes the target (canonical now lives here). |
| `contradicts`  | This note disagrees with a load-bearing claim in the target.       |
| `supports`     | This note backs / corroborates a claim in the target.              |
| `questions`    | This note raises an unanswered question against the target.        |
| `refines`      | This note sharpens / makes more precise a claim in the target.     |
| `example-of`   | This note is a concrete instance / case of the target (symmetric). |
| `derived-from` | This note's claim was extracted / inferred from the target.        |
| `cited-by`     | This note is referenced by the target (back-pointer; inverse `cites`). |
| `see-also`     | Loosely related; reader may want both (symmetric).                 |
| `broader`      | Target is a wider / parent concept (SKOS-style; inverse `narrower`). |

**Authoring surface:** YAML frontmatter ONLY. The in-alias `@verb` syntax
(Penfield Labs `[[Note|text @supersedes other]]`) is EXCLUDED per SPEC §2
— see `## SPEC §2 Advisory` below for reversal-evidence preserved verbatim.

**Closed-set rationale (DEEP-03 synthesis):** zettelkasten-mcp's
production-shipped `LinkType` Pydantic enum with `extra: "forbid"`
validation sets the upper bound on closed-vocab discipline. Constraining
the authoring surface improves LLM tool-call reliability — when an agent
authors `edges:` it can only pick from 10 options, removing the
hallucination surface that arbitrary-vocab relations introduce.

**Unknown verbs do NOT throw.** The `parseEdgesBlock` parser
(`bin/lib/vault-frontmatter.js`) buckets any non-vocabulary key under
`unknown_edges` for downstream lint surface in Phase 75 (v1.6-F
`gmd-vault-lint`). Fail-open per SPEC §4.

## Inverse Pairs

The vault stores both directions of an edge at write time. When a note
declares `supersedes: [[Phase 47]]`, `vault-sync` adds
`superseded-by: [[Phase 48]]` to Phase 47's frontmatter on its next pass.
The materialized inverse is what makes a neighbor reachable without
computing a reverse-index — markdown is cheap, query latency on a 5-pass
MOC regenerator is not.

Neighbor reads are EXPANSION, not filtering. `vault-query --expand-edges`
(VREAD-03 / Phase 78.1, default-OFF) parses each top-K text hit's `fm.edges`
block via `parseEdgesBlock` and pulls its 1-hop neighbors — including the
materialized inverse (e.g. a note's `superseded-by` neighbor) — as ADDED
retrieval candidates. Each pulled neighbor carries a +1 rank boost (the same
unit as the obsidian-cli confirmation bump, so a 1-hop edge ties but never
dominates a strong text hit) and a `matched_via: edge:<verb>` provenance
marker. This ADDS candidates to the result set; it does not remove or
restrict text hits the way a filter would.

**8 stored inverse pairs** (write both directions on every vault-sync pass):

| Forward        | Inverse           |
| -------------- | ----------------- |
| `supersedes`   | `superseded-by`   |
| `contradicts`  | `contradicted-by` |
| `supports`     | `supported-by`    |
| `questions`    | `questioned-by`   |
| `refines`      | `refined-by`      |
| `cited-by`     | `cites`           |
| `broader`      | `narrower`        |
| `derived-from` | `derives`         |

**2 symmetric verbs** (no inverse pair; same verb both directions):

| Verb         | Semantic                                                |
| ------------ | ------------------------------------------------------- |
| `see-also`   | Reader interest is mutual; both notes link the other.   |
| `example-of` | Both directions read naturally (X example-of Y; Y example-of X for the inverse navigation). |

vault-sync writes inverses on every pass per Plan 71-02; missing-target
inverses log `phantom_inverse` for the Phase 75 lint surface; lint
warnings themselves surface in Phase 75 (v1.6-F CURATOR). The writer is
Phase 71; the warning surface is Phase 75.

## Open-Text Fallback (relation_type:)

The closed 10-verb set is the canonical authoring surface. The escape
hatch for genuine outliers is a `relation_type:` sub-block sibling to
the closed-vocab keys, inside the same `edges:` block:

```yaml
edges:
  supersedes: [[NoteA]]
  relation_type:
    - target: [[NoteB]]
      verb: blocks
```

Single parse pass; one key (`edges:`) holds all edge data; mirrors
basic-memory's `relations:` convention.

Closed vocab is the authoring surface for LLM tool-call reliability
(zettelkasten-mcp argument); open-text fallback is the escape hatch
(basic-memory's empirical win). The open-text `relation_type:` is
currently parsed into `open_text` and round-tripped on write, but is
NOT consumed for frequency anywhere. The frequency consumer — a
vocab-promotion tracker that would surface high-frequency open-text
outliers as candidate closed-vocab promotions (promotion itself
requiring a SPEC §3.2 amendment) — is DEFERRED to a future milestone;
no such tracker exists in v1.7.1.

**`vault-query --expand-edges[=verbs]` scope:** EXPANSION, not a filter
(VREAD-03 / Phase 78.1; default-OFF). When enabled, the query reads
`fm.edges` on its top-K text hits and ADDS their 1-hop neighbors as extra
candidates — it never narrows or restricts the closed-vocab text result set.
The default verb subset is navigational/provenance only — `derived-from`,
`derives`, `broader`, `narrower`, `refines`, `supports`, `see-also` — chosen
for retrieval proximity (the relationship-semantics verbs `contradicts`,
`questions`, `cited-by`, `supersedes` are skipped from the DEFAULT boost set).
Pass `=verbs` (a comma-separated list) to request any of the 10 EDGE-01 verbs
instead. Traversal is bounded: HOP=1 (no transitive walk), a small per-source
NEIGHBOR cap (~5), a TOTAL cap (~15) on added candidates, and neighbors are
sourced ONLY from the top-K text hits (K = min(limit, ~5)).

Open-text `relation_type:` edges are NOT yet a queryable surface. `--expand-edges`
traverses the closed-vocab `fm.edges` keys only; a dedicated open-text
expansion/filter flag is deferred (not yet shipped) and is tracked as the
escape hatch, not the primary surface. Until it ships, treat `relation_type:`
as authoring-time metadata that lint surfaces (Phase 75) but query does not
expand on.

## SPEC §2 Advisory: Penfield in-alias syntax

Repo `obsidian-wikilink-types` (Penfield Labs) ships an authoring surface
that places typed-edge predicates **inside wikilink alias text** (e.g.,
`[[Analysis|The new research @supersedes and @contradicts the previous
analysis]]`) and syncs them to YAML frontmatter on save. This contradicts
SPEC §2 verbatim: *"Typed wikilink syntax inside alias text (à la
Penfield's `[[Note|text @supersedes other]]`) — Frontmatter is
authoritative; inline-syntax sugar deferred until real user demand
surfaces."*

Per CONTEXT.md DEEP-03 rule, **SPEC lock WINS**. Adoption is NOT proposed
for v1.6.

Reversal-evidence preserved verbatim from DEEP-03 synthesis
([obsidian-wikilink-types.md Section A](../.planning/research/v1.6-deep-dive/obsidian-wikilink-types.md)).
Penfield's strongest case (synthesized from 4 verbatim quotes):

1. **Authoring-velocity argument:** Edge type asserted at the moment the
   author is thinking about the edge — same line as the wikilink, same
   prose-flow. Out-of-band YAML requires context-switching out of prose.
   (README *"You never touch YAML. The `@` syntax is the authoring
   interface. The YAML is the storage and compatibility layer. The
   frontmatter is the authoritative source for all programmatic and AI
   uses."*)
2. **Reading-context argument:** A reader scanning prose sees the
   relationship's meaning in context. (README example: *"`[[Analysis|The
   new research @supersedes and @contradicts the previous analysis]]`"*)
3. **Graceful-degradation argument:** If the plugin/syntax disappears,
   prose still reads. Frontmatter still resolves. No data trapped in
   plugin-private syntax. (README *"If you uninstall the plugin: YAML
   frontmatter remains — no data loss"*)
4. **Cheap-to-ship argument:** Parser is 3-line regex (`AT_TYPE_RE =
   /(?:^|\s)@([\w-]+)/gm`) + 22-line scanner. Engineering cost ≈ zero.
   (parser.ts comment + signature)

**Trigger conditions for SPEC §2 reconsideration** (enumerated by the
DEEP-03 design-dive):

- Edge-creation rate < N edges per active vault per week
- Edge-type distribution heavily skewed toward `see-also` (cheapest YAML
  edge to author)
- User feedback mentioning "@-syntax" / "inline edges" / "I forgot to add
  a relationship"

Until any of those signals fire post-v1.6-B, **lock holds; reversal-evidence
preserved here for future re-evaluation**. SPEC §2 lock wins;
reversal-evidence preserved for future re-evaluation.

## v1.7-A Code-Edge Deferral

v1.7-A CDEEP phase resolves code-edge vocabulary binding per SPEC §6
Open Q7 LOCKED (vocabulary-level divergence intentional; mechanism-level
consistency preserved). This doc binds vault-edge vocabulary only.
Synthesis anchor: [v1.6-graph-primitives](../Knowledge/Patterns/v1.6-graph-primitives.md)
(DEEP-06 cross-milestone contract — `.planning/phases/70-v1-6-a-deep-research-substrate/deep-06-contract-marker.md`).

---

*Audit: `tests/audit-vault-edges-doc.sh` (Phase 48 FOUND-03). EDGE-01 vocabulary: `tests/audit-edge-vocab.sh` (Phase 71-01).*
*Implementation: `bin/lib/vault-wikilink.js::rewriteProseSpan` + `stripDroppedFormWikilinks` (Phase 47 FOUND-01); `bin/lib/vault-sync.js::detectOrphans` + `bin/lib/vault-clean-orphans.js::cmdVaultCleanOrphans` (Phase 48 FOUND-02); `bin/lib/vault-frontmatter.js::EDGE_VOCABULARY` + `parseEdgesBlock` (Phase 71-01 EDGE-01).*
