Writing Specs
Specifications are markdown documents containing requirements. Each requirement has a unique ID and describes a single behavior or constraint that is both implementable and testable.
Requirement markers
Define requirements using the syntax PREFIX[requirement.id]:
r[auth.login]
The system must accept a username and password and return a session token.The requirement marker must appear as its own paragraph — either at column 0 or inside a blockquote. Everything following the marker until the next blank line, heading, or another marker is the requirement text.
Blockquote form
Use blockquotes when a requirement contains multiple paragraphs, code blocks, or other rich content:
> r[api.error-format]
> API errors must follow this format:
>
> ``` json
> { "error" : "message" , "code" : 400 }
> ``` Inline markers are ignored
Markers that appear inline within text are treated as regular text, not requirement definitions:
When implementing r[auth.login] you should...This does not define a requirement. Only markers at column 0 or inside a blockquote count.
Prefixes
The prefix (r in r[auth.login]) identifies which spec a requirement belongs to. Tracey infers prefixes from what you write in your spec files — you don't configure them. Any lowercase alphanumeric string works:
r— common defaulth2— for an HTTP/2 specreq— more explicitapi— for an API spec
Each spec's prefix is determined by the markers in its markdown files. If your spec uses r[...], that spec's prefix is r.
Naming requirements
Requirement IDs are dot-separated segments:
section.subsection.nameEach segment may contain ASCII letters, digits, hyphens, and underscores. IDs must contain at least one dot.
Valid IDs:
auth.loginapi.v2.response-formatchannel.id_allocationcrypto.sha256.validation
Invalid IDs:
login— no dot (single segment)auth..login— empty segmentauth.login.— trailing dot.auth.login— leading dotauth.lo gin— spaces not allowed
Naming conventions
Use a consistent hierarchy that mirrors your spec's section structure:
# Authentication
r[auth.login]
...
r[auth.token-expiry]
...
## Password Policy
r[auth.password.min-length]
...
r[auth.password.complexity]
...Structuring your spec
Use markdown headings to organize requirements into sections. The tracey dashboard groups coverage by heading, so a well-structured spec produces a useful coverage outline:
# Channel Management
## ID Allocation
r[channel.id.sequential]
Channel IDs must be allocated sequentially starting from 0.
r[channel.id.parity]
Client-initiated channels must use odd IDs, server-initiated channels must use even IDs.
## Lifecycle
r[channel.lifecycle.open]
A channel must be explicitly opened before any data can be sent.
r[channel.lifecycle.close]
Either peer may close a channel at any time. The closing peer must send a close frame.Avoiding duplicates
Same file: The same requirement ID appearing twice in one file is an error.
Same spec, different files: The same requirement ID in two files belonging to the same spec is also an error. Requirement IDs must be unique within a spec.
Different specs: Different specs may reuse the same requirement ID freely, because they have different prefixes:
<!-- docs/internal-spec/api.md — prefix "r" -->
r[api.format]
API responses must use JSON.
<!-- vendor/messaging-spec/format.md — prefix "m" -->
m[api.format]
Messages must use Protocol Buffers.These don't conflict because r[api.format] and m[api.format] belong to different specs.
Versioning
Requirements can carry a version suffix like r[auth.login+2]. This is covered in detail in Versioning. The short version: when you change a requirement's text, you bump its version number so tracey can tell you which code references are stale.