A4.3 · Anatomy of a spec · dive 3
Constrain, not over-specify
A spec constrains the build without over-specifying the solution: it states what must be true, never how to write the code.
A constraint fixes a behaviour and leaves the implementation open; an over-specification
dictates the code and leaves the Author nothing to decide. The cleanest place to see the line is Scope's
Out, which constrains by deferring — naming the later rung each concern
moves to. Pick a statement on the right to classify it as constraining or over-specifying.
Constrains or over-specifies? · pick a statement
The line
Every section of a spec has the same job: constrain the build without over-specifying the
solution. A constraint states what must be true and leaves the code free; an over-specification
dictates the code and leaves the Author nothing to decide. “renders the empty state for an unknown
id” constrains — it fixes an observable behaviour and says nothing about how to produce it.
“use a case not a cond” over-specifies — it
dictates a solution detail a spec has no business holding.
The distinction matters because the Author is a fast, well-specified implementer: give it a behaviour and it builds; give it a forced solution and it builds the wrong thing precisely. A spec that over-specifies trades the implementer's judgement for the writer's, and usually loses. The hero figure classifies single statements; the next section shows how Scope draws the same line at the level of whole concerns.
- constrains
- States what must be true and leaves the code free — e.g. “renders the empty state for an unknown id”, “
/healthreturns200”, “calls only the facade”. - over-specifies
- Dictates the solution and leaves nothing to decide — e.g. “use a
casenot acond”, “name the variableconn”. - the test
- Ask: could two correct implementations both satisfy this? If yes, it constrains. If it admits only one shape of code, it over-specifies.
An Invariant is a constraint over every value — F6.1-INV1 (master), verbatim:
“PortalWeb calls only the Portal facade and renders only
the closed %Portal.Error{} set”. It forbids a class of code without writing any.
Defer, do not forbid
Scope's Out is where the line is sharpest. A weak spec forbids a concern outright; a
strong one defers it — naming the later rung it moves to. The difference is the
(→ F6.N) marker. “Ecto persistence” is not refused; it is
(→ F6.3) — out of this rung, in a named later one. That turns a refusal
into a constraint: it bounds the current build without pretending the deferred concern will never exist.
The figure below reads each Out entry from the rung's spec — F6.1 · Bootstrap the Phoenix PortalThe first web rung: stand the F5 engine up as a Phoenix app, with one facade-backed route and a liveness route.View the spec ladder → — and
reports the rung it defers to. The move is distinct from the hero: there you judge whether a statement
constrains; here you see how an Out constrains by pointing forward rather than
forbidding.
Each Out defers to a rung · pick one
Scope's Out constrains by deferring — the
(→ F6.N) marker turns a refusal into a bound on this rung, not a permanent
forbidding.
The line on the Portal
On the Portal — the learning platform this course builds — the rung's spec, F6.1 · Bootstrap the Phoenix PortalThe first web rung: stand the F5 engine up as a Phoenix app, with one facade-backed route and a liveness route.View the spec ladder →, holds
the line in every section. Its Deliverable D6 constrains: “a liveness route get
"/health", … returning 200 (a plain "ok")
without touching the domain” — it fixes the route, the status, and the body, but names no plug.
Its D4 constrains the call — the controller “calls only
Portal.courses_of/1” (which returns the courses a user is enrolled in) —
without dictating the control flow. And its Scope Out defers, not forbids.
The fragment below is the Scope Out, verbatim — five concerns, each with its
(→ F6.N) marker. It is markdown, not code: the line between constraining and
over-specifying is a property of the spec's words, and the deferral marker is the device that holds
it.
# f6.1.md — Scope · Out (each Out names the rung it defers to, not a forbidding). # concern left out of this rung deferred to multiple pipelines, scopes, custom plugs → F6.2 Ecto persistence → F6.3 context wiring beyond courses_of/1 → F6.4 rich templates, components, and forms → F6.5 LiveView → F6.6 # Each line constrains THIS rung by pointing forward — it bounds the build, # it does not over-specify how the code is written.
A spec constrains the build without over-specifying the solution — it states what must be true, never
how to write the code, and Scope's Out constrains by deferring.
The F6.1 · Bootstrap the Phoenix PortalThe first web rung: stand the F5 engine up as a Phoenix app, with one facade-backed route and a liveness route.View the spec ladder → rung's Out defers five concerns with
(→ F6.2)…(→ F6.6); its Deliverables fix behaviour and name no
implementation. Each is a bound, not a recipe.
A spec constrains the build without over-specifying the solution — and Scope's
Out draws that line by deferring each concern to a named later rung.
Recap — bound the build, free the code
A spec constrains the build without over-specifying the solution: a constraint states what must be true and
leaves the code free; an over-specification dictates the code and leaves nothing to decide. The test is whether
two correct implementations could both satisfy it. Scope's Out draws the line at the
level of whole concerns — it constrains by deferring, the (→ F6.N) marker
turning a refusal into a bound on this rung. With the anatomy now read whole — the six sections, the five
Ws, and the line every section holds — the chapter turns to deriving the proof a spec is accepted by.
A4.3 is complete: a spec is six named sections that constrain the build without over-specifying the solution. Return to the A4.3 module hub, then continue to A4.4 · From stories to a .stories.md from the A4 navigation.
References
The sources behind constraining without over-specifying, and where the line connects in the course.
Sources
- Adzic, G. — Specification by Example — specify behaviour by example, never the implementation that produces it.
- Humble & Farley — Continuous Delivery — thin rungs that defer scope to later, named slices down the pipeline.
- Cohn, M. — User Stories Applied — deferring detail to the last responsible moment, and to a later story.
Related in this course
- A4.3 · Anatomy of a spec — the module hub.
- A4.3.1 · The six sections — the first dive.
- A4.3.2 · The five Ws — the prior dive.
- A4 · The spec ladder — the F6.1 rung this dive cites, across the iterations git recorded.
- A4 · The spec layer — the chapter landing.
- A3.3 · Anatomy of a roadmap.md — the roadmap whose rungs the Out defers to.
- A1.04 · Two layers: roadmap and specs — the roadmap/spec separation.
- Companion · Phoenix (F6) — the chapter whose
f6.1.mdScope is read here.