From 2ab8254f60ccd570b4493b05f3d61a089f54790d Mon Sep 17 00:00:00 2001 From: River Lynn Bailey Date: Thu, 20 Aug 2026 08:56:37 -0600 Subject: [PATCH] fix(han-coding): gate out-of-scope production changes in tdd and automated-test-planning An honestly-evidenced finding at CRIT could walk a production change into a build that never authorized it. In automated-test-planning, a security item whose own text named "first add an explicit order" landed in the plan as a test to write. In tdd, the observed-failure gate confirmed the red was real, and green arrived by editing a shared engine every application in the monorepo consumes. Neither skill asked whether the test deserved to exist in that build. tdd gains a scope gate as the observed-failure gate's companion. Step 1 records a scope boundary in files and directories. Step 2 tests every list item against it and works a three-rung resolution ladder when one trips: redesign the test, defer the item as its own ticket, or escalate when the requested behavior cannot be delivered without the change. The Red phase names the files green would touch before touching any of them, and Step 5 reports what the gate moved. failure-modes.md documents the failure as mode 10, including the way it compounds when a later test leans on the new behavior for a deterministic fixture. automated-test-planning gains a prerequisite sweep before priorities are assigned. An item that cannot be tested until shipped code changes leaves the priority tiers for a Blocked by a Production Change section carrying the change, the file, and the file's other consumers. The sweep runs over security items on the same terms, because CRIT ranks a finding's severity and says nothing about whose ticket the fix belongs to. The blocked count sits in the Summary bullets, where a caveat beneath the Technical Reference would lose the attention contest to a CRIT label above it. Closes #188 --- .../docs/skills/automated-test-planning.md | 15 ++++- han-coding/docs/skills/tdd.md | 25 ++++++- .../skills/automated-test-planning/SKILL.md | 30 ++++++--- .../references/template.md | 14 ++++ han-coding/skills/tdd/SKILL.md | 66 ++++++++++++++++--- .../skills/tdd/references/failure-modes.md | 23 ++++++- 6 files changed, 149 insertions(+), 24 deletions(-) diff --git a/han-coding/docs/skills/automated-test-planning.md b/han-coding/docs/skills/automated-test-planning.md index 357bcebc..e8e5fdf6 100644 --- a/han-coding/docs/skills/automated-test-planning.md +++ b/han-coding/docs/skills/automated-test-planning.md @@ -46,6 +46,13 @@ to use the skill. For what the skill does internally, read the skill definition in parallel against it. The first confirms the plan leads with plain language and defers the implementation detail; the second confirms the plain-language layer is comprehensible on its own. Actionable edits are applied before the plan is finalized. +- **A test that needs a code change first is not a test.** Before priorities are assigned, the skill sweeps every + recommendation for one that cannot be written until shipped code changes: an added `order` on a query, a new + validation, a changed return value. That item leaves the priority tiers for a Blocked by a Production Change section + recording the change, the file, and the file's other consumers. It exists because a CRIT label ranks how serious a + finding is and says nothing about whose ticket the fix belongs to, so an item carrying one can otherwise walk a + production change into the plan dressed as a test to write. The sweep applies to security items on the same terms as + every other. - **Plan, not test code.** The skill does not write tests. It produces a plan describing what to test, how, and at what level. @@ -106,6 +113,9 @@ A structured test plan in-channel, leading with plain language and deferring the references, and risk assessment. - **Deferred Tests.** Items `test-engineer` excluded because brittleness risk outweighed value, with reasons. - **Dropped Edge Cases.** Items `edge-case-explorer` intentionally excluded, with reasons. + - **Blocked by a Production Change.** Items that cannot be tested until shipped code changes first, each with the + change it needs, the file that would carry it, and who else consumes that file. Separate work to ticket, not tests + to write. The Summary bullets carry the count so it does not sit unread beneath the priority tiers. - **Coverage Summary.** Counts by priority tier. - **Scope.** Scope type, file count, branch, language, test framework, file list. @@ -142,8 +152,9 @@ The skill walks a five-step process: the file list touches async or shared state. Add `adversarial-security-analyst` when it touches auth, input handling, isolation, crypto, uploads, or SQL/ORM. All run in parallel in the background. The skill waits for every dispatched agent. -3. **Merge and prioritize.** Classify findings into the four-tier priority scheme (security items auto-CRIT). Assign - unified IDs. Interleave by priority. Cap non-security items at 40. +3. **Merge and prioritize.** Classify findings into the four-tier priority scheme (security items auto-CRIT). Sweep out + any item needing a production change before it can be tested, into Blocked by a Production Change. Assign unified + IDs. Interleave by priority. Cap non-security items at 40. 4. **Generate output.** Fill the template at [`references/template.md`](../../skills/automated-test-planning/references/template.md), leading with plain language (Summary, What Needs Testing and Why, What Each Test Covers) before the Technical Reference region that diff --git a/han-coding/docs/skills/tdd.md b/han-coding/docs/skills/tdd.md index 76ccba58..331ea64e 100644 --- a/han-coding/docs/skills/tdd.md +++ b/han-coding/docs/skills/tdd.md @@ -14,7 +14,7 @@ the skill. For what the skill does internally, read the skill definition at - **When to use it.** You want a feature or behavior implemented test-first, the right way, instead of code with tests bolted on after. - **What you get back.** Working, tested code in your tree, grown behavior by behavior, with the test list, the - standards applied, and the verification output shown at the end. + standards applied, anything the scope gate declined to build, and the verification output shown at the end. ## Key concepts @@ -25,6 +25,13 @@ the skill. For what the skill does internally, read the skill definition at - **The observed-failure gate.** No production code changes unless a test has been run and watched to fail for the intended reason in that loop. A test that passes the first time it runs means red was never seen, which is a process violation, not a success. +- **The scope gate.** A genuine red proves the behavior is missing. It does not prove this build owns producing it. + Step 1 records a scope boundary in files and directories, and before every production edit the skill names the file it + is about to change and tests it against that boundary. An edit that lands outside, most of all in a shared library or + engine other applications consume, is a stop. The skill then works a three-rung ladder: redesign the test so it does + not need the out-of-scope behavior, defer the item as its own ticket, or, only when the requested behavior cannot be + delivered without the change, stop and ask you. A severity label on an incoming finding does not open the gate; CRIT + says the finding is real, not that the fix belongs to this ticket. - **Two hats.** Making a test pass and improving structure are different jobs done at different times. The skill never refactors while a test is red. Make it run, then make it right. - **BDD framing.** Tests describe observable behavior, named in your project's existing convention, asserting outcomes @@ -107,6 +114,8 @@ Code in your working tree, not a report. Specifically: - behaviors implemented - the state of the test list, including any deferred items with their reopen triggers - which coding standards and ADRs were applied and where + - anything the scope gate moved, with the rung that resolved it: the test it redesigned, or the ticket write-up for a + change this build declined to make - any YAGNI deferrals from refactor - the final test, lint, and build status, with output shown rather than asserted @@ -115,8 +124,10 @@ The skill resolves your test, lint, and build commands from CLAUDE.md's `## Proj manifest files (package.json, pyproject.toml, go.mod, Cargo.toml, Gemfile, mix.exs, pom.xml, gradle, .csproj, or a Makefile test target). Commands the script infers are treated as best-effort suggestions, surfaced in the scope report so you can correct them if you are watching, not trusted blindly. If none of those resolve the test command, the skill -asks you for it before the loop starts, because the loop cannot run without it. That is the only input that can block an -otherwise autonomous run. +asks you for it before the loop starts, because the loop cannot run without it. That is one of only two things that can +block an otherwise autonomous run. The other is the scope gate's top rung, reached when the behavior you asked for +cannot be delivered without changing code outside the boundary, where your call is to widen the scope or split the +work. ## How to get the most out of it @@ -128,6 +139,10 @@ otherwise autonomous run. [`/coding-standard`](./coding-standard.md) and [`/architectural-decision-record`](../../../han-documentation/docs/skills/architectural-decision-record.md), the skill finds and applies them. If they do not exist, it infers conventions from surrounding code, which is weaker. +- **Name the files when the boundary matters.** Step 1 infers the scope boundary from what your request names, falling + back to the application or package the behavior lives in. If shared code sits close to the work and you do not want it + touched, say so in the invocation. That turns a judgment call into a stated boundary, and the gate gets stricter for + free. - **Let the list be the scope signal.** If the open test list grows past about ten items, the skill flags a scope warning and keeps going, then recommends splitting the work in its final summary. Take that warning seriously: a ballooning list usually means the feature wanted to be planned, not grown in one sitting. @@ -179,6 +194,10 @@ tidy later; it is the wrong code. Stylistic and structural standards, the kind y hat. This keeps the green step minimal (the Three Laws still hold) while making sure the code that survives the cycle respects the project's architecture. +The scope gate is the newer of the two gates and answers the question the observed-failure gate cannot. That gate asks +whether the red is real; the scope gate asks whether the test deserved to exist in this build. Both have to hold before +a production edit, and a finding that is honest, severe, and correctly evidenced can still fail the second one. + The hardest honest limitation: the observed-failure gate is enforced by discipline and shown evidence (pasted runner output, the first-run-pass stop rule, strict step sequencing). It is not enforced by a mechanism that can physically prevent a premature write. No skill in the plugin model can enforce a "you must have observed X before doing Y" diff --git a/han-coding/skills/automated-test-planning/SKILL.md b/han-coding/skills/automated-test-planning/SKILL.md index 80ff1e65..a1758bd5 100644 --- a/han-coding/skills/automated-test-planning/SKILL.md +++ b/han-coding/skills/automated-test-planning/SKILL.md @@ -147,21 +147,30 @@ Combine findings from every dispatched agent into a unified, prioritized test pl - **han-core:concurrency-analyst items** (C1, C2, ...) when dispatched: races on auth/billing/isolation = CRIT; realistic load contention, async error swallowing = HIGH; theoretical interleaving = MED. - **han-core:adversarial-security-analyst items** (SEC-NNN) when dispatched: every item lands at CRIT. Retain the - SEC-### cross-reference in the unified item so the source is visible. + SEC-### cross-reference in the unified item so the source is visible. CRIT ranks the finding's severity and says + nothing about whose ticket the fix belongs to; the prerequisite sweep below still applies to every one of them. 2. **Apply the behavioral sweep** — walk every surviving recommendation and confirm it verifies observable behavior at a public seam (caller-supplied inputs, observed outputs and side effects, interactions with collaborating objects and services). Rewrite any item that asserts on private methods, internal state, or implementation structure so it tests the same behavior through the public boundary; if no public seam exposes it, drop the item and note that the behavior should be observed at a higher level rather than pinned to internals. Collapse multiple low-level items that protect the same observable behavior into the single behavioral test that catches the same realistic failure modes. -3. **Assign unified IDs** — sequential IDs: TP-001, TP-002, TP-003, etc. Include the original agent ID as a +3. **Apply the prerequisite sweep** — walk every surviving recommendation and ask what has to be true before the test + can be written at all. An item whose test approach needs a production-code change first, meaning an added `order`, + a new validation, a changed return value, a schema column, or any other edit to shipped code, is not a test to + write. It is a production change wearing a test's clothes, and folding it into the plan hands the implementer a code + change nobody authorized. Move it out of the priority tiers into Blocked by a Production Change, recording the + change it needs, the file that would carry it, and who else consumes that file, so a reader can see the blast radius + and open a separate ticket. Run this sweep before IDs are assigned, and run it over security items too: an honest + finding at CRIT is still out of scope if it cannot be tested without first changing shipped code. +4. **Assign unified IDs** — sequential IDs: TP-001, TP-002, TP-003, etc. Include the original agent ID as a cross-reference (e.g., "TP-001 (from T3)", "TP-002 (from C1, concurrency)", "TP-003 (from SEC-001, security)"). -4. **Order by priority** — interleave items from every agent by priority: all CRIT items first, then HIGH, then MED, +5. **Order by priority** — interleave items from every agent by priority: all CRIT items first, then HIGH, then MED, then LOW. Within each priority level, order by the agent's own ranking. -5. **Cap at 40 items** — keep a maximum of 40 items total, prioritized by severity. Security items (SEC-derived) are +6. **Cap at 40 items** — keep a maximum of 40 items total, prioritized by severity. Security items (SEC-derived) are exempt from the cap. If more than 40 non-security items exist, note how many were omitted and recommend running the skill again after addressing high-priority items. -6. **Apply the YAGNI sweep** — walk every test recommendation that survived classification and apply +7. **Apply the YAGNI sweep** — walk every test recommendation that survived classification and apply [../../references/yagni-rule.md](../../references/yagni-rule.md). Demote any test whose justification reduces to "completeness", "best practice", "for future flexibility", symmetry with another test, or hypothetical scaling/adversaries the change doesn't touch — these go to the Deferred Tests section with a @@ -207,7 +216,9 @@ Lead with behavior. These rules make the plan a human-readable overview first an **Fill in all sections:** 1. **Summary** — Plain-language paragraph plus orienting bullets (scope, coverage health, most significant gap, where to - start). This is the qualitative coverage assessment, promoted to the top and written for a non-author. + start, and how many items the prerequisite sweep blocked). This is the qualitative coverage assessment, promoted to + the top and written for a non-author. The blocked count belongs in the Summary because a caveat buried under the + Technical Reference loses every attention contest to a CRIT label above it. 2. **What Needs Testing and Why** — The themes, in plain language, each ending with the test IDs it covers. 3. **What Each Test Covers** — Every meaningful test as a plain-language line led by its TP-ID. 4. **Technical Reference → Test Plan** — All items from Step 3, grouped by priority tier. Every test plan item should @@ -215,8 +226,11 @@ Lead with behavior. These rules make the plan a human-readable overview first an paths, and risk assessments into the merged output. 5. **Technical Reference → Deferred Tests** — Items the han-core:test-engineer excluded due to brittleness risk. 6. **Technical Reference → Dropped Edge Cases** — Items the han-core:edge-case-explorer intentionally excluded. -7. **Technical Reference → Coverage Summary** — Counts by priority tier. -8. **Technical Reference → Scope** — Scope type, file count, branch, language, test framework, and the list of files. +7. **Technical Reference → Blocked by a Production Change** — Items the prerequisite sweep moved out of the priority + tiers, each with the production change it would need, the file that would carry it, and that file's other consumers. + Write each one as separate work to be ticketed, never as a test this plan asks anyone to write. +8. **Technical Reference → Coverage Summary** — Counts by priority tier. +9. **Technical Reference → Scope** — Scope type, file count, branch, language, test framework, and the file list. ## Step 5: Review the Output diff --git a/han-coding/skills/automated-test-planning/references/template.md b/han-coding/skills/automated-test-planning/references/template.md index af1ae2d2..6c657ac8 100644 --- a/han-coding/skills/automated-test-planning/references/template.md +++ b/han-coding/skills/automated-test-planning/references/template.md @@ -15,6 +15,7 @@ - **Coverage health:** {one-line qualitative assessment — solid, thin, uneven, absent} - **Most significant gap:** {the single most important thing currently untested} - **Start here:** {the test or area to write first} +- **Blocked by a production change:** {count, and one line on what the changes are — or "none"} ## What Needs Testing and Why @@ -124,6 +125,19 @@ Items the edge-case-explorer intentionally excluded: - **{title}** — {reason for exclusion} +### Blocked by a Production Change + +{If none: "No items require a production change before they can be tested."} + + + + + + +- **{SEC-###/T#/EC#}: {title}** — `{file_path:line_number}` — **Change needed:** {the production change the test + depends on}. **Other consumers:** {who else uses that file, or "none found"}. **Why it is blocked:** {what the test + would assert, and why it cannot be written against the code as it stands}. + ### Coverage Summary | Priority | Count | diff --git a/han-coding/skills/tdd/SKILL.md b/han-coding/skills/tdd/SKILL.md index 5d9b605e..ff6fb091 100644 --- a/han-coding/skills/tdd/SKILL.md +++ b/han-coding/skills/tdd/SKILL.md @@ -41,6 +41,10 @@ These constraints shape every step and override any instinct to move faster. progress. This single rule is what separates real TDD from TDD-flavored code. The verbatim Three Laws and Canon TDD steps it derives from are in [references/tdd-loop.md](./references/tdd-loop.md); pull that reference when a step needs the canon or the implementation gears. +- **The scope gate is the observed-failure gate's companion.** The observed-failure gate proves a red is genuine. It + does not prove the test deserved to exist in this build. No production-code change outside the scope boundary recorded + in Step 1, and least of all in shared or cross-application code other consumers depend on. A list item whose green + requires an out-of-scope edit is a stop, never an implement; Step 2 carries the resolution ladder that Step 3 works. - **Two hats.** Never refactor while any test is red. See [references/tdd-loop.md](./references/tdd-loop.md) for the canonical statement. - **One behavior at a time.** Exactly one test list item becomes one runnable test per loop. Newly discovered scenarios @@ -74,23 +78,33 @@ at five documents; if more than five look relevant, list them and read only the relevance — defer the rest until refactor surfaces a need.** These govern the green and refactor steps. If none exist, state that plainly and plan to infer conventions from the surrounding code instead. +**Resolve the scope boundary.** Name, in files and directories, what this work is allowed to change, because the scope +gate tests every candidate edit against it. Inside the boundary: the files, directories, or module the request names, +plus the tests that cover them. Outside it: everything the named code merely reaches, meaning shared libraries, engines, +packages, and any code a second application or consumer also uses, plus code another team owns per `CODEOWNERS`. When +the request names no files, take the application or package the requested behavior lives in as the boundary and treat +its dependencies as outside it. Record the boundary; you will test list items and production edits against it. + **Report scope, then proceed (no gate).** This skill runs autonomously after the initial request: it does not stop for confirmation. State to the user, in a few lines: the behavior or feature to be built, **whether this is net-new behavior or a fix to existing broken behavior** (a reported bug, a failing case, a fix being driven back in after `/investigate`, or code that already exhibits the error — recognize the fix case from those signals, not only from the word "bug"), the -resolved test/lint/build commands, the standards and ADRs found (or that none were), the current branch, and that the -skill will now write code in a red-green-refactor loop. If `current branch` from Project Context is the repository's -default branch (`main` or `master`), recommend working on a branch, but do not wait for an answer. This is a report the -user reads while the work runs, not a gate. Continue immediately to Step 2 without waiting for a response. +scope boundary you just recorded, the resolved test/lint/build commands, the standards and ADRs found (or that none +were), the current branch, and that the skill will now write code in a red-green-refactor loop. If `current branch` from +Project Context is the repository's default branch (`main` or `master`), recommend working on a branch, but do not wait +for an answer. This is a report the user reads while the work runs, not a gate. Continue immediately to Step 2 without +waiting for a response. **The one exception.** If the initial request or the provided context explicitly states the human wants to review, verify, or approve the plan or test list before implementation, then this becomes a gate: build the test list in Step 2, present it together with this scope report, and wait for approval before starting the Step 3 loop. Absent an explicit request like that, the skill runs to completion without further human input. -The one input that can still block is a missing test command: if it could not be resolved from CLAUDE.md, -`project-discovery.md`, the discovery script, or manifest inference, ask the user for it, because TDD is impossible -without a way to run tests. Exhaust inference before asking; this is a hard dependency, not a discretionary checkpoint. +Two things can still block a run, both hard dependencies rather than discretionary checkpoints. A missing test command +is the first: if it could not be resolved from CLAUDE.md, `project-discovery.md`, the discovery script, or manifest +inference, ask the user for it, because TDD is impossible without a way to run tests. Exhaust inference before asking. +The second is the top rung of the scope gate's resolution ladder in Step 2, reached only when the requested behavior +cannot be delivered without an out-of-scope change. ## Step 2: Build the BDD Test List @@ -121,9 +135,33 @@ Apply YAGNI to the list itself. A scenario earns a place only with evidence it i named dependency, an existing code path that breaks, a regulation, a real incident). Scenarios that fail the evidence test go to a deferred list with the trigger that would reopen them. Do not pad the list for symmetry or completeness. -Report the test list to the user. Unless the verify-plan exception from Step 1 applies, continue to Step 3 immediately -without waiting for approval. When that exception applies, present the test list together with the Step 1 scope report -and wait for approval before entering the loop. +**Then apply the scope gate to the list.** YAGNI asks whether a behavior has evidence it is needed. The scope gate asks +a question no amount of evidence answers: would making this test pass require changing a file outside the Step 1 +boundary? Ask it of every item, and ask it hardest of items that arrived from a test plan, an analysis report, or an +agent finding carrying a severity label. A CRIT or HIGH label is evidence the finding is real. It is never evidence the +fix belongs to this ticket, and an item whose own text names a production change as a prerequisite ("this requires +first adding an explicit `order`") is that production change wearing a test's clothes. + +**The resolution ladder.** Work it in order and stop at the first rung that resolves the item. Never skip to +implementing the out-of-scope change. + +1. **Redesign the test.** Most items that trip the gate are asking production code to supply something the test could + arrange for itself. Rebuild the setup so the assertion holds without the out-of-scope behavior, and the item stays on + the list in its rewritten form. A test that needs the out-of-scope behavior only to make a fixture deterministic + always resolves here: that is a test-design problem, and leaning on a production change to make it disappear is the + wrong direction of dependence. +2. **Defer the item as its own work.** When the test cannot be redesigned, move it off the list and write it up as a + ticket: the behavior, the file that would have to change, who else consumes that file, and the change it needs. + Report it in Step 5 as work this build did not own. The finding stays alive; it just stops being this build's job. +3. **Escalate.** Only when the requested behavior cannot be delivered at all without the out-of-scope change, stop and + ask the user. Name the file, its other consumers, the change it needs, and the two ways forward: widen this build's + scope to include it, or split it into separate work and drop the dependent behavior from this build. This is the one + rung that pauses an otherwise autonomous run, and it is a hard dependency, not a review checkpoint. + +Report the test list to the user, along with any item the scope gate moved and which rung resolved it. Unless the +verify-plan exception from Step 1 applies, continue to Step 3 immediately without waiting for approval. When that +exception applies, present the test list together with the Step 1 scope report and wait for approval before entering the +loop. ## Step 3: The Red-Green-Refactor Loop @@ -165,6 +203,12 @@ behavior already exists, cross the item off and pick the next one. If the test i item off** — rewrite it to assert the desired correct behavior, so it goes red until the fix lands. Do not write production code off an unobserved red. +**With the red observed, check where green would land.** Name the files you would edit to make this test pass, before +you edit any of them. If one sits outside the Step 1 boundary, the scope gate has tripped: do not edit it, and work the +resolution ladder from Step 2 instead. A genuine red says the behavior is missing. It does not say this build owns +producing it, and this is the only check that asks. Shared or cross-application code is where the gate matters most, +because the blast radius of an edit there reaches consumers nobody in this build is testing. + ### Green Write the minimum production code that makes this one test pass. Use the smallest gear that works: Obvious @@ -229,6 +273,8 @@ fix it (a lint or build break is not a "pre-existing error" to wave off) and re- Summarize for the user: - Behaviors implemented, and the state of the test list (done, and any deferred items with their reopen triggers). +- Any item the scope gate moved, with the rung that resolved it: the redesign that kept it, or the ticket write-up for + the out-of-scope change this build declined to make. - Which coding standards and ADRs were applied, and where they shaped the code. - Any YAGNI deferrals from refactor, each with its reopen trigger. - A scope warning if the test list exceeded roughly ten open items, with a recommendation to split future work. diff --git a/han-coding/skills/tdd/references/failure-modes.md b/han-coding/skills/tdd/references/failure-modes.md index d79ccf47..cfcb7ce8 100644 --- a/han-coding/skills/tdd/references/failure-modes.md +++ b/han-coding/skills/tdd/references/failure-modes.md @@ -111,7 +111,28 @@ once the fix lands. A bug-fix test that is green before the fix is the tell — boundary: asserting that the code raises is the _right_ test when raising is the specified desired behavior (raise on invalid input). The failure mode is asserting the error that _is_ the bug being fixed. +## 10. Making a test pass by changing code the ticket does not own + +**Symptom.** A list item goes red exactly as designed, and green arrives by editing a shared library, engine, or package +that other applications consume. The observed-failure gate is satisfied, every test is green, and the branch carries a +production change the ticket never authorized. It often compounds: a later test leans on the new behavior for a +deterministic setup, so the out-of-scope change becomes load-bearing for work that could have arranged its own fixture. + +**Why it happens.** The item usually arrives carrying authority. A test plan or an analysis agent found something real, +labeled it CRIT, and the label reads as permission. The red is genuine, the fix is small, and the finding is honest, so +every local signal says proceed. Nothing in the moment asks the one question that matters. + +**Discipline.** The scope gate. A genuine red proves the behavior is missing, never that this build owns producing it. +Before every production edit, name the file you are about to change and test it against the scope boundary recorded in +Step 1. A severity label ranks a finding's importance and says nothing about whose ticket it belongs to; an item whose +own text names a production change as a prerequisite is that change wearing a test's clothes. Work the resolution +ladder: redesign the test so it does not need the out-of-scope behavior, defer the item as its own ticket, and escalate +only when the requested behavior cannot be delivered without it. A test that needs the change only to make a fixture +deterministic never reaches the second rung. + ## The one check that catches most of these Before every production-code edit, you must be able to point to a specific test that you ran and watched fail for the -intended reason in this loop. If you cannot, you are in one of the failure modes above. Stop and get back to red. +intended reason in this loop, and to a scope boundary that contains the file you are about to change. If the first is +missing, you are in one of failure modes 1 through 9: stop and get back to red. If the second is missing, you are in +failure mode 10: stop and work the resolution ladder.