The Golden Path: One Feature, End to End
Who this is for — anyone who has read the rules in this book and wants to see them run once, on a real feature, in a real repository.
Read this if — you want to watch an agent build something, get it wrong, and get caught.
Skip this if — you only need the rules; they are in the Rule Index.Takeaways
- The harness is written before the feature, and it is the harness — not the reviewer — that decides when the feature is done.
- An agent’s failure is usually not a wrong line of code; it is a missing case nobody wrote a check for.
- A bug fix is not finished when the code is right. It is finished when the check that would have caught it exists.
Every other chapter in this book states a rule. This one runs a single feature through all of them, in order, in this repository:
The feature: a book’s fonts, size, leading, and accent color live in
spec/config.yaml— nowhere else. Changing a book’s look is a one-line spec edit.
That is the feature that produced the stylesheet you are reading this page in.
On Evidence
What is real here, and what is reconstructed. Every file path, command, test name, commit hash, code excerpt, and tool output below is taken from this repository and can be checked with
git show <hash>or by running the command yourself. The prompts and session dialogue are reconstructed — Git records what changed, not what was typed — and every reconstructed passage is labelled illustrative. Nothing in this chapter is a transcript pretending to be one.
Step 0 — Spec Before Code
The feature starts as data, not as code. spec/config.yaml gains a books section, one block per book:
books:
main:
font_family: "'Outfit', 'Inter', system-ui, sans-serif"
font_size: "16.5px"
primary_color: "#7c3aed" # Premium Royal Violet
line_height: "1.6"
margin: "1.2in"
Two things are already decided by writing this first. The generated stylesheet
src/custom.css becomes an output, not a source file — it carries the header
/* Generated from spec/config.yaml - DO NOT EDIT MANUALLY */ and is not
checked in. And the shape of the data is governed by spec/config_schema.json,
so an editor flags a typo before a build ever runs.
🧑✈️ Human holds the wheel — deciding which values belong in the spec. “Accent color, font stack, leading, margin” is a taste-and-architecture call: put too little in the spec and the theme drifts; put too much in and the spec becomes a stylesheet in YAML costume. No check can make this decision.
Step 1 — Canary Before Feature
The feature is only interesting if the books actually build and print. Both
depend on tools this repository does not control: mdbook and headless
Chromium. So scripts/canary.sh runs first — it builds a three-file throwaway
book and prints it to PDF:
make canary
The canary earned its keep immediately. Printing the throwaway book failed with
Failed to write file /tmp/tmp.XXXX/test.pdf: No such file or directory
Chromium was Snap-confined and could not see the OS /tmp. The fix was one
line — write the temporary book into ./.tmp-canary inside the project — and
it is still in the script today. The full story is in
Canary-First Development; the repository’s issues/001 closed
with the checkbox “Fix snap sandbox issue for chromium by using a local
.tmp-canary directory.”
The interesting part is what happened later. The machine moved to Fedora with
a flatpak Chromium behind a shim — a completely different packaging system,
same class of failure. docs/Toolchain.md records the resolution:
# allow headless captures to write into repo dirs (build/ etc.)
flatpak override --user --filesystem=home org.chromium.Chromium
One thirty-line canary has now caught the same sandbox boundary twice, across two packaging technologies, years apart. That is the entire argument for canary-first compressed into one script.
🤖 Agent takes the wheel — writing and re-running the canary. It is mechanical, it is cheap, and the output is unambiguous: the script prints
✓lines or exits non-zero.🛡️ The check that makes it safe —
make canary. It fails loudly ifmdbookis missing, if no Chromium is onPATH, ifmdbook buildproduces noindex.html, or if the printed PDF is zero bytes.
Step 2 — Harness Before Implementation
Now, and only now, is there a place to put a feature. make preflight is the
gate, and it is composed, not monolithic:
preflight: ⚙️ canary test styles-golden links rules render website vendor-mermaid-verify canary-mermaid ## run all gates
@echo "==> Verifying code formatting..."
./scripts/check_format.sh
@echo "✓ Preflight verification passed."
Every gate is a prerequisite or a script; the recipe itself decides nothing.
That is deliberate. A make recipe joins continuation lines into a single
shell line, so any real branching inside one collapses into the
semicolon-heavy if [ … ]; then … fi layout that
Bash — House Rules forbids. Logic that needs a conditional
belongs in a script, where it can be written the way the rules say:
unformatted=$(gofmt -l "$TARGET")
if test -n "$unformatted"
then printf 'ERROR: Go files are not formatted. Run '\''gofmt -w %s'\''\n' "$TARGET" >&2
printf '%s\n' "$unformatted" >&2
exit 1
fi
gofmt -l prints the offending file names and exits 0 either way — the
failure signal is the output, not the status. Converting one into the other is
the script’s entire job.
Eight layers, each catching a different class of wrong:
| Layer | Command | Catches |
|---|---|---|
| Environment | make canary | the toolchain moved, the sandbox changed |
| Behaviour | go test scripts/build_slides.go scripts/build_slides_test.go | the compiler renders the wrong thing |
| Presentation | make styles-golden | the theme changed without anyone deciding to change it |
| Links | make links | a dead cross-book link, or a file:// path that only works on one machine |
| Normative rules | make rules | a rule-index ID that skips a number, a duplicated rule, an invented enforcement value |
| Rendered output | make render | prose that is correct in Markdown and wrong in HTML |
| Placement | make website | a book whose placement: in the spec disagrees with the landing page |
| Hygiene | scripts/check_format.sh | code that will pollute the next diff |
The hygiene layer is the youngest to come from a reader. Two chapters opened with a header box whose “Read this if / Skip this if” had folded into the paragraph above it — correct Markdown, wrong HTML, and invisible in every editor preview. No layer above could see it, because none of them read the rendered page. A gate that never looks at the artifact the reader receives is not verifying the product.
The behaviour layer is where the spec feature lives. Before the loader existed,
the suite already held TestGoldenRenders — full slide decks rendered against
checked-in golden HTML under scripts/testdata/. The feature adds
TestLoadSpecConfig_Books_DisableHeadersFooters, which writes a miniature
config.yaml into t.TempDir(), loads it, and asserts one field survived the
round trip. No mocks, no framework, standard library testing — the house rule
from Go — House Rules.
Step 3 — The Agent Implements
The implementation is two functions in scripts/build_slides.go:
loadSpecConfig, a line-by-line YAML scanner (the project’s dependency budget
does not stretch to a YAML library for a forty-line config), and
generateBooksCSS, which writes one custom.css per book. main() wires them
before anything else happens:
config, err := loadSpecConfig("spec/config.yaml")
if err == nil {
g_config = config
// Generate custom.css files for books styling
if err := generateBooksCSS(config); err != nil { /* … exit 1 */ }
// Validate stop words in source directories
if err := validateSourceFiles(config); err != nil { /* … exit 1 */ }
}
That second call is the other half of the spec’s authority: validateSourceFiles
walks src, emojig/src, and styles/src, and aborts the build if any line
matches a pattern from the spec’s stop_words array — with the file, the line
number, and the offending text. The spec does not just style the books; it can
refuse to let them build.
🤖 Agent takes the wheel — writing the scanner, the CSS emitter, and the table-driven tests. Bounded, mechanical, fully specified by the YAML above.
🛡️ The check that makes it safe —
make test, which runsTestParseFrontmatter,TestFormatInline,TestRenderSlideToHTML_*,TestGoldenRenders,TestSlideTransitionCSS,TestParseYAMLValue, andTestLoadSpecConfig_Books_DisableHeadersFootersagainst the real compiler.
Commit 1513f5d, “feat(spec): adopt spec-driven books and slides compilation
with stop-words validation gate”, shipped all of it. Preflight was green.
And the book was wrong.
Step 4 — The Agent Is Wrong
Here is the line that shipped, inside loadSpecConfig:
val := strings.Trim(strings.TrimSpace(parts[1]), `"'`)
It is a reasonable-looking line. It strips whitespace, then strips quotes from
both ends. It handles font_size: "16.5px" perfectly. Now feed it the line the
spec actually contained at that commit:
primary_color: "#7e57c2" # Sleek purple accent
strings.Trim removes quote characters from the ends of the string. This
string does not end in a quote — it ends in a comment. Running the pre-fix
expression over the pre-fix spec lines reproduces the damage exactly:
in : "#7e57c2" # Sleek purple accent
old: #7e57c2" # Sleek purple accent
in : "'Outfit', 'Inter', sans-serif"
old: Outfit', 'Inter', sans-serif
(Reproduced by running the pre-fix expression on the pre-fix spec lines — the values, not a memory of them. The formatting of this listing is mine.)
Two distinct failures from one line. The color became a string containing a
quote, a hash, and four words of English, emitted straight into
--primary-color. And the font stack lost its leading quote while keeping the
rest, so the first family in the list was the unquoted garbage Outfit',.
Both are silently invalid CSS. Browsers do not raise; they discard the declaration and fall back. The book still built. The canary still passed. The golden tests still passed — because the golden corpus covered slide rendering, and nothing at all covered spec parsing.
⚠️ This is the honest shape of an agent failure. Not a hallucinated API, not a syntax error, not something a linter sees. A correct-looking one-liner that is right for the cases someone thought about and wrong for the case sitting in the config file three directories away. The harness did not catch it, because the harness had no test for it. What caught it was a human looking at a page whose colors were quietly wrong.
🧑✈️ Human holds the wheel — noticing that the rendered book looks off. No assertion in this repository was watching that value. Taste is still a detector, and it is the one you cannot delegate.
Step 5 — Iterate: Fix the Case, Then Fix the Blind Spot
Commit 79eb8fa, “feat(theme): enhance book layout styling and fix yaml parser
quote/comment handling”, does two things, and the order matters.
First, the parse is extracted into a named function with the comment rule ahead of the quote rule:
// parseYAMLValue parses a single line value from YAML, stripping comments and matched quotes.
func parseYAMLValue(raw string) string {
if idx := strings.Index(raw, " #"); idx != -1 {
raw = raw[:idx]
}
raw = strings.TrimSpace(raw)
if len(raw) >= 2 {
if (raw[0] == '"' && raw[len(raw)-1] == '"') || (raw[0] == '\'' && raw[len(raw)-1] == '\'') {
raw = raw[1 : len(raw)-1]
}
}
return raw
}
Note " #" with the leading space, and the matched-pair quote test rather
than a character set. Both are deliberate: value#no_space is a value, not a
comment, and "'Outfit', 'Inter'" must lose exactly one quote from each end.
Second — and this is the part that makes it a fix rather than a patch — the
same commit adds TestParseYAMLValue, a table with the two spec lines that
actually broke plus the edge cases the first attempt implied:
{`"'Outfit', 'Inter', sans-serif"`, `'Outfit', 'Inter', sans-serif`},
{`"#7e57c2" # Sleek purple accent`, `#7e57c2`},
{`'single quotes'`, `single quotes`},
{`no quotes`, `no quotes`},
{`value#no_space`, `value#no_space`},
{`value # comment with leading spaces`, `value`},
The blind spot is now covered. Restore the old one-liner today and make test
fails within a second, naming the exact input and the exact expectation.
🤖 Agent takes the wheel — turning the observed failure into a table of cases and making the table pass. This is the loop agents are genuinely best at: a precise oracle, fast feedback, no judgement required.
🛡️ The check that makes it safe —
TestParseYAMLValueinscripts/build_slides_test.go. It is the permanent memory of a bug that nobody has to remember any more.
Illustrative prompt — reconstructed, not a transcript. The instruction that turns this from a patch into a fix is the second sentence:
The generated
src/custom.csshas--primary-color: #7e57c2" # Sleek purple accent. Find where the spec value is parsed and fix it. Then add a table-driven test that fails against the current code, including the real lines fromspec/config.yamland thevalue#no_spacecase. Runmake preflight.
Step 6 — The Failure the Harness Cannot Catch
One release later the same feature broke again, at an altitude no unit test
reaches. The Style Codex book had its own hand-written theme,
styles/css/fancy.css, with a gold accent and a serif stack. The generated
custom.css had the spec’s values. Both loaded. The generated file won every
shared selector, and the book rendered — per the record — as “an incoherent
pink/gold hybrid”.
Every test passed. Every check was green. Nothing was broken in the sense a harness understands; two correct files simply disagreed about who was in charge.
The resolution was not code but a decision, written down as an architectural
decision record (docs/adr/0003-spec-baseline-theme-cascade.md): the spec is
the baseline, the theme refines it. Load the generated custom.css before the theme so the theme
wins ties by cascade order, with no !important anywhere. Themes must not
restate spec values — fancy.css derives its palette with
--codex-accent: var(--primary-color, …) and lightens it for dark themes via
color-mix(). Rules the baseline already provided were deleted from the theme,
not overridden.
The ADR closes with a sentence that is really a rule:
Theme rules that duplicate the generated baseline are treated as bugs.
🧑✈️ Human holds the wheel — arbitrating between two systems that are each internally correct. This is architecture: the output is a rule and a written rationale, and its value is that the next agent inherits the decision instead of re-litigating it.
Step 7 — Preflight, Then Commit
The turn ends the same way every turn ends:
$ make preflight
==> Running books toolchain verification script (canary)...
✓ mdbook: …
✓ chromium: …
✓ mdbook build generated HTML
✓ chromium printed PDF successfully
==> Running Go unit and golden tests...
…
==> Verifying code formatting...
✓ Preflight verification passed.
(Shape of the real output; the version strings and PDF size depend on your machine. Run it and see yours.)
Then one commit carrying code, tests, and spec together, in conventional-commit form, with the model that helped recorded in a trailer:
feat(theme): enhance book layout styling and fix yaml parser quote/comment handling
scripts/build_slides.go | 77 +++++++++++++++++++++++++++++++++------
scripts/build_slides_test.go | 23 +++++++++++
spec/config.yaml | 14 ++++----
Attribution is not decoration. At the time of writing, 16 of this repository’s
37 commits carry a Co-Authored-By: trailer naming the model that worked on
them; the earlier ones predate the convention. When a change looks strange a year from now, the trailer
is the difference between archaeology and a git log query. Which models are in
play at all is itself spec-governed — the registry lives in the models: array
of spec/config.yaml, and the Colophon says what each did here.
The Loop, In One Picture
graph LR
S[Spec: the values] --> C[Canary: the environment]
C --> H[Harness: the oracle]
H --> A[Agent: the implementation]
A --> V{make preflight}
V -->|red| A
V -->|green, but wrong| M[Human sees it]
M --> T[Add the missing check]
T --> H
V -->|green| K[Commit: code + tests + spec]
The branch that matters is the middle one. A harness that is only ever red or green describes a world where the tests are complete, and no test suite is. The third outcome — green and still wrong — is where the human belongs, and the only correct response to it is not “fix the bug” but “add the check, then fix the bug.” Every trip around that middle branch permanently shrinks the territory a human has to watch.
That is what “don’t automate the creation before you automate the verification” means on a Tuesday. Not that verification must be perfect before you start — it never is — but that every discovered gap gets closed in the harness before the turn ends, so the agent’s next run is safer than its last.