| name | traced-tdd |
| description | Plan tests so every failure traces back to one owning layer. Tests follow the architecture's dependency graph; each layer owns a typed error contract; trust compounds upward. Use whenever the user mentions tests, TDD, test strategy, mocking, coverage, "tests keep breaking", "where does this test belong", "what should I mock", "I can't tell what's failing", or is about to write code in a layered system. Fire broadly. Under-firing is the failure mode. |
Traced TDD
Every failure traces to one layer. Tests follow the dependency graph. Each layer owns a typed error contract. Trust compounds up. Ripples stop where you Resolve.
The property this buys
When production fails, the error type at the top boundary names the layer where handling broke. No log archaeology. The test suite is a diagnostic instrument, not a tripwire.
Six rules
-
Layers come from the architecture. The dependency graph is the test ownership graph. Draw it before writing tests.
-
Each layer declares a typed error set. Java throws, Rust Result, Go sealed errors, TS discriminated unions, Python sealed exception base. Machine-inspectable. No prose-only contracts. This is what makes attribution work — the error type carries the trace.
-
Every error gets one of three acknowledgments, visible in code:
- Resolve — catch, recover, ripple stops here. Trace terminates at this layer.
- Transform — catch, re-raise as a different declared error of this layer. Trace passes through with new identity.
- Propagate — declare it in this layer's set, pass through. Trace passes through unchanged.
-
Unknowns wrap into Critical. Catch-all handlers are legal only at a layer boundary and only when they wrap into a declared Critical signal carrying its origin tag. The unknown becomes known — and traceable — the moment it crosses a boundary.
-
Tests live where their declared error lives. One test per acknowledged error in the owning layer. L+1 does not test L's errors. L+1 tests L+1's declared set. This is what makes the trace tight: each test pins one layer's behavior to its contract.
-
Ripples are local. When a lower layer adds a new error, only the immediate consumer must decide Resolve/Transform/Propagate. Resolve stops the ripple.
Workflow
Run these in order. Do not skip ahead.
- Draw the layer graph. ASCII or mermaid. If the user has not picked an architecture, ask.
- List declared error sets per layer. Tag each error with origin (own, or from L_x). The origin tag is the trace.
- Build the acknowledgment table. For each consumed or originated error, write Resolve / Transform / Propagate.
- List tests, one per acknowledgment. No cross-layer tests.
- Verify mechanically. Every declared error has an acknowledgment branch. Every acknowledgment has a test. Every catch-all wraps to Critical.
See references/golden-example.md for the canonical worked example (config → db → mail → user → registration, with diagrams, tables, ripple walkthrough, Critical trace). Load it when concrete shape is needed.
Diagnosing existing test suites
When the user asks to review or fix a messy suite, check these. Each maps to a broken rule and a broken trace.
- Test at L+2 breaks when L's internals change → L+1 is probing through its mock. Rule 5. Relocate to L.
- Mock grows methods to match L's refactor → tests coupled to implementation, not contract. Rule 5.
- Same error caught at three layers → no single owner, trace is ambiguous. Rule 3. Pick one to Resolve.
catch (Exception) { swallow } → trace dies here. Rule 4. Declare it or wrap to Critical.
- Top layer has no enumerated error responses → trace never terminates. Rule 1.
- Adding one bottom error touches every layer → too much Propagate, not enough Resolve. Rule 6.
- Production failure cannot be attributed from the error alone → some layer is silently catching or transforming without declaring. Rule 2 or Rule 4.
Tone
Diagrams over prose. Tables over paragraphs. The user wants the how, not the why. Show the graph, show the table, show the test. Move.