ADR-0018 — Artifact schemas: a dependency graph, not a phase gate#
Status#
Accepted — 2026-08-15
Context#
A change produces artefacts — proposal, delta specs, design, tasks — and they relate to each other: you cannot sensibly write tasks before you know what is being built. The question is what that relationship means.
The instinct is to enforce it: refuse to create tasks.md until specs/
exists. That is a phase gate, and phase gates are the reason spec-driven
development gets a reputation for ceremony. It also does not survive contact
with real work, where the tasks are sometimes obvious before the design is.
Decision#
Model the artefacts as a dependency graph, and treat dependencies as enablers, not gates.
artifacts:
- id: proposal
generates: proposal.md
requires: []
- id: specs
generates: specs/**/spec.md
requires: [proposal]
- id: design
generates: design.md
requires: [proposal] # parallel with specs
- id: tasks
generates: tasks.md
requires: [specs, design]specgate change status reports each artefact as done · ready · blocked ·
skipped, in dependency order, and lists nextSteps. Nothing refuses to run
because an artefact is missing.
skipped is a first-class state, not an absence. A skipped artefact
satisfies its dependents without existing:
skip_specs: trueinchange.yaml— a tooling change with no behavioural impact.specsis skipped andtasksstill becomes ready.rigor: lite(the default) —designis skipped unless the author writes one anyway.
For this phase the spec-driven graph above is built in. Making it
configurable per schema (specgate schema init | fork | validate | which) is F3;
this ADR fixes the model so that work is a change of source, not of concept.
Rationale#
- Enablers, not gates, is the whole difference between a workflow and bureaucracy. The graph answers "what can I usefully write now?" — a question with a helpful answer — instead of "what am I forbidden from doing?"
skippedmust satisfy dependents. The alternative is that every lite change reportstasksas permanently blocked on adesign.mdnobody intends to write, which trains people to ignorestatusentirely.- Declaration order breaks ties. When two artefacts become ready at once,
they are reported in the order the schema declares, never alphabetically, so
"the first
readyentry" is a stable instruction an agent can follow. rigor: liteas the default. Most changes should stay lite. Full rigor — design decisions written down — is for cross-cutting work, contract changes, migrations and anything where ambiguity is expensive.
Alternatives considered#
- Enforce the order. Rejected — see above. It also breaks the legitimate flow where a spike produces tasks first and the proposal is written to explain what the spike found.
- No graph at all; just a fixed list of files. Rejected — then
statuscannot say anything more useful than "these files do not exist", and the configurable-schema work in F3 has nothing to build on. - Treat a skipped artefact as missing but non-blocking. Rejected — it
makes
isPlanningCompleteunable to distinguish "deliberately not doing this" from "not done yet", which is the distinction the field exists for. - Ship configurable schemas now. Rejected for sequencing, not on merit: the built-in graph has to prove itself on real changes before its shape is frozen into a user-facing file format.
Consequences#
Positive#
change statusis a useful instruction rather than a checklist scold.- An agent can drive the lifecycle from
statusalone: take the firstreadyartefact, write it, re-run. - F3 becomes a matter of loading the graph from YAML instead of a constant.
Negative / trade-offs#
- The graph is hard-coded in
scripts/change/cli.tsuntil F3. Anyone wanting a different workflow today has to fork the constant. - Four states (
done/ready/blocked/skipped) is more than a checkbox, and needs explaining once in the docs. - Because nothing is enforced, a change can be archived with a thin proposal
and no design. That is the intended trade: the gate that matters is
validate --strict-tddon the resulting requirement, not the paperwork.
Follow-ups#
- F3:
.specgate/schemas/<name>/schema.yaml,specgate schema init | fork | validate | which, and a built-inbdd-firstgraph (proposal → feature → spec → tasks) for teams who want the executable scenario to lead.
References#
scripts/change/cli.ts—ARTIFACTS,artifactState- ADR-0015 — the lifecycle these artefacts belong to
mejoras/openspec-benchmark-plan.md§F3