Locator Grammar — Syntax Reference
A locator is a string that addresses an addressable place: either a
position inside a board or a route — a column, a lane, a group, a split
section, a route stage, and the slot before, in, or after it — or an
entity of the organization named by a bare [[Name]] wikilink — a work
system, a unit, a person — resolved by the target’s type (Phase 42). Locators
are the connective tissue of the Inside-Out / Outside-In model: an external
entity (a meeting, an interaction, an agreement, a signal) points into a
document at a specific place, and the document renders a marker there without
the document itself declaring anything. A board marks the column, a topology
marks the work-system node, an org chart marks the person’s box, a units view
marks the unit’s box.
This document is the authoritative reference for the locator grammar. It is a
sibling of topology/index.md (the topology anchor surface)
and is written for both humans and AI agents. As with the Topology DSL, the
governing principle is machine writability and readability above human
terseness: field names are spelled out, the grammar is closed and documented,
and the parser never throws. An AI coding agent should be able to author or
repair a locator from this reference alone, without a UI.
The locator parser is packages/dwsd-dsl/src/common/parse-locator.ts
(parseLocator). It lives in common/ so every DSL can reach it — the board
segment kinds (col: / lane: / group: / split:) and the route stage:
segment (added in Phase 23; landed in Phase 26, see
§3). An entity is not a segment
kind at all: it is addressed by the bare [[Name]] head alone (see
§3).
The parser is pure TypeScript — no filesystem access, no DOM, no vscode — and
is tolerant: a malformed or partial locator degrades to whatever parts could
be recovered rather than throwing.
How it works
A locator is built from up to four parts:
[[file]] #boardId / path :: side- head —
[[file]], the canonical wikilink, resolved by lowercased basename (a board moved into a subfolder is still reachable by its unchanged filename). #boardId— which board within the file when a file holds several; for a route the same slot is#Route Name(§3).- path — a
/-separated chain of typedkind:Namesegments that drills into the structure (§3). - side — a trailing
::before/::in/::after(§4).
Only the path and side concern resolution into the tree; the head + id select which board (or route) the path runs against.
Resolution is exact-one (D-07). The resolver walks the normalized tree segment by segment; at each step the candidate set must narrow to exactly one node to continue. There are no wildcards, predicates, or node-sets — one locator addresses one position. The walk produces one of four outcomes:
| Status | Meaning |
|---|---|
resolved | Exactly one node matched — the marker is placed. |
resolved-cell | A synthetic lane×column cell coordinate (see the cell note in §3). |
ambiguous | More than one node matched the same segment (e.g. a board with both Doing and doing). |
not-found | Zero matches, an unsupported segment kind, or an empty / over-long path. |
One parser, both directions. The same parseLocator + resolver serve
both directions of the
Inside-Out / Outside-In model: read
forward, a locator turns an entity’s frontmatter value into a position; read in
reverse, the indexer builds a reverse index from those same locators so a
board or route can surface what points at it. Because the parser lives in
common/, the grammar is shared across DSLs — the board segment kinds plus
the route stage: extension (§3) — only the recognized
segment kinds widen.
1. Overview
A locator names one addressable place. There are two families of places:
| Family | What it addresses | Form | Rendered by |
|---|---|---|---|
| Positional | a place inside a board or a route | a [[file]] head + typed segments — col: lane: group: split: (board) · stage: (route) | the board / route that owns the position |
| Entity | a thing of the organization — a work system, a unit, a person | a bare [[Name]] wikilink, no #id, no path — resolved by the target’s type at lookup time | every picture that draws the target: the topology node (a work system), the unit box (a unit), the person box (an individual) |
A positional locator carries a [[file]] head to say which board or route
(§2) and a path to say where inside it. An entity locator is only the head:
[[Jonas Keller]] is complete on its own, because a person, a unit or a work
system is one thing across the workspace. Which box the marker lands on is not
spelled in the locator — it follows from what the target is: an individual
→ the person box in every org chart that draws them; a unit → the unit box in
every units view that draws it; a unit wearing the work-system hat → the
topology node and the unit box (one entity, one anchor set, both pictures);
a work system → the topology node. A same-named person and unit both receive the
bare form; the path form disambiguates (§2).
A locator is consumed in two places:
- The frontmatter of an external entity file — e.g. an interaction or
agreement note that anchors itself to a single board position via an
interaction-of:/agreement-of:value, or a signal that observes a person viaobserves:. - A body table in a container entity (a meeting) — one row per anchored position, so a single Daily Standup file can anchor N board positions at once (see §7 Table-row container convention).
In both cases the locator string is resolved by the workspace indexer against
the boards, routes, people and units it has indexed. When the locator resolves,
the addressed document renders a type-specific marker at that place (an
.interaction-chip for an interaction, a .policy-chip for an agreement, a pin
for a signal). When it does not resolve, nothing happens — the document
renders no marker there and never crashes (see
§6 Graceful degradation).
The board file does not list its inbound anchors. Discovery is outside-in:
the entities declare the positions, the indexer builds a reverse index, and the
board renders what points at it. The board author may also declare internal
agreements: / interactions: directly on a column/lane/group; when both an
internal declaration and an external anchor target the same position, the
renderer blends them into one surface with provenance badges (D-22.1-09).
2. Head forms
The head identifies the file (and optionally the board within the file) that the rest of the locator addresses.
locator := head ('#' boardId)? ('/' path)? ('::' position)?| Head form | Example | Meaning |
|---|---|---|
[[file]] (canonical wikilink) | [[Sprint Board]] | Reference the file Sprint Board (resolved by basename, case-insensitive). This is the canonical form (D-18). |
[[file|label]] | [[sprint-board|Sprint]] | Wikilink with a display label — only the part before the | is the file (sprint-board). |
The head resolves by basename, case-insensitive — the workspace indexer keys
files by lowercased basename, so [[Sprint Board]] and [[sprint board]]
address the same file, and a board moved into a subfolder is still reachable by
its unchanged basename.
The path form — [[individuals/Name]] / [[units/Name]] (same-named pair)
A head may carry a folder prefix: [[individuals/Aurora]] or
[[units/Aurora]]. The indexer matches a path-form head by file-path suffix
— the target’s own file path must end with /<the written path>.md (the
D-WL-03 rule lookupNote already uses for notes). This is how a same-named
person and unit are told apart on the entity surface (§3): a bare
[[Aurora]] lands on both the person box and the unit box — documented, not
diagnosed — while [[individuals/Aurora]] reaches only the person and
[[units/Aurora]] only the unit. The folder is whatever folder the target file
really sits in (matched case-insensitively); an unknown target still receives the
bare form.
A locator with no head (e.g. a bare #col:Doing::in in a container body
table) is a relative locator — it inherits its file/board from the container
entity’s frontmatter. See §7.
Inline (position-only) form — same-doc, no head (D-02)
There is a second head-less form that is not relative-by-inheritance: the
position-only (inline) locator. It carries no [[file]] head and no
#boardId — just a typed path and an optional ::side:
inline-locator := path ('::' position)? (no head, no '#boardId')It is used by an annotation authored inside the very document it addresses —
the inline notes: surface (Phase 33). Because the note lives in the same
board / route / topology document, there is nothing to point at by filename:
the locator resolves against the same doc, so the wikilink head is dropped
entirely rather than inherited from a container. This is the deliberate same-doc
simplification versus the external-entity model.
| Form | Head | Resolves against | Example |
|---|---|---|---|
| Full | [[file]]#Id | the addressed file/board | [[Sprint Board]]#Sprint Board/col:Done::in |
| Relative | none (inherits container frontmatter) | the container’s declared head | col:Done::in in a meeting body table |
| Inline (position-only) | none (same-doc) | the document the locator is authored in | col:Done::in inside that board’s own notes: |
The path grammar and the ::side qualifier are identical across all three
forms — the inline form simply reuses the same parseLocator typed segments
(§3) and position qualifier
(§4) with the head omitted. Each DSL accepts the segment
kinds its structure supports:
- Board —
col:/lane:/group:/split:(e.g.col:Doing::in,lane:Alice/col:Review::before). - Route —
stage:only for v1 (e.g.stage:epic @ Coordination::before). A non-stage route position (a band, a trigger, an edge) has no locator segment yet and stays out until the NAV-05 / v2 grammar extension — it degrades to no marker, never a blank. - Topology — the work-system node id (the node name).
- Org chart —
[[Name]]or a bareName(the person’s file basename); no.doc-note yet — the header is painted, but neither structure view has a doc-note bubble. - Units —
[[Name]]or a bareName(the unit’s file basename).
The inline form inherits the same exact-one resolution and graceful
degradation contract (§6): a position-only locator
that no longer resolves is orphaned. Unlike the external surfaces (which
silently drop an unresolved marker), the inline note surface preserves an
orphaned note — see board/notes.md and its route /
topology siblings for the orphan tray + diagnostic behavior.
3. Typed path segments
After the head (and optional #boardId), a /-separated path drills into
the board’s structure. Every segment is kind:Name:
path := segment ('/' segment)*segment := kind ':' name (kind ∈ lane | group | col | split | stage)| Segment | Addresses |
|---|---|
col:Name | A column named Name |
lane:Name | A lane (horizontal swimlane) named Name |
group:Name | A group named Name |
split:Name | A split section named Name |
stage:Name | A route stage — addresses a Flight Route, not a board. Name is the full itemType @ Band pair (see The stage: route segment below). |
Every segment kind is positional. A person, a unit or a work system is not
reached through a segment — it is the bare [[Name]] head with no path (see
Entity wikilinks
below).
Segments chain to reach nested positions. Examples:
col:Done— theDonecolumn.lane:Alice/col:Doing— theDoingcolumn within theAlicelane.group:Delivery/lane:Bob/col:Review— theReviewcolumn in theBoblane inside theDeliverygroup.col:Build/split:QA— theQAsplit section inside theBuildcolumn.
Case-insensitive matching (D-12). Both the kind and the Name match
case-insensitively, so col:done, COL:Done, and col:Done all address the
same column. A name may legitimately contain an interior colon
(col:Review: Notes) — only the first colon separates the kind from the name.
A segment whose kind is not one of the five recognized kinds, or whose name is
empty, is dropped (tolerant degradation) — the rest of the path still parses.
There is no authorable
cell:segment. A cell is a resolution result, not something you write. When alane:L/col:Cpath descends into a lane that owns no column nodeC, butCis a board-global column on a shared-columns grid, the resolver returns a synthetic lane×column cell coordinate ({ laneName, columnName }) so the marker lands in the same visible cell regardless of which board structure the author used — this is theresolved-celloutcome in How it works. You still authorlane:L/col:C; the cell is what the resolver hands back.
The . document-root path (whole-document scope)
A path may begin with a . document-root token. It names the position
from the root of the document itself rather than as an implicit same-document
path — the XPath / filesystem root convention. / stays the path separator,
so . (not /) is the free root token:
path := '.' | '.' '/' segment ('/' segment)* | segment ('/' segment)*| Path form | Resolves to | Equivalent to |
|---|---|---|
. | the whole document — an empty path, document scope | (the document itself; no per-position marker) |
./col:Doing | a path from the document root | the bare col:Doing |
col:Doing | an implicit same-document path (unchanged) | — |
.alone = the whole document. The empty path resolves to document scope: the marker is not pinned to any column / lane / group / split (or stage / node) but to the document as a whole. On the inlinenotes:surface this renders as one marker in the document header, to the left of the doc-tag chips; when two or more.doc-notes exist they collapse to one header marker with a count badge (seeboard/notes.mdand its route / topology siblings)../<path>= a path from the root, equal to the bare path. A leading.on a longer path (./col:Doing) is exactly equivalent to writing the bare path (col:Doing) — the.names the root the path descends from and is optional. No YAML quoting is strictly required for., but the convention is to quote it (on: ".") for clarity.
The stage: route segment (landed Phase 26)
stage: is the route extension of the locator grammar — the one segment kind
that addresses a Flight Route instead of a board. The route DSL reuses the
very same parseLocator (D-26-01 / D-26-02 / D-26-03), so a route position is
just another typed path segment. It behaves like the four board kinds, with two
route-specific specifics:
- Name = the full
itemType @ Bandpair. A route stage is identified by its(itemType, band)pair, spelleditemType @ Bandwith a single space each side of the@— the same spelling used inpath:edges (see Path & bands). Sostage:epic @ Coordinationnames theepicstage in theCoordinationband. The name is opaque toparseLocator: interior spaces and the@are fine, and the name runs to the next/(or to the::sidequalifier). A whitespace drift simply never matches → no marker (graceful). The item-type half is the stage’s name: its token, or for a stage written as a wikilink ("[[Epic]] @ Coordination") its link target —stage:Epic @ Coordination, never with[[…]]inside the locator. The match is exact (the band half stays case-insensitive); the old lowercase spelling of a stage that became a wikilink still lands for one release and is reported as a deprecation (see Route: a stage’s item type). - Multi-route disambiguation reuses the
#Nameslot. In a Markdown file hosting several route fences,[[file]]#Route Name/stage:epic @ Coordinationselects the route by name — exactly like#boardIdselects a board (§2). The bare[[file]]/stage:epic @ Coordinationform also resolves for the common one-route-per-file case.
The ::before / ::in / ::after side (§4) applies
unchanged. The route-side delta and its markers are documented in
route/anchors.md; this page is the authority for the
locator string itself.
Board scope, and a board locator without #Title
Three board-locator forms differ only in what they address; all use the head resolution above.
| Form | Example | Addresses |
|---|---|---|
| board scope | [[Order Studio Board]]#Order Studio::in | the whole board — a #Title and no path. Shown in the board header (see board anchors). |
| board position | [[Order Studio Board]]#Order Studio/col:Flagged::before | one unit; the ::before / ::in / ::after side picks the set at that unit |
| bare entity | [[Order Studio Board]]::in | not a board anchor: no #Title and no path make it an entity locator (the entity named Order Studio Board) |
The two-slot rule. A board locator written without #Title
([[Sprint Board]]/col:Doing::in) lands on a titled board the same way a route
locator without a title lands on a titled route: the resolver tries the board’s
title and the empty title. In a Markdown file that hosts several titled dwsd.board
fences it lands on every fence that has the unit. A locator with #Title
stays exact — it lands only on the board of that title. A titled locator against an
untitled board does not land.
A locator that lands nowhere is a warning. VS Code marks a board locator that lands on no board unit with a warning on the line that writes it, naming one of three reasons: the addressed document is not in the workspace, the document has no board of that title, or the board has no unit at that path.
Entity wikilinks — resolution by target type, the role bridge, the path form (Phase 42)
An entity wikilink is the bare head with nothing after it — [[Name]], no
#id, no path, no ::side. It addresses a thing of the organization rather
than a position inside a document, and it is the one form for doing so:
a work system, a unit and a person are all written the same way. parseLocator
yields { file: 'Name', segments: [] }; the indexer keeps such values in
name-keyed reverse maps without asking what the target is. The picture that
draws the target decides at lookup time:
The target [[Name]] is … | The marker lands on … |
|---|---|
an individual | the person’s box, in every org chart that draws them |
a unit | the unit’s box, in every units view that draws it |
a unit wearing the work-system hat | the topology node and the unit box — one entity, one anchor set, both pictures |
| a work system | the topology node |
| nothing the indexer knows | nowhere — no marker, no error (§6) |
Because resolution happens when a picture asks, file order never matters: a signal indexed before the person it observes still lands once the person’s file is known.
Casing does not matter either. Like the head form (§2), an
entity wikilink matches the target’s basename case-insensitively:
observes: "[[jonas keller]]" reaches Jonas Keller.md,
for-domain: "[[head of operations]]" still bridges through the role, and two
links that differ only by case address one target. The path form
([[individuals/jonas keller]]) compares the same way.
The structure keys reports-to: and role-keeper: are not locators and still expect the file’s authored spelling.
Which frontmatter keys accept an entity wikilink:
| Key | Entity types | What renders |
|---|---|---|
interaction-of: | interaction · meeting | an interaction marker; a drawer row under “Interactions” |
agreement-of: | agreement | an agreement marker; a drawer row under “Agreements” |
bound-to: | ai-agent | an agent marker; a drawer row under “AI Agents” |
mandate-of: | mandate | a mandate chip or marker, labeled with the holder; see Mandates |
observes: | signal · insight | a pin on the box / node, listed under the drawer’s “Change conversation” band |
doc-level notes: - on: | inline notes in an org chart / units document | the note marker on the box (org chart notes, units notes) |
The observes: row lists the two pin types whose observes: is read (format 1.3), which is what the
code accepts: isObservesPinType in common/anchor-registry.ts tests membership in
ANCHOR_OBSERVES_PIN_TYPE_KEYS (signal, insight), and the indexer gates every observes: pin on it.
That accept-set is the authority — if it and this row ever disagree, the row is the one that is wrong.
A driver or proposal is a pin too (the change pill has four stages), but its observes: is not
read at all: neither a pin nor the ? fall-through. It names its domain with for-domain:, a plain
wikilink. An entity whose type: is outside the pin family does not vanish: its observes: falls through
to the unknown-anchor affordance and renders a dashed ? marker instead of a pin.
These keys are the value-polymorphic ones of §5;
the entity wikilink is their “bare [[…]]” shape. mandate-of: takes the same value forms as
interaction-of: and agreement-of:. The Mandate of cell of a mandate row on a holder’s file takes
them too, but it must be absolute (it starts with [[ or @): a relative locator in that cell
inherits nothing, because a holder is not a board, and the editor warns about it (mandate.relative-locator). A list value puts the same
entity under each name it lists (interaction-of: ["[[Jonas Keller]]", "[[Mara Holt]]"] is a 1:1 on both boxes).
for-domain is different — it names a domain, not a target. for-domain:
is not value-polymorphic (§5); it points up at
a domain, and a domain is a work system or a role:
for-domain: [[Work System]]— the topology’s anchor surface for agreements, drivers, proposals, decision records and flight routes; a unit with thework-systemhat shows the same agreements / drivers / proposals / decision records on its unit box. A role’s ownfor-domain: [[Work System]](a single wikilink or a list) puts a role marker on that work system’s topology node and on the unit box of a unit that wears thework-systemhat; a plain team (no hat) and a person get none.for-domain: [[Role]]— the role bridge: the entity lands on the box of every keeper of that role (a shared role reaches each keeper), and the drawer row says via. It is one hop: the role’s own for-domain: [[Work System]]is never followed further, so the topology learns nothing from a role-scoped record. The bridge applies to every type that carriesfor-domain— agreement, interaction / meeting, AI agent, decision record, driver, proposal (not flight routes, not roles). A record that names the person directly (through a polymorphic key) and their role shows once, without via.for-domain: [[Person]]lands nowhere — a person is not a domain. The org chart validator says so with aninfodiagnostic on the view’smodeline:orgchart.for-domain-names-individual(point it at the person’s role instead); only an anchoring document is reported — a file whosetype:the indexer does not know is never named by this diagnostic. Roles are anchoring documents since Phase 48, so atype: rolescoped to a person is reported too (its marker has nowhere to land). An unresolved target keeps today’s behavior — silence.for-domain: [[Plain Unit]](a unit without the hat) is a valid target and raises no finding: format 1.1 counts a unit as a validfor-domaintarget (a mandate can name one, see Mandates). Its box in the units view shows the document.
The path form. [[individuals/Aurora]] / [[units/Aurora]] tells a
same-named person and unit apart (§2);
the bare [[Aurora]] lands on both.
A leftover type prefix is skipped. The grammar recognizes only the five
positional kinds above; a value such as a type word, a colon and a name is an
unknown kind and is dropped by the tolerant parser — an anchor lands nowhere, a
note becomes an orphan (orgchart.note-orphan / units.note-orphan). Write the
wikilink.
4. Position qualifier
A trailing ::before / ::in / ::after selects where, relative to the
addressed position, the marker sits:
position := 'before' | 'in' | 'after'| Qualifier | Slot |
|---|---|
::before | Immediately before the position |
::in | In / on the position itself (the default) |
::after | Immediately after the position |
Bare = in (D-04). Omitting the qualifier is identical to writing ::in:
[[Sprint Board]]#Sprint Board/col:Doing and
[[Sprint Board]]#Sprint Board/col:Doing::in resolve to the same position.
An explicit ::in is accepted and normalizes to the same in — authoring it
does not produce a doubled key (a real bug fixed in Plan 22-08 / RC3).
The qualifier is peeled from the end of the string before the path is split,
so col:Review::before yields a column named Review (not Review::before).
Runtime target forms: chip: and @doc are not authored
Marks (Phase 52) address a chip, a pin or a drawer entry with two further forms that exist only at runtime, in a host’s mark set and in an agent’s request:
<position>[::before|in|after]/chip:<bucket>[:<entry>] e.g. col:doing/chip:agreement:team-working@doc/chip:<bucket>[:<entry>] e.g. @doc/chip:domainThey are not accepted in bound-to:, in a note’s on: or in any other field this grammar governs:
parseLocator does not know a chip: segment, and it silently drops an unknown segment kind, so a
chip: written into an authored locator would quietly address the position in front of it. Nothing to
author, nothing new to migrate.
@doc is a reserved head for the document scope (the whole diagram: a route’s header chips, a
board’s header chips). A legacy @<file> head whose name is literally doc is therefore not addressable
as a mark target. The contract for both forms (buckets, entry slugs, the ::side qualifier, limits) is
docs/host-integration/marks.md in the source repository.
5. Value-polymorphic keys
The two relationship keys interaction-of: and agreement-of: are
value-polymorphic (D-22.1-01): the same key accepts either of two value
shapes, and the renderer dispatches on the shape, not on a different key.
| Value shape | Meaning | How to recognize it |
|---|---|---|
Entity wikilink — [[X]] | An entity anchor: the entity relates to the whole thing X names — a work system (the topology anchor surface), a unit (the unit box), a person (the person box); resolved by X’s type (§3). | A bare [[…]] with no #… / /col: / /lane: … path segments. |
Board-position locator — [[board]]#Title/col:Name::in | A position anchor: the entity points at one specific board position. | Presence of a #… board id and/or /col: · /lane: · /group: · /split: path segments. |
At a glance, the indexer branches route → board → bare wikilink: a
stage: segment makes it a route-position locator; otherwise, if the value
carries a # boardId or any kind: path segment, it is a board-position
locator; otherwise it is an entity wikilink. Route and board values go to
the route / board reverse indexes and render a chip at the position; a bare
wikilink goes to the name-keyed entity reverse index, and the picture that draws
the named thing renders the marker.
# Entity anchor (value = bare wikilink, no path) — a work system here:interaction-of: [[Delivery Squad]]
# Entity anchor — a person (a 1:1) and a unit:interaction-of: ["[[Jonas Keller]]", "[[Wholesale]]"]
# Position anchor (value = board-position locator, has #boardId + /col:…):interaction-of: "[[Sprint Board]]#Sprint Board/col:Done::in"interaction-of:, agreement-of: and bound-to: accept both shapes;
observes: (the pin types, Phase 31) is value-polymorphic the same way — a
bare observes: [[X]] pins on X’s box or node. for-domain: (the domain key)
is unrelated and is not value-polymorphic — it remains the canonical
domain key, a plain wikilink naming a work system or a role, and is untouched by
this surface (D-22.1-02; the role bridge is described in
§3).
6. Graceful degradation
The locator surface never crashes and never blanks the board (D-10). Concretely:
- An unresolved locator (the file does not exist, the board id is wrong, the column name does not match) simply renders no marker at that position. The rest of the board renders normally.
- A malformed locator degrades to whatever parts the tolerant parser could
recover — an unrecognized segment kind is dropped, an empty path yields the
board-level position, an empty string yields
{ segments: [], position: 'in' }. - The parser performs no filesystem access — resolution happens in the host indexer, and a failed resolution is a silent no-op, not an error.
A future diagnostic surface (the B3 work, currently deferred) will surface unresolved locators back to the author so they are not silently lost. Until then, the principle is “show as much as possible, never blank, never break” — an unresolved anchor costs you one missing chip, nothing more.
7. Table-row container convention
A container entity (a type: meeting) can anchor N board positions at
once via a body table — one row per position. This is the
Inside-Out / Outside-In workhorse: a Daily Standup meeting whose individual
agenda items each anchor a different corner of one board.
The container declares a ## Interactions (for meetings/interactions) or
## Agreements (for agreements) heading, followed by a table whose “Interaction
of” / “Agreement of” column holds one locator per row:
---type: meetingtitle: Daily Standupinteraction-of: [[Sprint Board]]---## Interactions
| Name | Outcome | Interaction of ||---------------|----------------------|-------------------------------------------------|| What's done? | Items moved to Done | [[Sprint Board]]#Sprint Board/col:Done::in || Where blocked?| Blocked items visible| [[Sprint Board]]#Sprint Board/col:Blocked::in |- One row = one position anchor. N rows = N anchors. The two rows above
produce two markers on the
Sprint Board— one onDone, one onBlocked. - The extractor scans the full body (not a head-byte cap), so a long meeting with many rows is fully indexed.
Locator-inheritance chain (D-22.1-12)
Each row’s locator follows a full / relative / bare trichotomy:
| Row locator form | Resolution |
|---|---|
Full — [[Sprint Board]]#Sprint Board/col:Done::in | Used as-is; overrides the container frontmatter (the row names its own file + board). |
Relative — #col:Done::in or col:Done::in (no [[file]]) | Inherits the container’s frontmatter interaction-of: / agreement-of: head, which is grafted on to form a full locator before resolution. |
| Bare / unresolved — a position with no inheritable head | Skipped gracefully — no anchor, no crash (D-10). |
The relative form keeps container tables terse: declare the board once in
interaction-of: frontmatter, then list only the in-board positions in the rows.
7a. Outcome channel (D-09 / D-10)
Each anchored entity can carry an outcome — a short line of context shown in the unified drawer next to the marker. There are two sources, with a strict precedence (D-09):
- Primary — the body-table
Outcomecell. In a container table (§7), the per-row “Outcome” column is the primary outcome channel: one outcome per anchored position, row-granular. - Fallback — a
## Outcome(s)section. When an anchored entity has no body-table Outcome cell, the renderer falls back to a markdown## Outcome(s)H2 section in the entity body and uses its first paragraph as the drawer outcome. The fallback fires only when no table outcome exists; a table Outcome cell always wins.
The recognized-heading allowlist (D-10)
The fallback matches a closed allowlist of H2 headings — not “any heading
containing the word outcome”. After stripping the ## marker and trailing
punctuation/whitespace, the heading must match, case-insensitively:
| Recognized heading | Notes |
|---|---|
## Outcome | singular |
## Outcomes | plural |
## What is the outcome? | the question form; the trailing ? is optional |
The exact literal (mirrored verbatim from the workspace indexer’s
OUTCOME_HEADING_RE, so a reviewer can diff the two and see they agree):
/^(?:outcomes?|what is the outcome)\??$/iDeliberately rejected — these do not match, so unrelated prose never leaks into the drawer:
## Outcome history## Bad outcomes to avoid- any other
…outcome…heading.
A broad /##.*outcome/i match is intentionally avoided: it would
false-positive on the rejected headings above. The allowlist is a single
anchored, non-backtracking alternation (no nested quantifier), so it is also
ReDoS-safe on hostile workspace input (T-22.3-01). The captured first paragraph
is truncated to 280 characters; on overflow a single … affordance is
appended (renderer-resilience — never a hard mid-word clip), and the text always
reaches the drawer as a plain text node, never as HTML.
8. Worked examples per DSL
(a) Interaction anchored to a column
An interaction note that points at the Done column of the Sprint Board:
---type: interactiontitle: Done Reviewinteraction-of: "[[Sprint Board]]#Sprint Board/col:Done::in"---Renders an .interaction-chip in the Done column.
(b) Agreement anchored to a split section
A working agreement that points at the QA split section inside the Build
column:
---type: agreementtitle: QA Gateagreement-of: "[[Sprint Board]]#Sprint Board/col:Build/split:QA::before"---## SummaryNo story leaves Build until QA signs off.Renders a .policy-chip before the QA split section.
(c) Meeting container with N rows
A Daily Standup meeting anchoring two positions of one board via its body table —
see the full example in §7. One file → two
markers (Done + Blocked), demonstrating the one container → N anchors
invariant.
(d) Interaction anchored to a route stage
An interaction note that points at the epic @ Coordination stage of the
Referenced Route (the route refs fixture):
---type: interactiontitle: Epic Kickoffinteraction-of: "[[Referenced Route]]/stage:epic @ Coordination::before"---Renders an interaction marker before the epic @ Coordination stage. The
stage: segment is the route extension of the grammar
(§3); the route-side marker + drawer
behavior is documented in route/anchors.md.
9. Out of scope
| Not covered (yet) | Where it lives |
|---|---|
Wildcard / predicate queries — e.g. “every column whose name matches *Done*” or “all lanes in group X” | Future work; the current grammar addresses one exact position per locator. |
| Topology anchors — anchoring to a Work System rather than a board position | See topology/index.md; the value-polymorphic interaction-of: / agreement-of: keys cover the WS-wikilink shape (see §5). |
Reference parser: packages/dwsd-dsl/src/common/parse-locator.ts. Sibling
reference: topology/index.md.
Like what you see? → Start — build your own org folder in ~2 min