Skip to content

The scenario is a body section, not a header field

The scenario is a body section, not a header field

majordomus://adr/adr-0016accepted use-casesschemascontext

16. The scenario is a body section, not a header field

Refines ADR 8 (use cases are executable canonical objects), which established the scenario and put it in the front matter.

Context

ADR 8 made the use-case scenario canonical data and stored it as a scenario: mapping in the document's YAML front matter. It works — every scenario runs — but it puts the largest and least declarative thing in the file into the half of the file reserved for the smallest and most declarative.

Front matter is the machine side of a document: what the object is, and how it is classified. Every other key of a use case is one of those — an id, a title, a category, a list of commands it names. scenario is neither. It is a program: a setup, an ordered list of invocations, and an assertion on each one's exit code and output. In this repository the longest one runs past forty lines, so a reader opening a use-case file met forty lines of executable YAML before the first sentence of prose, and the narrative the document exists to tell — # Situation, # Outcome — was pushed below the fold.

The allow-list showed the same distortion from the other side. Eleven of the twenty rows of share/allow/use-case.txt were ^scenario\.steps\.[0-9]+\....$ patterns: the front-matter contract was mostly not about front matter.

Nothing about the scenario needs to be in the header. It is not identity, nothing classifies by it, and no listing sorts on it. The one thing readers and the tool both need is that it be found reliably and parsed exactly, and a fixed section name gives that as well as a fixed key does.

Decision

The scenario moves out of the front matter into a body section named # Scenario, carrying exactly one fenced yaml block, positioned between # Situation and # Outcome.

The front matter keeps identity and classification only. Header.scenario is gone from share/schemas/majordomus/use-case/use-case.v1.proto, and Body gains a required, non-empty Scenario section, so majordomus generate projects the section into share/sections/use-case.txt and drops every scenario.* row from share/allow/use-case.txt.

The Scenario, Step, Expect and FileContains messages stay in the proto. They are reached from the body rather than from Header, and they remain the single written description of what the fenced block contains.

The block is YAML rather than prose or a table because the scenario is still executed. It is raw text in a named section, which is what a reader wants, and a parseable document, which is what the runner needs; a prose or tabular form would have bought readability with quoting rules and a bespoke parser.

lib/usecase.sh folds the parsed block back under the scenario. prefix of the same flat namespace the front matter produces, so every reader below the loader — usecase list, validate, run, coverage, impact, the site generator — is unchanged. The allow-list check drops those folded keys before measuring, because they came from the body, where the section list governs instead.

Consequences

A use-case file now reads in the order things happen: the situation, the proof, the result. The front-matter contract is about front matter again.

majordomus usecase validate requires # Scenario alongside # Situation and # Outcome, and usecase scaffold writes the section rather than the key. A use case written against the old shape parses, loses its scenario, and is reported as an active use case targeting a guarantee with no executable evidence — a named failure, not a silence.

The generator learned nothing new: the change is expressible in the option vocabulary share/schemas/majordomus/options.proto already had. A section whose content has a declared shape is still only described by a comment and the message beside it; if more kinds grow fenced sections, that binding is worth making explicit in the option set.

The scenario is no longer visible to a consumer that reads only front matter. Nothing in the tool did — the Rust indexer never referenced it — but an external reader that parsed the YAML header alone would now have to read the body.

Where this comes from

canonical file
.ai/repo/adrs/0016-the-scenario-is-a-body-section-not-a-header-field.md
identity
adr-0016
also served as
MCP resource majordomus://adr/adr-0016 · majordomus entity show adr/adr-0016 · GET /api/v1/entity?uri=majordomus://adr/adr-0016
in the Cockpit
/cockpit/objects/adr/adr-0016

This page is generated from the file above by scripts/generate-site-data; editing the page changes nothing, editing the file changes every surface. The relations above are edges of docs/generated/graph.json, which the executable writes from the same index.

Searches the titles and descriptions of every page this site publishes.