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.Diagrams
Spec files support Mermaid diagrams using fenced code blocks with the mermaid language tag:
``` mermaid
graph TD
A[Client] -->|request| B[Server]
B -->|response| A
``` The diagram is rendered client-side by Mermaid.js when the spec is viewed in the dashboard. The source text is stored in the spec file as-is, so it's readable and diffable in plain text.
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.
StrictDoc format (.sdoc)
Tracey also reads StrictDoc .sdoc files as a sibling to markdown. The format is picked from the file extension — there is no format field on SpecConfig. Mix .md and .sdoc files inside the same include glob freely; both end up in the same coverage table.
Minimal example:
[DOCUMENT]
TITLE: Channel management
[REQUIREMENT]
UID: CH-001
TITLE: Sequential ID allocation
STATEMENT: Channel IDs MUST be allocated sequentially starting from 0.
[REQUIREMENT]
UID: CH-002
TITLE: Parity rule
STATEMENT: Client-initiated channels MUST use odd IDs, server-initiated channels MUST use even IDs.Requirements without a UID: field are skipped silently; tracey can't reference a requirement it can't name.
Source-side prefix. .sdoc has no PREFIX[…] marker, so tracey uses the synthetic prefix r for these requirements. References from your code use the same r[…] syntax that markdown specs use, or — if you'd rather match StrictDoc's own conventions — the @relation(...) form.
UID case is preserved. A spec declaring UID: CH-001 is queried as tracey query rule CH-001 — uppercase end-to-end. CH-001 and ch-001 are distinct rule IDs.
STATEMENT rendering. When the document declares OPTIONS: MARKUP: Markdown, tracey renders STATEMENT fields through its markdown pipeline. Other MARKUP: values (Text, RST, or absent) are HTML-escaped and wrapped in <p>; an RST renderer is not bundled.
Refer to the StrictDoc documentation for the full grammar; tracey reads the common subset ([DOCUMENT], [[SECTION]], [REQUIREMENT], single-line and heredoc field values, and the document-level OPTIONS: block).