# Writing Requirements — A Styleguide

> Author: **Tim Georg** — © 2026
> Licensed under the **Creative Commons Attribution 4.0 International License (CC BY 4.0)**.
> See https://creativecommons.org/licenses/by/4.0/legalcode.

How to write a requirements document that stays true. It covers the file layout in a
repo, how one requirement is identified and worded, and the rules that keep a spec from
describing something that no longer exists.

This is written for a repository you own, in any language and any stack. It assumes
requirements are the artefact you hand to whoever builds the thing — a colleague, a
contractor, or an agent. It does not assume a team, a tracker, or a tool.

---

## 1. What a requirements document is

A requirements document answers one question: **what must be true when this is done?**

Not how it is built (that is the plan), not what was built (that is the changelog).

A requirement is, normatively, **"a capability or property that a system shall have"**.
That sentence is the capability-only rule: a technology is not a capability, and a
structure is not a property.

Three properties matter, in this order:

1. **Addressable.** Every requirement has an ID, so a plan, a pull request, a test and a
   retro all point at the same thing. Nobody has to quote prose that may have moved.
2. **Verifiable.** Every requirement states how you would know it is satisfied — by a
   test, a measurement, or an inspection. "Fast" is not a requirement. "First token
   within 10 s on a quantized 7B model" is. "Nothing leaves the machine" is also, and it
   is checked by inspection, not by a test.
3. **Alive.** A requirement that changes silently is worse than no requirement. Change
   goes through the file *and* the changelog, in the same commit.

A spec is not a wish list and not a design document. If a sentence would be equally at
home in an architecture decision record, it belongs in the architecture decision record.

### 1.1 Where the defects actually are

This is the part most style guides get wrong, so it comes before the rules.

Requirements defects have a measured distribution:

| Error class | Share of defects |
|---|---|
| Incorrect fact | **49%** |
| Omission (a requirement never written) | **31%** |
| Inconsistency | 13% |
| Ambiguity | 5% |
| Misplaced requirement | 2% |

*(Hooks & Farry, reproduced in Ralph R. Young, The Requirements Engineering Handbook,
2004, table 7.3.)*

**Eighty per cent of requirements defects are wrong facts or missing requirements.**
Neither is fixed by better wording. The rules in section 5 — one claim per criterion,
name the actor, no semicolon — reach only the 18% that is inconsistency plus ambiguity.
Ambiguity alone is 5%.

So wording is the *third* instrument, not the first:

| Instrument | Reaches | Where |
|---|---|---|
| A provenance mark on every requirement | the 49% | section 3.2 |
| A coverage sweep before you finish | the 31% | section 6 |
| Careful wording | the 18% | section 5 |

A cleaner sentence about an unchecked belief is a better-written defect. Write the
mark first.

---

## 2. The folder contract

Keep all product requirements in `docs/requirements/`, one file per domain, with a
numeric prefix that says what kind of file it is. The number is the file's address. it
does not change when the content does.

| Range | Holds | Examples |
|---|---|---|
| `00` | The index. Navigation only, no content of its own | `00_INDEX.md` |
| `01–02` | Core: vision and principles, system context | `01_VISION.md`, `02_CONTEXT.md` |
| `10–19` | Feature domains, one per vertical | `10_COLLECTIONS.md`, `13_LIBRARY_VIEW.md` |
| `20–29` | Data contracts: the shapes on disk and over the wire | `21_COLLECTION_JSON.md` |
| `30–39` | Architecture and integration extensions, when a repo needs them | `30_EXPORT.md` |
| `90–94` | Global files that cut across every domain | `90_NFRS.md` through `94_ASSUMPTIONS.md` |
| `99` | The changelog | `99_CHANGELOG.md` |

**One number, one file.** Two files sharing a prefix means the number no longer
identifies anything, and every link that says "see 15" becomes ambiguous. Take the next
free number instead.

**Why split at all.** Most work touches one vertical. One file per domain means a change
touches one requirement file plus the changelog, and two people working on different
domains never conflict. The alternative is one `REQUIREMENTS.md` that everything appends
to. That turns every change into a merge conflict, and it makes "what does the spec say
about X" a full-file read.

**A single monolithic document is frozen, not maintained.** If a repo still has one from
before the split, it stays for historical reference and says so at the top. Editing both
is how the two copies drift apart.

**A long well-structured document beats a short unstructured one**, because structure
lets a reader read selectively. Do not cut a spec because it grew. cut it because a
section stopped being true.

---

## 3. One requirement

One requirement is one headed block:

```markdown
### COL-1 - Create collection from PDF files

Status: Done
Priority: P0
Basis: V — owner confirmed 2026-06-03; issue ISSUE-12 section 2
Serves: turn a private document collection into a queryable knowledge base

Verification criteria:

- VC1: Only .pdf files are accepted; a non-PDF selection raises an error that names the file.
- VC2: Duplicate basenames in one request are rejected before any disk write.
- VC3: PDFs are copied to a sources/ directory so the originals can move or be deleted safely.
```

### 3.1 The ID

A short domain prefix and a stable integer: `COL-1`, `CHAT-3`, `MM-17`, `LIC-4`. The
prefix is the domain's abbreviation, chosen once and reused in plans, issues, commit
messages and test names. The integer is never reused after a requirement is dropped — a
dead ID is a tombstone, not a vacancy.

### 3.2 `Basis:` — the field that carries the most weight

Four values:

| Value | Means | What it must say |
|---|---|---|
| **V** | Verified against an authoritative source | Cite it: a person and date, a file and section, a standard |
| **N** | Not yet researched | Name the belief and what would settle it |
| **Q** | Questionable | Name the doubt |
| **I** | Inferred by whoever wrote this, not stated by anyone | Say what it was reasoned from |

**A mark with no stated reason is not allowed.** The mark exists to carry the reason.
`Basis: N` alone is a to-do list with no item.

`I` is there because an inference that is not labelled reads exactly like a fact. It is
the commonest failure when requirements are drafted from a conversation. The writer fills
a gap they believe is self-evident, and nobody ever says it out loud.

**One escalation rule makes `N` work:** *if no authoritative source can be found, write
the rationale instead.* A belief with a stated rationale is a decision. A belief with
nothing is a defect waiting for a reviewer.

**`Basis:` is not the same field as `Source:`.** `Source:` is document provenance — which
file or decision the domain came from, at the top of the file. `Basis:` is whether anyone
has checked the claim. Collapsing the two looks tidy and does not work. Most `Source:`
lines in a mature repo record a document, so the mark has nowhere to live. A reader then
cannot tell an unmarked belief from a document reference.

A file-level `Source:` may legitimately point at a document that has since been split or
renamed. It records where the requirement came from, not what justifies it.

### 3.3 `Serves:` — the goal

The goal this requirement is a narrowing of. One line.

It is a deletion instrument rather than an archive. A requirement that cannot name its
parent goal is either unnecessary, or it *is* a goal and belongs in the domain intro. It
also supplies the why that a retro will ask for.

Three kinds, and the test is **who can be held to it**:

- a **goal** needs more than one party to act — it is a heading, not a requirement.
- a **requirement** is a goal narrowed until the software alone can be held to it.
- an **assumption** is a goal narrowed until something *outside* the software must hold
  it — a device, an API's behaviour, a file format, a human's habit.

The moment a sentence's subject is the user, the operating system, a remote service or
the filesystem, it is an assumption. It goes in `94_ASSUMPTIONS.md`, where it can be
given a break condition. No acceptance criterion can ever verify it from inside the
software, and yet the capability silently depends on it.

### 3.4 `Status:`

One of:

| Value | Meaning |
|---|---|
| `Active` | Specified, not built |
| `Partial` | Some verification criteria hold, the rest do not |
| `Done` | Every verification criterion holds |
| `Proposed` | Written ahead of a decision — may never be built |
| `Deferred` | Deliberately postponed, with a named version or trigger |
| `Rejected` | Considered and refused — keep it in place **with the reason** |
| `Superseded` | Replaced by another requirement — name the replacement **and the date** |
| `Obsolete` | No longer applicable — say why, and when it stopped being true |

`Superseded` and `Obsolete` are not the same thing, and neither is `Done`. A requirement
whose feature was removed is `Obsolete`, not `Done`. leaving it as `Done` tells the next
reader the behaviour still exists.

`Rejected` is the state that makes the decision record useful. Keeping a refused idea in
place with its reason is what stops it being re-proposed every few months.

`Superseded` needs a baseline to mean anything. Without a named replacement and a date it
only says "a newer file exists". The changelog is the version record. `Status:` is the
state record. They are orthogonal, and a requirement can pass through many versions
without a state change.

**Never edit a `Done` requirement's text in place.** Supersede it and add the next, so the
history keeps the reason. The same applies to any requirement a plan or an issue already
cites: its text is an address, and rewriting it silently moves the address.

### 3.5 `Priority:`

Four levels, and the definitions are written down once because a level without criteria
is a label, not a priority:

| Level | Means |
|---|---|
| `P0` | The product is not shippable without it |
| `P1` | The next thing built |
| `P2` | In scope, not scheduled |
| `P3` | Nice, deliberately not planned |

**Prioritise at one level only.** Vision statements always look more important than
capabilities because they subsume several. Criteria always look less important than the
capability they serve. Verification criteria inherit. they do not get a `P` value.

---

## 4. The capability-only rule, and its two licensed exceptions

Requirements do not name a technology, a library, a framework, a database, a file format
or a code structure. A requirement states a capability.

Stated that way the rule is unenforceable, because it has no exceptions and every real
project has technology pins that are genuinely requirements. There are exactly two homes
for a technology statement, and neither is a requirement block.

**1. A constraint.** Technology *imposed from outside*. Take any of these:

- the platform must be Windows 10+
- the file must stay readable by another tool
- the licence must verify offline
- a regulator requires an algorithm
- an existing component must be included

A constraint is normatively "a requirement that limits the solution space **beyond what
is necessary** for meeting the given functional requirements and quality requirements".

Write it with the **forcing reason**. A constraint without a reason is a plan decision
wearing a costume.

**2. A plan decision.** Technology chosen for convenience, taste or speed. It belongs in
the plan. When it appears in a requirements file, it is leaking.

**The test that catches more than "does it name a technology":** requirements models
depict the problem. design models document the solution. "The layout is computed headless
and the renderer only draws positions" names no technology and is still a design
statement.

**Six documented reasons the line cannot always be held**, so a review can cite a reason
instead of relitigating: existing components must be included. external prescriptions fix
structure and detail. describing the solution is easier. only the structured system is
surveyable. consistency is only clarified by realisation. and the questions about the
specification only arise in the design.

---

## 5. Verification criteria

Numbered `VC1` to `VCn`, or `AC1` to `ACn` in older files. One criterion is one claim you
can decide true or false without asking the author what they meant.

**Verifiable admits three instruments**: a test, a **measurement**, or an **inspection
procedure**. "Observable from outside the code" is too narrow. It assumes tests, which
pushes every property toward testability. Inspectable properties then get squeezed out:
no outbound socket, no mock code in a production bundle, no secret in the tree.

Four rules carry most of the weight:

- **One claim per criterion.** "Rejects a non-PDF file and reports the reason" is two
  criteria. Split it, or only half of it can fail.
- **Name the actor.** "Non-PDF files are rejected" leaves open who rejects them. "The app
  rejects a non-PDF file" is testable.
- **Keep the modal verb.** `must` and `should` are different requirements. Do not promote
  `should` to `must`, and do not turn a hedge into a fact.
- **No semicolon.** Two clauses joined by a semicolon read as one criterion and test as
  two.

**An adjective is a placeholder** for a number, a named standard, or a named checker:

| Written | Means |
|---|---|
| "fast" | within 400 ms |
| "robust" | under no circumstances ends abnormally |
| "portable" | portable by one developer in at most 6 hours |
| "clearly commented" | every module has a header comment per a named standard |
| "platform independent" | all platform-specific code lives in one module |

A criterion that still fails "how would I know this is true" has no threshold, not bad
wording. Send it back for a number instead of polishing the sentence.

Six linguistic patterns hide a defect, so run them as a pass over each criterion:

1. **Passive voice** hides who does it. "Data is deleted" — by whom?
2. **Copula instead of a verb.** "The data are consistent" — after what checked them?
3. **A missing object.** "The component reports an error" — to whom?
4. **An incomplete condition.** For an if/then, the else must also be described.
5. **A universal quantifier.** "all users", "every file", "never" — are there
   restrictions? Are "all users" all users, or the ones present?
6. **A nominalisation.** "Registration" hides who registers where and for what.

---

## 6. Sweep for what you did not write

Omission is 31% of defects and invisible from inside the document. You cannot review your
way to a requirement that was never written. Five passes, cheapest first.

**(a) The door dialogue — five minutes.** State the most banal fact about the capability,
then ask *"and when does that not hold?"* along four axes:

| Axis | Ask |
|---|---|
| **time** | First run? After an update? At midnight? Leap day? |
| **actor** | New user? Wrong user? Two users at once? No user? |
| **failure** | Disk full? Network gone? File corrupt? Permission denied? |
| **term** | What does the word mean at the edge of its range? |

Every answer that changes behaviour is a criterion. This is where the exception criteria
nobody writes come from.

**(b) Continuity.** Most requirements in a mature system are *continuity* requirements, not
change requirements. People describe what displeases them, so a list driven by complaints
records the differences and silently omits everything that must stay the same.

Give every domain file a short section:

```markdown
## Must not change
- Existing library files stay readable; a variant never becomes a second row.
- A scan that only adds files never rewrites an existing record.
```

If you cannot name anything that must not change, say so explicitly. That is a finding,
and for anything older than a prototype it is usually wrong.

**(c) Negate the verb.** For each significant capability, write the failure forms by
negating the verb and each qualifier — "message not sent / sent late / sent to the wrong
party". Then pick one of six moves and write it as a line:

| Move | What it adds |
|---|---|
| substitute the goal | a different way to achieve the same end |
| substitute the actor | someone or something else does it |
| weaken the goal | a smaller promise that still holds |
| restore after failure | a recovery requirement |
| mitigate | a narrower requirement with its own criteria |
| prevent | a guard, expressed as a requirement |

**(d) Walk the quality categories.** Six, one line each, most of them "nothing to say":
functional suitability, performance efficiency, compatibility, usability, reliability,
security. A blank category is a decision, not an oversight — but it has to be a decision.

**(e) Read as the tester.** For each criterion ask: can I derive a test from this? If not,
either the criterion is unverifiable or the requirement is under-specified.

**Run the full sweep only where it pays.** Classify the capability first:

| Class | When | Passes to run |
|---|---|---|
| **H** | Data loss, money, irreversible action, security boundary, or a failure that is silent | all five |
| **M** | Reversible, visible, user-facing | (a) and (b) |
| **L** | Cosmetic, internal, trivially reversible | none beyond the wording pass |

Most work is M or L. A checklist applied uniformly stops being read. a checklist applied
where the risk is is the only kind that survives.

---

## 7. Two more checks before you hand it over

### 7.1 Re-read it in a fresh context

**You cannot validate a requirement you just wrote.** Not because of bias, but because of
**silent completion**: your prior knowledge fills the gaps while reading, so the gaps are
never seen. This is structural. It is worse for a requirement drafted by an agent, which
has no memory of hesitating.

There is no second reader here, so use a substitute, cheapest first:

1. **Fresh-context re-read.** Give someone — or a clean session — only the requirement
   block and the domain file. Ask it to state what it would build and what it would have
   to guess. Every guess is a gap.
2. **Transcription.** Restate the requirement as a table row, a given/when/then clause, or
   a data-shape sketch. What cannot be transcribed is the ambiguity.
3. **Artefact construction.** Write one test case, one error message, or one data shape
   from the requirement. This is the highest-yield move available to one person, and it is
   not cheap — the cost is what forces the gap into view.

Run at least (1) for every H capability, and for any requirement marked `N`, `Q` or `I`.

### 7.2 Check a claim against its source, when there is one

A fresh-context re-read finds ambiguity. It does not find a **wrong fact**, because the
re-reader has the same beliefs you do. Only the source settles that.

`Basis: V` is a claim that a document says something, so it is checkable — but only if the
source is a link. Sort the citations first, because only one kind is worth reading:

| Kind | Example | What settles it |
|---|---|---|
| A download endpoint | a vendor's installer URL | an HTTP status |
| The vendor's own page | their marketing site | nothing: a vendor agrees with itself |
| **A specification** | a published file format, a standard clause | **the page, read properly** |

A source list is cheap to check for liveness and expensive to check for meaning. Spend the
effort on the specifications, and treat "the vendor's homepage does not say that" as a
question rather than a finding.

---

## 8. The global files

Five shapes shared across every domain, because each answers a question that does not
belong to one vertical.

### `90_NFRS.md` — non-functional requirements, one row each

```markdown
| ID | Kind | Category | Requirement | How it is measured |
|---|---|---|---|---|
| NFR-1 | Constraint | Security | No document, query or user data leaves the local machine | Inspection: no outbound socket at runtime |
| NFR-3 | Quality | Performance | Collection create for a 100-page PDF completes in under 60 s on a mid-range CPU | Timed, 3 runs, median |
```

**`Kind` is not decoration.** "Non-functional requirement" is an alias for two different
things — a **quality requirement** or a **constraint** — and they validate differently.

A quality requirement is *graded*. "First token within 10 s" is met or missed on a scale,
and its measurement method is part of the requirement. A constraint is a *hard limit*.
Either it holds or the build is wrong, and you check it by inspection.

Calling both NFR hides which of the two rules applies. It is also how a coding standard
("clean architecture: small focused components") ends up in a requirements file where no
criterion can ever verify it.

### `91_INVARIANTS.md` — properties that must never be false

```markdown
| ID | Constraint | Enforcement Points |
|---|---|---|
| INV-1 | The embedding model is immutable per collection after first indexing | UI disable, hook validation, backend rejection |
| INV-4 | Collection IDs are UUIDs and validated for all file operations | Path-resolution guard |
```

The third column is what separates an invariant from a wish. If nothing enforces it, it is
either a requirement or a note, and putting it here makes the table lie.

### `92_PLANNED_FEATURES.md` — the backlog

One line per item, each pointing at the issue or plan that carries the detail. A planned
feature is not a requirement until it has verification criteria. until then it is one line
and a link.

### `93_OUT_OF_SCOPE.md` — explicit non-goals, with the reason

| Topic | Rationale |
|---|---|
| Cloud or remote backend | Conflicts with the local-first principle |
| Multi-user sharing | The product is a single-user desktop app |

This file exists to end the same conversation twice. A non-goal without a rationale gets
re-proposed every few months. with one, the next discussion starts from the answer.

**Scope is not the system boundary.** They are different axes: a component *inside* the
boundary that must be reused is out of scope. a thing *outside* the boundary that can be
redesigned is in scope. Write each entry as a scope statement, not as a boundary claim.

### `94_ASSUMPTIONS.md` — what the world must hold

```markdown
| ID | Assumption | Breaks if | Owner |
|---|---|---|---|
| ASM-1 | The data directory stays at the same path between sessions | The directory is moved or mounted elsewhere | user |
| ASM-2 | The export format's importer tolerates absolute file URIs with escaped spaces | A future version normalises paths differently | vendor |
```

**Why this is a global file and not a section.** A requirement is something the *software
alone* can be held to. The moment a sentence's subject is the user, the operating system,
a remote API or the filesystem, no criterion can verify it. Yet the capability silently
depends on it. Those sentences are assumptions, they are load-bearing, and without this
file they have nowhere to live.

The `Breaks if` column is the whole value. An assumption nobody can name a break condition
for is decoration: delete it or turn it into a requirement. Review assumptions **when a
dependency changes**, not on a calendar.

---

## 9. The changelog rule

`99_CHANGELOG.md` is append-only, newest first, one `## YYYY-MM-DD` section per change,
each naming the requirement IDs it touched and why.

**A behaviour change updates the matching requirement file and the changelog in the same
change.** Not a follow-up commit, not a cleanup ticket. If the spec is updated later, it is
updated wrong, because the person who knew why has moved on.

The changelog is not a git log. It records *decisions about requirements* — a new
requirement, a changed criterion, one retired as obsolete — and it survives the history
being rewritten.

### Changing a requirement

**Two change moments, one gate.** Before a requirement is `Active`, changes flow in
directly. edit the text. After it is `Active`, they go through impact analysis first.

Impact analysis, in three questions:

1. Which other requirements does this touch? (depends-on, conflicts-with, refines)
2. Which verification criteria stop holding?
3. Which implementation is now wrong?

Then decide — and the decision *is* the changelog entry plus the status change. There is no
board, and one person does not need one.

Four reasons to reject, each written down: too costly for the benefit, **contradicts
another requirement**, **would destabilise the system**, not covered by the existing scope.
The middle two are the ones that get forgotten.

---

## 10. The index rule

`00_INDEX.md` lists every file in the folder with a one-sentence summary, ordered by
number, grouped by the ranges in section 2. It is the only way into the folder. a file that
is not in the index does not exist as far as a reader is concerned.

**Pick one index name and delete the other.** `00_INDEX.md` and `00_Index.md` both existing
is a guaranteed disagreement, and it is one of the most common ways a tidy-looking folder
rots.

---

## 11. What a script can and cannot check

A script can check the things nobody reliably remembers. Every file has an index entry.
IDs are unique. The changelog was touched. Every referenced ID exists. Every requirement
block has a `Basis:` mark. A path named in a `Source:` still exists.

Automate those. They decay at the rate of reviewer attention, and that rate is zero when
nobody is reviewing.

A script cannot check whether a requirement is any good. These stay with a person:

- whether a verification criterion is observable by a test, a measurement or an inspection.
- whether a `Basis: V` cites something that actually says that, or whether the belief was
  merely confident. 
- whether an NFR number has a measurement method, or is a number someone liked.
- whether a `Kind: Constraint` limit is really imposed from outside, or is a plan decision
  wearing a costume. 
- whether an invariant's enforcement column is true, or aspirational.
- whether a `Done` status still matches the running product.

That gap is deliberate. A requirement nobody can verify is a rule people will route
around.

---

## 12. The short version

A requirement is a capability the software alone can be held to. Say what must be true when
it is done, and how you would know.

Mark each one with how you know: verified, not yet researched, doubtful, or inferred by
you. A mark without the reason is worthless. The mark itself is worth more than any
amount of careful wording, because 49% of defects are facts nobody checked.

List what must not change before you list what is new.

Sweep for what you did not write — the door dialogue, the continuity section, the negated
verb — and sweep hardest where failure is silent, not everywhere.

Then re-read it in a context that does not already believe you, because you cannot see the
gap your own knowledge fills.

Everything else is tooling.
