Reading an artifact
Every artifact Groundwork writes is a single Markdown file with YAML
frontmatter. This page walks one of them end to end, so the files in
.sdlc/ read as documents rather than as output.
One artifact, one file
There is no combined document. Each requirement gets its own file, filed under the directory for its type and named after its ID:
<ID>-<kebab-title>.mdThe kebab title is the artifact’s title, lowercased with non-alphanumerics
collapsed to single hyphens, so the filename tells you what is inside without
opening it.
IDs are categorical and zero-padded: the prefix encodes the type — FR
functional, NFR non-functional, CON constraint, BR business rule, UC
use case — and an optional uppercase category infix is allowed, as in
FR-ORDER-014.
IDs are never re-used. An artifact that stops applying is not deleted; its
status is set to obsolete and it stays where it is. That is what makes a
reference from a design artifact or a commit message durable: FR-007 means
the same thing next quarter as it does today.
A real artifact
This is FR-001 from the tamagotchi worked example. The frontmatter is
complete and verbatim. From the body, only the Description and the first of its
three acceptance criteria are reproduced here; the Rationale, the other two
criteria and the Fit Criterion are on its
published page.
---
id: FR-001
type: functional
tier: stakeholder
title: Persist pet state on stat change and app close
description: When a pet stat value changes or the owner closes the application, the system shall persist the current pet state to local storage.
rationale: "Without persistence the pet's progress and current condition would be lost between sessions, defeating the premise that the pet is a persistent companion the owner returns to — the core mechanic the daily-return habit loop depends on. Persistence covers the pet state in full and not only its stat values: FR-006's Sleeping state and FR-011's wake deadline both survive an application close only because the Awake/Sleeping field and the sleep-entry timestamp are persisted with everything else."
fit_criterion: "Every field of the pet state held at close round-trips unchanged: across 50 restart cycles, 100% of persisted fields are present in the restored state and equal to their pre-close values, with 0 fields absent and 0 fields differing. The fields under test include, and are not limited to, every pet stat value, the health status, the Awake/Sleeping state field, the sleep-entry timestamp wherever the pet is Sleeping, and the last-saved timestamp."
priority: must
confidence: high
verification_method: test
ears_pattern: event
status: draft
created_at: 2026-08-24
traces_from: [CON-002, BR-002]
traces_to:
design: []
tests: []
code: []
scope: project
parent_scope: null
---
# FR-001 — Persist pet state on stat change and app close
## Description
When a pet stat value changes or the owner closes the application, the system shall
persist the current pet state to local storage.
## Acceptance Criteria
### AC-1 — State is written when a stat changes
```gherkin
Given the owner performs a feed action that changes the pet's hunger stat
When the stat change is applied
Then the updated pet state, including the new stat value and a save timestamp, is written to local storage immediately
```The frontmatter
| Field | Type | Required | Values | Meaning |
|---|---|---|---|---|
id | string | always | — | Categorical, zero-padded, stable ID. Prefix encodes type: FR->functional, NFR->non_functional, CON->constraint, BR->business_rule, UC->use_case. An optional uppercase category infix is allowed (e.g. FR-ORDER-014). |
type | string | always | functional non_functional constraint business_rule use_case | Requirement category. |
tier | string | always | business stakeholder solution transition | BABOK v3 four-tier hierarchy level. |
title | string | always | — | Short human-readable title (matches the kebab-case used in the filename). |
description | string | always | — | For functional requirements: an EARS-phrased statement. For NFRs/constraints/etc.: a clear single statement of the requirement. |
rationale | string | always | — | Volere mandatory rationale: the WHY behind this requirement. |
fit_criterion | string | always | — | Volere mandatory fit criterion: a measurable test oracle for the requirement. |
priority | string | always | must should could wont | MoSCoW priority. |
confidence | string | always | high medium low | Confidence level for human-in-the-loop triage. |
verification_method | string | always | test inspection analysis demonstration | How the requirement is verified. |
ears_pattern | string | functional | ubiquitous event state unwanted optional complex | EARS sentence pattern. Required for functional requirements only; omitted for other types. |
status | string | always | draft reviewed approved implemented verified obsolete | Lifecycle status. IDs are never re-used; set status to obsolete instead of deleting. |
created_at | string | always | — | Creation date in YYYY-MM-DD format. |
traces_from | array | always | — | Upstream traceability: IDs of higher-tier requirements this one derives from. May be empty for top-tier (business) requirements. |
traces_to | object | always | — | Downstream traceability mapping to design, tests, and code artifacts. Any of the three keys may be omitted or empty. |
scope | string | optional | project epic story | RESERVED (STO-98/agile). Agile planning scope. Present-but-optional. |
parent_scope | string | null | optional | — | RESERVED. Parent scope ID, or null. Present-but-optional. |
Most of those are self-explanatory once you have seen them. A handful are worth saying more about, because they are where the judgement lives.
rationale and fit_criterion are both mandatory, on every requirement.
The rationale is the why — the business or user driver. Without one, a
requirement cannot be challenged or descoped, because nobody can tell what it
was for. The fit criterion is a measurable test oracle: if you cannot write
one, the requirement is too vague and needs sharpening rather than accepting.
Look at FR-001 above — the fit criterion is “across 50 restart cycles, 100%
of persisted fields are present … 0 fields absent and 0 fields differing”,
which is a thing you can fail.
ears_pattern is functional-only. It records which EARS sentence pattern
the description uses — ubiquitous, event, state, unwanted, optional
or complex. The schema requires it for type: functional and omits it
everywhere else, and the cross-file validator checks that every functional
requirement declares one. Non-functional requirements, constraints, business
rules and use cases are not written in EARS and carry no pattern. FR-001 is
event, which is why its description opens with “When a pet stat value
changes…”.
traces_from and traces_to are the two directions of traceability.
traces_from points up: the higher-tier requirement IDs this one derives from.
It may legitimately be empty for a top-tier business requirement. The validator
checks that every ID in it resolves to a requirement that exists, so a dangling
reference fails the gate. traces_to points down, at design, tests and code;
any of its three keys may be omitted or empty, and they start empty because the
downstream artifacts do not exist yet when the requirement is written.
FR-001’s traces_from: [CON-002, BR-002] says an offline constraint bounds
this requirement and a business rule is implemented by it. Neither edge was
written on this file directly: a constraint or business rule declares a
transient applies_to naming what it bounds or what implements it, and the
formatter back-fills that ID onto the named requirement’s traces_from, so the
edge is stored once, in one direction. You can follow both.
confidence is triage, not quality. It is set deliberately: high when
something was directly stated or confirmed by you, medium when it was
reasonably inferred from context, low when it rests on an open question or an
unconfirmed assumption, or fills a gap you did not address.
priority is MoSCoW — must, should, could, wont — set per
requirement rather than defaulted, and wont is a legal value the schema
accepts.
confidence: low and the review queue
A low-confidence artifact is not a defect. It is the pipeline telling you which
items to check first. Any requirement affected by an open question is forced to
low, and low-confidence items are the human triage queue by design — which is
why the pipeline is told to assign confidence honestly rather than
optimistically.
You see the same list twice: once in the summary at the sign-off gate, and once
persisted on disk as the review_queue in .sdlc/requirements/index.yaml.
Start a review there rather than re-reading the whole set.
The body
Below the frontmatter, a functional requirement carries four sections in a fixed order:
- Description — a single EARS sentence.
- Rationale — why this is needed.
- Acceptance Criteria — one or more named scenarios, each a fenced
gherkinblock in Given/When/Then form. At minimum one happy path, plus a negative or edge scenario where an unwanted condition exists. - Fit Criterion — the measurable oracle.
The acceptance criteria are Gherkin because they are meant to become tests.
generate_dod.py emits one gate per functional requirement in
.sdlc/definition-of-done.md that asserts “all of that FR’s
acceptance-criteria scenarios pass”, and it links the requirement file rather
than inlining the Gherkin — the requirement file stays the authoritative,
executable copy. That is also why verification_method on a functional
requirement is usually test.
Design artifacts differ
Design artifacts share the same file-per-artifact shape and the same core
field names — id, type, title, description, traces_from,
traces_to, status, confidence, created_at, with the enums adjusted for
design types (component, interface, adr, diagram). There is no tier,
priority, rationale, fit_criterion, verification_method or
ears_pattern; those are requirement fields. What replaces them is the
type-specific half:
| Field | Type | Required | Values | Meaning |
|---|---|---|---|---|
id | string | always | — | Categorical, zero-padded, stable ID. Prefix encodes type: CMP->component, IF->interface, ADR->adr, DIA->diagram. An optional uppercase category infix is allowed (e.g. CMP-AUTH-004). |
type | string | always | component interface adr diagram | Design-artifact category. |
title | string | always | — | Short human-readable title (matches the kebab-case used in the filename). |
description | string | always | — | One clear statement of what this element is. |
traces_from | array | always | — | Upstream traceability: requirement IDs (FR-/NFR-/CON-/BR-/UC-) this element satisfies. May be empty. Existence is resolved by the cross-artifact validator (STO-102), not this schema. |
traces_to | object | always | — | Downstream traceability to ADRs, diagrams, code, and tests. Any key may be omitted or empty. |
status | string | always | draft reviewed approved implemented verified obsolete | Lifecycle status. IDs are never re-used; set status to obsolete instead of deleting. |
confidence | string | always | high medium low | Confidence level for human-in-the-loop triage (STO-138). |
created_at | string | always | — | Creation date in YYYY-MM-DD format. |
scope | string | optional | project epic story | RESERVED (agile planning scope). Present-but-optional; mirrors the requirement schema. |
parent_scope | string | null | optional | — | RESERVED. Parent scope ID, or null. Present-but-optional. |
responsibility | string | component | — | The single clear purpose of the unit. |
boundary | string | component | internal external | Whether the unit is inside the system boundary or an external system modelled as a component (C4). |
depends_on | array | component | — | The interface contracts this component consumes. IF- IDs only; may be empty. Existence is resolved by the design validator. |
provider | string | interface | — | The single component that provides this contract. Exactly one CMP- ID. |
operations | array | interface | — | The operations this contract exposes, at architecture altitude (name + summary + interaction; no payload schemas). |
error_modes | array | interface | — | How this contract can fail. An interface that does not say how it fails is a gap; at least one mode is required. |
decision_status | string | adr | proposed rejected accepted deprecated superseded | MADR decision status. Distinct from `status`, which is the artifact lifecycle. |
considered_options | array | optional | — | The options weighed. Never fabricated: when a second option cannot be recovered from the recorded decision, no ADR is emitted at all. |
chosen_option | string | optional | — | The option taken. Absent while the decision is still proposed. |
level | string | diagram | context container component | C4 level. Determines the Mermaid header directive: context->C4Context, container->C4Container, component->C4Component. |
container | string | optional | — | The container key whose internals this view depicts. Present only on level: component. |
responsibility and boundary belong to components. The first is the single
clear purpose of the unit — one, not a list. The second says whether the unit
sits inside the system boundary or is an external system modelled as a
component: the C4 distinction. depends_on, also on components, lists the
interface contracts the component consumes; it holds IF- IDs only, may be
empty, and its references are resolved by the design validator. provider is
the mirror of it on an interface: exactly one CMP- ID, the single component
that provides the contract. Between them, depends_on and provider are the
component graph, and the design formatter runs generate_c4.py to project
that graph into the diagrams — projected from the artifacts by script rather
than drawn by hand beside them.
Next
- The requirements stage — how these files come to exist.
- Frontmatter fields — the same tables, generated straight from the schemas.
- Gates — what the validators check and what a failure means.