The .ttrs recipe format

A recipe is prose with {{ … }} directives in it. The prose is copied through as written; each directive names something in a Transitrix model and is replaced by what it resolves to.

On this page

What a recipe contains

One text file, in two parts: a YAML header, then the recipe body.

The body is ordinary prose. Everything outside {{ … }} is fixed text and is copied through verbatim; each {{ … }} is a directive that names something in the model and is replaced by what it resolves to.

---
document: Market Requirements Document   # required — the name its readers use
kind: mrd                                # required — matches the filename's middle segment
template_id: product.mrd                 # required — named in the run record
template_version: "1.0"                  # required — named in the run record
canon: ../canon                          # optional — the model repository
---

Four header fields are required; canon: is not. A recipe that names no model object and no derived figure resolves standalone, with no repository configured at all — that is a legitimate input in its own right, and an implementation reading such a recipe reports the no-repository state rather than treating it as a failed lookup.

How the filename is read

<basename>.<kind>.ttrs        e.g.  product.mrd.ttrs
                                    platform.srs.ttrs
                                    gateway.sdd.ttrs

The middle segment is the document kind — never a notation. A kind is a shape over the model: which elements a document of that kind selects, and how they are arranged. It carries no content of its own, gets no directive language of its own, and a new kind never means a new language. The kinds specified today are mrd, srs, sdd and sds; the header's kind: must agree with the filename.

Each notation has exactly one canonical extension, and it is either *.<short-name>.transitrix.yaml or *.<short-name>.ttrs. Which one a given notation takes is a property of that notation. Where .ttrs applies it replaces the longer suffix in full — it is never appended to it, and no notation carries both.

.trs is one keystroke away and is a different, widely used format. A file ending .trs where a recipe is expected is reported as that near-miss by name, not as an unknown file.

The directive language

Delimiters are {{ and }}. Whitespace just inside them is insignificant, so {{REQ-14}} and {{ REQ-14 }} are the same directive. \{{ renders a literal {{ and is the only escape in the language. No directive nests inside another, except that the two block forms below take a body.

Inline forms

Block forms — there are exactly two

{{# each <TYPE> [where …] [order by <field>] }} … {{/ each }} repeats its body once per selected element, binding each in turn as the current row. The where clause is deliberately small: and only, no or and no parentheses; = and != only; a literal on the right-hand side, never another field. order by takes a single field, and is what makes a rendered document reproducible.

{{# instruct <slot-id> }} … {{/ instruct }} declares an instruction slot — a section a deterministic pass does not fill, carrying question: and sufficient: (both required) and an optional comma-separated inputs:. A slot body is opaque: a parser scans from the opening tag straight to the matching close and keeps everything between as raw text, so a {{ REQ-14 }} written inside a slot is instruction prose and is never resolved. Slot ids are lower-case letters, digits and hyphens, and must be unique within a document.

Identifiers

An id follows the canonical grammar — REQ-14, CAP-1, BUSINESS_SERVICE-3. A capability id embeds a diagram address whose dots belong to the id, so a parser splits the CAPABILITY-V / CAPABILITY-H prefix off before it reads a field path: {{ CAPABILITY-V1.2.3 }} is one id and no field path, while {{ CAPABILITY-V1.2.3.name }} is that same id with the field path name.

A worked fragment

The committed conformance fixture, in full — a header, fixed text, a reference, a field path, a two-step path, a derived figure with a cross-reference to it, and one instruction slot.

---
document: Market Requirements Document
kind: mrd
template_id: product.mrd
template_version: "1.0"
canon: canon
---
# {{ CAP-1.title }}

## Scope

This document covers {{ CAP-1.text }}

## Requirements

**{{ REQ-14 }}** — {{ REQ-14.text }}

Its parent capability is {{ REQ-14.parent.name }}.

{{ view diagrams/context.blocks.transitrix.yaml as = context fit = width }}

{{ figref context }} shows where the capability sits.

## Market

{{# instruct market-size }}
question: How large is the addressable market for this capability, and how fast is it growing?
inputs: CAP-1, REQ-14
sufficient: A market size with a currency and a year, a growth rate, and the method used to derive both.
{{/ instruct }}

The file is product.mrd.ttrs, and the output it must produce is frozen beside it as product.mrd.expected.md.

Reference states

Resolving a reference yields exactly one state. Besides ok — resolved, admitted, and inside its validity interval — the language names five, and an implementation must keep every state it reports distinct from every other.

The first three are classified in that order: existence, then admission, then validity. Collapsing any two states into one is non-conformance rather than a simplification — you have no repository and your repository lacks this id are different problems with different fixes, and each state names a different thing for its reader to go and do.

A non-ok reference never renders as its bare value. The state that reads as correct text is the one a reader has no way to see.

⚑S is derived from commit history rather than read from a file, and is the one state an implementation may decline to compute. Declining is permitted; being silent about it is not — three outcomes have to stay distinguishable: suspect, checked and clean, and never checked. A document that renders with no ⚑S anywhere is making a claim, and an implementation that never ran the check has made no claim at all.

Render profiles

An implementation offers at least two profiles, both selectable, and names which one a given run used:

Two rules bind the pair: the lenient profile detects exactly what the strict one fails on — not a subset — and neither profile renders a non-ok reference as its bare value. The names above are the roles rather than a required spelling.

An implementation may leave any construct of the language unimplemented, and admitting a subset is expected. What it states in its own documentation is both halves: what it admits, and what it recognises and declines. A recognised-but-unimplemented construct fails by that name, under a code distinct from the one used for unknown or malformed syntax — the two messages are this is not in the language and this is in the language and not in this tool, and they send an author to different places.

What exists today

Reading further — the class index is notations/views/documents/README.md; the shared header and extension rules every notation follows are in CONTRACT.md. For the YAML notations and their file shapes, see the DSL examples.