DWSD.md

Flight Route DSL — Path & bands

A flight route’s path: is the abstract design layer of a delivery. It is built from three things and nothing else:

  • bands — the horizontal lanes work moves through, declared in bands:;
  • stages — an item type at a band (itemType @ Band), the nodes the path visits; and
  • edges — the glyph-typed transitions between stages, authored in path:.

The path is authored unbound — it references no board. Mapping a band onto a concrete board is a separate concern handled by the bound-to: layer (see Binding). This page covers bands:, stage identity, and external triggers:; the edge vocabulary has its own page (Edges). See the reference index for the route header and the full diagnostics family.

When prose and code disagree, the code wins. Every key and diagnostic on this page was authored from src/route/yaml-parser.ts, src/route/types.ts, and the validator rules src/route/rules/*.

Bands (bands:)

bands: is a list of band records. Each band is an object with a required name and an optional fl flight-level tag that drives its color.

FieldValues / shapeAST fieldDefaultDiagnostic on misuse
namestring (required) — the band’s display name and its stage-address keyRouteBand.name—route.invalid-band (entry skipped when missing/empty or not an object)
fla single number → one flight level; OR a numeric array (e.g. [2, 3]) → a multi-FL spanRouteBand.fl?(none — a pure custom band omits it)route.invalid-band when neither a number nor a numeric array (the key is omitted); route.invalid-band-fl (warn) when the value is outside the canonical 1..3

fl drives the band’s color. A single number picks the flight-level color; a multi-FL span (fl: [2, 3]) resolves to a single color — there is no gradient across the span. A band with no fl at all is a pure custom band (e.g. a “Company Wall” outside the FL model); the fl key is simply omitted from the AST. A fl value outside the canonical 1..3 set is kept but flagged with a route.invalid-band-fl warning, so an author sees exactly what they wrote.

Stage identity — itemType @ Band

A stage is a (itemType, band) pair — for example epic @ Strategy. That pair is the stage’s identity (StageRef { itemType, band } in the AST). The item type is plain text or a quoted wikilink: epic @ Strategy describes the item locally to the route (never looked up), "[[Epic]] @ Strategy" is a real connection to a flight-item-type entity (StageRef.link: true, itemType holds the link target Epic), and [[Customer Order|order]] @ ERP keeps a short label (StageRef.alias). The two forms mix freely, also on one line; a line holding a wikilink is quoted. See A stage’s item type is text or a wikilink.

Stages are never declared separately. There is no stages: section. Instead the parser infers the set of stages by walking the endpoints of every edge in path: (and every generates: target of a triggers: entry) and deduping by (itemType, band). So the same itemType @ Band written on two different edges is the same stage, visited twice — not two stages. The author form is always the string itemType @ Band (or "[[ItemType]] @ Band"); the @ splits on its first occurrence, so a multi-word band name (Core Products) is preserved, and a wikilink target is read up to its closing ]]. A stage is named by its token, or for a wikilink by its link target (not the alias) — that name is what locators and bound-to keys use. Two names in one band that differ only by case get a route.ambiguous-stage-name warning.

A stage carries no tags: — descriptive tags are document-scope only (see The document header). At a stage a tag would only repeat what the band position already says.

External triggers (triggers:)

triggers: declares external notes that sit OUTSIDE the bands — the events that kick a route off (a “new feature idea”, a “software defect”). Each trigger is an object with a required name and a generates target list; it is fan-out capable — one trigger can generate more than one stage.

FieldValues / shapeAST fieldDefaultDiagnostic on misuse
namestring (required) — the note’s labelTrigger.name—route.invalid-trigger (entry skipped when missing/empty)
generatesa scalar itemType @ Band, OR a list of them → one or more stagesTrigger.generates (StageRef[])[]route.invalid-trigger (an unparseable target is skipped)

A trigger’s generates uses the same itemType @ Band stage form as edge endpoints, so a trigger’s targets participate in stage inference exactly like edge endpoints do (generates: "[[Feature]] @ Train" is a wikilink stage). A generates entry that is not a valid itemType @ Band or "[[ItemType]] @ Band" string is skipped with a route.invalid-trigger warning; the rest of the trigger survives.

Worked example — multiple bands, one per flight level

The bands.dwsd-flightroute corpus fixture — three named bands, one per flight level (3/2/1), with stages living across them and copy edges between bands:

# bands — one named band per flight level; stages live across named bands.
title: Multi-Band Route
for: feature
bands:
  - name: Strategy
    fl: 3
  - name: Coordination
    fl: 2
  - name: Delivery
    fl: 1
path:
  - vision @ Strategy --> epic @ Coordination
  - epic @ Coordination --> story @ Delivery

Strategy (FL3), Coordination (FL2), and Delivery (FL1) are one band per level; vision, epic, and story are the three inferred stages, one per band. Both edges are copy (-->) between different bands — see Edges for the edge vocabulary.

A band may span several flight levels

The worked example above keeps one band per level for a clean 3/2/1 picture, but fl also accepts a numeric array — a multi-FL span (one color, no gradient across the span, per the bands: field table above). Here a single Product Stream band covers FL1+FL2:

title: Span Example
for: feature
bands:
  - name: Product Stream
    fl: [1, 2]
path:
  - epic @ Product Stream -> story @ Product Stream

A span is a valid authoring choice; the gallery example just prefers the clean one-band-per-level form.

Worked example — a fan-out trigger

An external trigger that generates a stage, then a copy edge carries it onward:

# a route kicked off by an external trigger sticky outside the bands.
title: Triggered Route
for: feature
bands:
  - name: Coordination
    fl: 2
  - name: Delivery
    fl: 1
triggers:
  - name: new feature idea
    generates:
      - epic @ Coordination
path:
  - epic @ Coordination --> story @ Delivery

The new feature idea note sits outside the bands and generates the epic @ Coordination stage; the path then carries a copy into Delivery.

Like what you see? → Start — build your own org folder in ~2 min

path-and-bands.md
# Flight Route DSL — Path & bands
A flight route's `path:` is the **abstract design layer** of a delivery. It is
built from three things and nothing else:
- **bands** — the horizontal lanes work moves through, declared in `bands:`;
- **stages** — an *item type at a band* (`itemType @ Band`), the nodes the path
visits; and
- **edges** — the glyph-typed transitions between stages, authored in `path:`.
The path is authored **unbound** — it references no board. Mapping a band onto a
concrete board is a separate concern handled by the `bound-to:` layer (see
[Binding](./binding.md)). This page covers `bands:`, stage identity, and external
`triggers:`; the edge vocabulary has its own page ([Edges](./edges.md)). See the
[reference index](./index.md) for the route header and the full diagnostics
family.
> **When prose and code disagree, the code wins.** Every key and diagnostic on
> this page was authored from `src/route/yaml-parser.ts`, `src/route/types.ts`,
> and the validator rules `src/route/rules/*`.
## Bands (`bands:`)
`bands:` is a list of band records. Each band is an object with a required
**`name`** and an optional **`fl`** flight-level tag that drives its color.
| Field | Values / shape | AST field | Default | Diagnostic on misuse |
|-------|----------------|-----------|---------|----------------------|
| `name` | string (**required**) — the band's display name and its stage-address key | `RouteBand.name` | — | `route.invalid-band` (entry skipped when missing/empty or not an object) |
| `fl` | a single number → one flight level; OR a numeric array (e.g. `[2, 3]`) → a multi-FL **span** | `RouteBand.fl?` | (none — a pure custom band omits it) | `route.invalid-band` when neither a number nor a numeric array (the key is omitted); `route.invalid-band-fl` (warn) when the value is outside the canonical `1..3` |
**`fl` drives the band's color.** A single number picks the flight-level color; a
**multi-FL span** (`fl: [2, 3]`) resolves to a **single color — there is no
gradient** across the span. A band with no `fl` at all is a **pure custom band**
(e.g. a "Company Wall" outside the FL model); the `fl` key is simply omitted from
the AST. A `fl` value outside the canonical `1..3` set is kept but flagged with a
`route.invalid-band-fl` warning, so an author sees exactly what they wrote.
## Stage identity — `itemType @ Band`
A **stage is a `(itemType, band)` pair** — for example `epic @ Strategy`. That
pair **is** the stage's identity (`StageRef { itemType, band }` in the AST). The item
type is **plain text or a quoted wikilink**: `epic @ Strategy` describes the item
locally to the route (never looked up), `"[[Epic]] @ Strategy"` is a real connection
to a flight-item-type entity (`StageRef.link: true`, `itemType` holds the link
target `Epic`), and `[[Customer Order|order]] @ ERP` keeps a short label
(`StageRef.alias`). The two forms mix freely, also on one line; a line holding a
wikilink is quoted. See [A stage's item type is text or a wikilink](./index.md#responsibility-and-references).
Stages are **never declared separately.** There is no `stages:` section. Instead
the parser **infers** the set of stages by walking the endpoints of every edge in
`path:` (and every `generates:` target of a `triggers:` entry) and deduping by
`(itemType, band)`. So the same `itemType @ Band` written on two different edges
is the **same stage**, visited twice — not two stages. The author form is always
the string `itemType @ Band` (or `"[[ItemType]] @ Band"`); the `@` splits on its **first**
occurrence, so a multi-word band name (`Core Products`) is preserved, and a
wikilink target is read up to its closing `]]`. A stage is **named** by its token, or
for a wikilink by its link target (not the alias) — that name is what locators and
`bound-to` keys use. Two names in one band that differ only by case get a
`route.ambiguous-stage-name` warning.
> A stage carries **no `tags:`** — descriptive tags are document-scope only (see
> [The document header](../document-header.md#route-s-tags-are-document-level-only)).
> At a stage a tag would only repeat what the band position already says.
## External triggers (`triggers:`)
`triggers:` declares **external notes that sit OUTSIDE the bands** — the events
that kick a route off (a "new feature idea", a "software defect"). Each trigger is
an object with a required **`name`** and a **`generates`** target list; it is
**fan-out capable** — one trigger can generate more than one stage.
| Field | Values / shape | AST field | Default | Diagnostic on misuse |
|-------|----------------|-----------|---------|----------------------|
| `name` | string (**required**) — the note's label | `Trigger.name` | — | `route.invalid-trigger` (entry skipped when missing/empty) |
| `generates` | a scalar `itemType @ Band`, OR a list of them → one or more stages | `Trigger.generates` (`StageRef[]`) | `[]` | `route.invalid-trigger` (an unparseable target is skipped) |
A trigger's `generates` uses the **same `itemType @ Band` stage form** as edge
endpoints, so a trigger's targets participate in stage inference exactly like edge
endpoints do (`generates: "[[Feature]] @ Train"` is a wikilink stage). A `generates`
entry that is not a valid `itemType @ Band` or `"[[ItemType]] @ Band"` string is
skipped with a `route.invalid-trigger` warning; the rest of the trigger survives.
## Worked example — multiple bands, one per flight level
The `bands.dwsd-flightroute` corpus fixture — three named bands, one per flight
level (3/2/1), with stages living across them and `copy` edges between bands:
```dwsd.flightroute
# bands — one named band per flight level; stages live across named bands.
title: Multi-Band Route
for: feature
bands:
- name: Strategy
fl: 3
- name: Coordination
fl: 2
- name: Delivery
fl: 1
path:
- vision @ Strategy --> epic @ Coordination
- epic @ Coordination --> story @ Delivery
```
`Strategy` (FL3), `Coordination` (FL2), and `Delivery` (FL1) are one band per
level; `vision`, `epic`, and `story` are the three inferred stages, one per band.
Both edges are `copy` (`-->`) between different bands — see [Edges](./edges.md)
for the edge vocabulary.
### A band may span several flight levels
The worked example above keeps one band per level for a clean 3/2/1 picture, but
`fl` also accepts a numeric array — a **multi-FL span** (one color, no gradient
across the span, per the [`bands:` field table](#bands-bands) above). Here a
single `Product Stream` band covers FL1+FL2:
```dwsd.flightroute
title: Span Example
for: feature
bands:
- name: Product Stream
fl: [1, 2]
path:
- epic @ Product Stream -> story @ Product Stream
```
A span is a valid authoring choice; the gallery example just prefers the clean
one-band-per-level form.
## Worked example — a fan-out trigger
An external trigger that generates a stage, then a `copy` edge carries it onward:
```dwsd.flightroute
# a route kicked off by an external trigger sticky outside the bands.
title: Triggered Route
for: feature
bands:
- name: Coordination
fl: 2
- name: Delivery
fl: 1
triggers:
- name: new feature idea
generates:
- epic @ Coordination
path:
- epic @ Coordination --> story @ Delivery
```
The `new feature idea` note sits outside the bands and generates the
`epic @ Coordination` stage; the path then carries a `copy` into `Delivery`.