Getting started
Write your first chapter, understand every part of it, and get the gate green.
This walks one complete chapter part by part. The chapter is real — it ships at docs/example/ and goldspec's own test suite runs the gate over it on every run, so what you read here is what the gate accepts.
You need goldspec installed and a directory. Nothing else — no project, no test suite, no language.
mkdir -p spec/diagrams
A spec is markdown a person reads. There is no vocabulary to import and no module a runner has to collect.
1. A chapter is a process flow
Not a subsystem, not a component, not a team's area. A chapter is one process, from start to done, and it contains many user stories — one for every step in it.
That is the single most important sentence on this page, and getting it wrong produces a chapter called the payments service, which accepts any sentence about payments and grows until nobody reads it. A process has a start, an end, and steps in between; most sentences are not one of its steps, so most sentences are refused on sight.
The chapter opens with the process's own story:
As a customer I want to place an order and be told it was accepted, so that I know I will be charged for something that is actually coming.
Every step is a user story too
A node in the flow is something a person does for a reason — so it takes the same shape:
As a customer I want to be told a line is short before my card is touched, so that I am not refunding money I should never have paid.
| Part | What it is for | What goes wrong without it |
|---|---|---|
| As a \<role\> | who this step is for | a step with no role is a task, and tasks have no acceptance |
| I want \<something\> | what they are trying to do, in their words | written in the system's words it becomes a design note and stops being arguable |
| so that \<goal\> | why the step is worth performing | nothing can say the step is finished — only that code for it exists |
The role can be a machine, and usually should not be. As a firewall I want to drop the packet is legal. It is also a sign you have stopped asking who is being helped — this is technology built for people, not for its own sake, and a flow whose every actor is a component has quietly become an architecture diagram. Ask who is worse off if the step never happens. If the answer is a component, look one level out for the person behind it.
The three things that are not the story
All three read as diligence, and all three are the usual first-chapter failure:
- The solution. As a customer I want my basket held in Redis with a 30-minute TTL. That is an implementation choice. The story is what the customer wants; how you do it is the design, and the design belongs where it can be argued with and replaced without touching the story.
- The incident. On 12 March a duplicate charge occurred because the retry handler… is history. Put it in the commit message, which is version-anchored and stays true after the state it describes has moved on. A story that narrates an incident has to be rewritten every time the incident recedes, and nobody rewrites it, so it reads as current forever.
- The status. …(done, verified in staging) is an authored verdict. A chapter never carries status. What is proven is derived from a run, and a
Status:line is true because somebody typed it — which is the failure this whole method exists to remove.
The test is mechanical: if deleting the sentence would change what the system must do, it belongs in the chapter. If it would only change what somebody knows about the past, it belongs in a commit message.
2. The hierarchy, and why you cannot get the altitude wrong
A flow can turn out to be a step inside a larger flow, and that costs you nothing. It is the normal case. Interview a business and they will tell you where they think their process starts; half-way through you discover it begins two departments earlier. The flow you drew does not get thrown away — it acquires a parent, and becomes one node in it.
So a step that is itself a process gets its own file, and the file name carries the drill-down:
| File | ids inside it |
|---|---|
chapter-01-ordering.md |
ch1-1, ch1-2, … |
chapter-01.3-charging.md |
ch1-3-1, ch1-3-2, … |
chapter-01.3-charging.md would be the drill-down of node ch1-3. Every id inside it is prefixed ch1-3, and in the parent that node is drawn as a sub-flow — [[…]] :::subroutine — so a reader knows to follow it.
(Illustrative. The worked example has no drill-down: it fits on a page, which is the whole test. A node drawn as a plain action is a node that did not need one.)
A sub-chapter carries all the same sections. It is a flow like any other; nothing about it is abbreviated.
When to split, and when to fold
One rule, and it is a legibility rule: a flow should print legibly on one sheet of paper.
- Too dense to read? A step is hiding a process. Give it its own file.
- Too sparse to be worth its own page? Fold it into its parent.
It applies to the expansion as well as the drawing, and that half catches more. A node whose story and reasoning run past a page is a node hiding a flow — its steps had nowhere to be, so they became sentences. If one of your nodes is turning into an essay, you have found a sub-process, not a badly-written story.
How big is too big, in numbers
Nobody has measured this, so what follows is a working heuristic and not a rule the gate holds you to:
| Nodes | What it usually means |
|---|---|
| 1–3 | too thin to be its own flow — fold it into its parent |
| 4–9 | the ordinary case |
| 10–14 | look hard for one step that is really a process |
| 15+ | there is a sub-flow in there, and probably two |
Applied to the drawing. The expansion of a ten-node chapter runs to several pages and that is normal — what the rule asks of the expansion is that no single node needs more than about a page, not that the chapter fits on one.
Treat all of it as a prompt to look rather than a threshold to obey. A ten-step process that genuinely has ten steps is a ten-node chapter, and splitting it to hit a number would be the tool choosing the shape of somebody's business.
Where the hierarchy stops
Downward, it stops when the business stops saying "oh, I forgot to tell you about…"
Upward, it stops somewhere different in kind: when the steps stop being things a person does and become quantities to tune toward a goal. A flow made mostly of parameters rather than stories is at the ceiling. That is a thing you notice rather than decide.
3. Draw the flow first
Write the diagram before any prose. It is the cheapest part to change and it finds the most, because a flow will not let you leave a step out — a box has to connect to something.
mermaid source for this flow
flowchart TD
START([START · somebody with a basket and a card]):::terminal
TAKE[ch1-1 · take the order]:::process
STOCK{ch1-2 · is every line actually available?}:::decision
SHORT[/REFUSED · said before the card is charged/]:::bad
CHARGE[ch1-3 · charge once]:::process
TELL[ch1-4 · tell the customer]:::process
DONE([END · an order the shop can act on]):::good
START --> TAKE --> STOCK
STOCK -->|no| SHORT
STOCK -->|yes| CHARGE --> TELL --> DONEWhat the gate checks:
- Steps and decisions carry ids; terminals and legend nodes do not. An id in the diagram must have a
###block, and a block must have an id in the diagram — the gate reports either alone.START,END, a refusal, and every node inside the legend are unidentified, which is what keeps them out of that check. - The flow has a start and an end. If every node is pointed at by another, nothing says where it begins. Two ends are fine, and so is a cycle.
- Every class a node names is defined by a
classDefin the same chapter. Use one that is not and the node renders unstyled, so the gate names it rather than letting the mistake be invisible.
What is advice, and worth taking:
- Shape carries the role — rectangle acts, diamond decides, stadium terminates.
- A refusal is a real ending. The order is short, so nothing is charged is an outcome the system produces on purpose. Flows that draw only the happy path ship with unhandled failures.
- Colour is never the only encoding (see below).
The drawing is what finds the defects. In one real conversion, three defects had sat undisturbed for as long as they were prose and became obvious the moment each had to be a box with edges — including an activity happening twice under two names, which is how a step gets done twice or not at all.
The shapes, and how to draw the ones that are not boxes
Mermaid flowchart, and seven roles. Copy this block into your first chapter and you have the whole dialect:
| Role | Mermaid | What it is |
|---|---|---|
| start / end | ([…]) :::terminal |
where the flow begins, and where it finishes |
| action | […] :::process |
a step somebody performs |
| decision | {…} :::decision |
a question with named arms |
| sub-flow | [[…]] :::subroutine |
a step that drills down into its own chapter |
| reached | ([…]) :::good |
a successful ending |
| refused | [/…/] :::bad |
an ending that is a refusal, and is on purpose |
| unknown / parameter | >"…"] :::unknown / :::parameter |
the flag shape — not a step |
An unknown or a parameter is attached to the node it affects by a dotted edge, and the label goes between the dots:
mermaid source for this flow
flowchart TD
STOCK{<b>ch1-2</b> · is every line<br/>actually available?}:::decision
CHARGE[<b>ch1-3</b> · charge once]:::process
U1>"<b>ch1-U1</b> what happens to a basket<br/>abandoned mid-charge"]:::unknown
P1>"<b>ch1-P1</b> how long a reservation<br/>is held"]:::parameter
CHARGE -.exposes.-> U1
STOCK -.tuned by.-> P1
classDef decision fill:#e9eef5,stroke:#2a78d6,color:#0b0b0b;
classDef process fill:#e9eef5,stroke:#2a78d6,color:#0b0b0b;
classDef parameter fill:#f4f2ee,stroke:#8a6212,color:#0b0b0b,stroke-dasharray:2 2;
classDef unknown fill:#f4f4f2,stroke:#898781,color:#0b0b0b,stroke-dasharray:2 2;Draw an unknown and a parameter; do not draw an NFR. An unknown must appear in the diagram and in ## Open unknowns — the gate checks that neither has it alone. A parameter is drawn for the same reason, so a reader can see which step it tunes. An NFR is a constraint rather than a thing in the flow, so it is not drawn at all.
A flag node never gets a ### block. Only nodes do. An unknown's prose is its bullet in ## Open unknowns; a parameter's and an NFR's prose sit inside the block of the node they affect (§5).
Colour is never the only encoding, and this dialect needs the help: start / end and reached are both ([…]), so in greyscale they are the same box. The text is what separates them — shout START, END and REFUSED in the label, as the example does. A reviewer may be colour-blind and a printed page is always grey.
Ship a legend, and the classDef lines under it. Both go inside the same mermaid fence as your flow. The legend is a subgraph holding one sample node per role; its nodes carry short names of their own — LT, LA, LD — and no ids, which keeps them out of the node-block check, and they connect to nothing, which the start-and-end check does not mind. The classDef lines are identical in every chapter, and a class you use without defining is a fault the gate will name.
This is the whole block, drawn and then given as source. Paste it into your chapter and you have the dialect:
mermaid source for this flow
flowchart TD
subgraph LEGEND["legend — shape carries the role"]
direction LR
LT([START / END — named in caps, so it reads in greyscale]):::terminal
LA[action]:::process
LS[[sub-flow — drills down]]:::subroutine
LD{decision}:::decision
LG([reached]):::good
LB[/REFUSED — a different shape, so it reads in greyscale/]:::bad
LP>parameter — a value to discover]:::parameter
LU>open unknown — a decision to take]:::unknown
end
classDef terminal fill:#eef0ee,stroke:#52514e,color:#0b0b0b;
classDef process fill:#e9eef5,stroke:#2a78d6,color:#0b0b0b;
classDef decision fill:#e9eef5,stroke:#2a78d6,color:#0b0b0b;
classDef subroutine fill:#e9eef5,stroke:#2a78d6,color:#0b0b0b;
classDef good fill:#e2f2e2,stroke:#0ca30c,color:#0b0b0b;
classDef bad fill:#f7e1e1,stroke:#d03b3b,color:#0b0b0b;
classDef parameter fill:#f4f2ee,stroke:#8a6212,color:#0b0b0b,stroke-dasharray:2 2;
classDef unknown fill:#f4f4f2,stroke:#898781,color:#0b0b0b,stroke-dasharray:2 2;A sub-flow node may point at a chapter you have not written yet. Nothing requires chapter-01.3-charging.md to exist before you draw ch1-3 as a sub-flow. Draw the process you have; the file follows when the node grows past a page. In the parent, a sub-flow node keeps an ordinary ### block with In / Held / Out stated at the parent's altitude — what the whole sub-process takes and leaves, not what its steps do.
Node labels may carry markup, and usually should. The snippets on this page are written plainly so the shape is visible, but real labels get long: <b>ch1-1</b> sets the id off from the text and <br/> wraps a label that would otherwise stretch the diagram into one very wide row. The worked example does both. Nothing is checked here — it is the legibility rule again, applied to the drawing.
4. ## Elements — the flow's data, in one place
Every piece of information the flow moves is defined once, at the flow, in six columns exactly. Rows are read positionally, so a table with a different header is not read at all:
| Element | Type | Card. | Domain | Enforced | Provenance |
|---|---|---|---|---|---|
| `basket` | list<line> | 1 | at least one line | yes | authored |
| `line` | record | 1..n per basket | — | — | authored |
| `line.quantity` | integer | 1 per line | 1 or more | yes | authored |
| `order` | record | 0..1 | — | — | derived |
| `order.reference` | string | 1 per order | unique across all orders | yes | generated |
| `reservation` | record | 0..1 per order | — | — | derived |
| `refusal` | record | 0..1 | names one short line | no | derived |
Every element the node blocks in §5 move is in that table, and that is not a coincidence — it is the rule. A refusal is an element like any other: it is a thing the flow produces, it has a shape, and a node that leaves one has to be able to name it.
The element's name is its id. It is unique across the whole corpus, so order.reference means one thing everywhere and nothing needs a lookup table.
Type is open, and deliberately — it is the only column that is. This is the logical model, the one a business person can read, so write what the thing is in words your business would use: record, string, integer, date, money, list<line>, set<rung>. It is not a database type and there is no closed set to learn. VARCHAR(20) is the physical model, which is a later conversation with different people in the room.
Card. counts instances of the element, and names a parent whenever the count is per something — 1 per line, 0..1 per order, 1..n per basket. Where the element is not inside anything, a bare 1 or 0..1 is right and names no parent: an order either exists or does not, and there is nothing for it to be per. Legal forms are 1, 0..1, —, and <range> per <parent> — so 1 per line, 0..1 per order, 1..n per basket. A bare 2 is refused: write 2..2 per device. A collection's size is part of its type, so list<line> is one value and never carries 0..n.
Domain is the set of legal values. Enforced says whether anything actually rejects one outside it:
Enforced |
Means |
|---|---|
yes |
a value outside the domain is rejected by something you can point at |
no |
the domain is stated and nothing rejects a value outside it |
— |
no domain is stated — and then Domain is — too |
Something you can point at may be somebody else's. A statute, a scheme, a regulator or a payment processor rejecting an out-of-domain value is enforcement, and yes is right — it does not have to be your code. What makes it no is that nothing rejects it, wherever that thing would live. This matters most in compliance-shaped processes, where most domains are enforced by somebody who is not you.
That last equivalence is what makes the column falsifiable. no is an honest and common answer: it says the design intends something the code does not yet hold you to, which is a gap you can see rather than one you cannot.
Provenance is a closed set of four. measured and assumed are refused:
| Value | Means | Read it as |
|---|---|---|
authored |
somebody decided it | ask its author |
observed |
read off a running system | run it again |
derived |
computed from other elements | recompute it |
generated |
minted from randomness | if lost, it is gone |
The question the column answers is if this were lost, could you get it back — which is why a generated passphrase is not derived.
The two halves of the section carry bold run-in labels — **Defined here.** above the six-column table and **Imported.** above the import table, each free to carry a sentence of explanation after it. There is no ### heading for either, and no ## Imports section: a fifth ## would fail the section-set check.
Elements you did not define
A flow consumes things its neighbours own. Name them and stop:
**Imported.**
| Element | Defined by |
|---|---|
| `customer` | ch3 |
The Defined by cell names a corpus — a chapter, a spec directory, a product — in whatever words a reader would recognise. The row itself is what makes the element known, so naming a chapter you have not written yet resolves and does not fault. That is often the honest entry: a flow pretending to define its own customer is lying, and an import pointing at work still to come is a fact.
siblings is for the other case — an element you did not import, defined in another spec directory of the same corpus. Without it the gate sees half a corpus and cannot tell a correct reference to a neighbour from a reference to nothing, so it would report yours as undefined. Paths in siblings resolve relative to the goldspec.toml that names them.
A dotted name does not oblige its parent to have a row. order.reference may be defined with no order beside it — define the parent when the flow moves the parent, and not to satisfy the notation.
An import names the corpus and nothing else. Restating the type or the provenance would be a second statement of a fact you do not own, and the gate refuses it. Where the corpus spans directories, say so with siblings (below) rather than letting the gate guess — a gate seeing half a corpus cannot tell a correct reference to a neighbour from a reference to nothing.
5. ## Nodes — the story, and the information it moves
One ### block per node. The node carries its story, then what it takes, holds and leaves:
### ch1-2 · is every line actually available?
> **As a customer I want to be told a line is short before my card is touched, so that I am not
> refunding money I should never have paid.**
| | |
|---|---|
| **In** | `order`, `line`, `line.quantity` |
| **Held** | `reservation` |
| **Out** | `order`, or `refusal` |
In is what the step needs to begin. Out is what it leaves for the next one. Held is the one people skip: information that exists only inside this step — a reservation between the availability check and the charge, an idempotency key, a working total. It is neither an input nor an output, and writing it down is what lets somebody check that a step actually has what it needs.
Held counts. An element that appears only in one node's Held and nowhere else is properly defined and properly used — the gate's "defined here and no node moves it" fault does not fire on it. If Held did not count you would have to fake an In or an Out for every one of them, which would lose exactly the thing the row is for.
A node references elements; it never defines them. No type, no cardinality, no provenance in a node block — those live in ## Elements, once. This is the rule that stops the format growing a second home for every element more than one node touches, and the gate enforces it.
— is a legal entry. A step that holds nothing says so.
A cell is a list of element names, and or is allowed in it. Writing order, or refusal says a step leaves one or the other, which a comma alone would not. The gate reads the names and ignores the words between them, so write what a person would read.
Everything a node block holds, in order
### ch1-2 · <the step, named as the flow names it>
> **As a <role> I want <something> so that <goal>.** <- the story
| | | <- In / Held / Out
|---|---|
| **In** | … |
| **Held** | … |
| **Out** | … |
<one paragraph: why the step is the way it is> <- the closing argument
**Parameter `ch1-P1` — …** <- parameters and NFRs, last
**NFR `ch1-N1` — …**
Parameters and NFRs live at the foot of their node's block, and nowhere else. There is no ## Parameters section and no ## NFRs section — adding one fails the section-set check, and the fault names the wrong set without telling you where the content should have gone. A parameter or an NFR belongs to the step it constrains, so it sits with that step.
A constraint on the whole flow goes between the diagram and ## Elements, in the same bold form:
```mermaid … ```
**NFR `ch1-N1` — the processor's reconciliation window closes at 02:00.** …
## Elements
Some constraints genuinely are not one step's — a regulatory clock that binds the whole process is not the property of whichever node happens to start it, and hanging it on one node to satisfy the format is bending the model to fit the tool. Flow-level and node-level NFRs share one number series per chapter.
The four kinds of subject
A subject is anything that carries an id, and there are four kinds. Three of them hang off the node they affect. The discriminators are worth learning once:
| Kind | Id | Known? | Has a model? | Waiting on |
|---|---|---|---|---|
| Story | ch1-2 |
the want is known | — | proof it is done |
| Open unknown | ch1-U1 |
the question is; the answer is not | no | a decision |
| Parameter | ch1-P1 |
it exists; the value does not | yes | a discovery |
| NFR | ch1-N1 |
fully known, external | no | to be accounted for |
Unknown or parameter? Both are not-knowing. An unknown is settled by deciding; a parameter is settled by measuring, and what makes it a parameter is that you can write the model today — a statement of what would confirm the value. How long is a reservation held has a model (the distribution of reservation-to-charge times bounds it from below; stock held-and-abandoned bounds it from above), so it is ch1-P1. What should happen to a basket abandoned mid-charge has no model, only a choice, so it is ch1-U1.
A parameter that nothing has measured shows on the board as unvalidated — the honest word for a constant nobody has tested. That word is derived and you never type it into a chapter. Write the model; whether it has been run is a fact about runs, and a parameter carrying its own status in the prose would be the authored verdict this method exists to remove, wearing a different word.
What is an NFR? An external imposition that restricts how the process can work, rather than something it enables, which the flow has to account for:
**NFR `ch1-N1` — the processor's reconciliation window closes at 02:00.** External, known, and
restricting: a charge submitted near it may settle on either side of the boundary, so nothing
here may assume same-day settlement. It is not this shop's to negotiate.
A plain bold run-in paragraph, never a blockquote — the blockquote is reserved for a story, which is how a reader tells the two apart at a glance. Parameters take the same form.
It gets an id even though nothing is undecided about it, and the reason is different from every other id here: so that a room full of people can point at the same thing. A second reason shows up as soon as you write two — constraints in tension become visible. In prose, two constraints fifty words apart read as one continuous line of reasoning.
6. ## Open unknowns — and why there is no backlog
An unknown lives in both of its homes: a bullet here, and a node in the diagram attached to the step it affects. The gate checks that neither has it alone.
- **ch1-U1 — what happens to a basket abandoned mid-charge.** The card was submitted and no
response came back. The order is neither placed nor refused, and the customer has closed the
tab. Settling it needs the processor's reconciliation behaviour, which nobody has read yet.
This is what replaces a backlog. An unknown is a question anchored to a step; an unproven subject is the work item. Anything anchored to nothing is not undefined — it is uncommitted, and it should be dropped rather than filed. A side list of open questions is how a backlog regrows under a new name, and it goes stale exactly the same way.
Write what would settle it. We should look into caching is not an unknown. Whether the processor's reconciliation window closes before ours does — needs their documentation read is one, because a reader knows what to go and do.
7. ## Glossary — the words this flow uses differently
Last section, and it is not a dictionary of every noun. It is for words that mean something specific to this process, and likely mean something else in a neighbouring one or in ordinary English:
| Term | Meaning |
|---|---|
| Order | A basket the shop has accepted responsibility for. It has a reference from the moment it exists |
| Short | A line the shop cannot fill in the quantity asked for. Not the same as *out of stock*, which is a property of the item rather than of this order |
Order earns a row because the moment a basket becomes one is exactly what this chapter is about. Short earns one because a neighbouring flow uses it to mean something else.
8. The whole file, in order
Four ## sections, and the gate enforces both the set and the order:
# Chapter 1 — <the process, named from a person's side> <- H1 title
> **As a <role> I want <something> so that <goal>.** <- the flow's story
<a paragraph or two of context, optional>
```mermaid … ``` <- the flow
## Elements
## Nodes
## Open unknowns
## Glossary
The title, story and diagram come first, in that order, above the sections. A reader looking for the glossary should never have to hunt for it.
Small things that are part of the format, so you do not have to guess them:
- The
###node headings read### ch1-2 · take the order. The id comes first; the·is conventional and a—or-reads the same. ---rules between node blocks are decoration. Use them or do not.- The In / Held / Out table has an empty header row —
| | |then|---|---|. It is a two-column table whose columns are unlabelled, because the labels are in the rows. - A node's closing paragraph is part of the format, and it is where most of a chapter's value ends up: a short argument for why the step is the way it is, usually about which failure is more expensive. The story says what somebody wants; this says why it is worth the trouble. Keep it to a paragraph — if it runs past that, see the density rule in §2.
- A cycle is legal. Rework loops, retries and go back and amend are real shapes of real processes, and drawing a back-edge does not trip the start-and-end check. Model the process you actually have.
- An empty section is legal. A chapter with no open unknowns still carries the heading.
9. Run the gate
$ goldspec spec
goldspec: conventions from goldspec.toml
goldspec: ids namespaced 'ch', flows in diagrams/chapter-*.md
0 fault(s) in spec
Exit 0 clean, 1 with faults. Every run names the conventions it judged by — read that line first when a fault surprises you, because every message the gate emits is phrased as a verdict on your spec, and being handed the wrong strings is the one cause it cannot phrase.
What a refusal looks like
Faults name the file, the id where one applies, and what to do:
chapter-01-ordering.md#ch1-2 moves `coupon`, which no element list defines here,
imports, or resolves in a sibling
chapter-01-ordering.md#ch1-5 is drawn in the flow and has no node block — the
diagram is the authority and the expansion is what a reader follows it into
chapter-01-ordering.md: `refund` is defined here and no node moves it — delete
the row, or the step that needs it is missing
chapter-01-ordering.md: sections are ['Elements', 'Nodes', 'Glossary', 'Open
unknowns'], not ['Elements', 'Nodes', 'Open unknowns', 'Glossary']
chapter-01-ordering.md: the import of `customer` says more than where it is
defined — an importing chapter names the corpus and nothing else
The last one catches a habit rather than a typo: an import row names the corpus and stops, because anything more is a second statement of a fact you do not own.
Expect a few rounds on your first chapter. Every convention the gate enforces is on this page — if you hit one that is not, that is a defect in this page and worth telling us about.
10. Your conventions, in one file
Ids namespaced REQ or sh, flows in a directory named to suit you: all yours. Say so in a goldspec.toml, beside your chapters or beside the spec directory:
[conventions]
namespace = "ord" # ids read ord1-1, ord1-2
flow_dir = "flows" # where the process flows live, relative to the spec
flow_glob = "story-*.md" # how a flow file is named
data_definitions = true # hold chapters to the six-column table
node_format = true # hold chapters to the node format
siblings = ["../shared/spec"] # more of this corpus, read for what it defines, never checked
Every key is optional, and the defaults are our habits: namespace = "ch", flow_dir = "diagrams", flow_glob = "chapter-*.md", data_definitions = true, node_format = false, no siblings. A project that took all of them writes no file at all.
Write only what differs. A key restating a default is a copy, and it will not rot at the same rate as the original — if a default ever moves, your file silently disagrees with it. The example's whole conventions table is one line.
The file name decides the id prefix, and it is checked. chapter-01-ordering.md under namespace ch gives ch1, so every id in it must read ch1-…; chapter-01.3-charging.md gives ch1-3. Get the number wrong and every id in the file is reported as not namespaced to its chapter.
data_definitions and node_format are about different things and both apply. The first holds the six-column element table to its rules — the columns, the cardinality grammar, the closed provenance set — wherever that table lives. The second holds the chapter to the node shape. Under node_format = true the table sits under ## Elements instead of ## Data definitions, and data_definitions still governs what is in it.
The command reads this file and so does the library, so a project running both cannot end up with two gates over one spec that disagree.
Put it beside your chapters if one repository holds more than one spec — a file at the root is found for every spec directory beneath it, which is right for a project with one spec and wrong for a project with two.
node_format is off by default and that is a phase. Every corpus written before this format exists in the older shape — one ## Input → process → output and one ## Data definitions per chapter — so a chapter that has not converted is reported on rather than failed. A new project has nothing to convert, so a new project turns it on, which is what docs/example/goldspec.toml does.
11. What a spec does not hold
| Not this | Why | Where it goes |
|---|---|---|
Status, percent complete, Status: accepted |
typed, therefore true; the superseded ones read exactly like the current ones | derived from a run |
| A backlog or task list | grows endlessly, stale the moment it is written | an unproven subject is the work item |
| What went wrong on a given day | history, and it stops being true | the commit message |
| A second copy of a decision | two homes for one fact, drifting from the moment either moves | one subject, cited by id |
| A mapping table of old ids to new | an id resolving only through a lookup is a second name for something that already has one | rewrite the citations |
A spec states the plan, not the state. A subject says what the design is; whether you have got there is what a run reports, and it is never folded back into the claim. An unproven subject is not a failure of the spec — it is the work, visible.
Where to go next
- The worked example — the chapter above as a spec directory you can copy and run
goldspecagainst. - From decision records to user stories — a real project that converted, why its prose kept log-jamming, and what the conversion found.
- Plumb — derives whether a story is actually proven by a test run. goldspec checks that what you wrote down is coherent; Plumb checks that the code honours it. Neither requires the other.