The Pipeline Spec Format
A pipeline spec is a YAML file under docs/methodology/specs/, with schema: branes.pipeline/1.
It is a development artifact: the tool reads it and generates the site’s pages, and no code in
sdk/ or daemons/ parses it. All paths are relative to the repository root. The worked example
is docs/methodology/specs/openvins-msckf.yaml.
Top level
Section titled “Top level”| Key | Required | Meaning |
|---|---|---|
schema | yes | branes.pipeline/1 |
name | yes | kebab-case; names the generated page and figure |
title | yes | the pipeline’s name on the site |
summary | no | a paragraph: what the algorithm does |
sources | yes | the papers: {id, cite} each; every node cites one |
edges | yes | the data structures (below) |
nodes | yes | the transformations (below) |
schedule | yes | the call sequence (below) |
system_invariants | no | what the whole graph must satisfy (below) |
base, unchanged_consumers | delta only | see Delta specs |
Edges: the data structures
Section titled “Edges: the data structures”| Key | Meaning |
|---|---|
id | the name nodes use in reads and writes |
kind | data (default); input (a sensor or a calibration: no node writes it); state (the shared mutable state); output (leaves the graph: no node reads it) |
contents | what it holds, with units |
code, symbol | the file and the type or function that implements it; the symbol must occur in the file |
invariants | a list of {text, check, bound?}; check is a reference, bound is below. An invariant without a check is a gap |
precision | free text: how narrow a type this structure tolerates |
deviations | how the implementation departs from the paper; each is a gap |
Nodes: the transformations
Section titled “Nodes: the transformations”| Key | Meaning |
|---|---|
id, stage, title | the node’s name, its stage label (e.g. S2), its title |
does | one line: inputs → outputs |
reads, writes | edge ids. A node that reads and writes the same edge updates it in place |
source | {ref, where}: a sources id and the section or equations |
pre, post | the pre- and post-conditions, one per list item |
code | the sdk/ header that implements it |
bench | a reference to its stage bench |
tests | a list of references to its unit tests |
status | implemented, partial, planned (specified ahead of the code: the schedule may call it, and it is reported as a gap) or missing (deliberately left out: the schedule must not call it) |
optional | true for a node the algorithm can run without |
precision | {policy, note}: the node’s arithmetic-type policy and what was measured |
decisions | the choices the paper leaves open, below |
deviations | how the implementation departs from the paper; each is a gap |
Schedule
Section titled “Schedule”schedule: driver: sdk/include/branes/sdk/msckf_backend.hpp # the code that runs it events: - event: imu # not `on:` — YAML reads a bare `on` as true rate_hz: 200 steps: - {call: propagate, when: initialized}Each step is {call, when?, foreach?, note?}. Every node that isn’t missing must be called, and
a missing node must not be. A spec written before its code calls its planned nodes.
System invariants
Section titled “System invariants”{id, text, check?, bound?, status?, note?}, where status is holds, failing or unknown (the
default). A failing invariant is reported as a gap together with its note.
Decisions
Section titled “Decisions”A paper often leaves a choice open, or names options: a zeroth-order hold or RK4 for the IMU mean, a gate line, a prior. Each is recorded on the node that makes it:
decisions: - question: the IMU input over each interval chosen: the sample that ends it, held constant alternatives: [the previous sample held, "both samples, interpolated and integrated by RK4"] reason: "…" # what was measured or argued; omitted, the choice was a defaultquestion and chosen are required. A decision without a reason is reported as a gap: the
choice was made by default rather than decided. The rule exists because of #616. The VIO mean
integrator was a one-sample Euler step from the first commit (#186). The canonical pipeline document
named RK4 as OpenVINS’s alternative, but nothing recorded a decision. Five months later the choice
turned out to hold each IMU sample 2.5 ms off the camera: NIS 1.1–1.9 on the fast sequences, and
ATE 10–15× worse there than with both samples used.
Bounds
Section titled “Bounds”A check’s tolerance comes from what the node that consumes the result must resolve, never from what the implementation currently achieves:
bound: value: 1.1e-4 rad over a 0.55 s clone window consumer: msckf_update # a node id: whose resolution the bound comes from derivation: a tenth of the 0.5 px track noise the update resolves, at fx = 458.65value and derivation are required, and consumer must name a node in the spec. The rule
exists because of #613. Its first S2-closure test measured S2’s first-order error, then set its
threshold to accept it, and so passed exactly the defect #616 later found. A bound derived from the
camera’s resolution failed it 28×.
References
Section titled “References”A reference is {file, symbol?, case?}. The file must exist; a symbol must occur in its code as
an identifier (a mention in a comment or a string literal doesn’t count, so prefer a distinctive
type or function name); a case must be the beginning of the name of one of its active TEST_CASE
or TEMPLATE_TEST_CASE calls (a commented-out case doesn’t count). A broken reference is an
error, so a rename in the code fails the spec’s check rather than leaving a stale page.
Delta specs
Section titled “Delta specs”A paper that changes an existing pipeline is written against it:
schema: branes.pipeline/1base: openvins-msckf.yaml # relative to this file; must be a full specname: riekf-msckftitle: Right-invariant MSCKFedges: - {id: FilterState, op: replace, kind: state, contents: ...}nodes: - {id: propagate, op: replace, ...}unchanged_consumers: FilterState: {sensor_model: "reads only the calibration"}- A delta has its own
nameandtitle; reusing the base’s name is an error, since it would overwrite the base’s generated page. - Each edge or node entry carries
op:add(the base must not have it),replace(it must) orremove(it must). A replaced entry is written out in full. sourcesare appended to the base’s; ascheduleorsystem_invariantsgiven in the delta replaces the base’s.- When a delta replaces or removes an edge, every base node that reads or writes it must be
replaced or removed in the delta, or named under
unchanged_consumers.<edge>.<node>with the reason it is unaffected. Otherwise the tool reports an error.
YAML pitfalls the tool catches
Section titled “YAML pitfalls the tool catches”#starts a comment in a plain scalar, so(#474, #574)would silently become(#474,. The tool reports any string with unbalanced brackets. Quote strings that cite issues.- A repeated key keeps only its last value in plain YAML, so a second
deviations:would erase the first. The tool rejects a duplicate key. - A comma splits a flow list, so
pre: [at rest, or moving]is two conditions. Use a block list of quoted strings for prose.