# TDD Mode — Vertical Tracer-Bullet Discipline (apt:execute appendix)

This appendix loads into `apt:execute`'s context when `tdd_active === true`
(see SKILL.md §0a.1). It carries the vertical-tracer-bullet discipline
the `tdd` skill captured. The audit gate G9 (`tdd-iron-law`) enforces
the commit order; this prose explains the **why**.

## Why vertical tracer bullets, not horizontal layers

A "vertical slice" of TDD means: for each subtask, write one failing
test that describes the **smallest end-to-end behavior change the user
will care about**, then implement only enough to make it green.
Horizontal TDD ("write all the data-layer tests first, then all the
service tests, then all the UI tests") accumulates dead inventory and
hides whether the slice actually delivers user value.

Concretely:

- A vertical-slice subtask titled "Solo dogfooder gets a PRD-shaped
  spec on STANDARD" should commit one `test(plan): STANDARD emits PRD
  shape` red test first — exercising the user-visible behavior — then
  one `feat(plan): branch spec.md on track` green commit. Two commits.
  Done.
- A horizontal-slice subtask titled "edit apt-plan SKILL.md" gets you a
  test that pins prose, not a test that pins user value. The G9 audit
  will pass technically but the discipline failed.

The vertical-slice subtask schema (`user_value` field, mandatory on
STANDARD/DEEP per AC2) is **upstream** of this discipline — TDD bites
hardest when the subtask was already framed as user value, not file
scope.

## The five rules of vertical tracer bullets

These five rules are the load-bearing discipline; ignoring any one of
them collapses TDD back into ceremony.

1. **Write the failing test from the spec, not from the implementation
   you're about to write.** If you write the test after you mentally
   draft the implementation, the test only pins what you wrote —
   not what the spec required. Read the relevant `acceptance_criteria`
   id-ref, then write a test that fails because that AC isn't met yet.

2. **Verify the test fails for the *right reason*.** A `ReferenceError`
   because the function doesn't exist yet is not the same as the
   assertion failing on a value mismatch. The G9 audit treats any
   non-zero exit as RED, but the discipline demands you check the
   failure mode is "behavior wrong", not "code shape wrong".

3. **Implement the minimum to turn the test green.** No speculative
   abstractions. No "while I'm here, I'll refactor X." The vertical
   slice is one test + one minimum impl. Refactor lives in a follow-up
   subtask with its own test.

4. **Commit the test before the implementation.** This is the iron-law
   commit order audit gate G9 enforces. A `feat(...)` commit that
   precedes the matching `test(...)` commit on the same file set is a
   blocking failure at `/apt:verify`.

5. **Don't grow the test surface to cover edge cases the spec didn't
   list.** Edge cases are subtasks of their own (with their own AC
   id-refs). The vertical-slice rule applies recursively: each edge
   case is a new tracer bullet through the whole stack, not a wider
   suite of tests for the same slice.

## Fast Path Guarantee — QUICK is hard-exempt

`tdd_active` is forced `false` when the task's track is `"QUICK"`,
regardless of flag or config. The rationale: QUICK is the one-shot
fast path for trivial fixes (typo, copy change, single-line config
update). Forcing TDD ceremony on those tasks violates the Fast Path
Guarantee (ID-05) — the user opted into speed, not discipline. If a
QUICK-routed task genuinely needs TDD, the right move is to re-route
it as STANDARD via `/apt:plan` (which the router will do for any task
with >1 file or >30 words of description).

The skill body emits a one-line note in the run report when `--tdd`
was passed on a QUICK task: "TDD requested but skipped — QUICK Fast
Path Guarantee (ID-05)". This is intentional surface — the user sees
that their flag was overridden by the constraint.

## Backward-compat alias

The legacy `config.tdd.iron_law` key continues to enable TDD for
projects that adopted Aperant before content-format
upgrade. The new computation rolls it into `tdd_active`:

```
tdd_active = (
               (--tdd flag) ||
               (coding.tdd_default === true) ||
               (config.tdd.iron_law === true)
             )
           && (track !== "QUICK")
           && !(--no-tdd flag)
```

The two config keys (`coding.tdd_default` and `tdd.iron_law`) coexist
without conflict; either set to `true` enables TDD by default.
