May 2026 · Series "Trace Lock — Governance notes from pairing with AI to write code" · Post 2 of 9
The previous post (E meta) mentioned that Claude and I gradually identified 4 patterns over 6 months. The third one we call "Trace Lock" (working name, my own). This post unpacks what Trace Lock actually looks like and how you lock down a single trace.
This post is the plain-language version for self-taught developers. If you're an engineer and want code-level detail (which file goes where, how the SQL looks, how the governance rule is written), see the A2 engineering post (same topic, different audience).
Goal of this post: someone who has never written a governance rule or a pinning test should finish reading and know "Trace Lock is these 5 artifacts, why each one is needed, and roughly how long locking one trace takes."
First, the bug that triggered this whole thing
May 25th, early morning. A customer messaged: "I'm trying to order 2 drip-bags of the San Agustin coffee, why does the system say max 1?"
Each drip-bag is 103 grams. The backend roasted-bean barrel had 244 grams left, so 2 bags should fit. I went to check why the system calculated 1.
Tracing through:
- Backend correctly wrote 244 grams to
products.stock_virtual_grams✅ - Frontend correctly read 244 grams ✅
- But the frontend divided 244 by the half-pound product's gram weight (227g) = 1 bag ❌
The half-pound product had 4 variants: half-pound whole bag, quarter pound, one pound, drip-bag. The bug: the system didn't differentiate by variant. It always used "the half-pound product's gram weight" to compute orderable count. Drip-bags should use their own gram weight (103g).
Fixing this bug: 30 minutes. But it raised a deeper question:
Why have I hit 5 of these "cross-layer contract drift" bugs in 6 months?
- Order payment_status didn't sync with status
- Top-up plans got pushed into the packaging worksheet
- 13 coffee products showed "out of stock" to consumers (admin view was fine)
- A VVIP customer topped up $5000 and got the wrong bonus amount
- After adding a medium_dark roast tier, the UI showed the English enum code
Common factor: data flows from a write-side to a render-side, and somewhere a layer forgot to sync. Teams have code review, SRE, QA, PM to catch these. Solo devs don't.
Cross-layer relationships live in your head = forgotten in 3 months
I noticed something: every time I fix one of these bugs, I spend 30-60 minutes from scratch grepping the code to re-figure-out "which write-side, which intermediate layers, who renders."
I fixed a similar one 3 months ago, but the details are gone. Back then, the "cross-layer relationship" only lived in my head's working memory. Session ends, it's cleared.
AI pair-programming tools work the same way. Every new conversation, Claude starts from zero. It doesn't automatically know "last time you changed A and forgot B and it cost you 3 days."
So this time Claude and I thought: can we pull the cross-layer relationship out of my head and store it as a codebase-visible artifact, so 3-months-later me (and the next new-session AI) sees it the moment they open the project?
That's Trace Lock.
The 5 artifacts
Each trace (one cross-layer relationship) is composed of 5 artifacts:
Artifact 1: Registry
A markdown table. Each row is one trace, columns include:
- ID (like T-001)
- Anchor SSOT (which function/field is this trace's "source of truth")
- Trace nodes (the 8-12 files/functions the data passes through on its way from source to render)
- Why (why this trace exists, historical context)
- Last edited (when was it last touched)
The registry isn't documentation. It's an index. Three months from now, when I want to find "how the half-pound drip-bag orderable count is calculated," I look in the registry and know which function is the anchor.
Artifact 2: Fuse test (pinning test)
A pure-function test that pins down the anchor's currently-correct behavior.
Note: "currently-correct" not "what it should be." This distinction matters. More on it below.
When someone changes the anchor in the future, the test fails red, giving them a chance to ask "was this change intentional?"
Artifacts 3 + 4: Two auto-inspectors (governance rules)
- Inspector A: every trace in the registry must have a corresponding test file (prevents "registered but no test written")
- Inspector B: every test file must actually import the anchor function it claims to test (prevents the test from rotting into "testing something else but claiming to test the anchor")
Both inspectors run automatically before commit / push. If they fail, the push doesn't go through.
Artifact 5: AI reminder (skill)
A markdown file that says: "When you (the AI) are about to edit any node listed in the trace nodes, you must first do these 5 steps: list the chain → run the current test → make the change → run the test again → update the registry's Last edited."
The AI tool I use (Claude Code) supports skill auto-triggering. It sees the file I'm about to edit is in a trace nodes list and proactively triggers this skill. If you use a different AI tool:
- Cursor: write into
.cursorrules - Aider: write into
CONVENTIONS.md - ChatGPT: put it in system prompt or custom instructions
Same principle. Make the AI pair tool automatically inherit your governance.
Why all 5 are needed
I thought "registry + test" would be enough at first. Doing it, I realized any missing piece breaks the chain:
- No registry → tests have no index, 3 months later nobody knows which traces have been locked
- No test → registry is just text, nobody reads it
- No Inspector A → registered without a test, nothing blocks it
- No Inspector B → tests "rot" into testing something else, giving a false sense of safety
- No AI reminder → new AI conversations don't trigger the governance flow automatically
How long does locking one trace take?
First-time setup: 4 hours (build the registry format, write the first trace, write two inspectors, write the skill).
Each additional trace: 30-45 minutes (add a row to the registry, write the corresponding test, do reverse verification).
Claude and I calculated the break-even point: after locking the 7th trace, total time spent is less than "grepping from scratch each time." I've now locked 13.
A common trap: fuse tests are NOT unit tests
The most common mistake writing fuse tests: treating them like unit tests.
The difference:
- Unit tests pin down "what it should be": find a bug, change both the test and the code
- Fuse tests pin down "what is currently correct": find anchor behavior that contradicts intuition, pin the current behavior first, don't fix it in passing
Example: while writing one trace, I found that getRoastLevelLabel(null) returned "medium roast" instead of "no roast label." Gut reaction: "this is a bug, fix it."
But the fuse test's job is not to correct "currently-designed choices." Its job is to prevent the current behavior from changing unintentionally in the future. Whether to change the null → medium roast design is a separate discussion (should the UI distinguish "unfilled" from "medium roast"), not something the AI casually decides.
So the test I wrote was: expect(getRoastLevelLabel(null)).toBe('medium'). And I added a comment in the registry: "Currently designed choice: no roast data = default to medium. To distinguish 'unfilled' vs 'medium', that's a product discussion, not an anchor change."
If anyone (or AI) wants to change the anchor in the future, the test fails red, forcing them to see the comment and realize "this isn't a bug, it's a design choice."
Always do reverse verification
After locking each trace, I deliberately break the anchor function (return wrong direction, return null) to confirm the test actually fails red. Then revert, confirm the test is green again.
Sounds redundant? It caught me twice writing "tests that are too loose":
- Once only 2 cases failed because many tests used a single input that didn't exercise the distribution algorithm
- Another time 5 cases failed (normal water level)
Reverse verification is the fuse for the fuse. A test without reverse verification might be "tested but doesn't actually test," worse than no test at all (gives false confidence).
Why this is especially effective for AI pair-programming
Claude and I have noticed two AI failure modes:
- AI doesn't know some invariant: e.g., "VIP tier can only be customer/vip/vvip/svip"
- AI knows but is misled by the user: e.g., I say "add a 'gold' tier," the AI doesn't check governance and just adds it
Trace Lock catches both:
- Mode 1 → registry + skill surface the rules to the AI; the AI reads CLAUDE.md and triggers the skill at session start
- Mode 2 → the two inspectors block commits even if the AI was misled
Compared to "please make the AI more careful" (unactionable), "write the rule in .mjs and run it automatically" is an actionable engineering intervention.
A transferable suggestion
If you're a solo dev who pairs with AI a lot, ask yourself 3 questions:
- Which 5-10 "if I change A I must also change B" chains exist in your codebase? (those are your trace candidates)
- How are these chains currently protected? (tests? docs? personal memory? None are enough)
- Tomorrow when you /clear and start a new conversation, will your AI remember these? (probably not)
If no → you need Trace Lock (or something like it).
Minimum viable version:
- Add a "Critical Traces" table to your docs (paper and pen works)
- Write 1 fuse test pinning the first trace
- Write 1 script that scans the registry to confirm the test exists
- Add the script to a pre-commit hook
- Write a skill (or
.cursorrules/CONVENTIONS.md) telling the AI "before changing a trace, run the test first"
Step 5 matters most. Without a reminder, the AI doesn't proactively trigger the governance flow, and the first 4 steps just sit there decoratively.
Related posts
- E · meta — How we found the methodology through AI conversations (previous in series)
- A2 · Defensive Trace Lock Engineering Edition — 5 artifacts implementation detail (same topic, engineer-facing depth)
- 中文版
About this post
This post is an organized record of conversations I had with Claude (an AI pair-programming tool)
during May 2026. I noticed some patterns worth keeping for my own future reference,
so I asked Claude to help structure them into writing.
A few things I'm not claiming:
- Terms used in this post (Trace Lock / fuse test / auto-inspector / AI reminder) are working names I gave them myself, not industry-standard terminology. Pinning test is a concept Michael Feathers wrote about in "Working Effectively with Legacy Code," but my usage may not be rigorous
- My system has a specific shape (solo-maintained, many cross-layer dependencies, ambiguous business contracts). These patterns may not apply to your context
- I'm not a software engineer, just a barista who pairs with AI to write code
If a professional engineer spots misuse, or there's already a more standard name for any of these concepts, I genuinely welcome corrections.
Top comments (0)