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]:

markdown
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:

markdown
> 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:

markdown
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 default
  • h2 — for an HTTP/2 spec
  • req — more explicit
  • api — 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.name

Each segment may contain ASCII letters, digits, hyphens, and underscores. IDs must contain at least one dot.

Valid IDs:

  • auth.login
  • api.v2.response-format
  • channel.id_allocation
  • crypto.sha256.validation

Invalid IDs:

  • login — no dot (single segment)
  • auth..login — empty segment
  • auth.login. — trailing dot
  • .auth.login — leading dot
  • auth.lo gin — spaces not allowed

Naming conventions

Use a consistent hierarchy that mirrors your spec's section structure:

markdown
# 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:

markdown
# 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:

markdown
```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:

markdown
<!-- 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:

sdoc
[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).