# Defensive Trace Lock, engineering edition — 5 artifacts in detail

> Source: <https://dev.to/dexterlung/defensive-trace-lock-engineering-edition-5-artifacts-in-detail-4bie>
> Published: 2026-07-28 15:24:33+00:00

**May 2026** · Series "Trace Lock — Governance notes from pairing with AI to write code" · Post 6 of 9

This post is the engineering version of [A1 Defensive Trace Lock](https://./trace-lock-a1-defense-en.md).

A1 covered "why" and "what the 5 artifacts look like at a high level." This post unpacks each artifact: code shape, why it was designed that way, what you can tune.

Written for readers who already know what governance rules, pinning tests, and config-as-code are. If you have a non-engineering background, A1 is more suitable.

My stack: Vue 3 + Vite + Vitest + Supabase (PostgreSQL) + Node.js scripts. But the pattern in the 5 artifacts below is independent of the frontend framework. It depends mainly on filesystem conventions and regex parsing.

| # | Artifact | Role | File type |
|---|---|---|---|
| 1 | Registry | The trace directory, hand-written markdown | `文檔/data-source-registry.md` |
| 2 | Trace test (fuse test) | A pure-function test that pins the trace's current correct behavior | `frontend-app/src/__tests__/traces/T{id}-*.trace.test.js` |
| 3 | Governance rule A | Checks every registry trace has a corresponding test file | `scripts/governance-guard.mjs` |
| 4 | Governance rule B | Checks the trace test actually imports the declared anchor | `scripts/governance-guard.mjs` |
| 5 | AI reminder skill | When AI touches trace nodes, auto-triggers the 5-step checklist | `.claude/skills/trace-lock-modify/SKILL.md` |

Total ~600 lines of code (registry parser + 2 governance rules + skill markdown). First setup takes about 4 hours. Each subsequent trace costs 30-45 minutes.

The registry is a markdown file. Not YAML, not JSON, not a database. The reason: it needs to be maintainable by humans AND parseable by scripts. Markdown is acceptable in both directions.

Each trace is a `### T-{id}: title`

block. Fields use the `**Field Name**: value`

convention, which makes them easy to grab with regex. Here is a trimmed version of the real T-001:

```
### T-001: Roasted bean stock grams → POS variant orderable count

- **Type**: data-flow trace
- **Status**: locked (2026-05-25)
- **Anchor SSOT**: [`frontend-app/src/lib/business-rules/variantInventory.js`](../path)
- **Trace test**: [`frontend-app/src/__tests__/traces/T001-variant-inventory.trace.test.js`](../path)
- **Trace nodes** (write side to display side):
  1. **DB column**: `roasted_batches.remaining_weight`
  2. **DB trigger**: `trg_sync_products_stock_virtual_from_batch_iu`
  3. **DB column**: `products.stock_virtual_grams`
  4. **Frontend fetch**: useProducts.js fetchProducts
  5. **SSOT helper**: variantInventory.getVariantAvailableQuantity
  6. **Entry A (modal)**: ProductVariantModal.variantInventoryLimit
  7. **Entry B (checkout)**: AdminPOS.cartStockSafety
- **Related incident**: San Agustin drip-bag-only-1-pack issue
- **Last edited**: 2026-05-25
```

**Anchor SSOT** is the one trustworthy compute entry for this trace. All Entry points must converge on the anchor. If the anchor changes, the trace contract changes. That counts as a major change.

**Trace nodes** lists every node from write side (DB column, trigger) to display side (UI component), in order. Each node is a candidate file where "touching this might affect trace behavior."

**Entry points** are the multiple entry pathways to the same anchor SSOT. For example, T-001's anchor `variantInventory.js`

is called by both ProductVariantModal (when the user picks a variant in POS) and AdminPOS (the cart safety check before checkout). Entry points form the audit checklist for "is this SSOT actually used everywhere it should be used?"

**Last edited** is for AI to read. After each trace-node edit, the human bumps this field. The next time AI looks, it sees "someone touched this recently, context might have drifted."

I considered YAML but rejected it. Reasons:

The cost is that parsing is ~30% trickier than YAML (regex is more error-prone than `yaml.parse`

). I accept the tradeoff because the registry has a practical upper bound of ~30 traces per project, so parsing complexity is also bounded.

The T-001 trace test contains 5 `describe`

blocks. Each section pins one semantic facet of the trace:

``` js
import { describe, expect, it } from 'vitest'
import {
  getVariantWeightPerPack,
  getVariantAvailableQuantity,
  resolveCartItemVariantFormat,
} from '../../lib/business-rules/variantInventory'  // ← import the anchor SSOT

describe('T-001 trace: variant weight contract', () => { /* Section 1 */ })
describe('T-001 trace: variant available quantity (incident pinning)', () => { /* Section 2 */ })
describe('T-001 trace: edge cases', () => { /* Section 3 */ })
describe('T-001 trace: cart item variant format resolution', () => { /* Section 4 */ })
describe('T-001 trace: SSOT cross-entry consistency', () => { /* Section 5 */ })
```

**Section 1, Contract.** Declares the "base contract" the trace covers. For T-001, the contract is "each variant format maps to a specific net weight per pack." This section tests pure spec mapping, no state.

**Section 2, Incident pinning.** Writes the customer-event that triggered creating this trace as a test case. For T-001, that's the San Agustin drip-bag incident (244g barrel → 1 half-pound or 2 drip-bag packs). This section is the historical memory: without it, the trace can rot back to broken.

**Section 3, Edge cases.** null, undefined, 0, negative numbers, missing fields. None should throw; all should fall through to a safe fallback. This section exists so future AI edits to the helper don't regress edge cases.

**Section 4, Reverse resolution.** If the trace requires "reversing from an entry point back to a format the anchor understands," this section tests reverse resolution. For T-001, `resolveCartItemVariantFormat`

derives variant format from a cart item object.

**Section 5, Cross-entry consistency.** The same input passed to N entry points must return the same answer. This section tests that "entries actually share one path, not duplicated implementations."

Not every trace needs all 5 sections. Minimum is Section 1 + Section 2 (contract + incident). Sections 3-5 are added based on the trace's shape.

Artifact 4 (governance rule B) checks that the test file's first `import`

actually points at the anchor path declared in the registry.

Reason: if the trace test doesn't import the anchor, what it's testing is "**a copy-pasted logic snippet inside the test file**," not the SSOT. When the anchor later changes, the test can't see it. The trace rots while the test stays green.

This check is simple but load-bearing. I had one trace (it never made it to the registry during a debugging detour) that rotted exactly this way. The test was green, but the actual logic was rewritten by a different PR.

The two governance rules live in `scripts/governance-guard.mjs`

and run on every pre-push or CI cycle. The core is `parseTraceRegistry()`

, which extracts markdown into an object array:

``` js
function parseTraceRegistry() {
  const registryPath = path.join(repoRoot, '文檔/data-source-registry.md')
  if (!fs.existsSync(registryPath)) return []

  const content = fs.readFileSync(registryPath, 'utf8')
  // Only parse the content between ## Critical Traces and the next ## heading
  const sectionMatch = content.match(/##\s*Critical Traces[\s\S]*?(?=\n##\s|$)/)
  if (!sectionMatch) return []

  const section = sectionMatch[0]
  const traceBlocks = section.split(/\n###\s+(?=T-\d+:)/).slice(1)

  for (const block of traceBlocks) {
    const idMatch = block.match(/^T-(\d+):\s*(.+?)$/m)
    if (!idMatch) continue
    // ... extract Anchor SSOT / Trace test / Type fields
    traces.push({ id, title, anchorPath, testPath, isSqlOnly })
  }
  return traces
}
```

A few engineering details:

**Use [\s\S]*?, not .*?.** JS regex doesn't match newlines with

`.`

by default. Markdown blocks span multiple lines, so `[\s\S]`

is required.**Use (?=\n##\s|$) as the terminator.** The lookahead ensures parsing stops at the next

`##`

heading or end-of-file. Without it, the parser would scoop up unrelated sections.** .slice(1) skips the prelude.** The first chunk from

`split`

is the content between the `## Critical Traces`

heading and the first `###`

. That's the section preamble, not a trace.**isSqlOnly carveout.** T-021 (FIFO consumption trace) is sql-only. Its test is a `.sql`

file, not `.js`

. The Type field is marked `sql-only-trace`

and rule B skips the import check (because SQL tests have no import concept).

``` js
function checkTraceRegistryTestCoverage(violations) {
  const traces = parseTraceRegistry()
  for (const trace of traces) {
    if (!trace.testPath) {
      violations.push({
        message: `T-${trace.id} registry has no Trace test field`,
      })
      continue
    }
    const absTestPath = path.join(repoRoot, trace.testPath)
    if (!fs.existsSync(absTestPath)) {
      violations.push({
        message: `T-${trace.id} declared Trace test file does not exist`,
      })
    }
  }
}
```

Blocks two failure modes: (a) a trace declared in registry without a test path field; (b) test path declared but the file doesn't exist (path typo, or not created yet).

``` js
function checkTraceTestImportsAnchor(violations) {
  for (const trace of traces) {
    if (!trace.testPath || !trace.anchorPath) continue
    if (trace.isSqlOnly) continue

    const testSource = fs.readFileSync(absTestPath, 'utf8')
    const anchorBasename = path.basename(trace.anchorPath, path.extname(trace.anchorPath))

    const importPattern = new RegExp(
      String.raw`(?:from|require\s*\()\s*['"\`][^'"\`]*${anchorBasename}(?:\.[jt]s)?['"\`]`,
      'g',
    )
    if (!importPattern.test(testSource)) {
      violations.push({
        message: `T-${trace.id} trace test must import its declared Anchor SSOT`,
      })
    }
  }
}
```

Loose match on the anchor basename (path-relative-flexible) so any of `import { ... } from '...variantInventory(.js)'`

or `require(...)`

passes. `String.raw`

avoids backslash escape hell.

Both rules combined: roughly 60 lines of code.

The skill is a markdown file. The frontmatter `description`

declares the trigger condition. The body holds the 5-step audit checklist. Claude Code auto-loads it when a task description matches:

```
---
name: trace-lock-modify
description: "Use when modifying any file listed in 文檔/data-source-registry.md > Critical Traces 的 Anchor SSOT / Trace nodes / Entry points. Cross-layer trace guardrail. AI must list the trace's full chain + run the trace test to pin baseline + re-run after the edit + remind user to bump Last edited."
---
```

A few engineering details:

**The description must start with "Use when ...".** Claude Code uses this phrasing to judge trigger conditions. Other phrasings have measurably lower trigger rates (observed across 4 skill rewrites).

**List specific file paths.** The description directly lists the three categories (`Anchor SSOT / Trace nodes / Entry points`

). When AI sees a task description that mentions these paths, the match rate is high.

**The 5-step checklist lives in the body.** After triggering, AI actually reads the SKILL.md body. Steps in the body need to be runnable (grep command, vitest command, "re-run after edit," etc.).

`Last edited`

field in the registryStep 2 matters. AI should not assume the user remembers trace details. Listing the chain every time forces user and AI to sync mental models.

Regex parsing has 3 common failure modes:

`**Anchor SSOT**: [\`

path`](url)`

and `**Anchor SSOT**: path`

need to be recognized. My regex uses `\[?`

/ `\]?`

to accept either.`/\*\*Type\*\*:\s*`

plus a `?`

accepts cases like `**Type**: data-flow ⚠️`

.The Node.js script runs on Windows, Mac, and Linux:

``` js
const repoRoot = path.dirname(fileURLToPath(import.meta.url)).replace(/scripts$/, '')
const registryPath = path.join(repoRoot, '文檔/data-source-registry.md')
```

Never hardcode `'/'`

or `'\\'`

. Always use `path.join`

. CJK folder names work on Windows under UTF-8 (Node.js fs handles it automatically), but console output on Windows requires care with cp950 encoding (`chcp 65001`

+ `LC_ALL=C.UTF-8`

).

`scripts/governance-guard.mjs`

is written as a standalone entry. Pre-push hooks and GitHub Actions both run it:

```
# pre-push hook
node scripts/governance-guard.mjs || exit 1
```

A `violations`

array collects all violation messages. At the end, it prints them and exits with code 1. Key point: **all trace rules are BLOCKER tier** (not advisory). They block the push. Reason: traces are by design "relationships that should already be stable." Advisory equals no enforcement.

Each new trace (not the first one, but a new one added with the framework already in place):

`Last edited`

: 30 secondsTotal 30-45 minutes per trace. Consistent with the estimate from A1.

How portable each artifact is when copied to a different project:

| Artifact | Portability | Why |
|---|---|---|
| Registry markdown format | ★★★★★ | Pure convention, project-independent |
| Trace test 5-section structure | ★★★★☆ | Vitest / Jest / Mocha all work; only the import syntax changes |
| Governance rule code | ★★★★☆ | Node.js script, replace the registry path |
| AI reminder skill format | ★★★★☆ | Claude Code skill format; other AI tools use different syntax |
| Specific trace contents | ★☆☆☆☆ | 100% business-specific; reinvented per project |

In other words: **the framework layer is ~80% portable; the content layer is 0% portable.** This ratio is consistent with the "80% framework portable, 20% content reinvented" estimate from [C1 Offense + Defense combined](https://./trace-lock-c1-combo-en.md).

Detailed cross-project reuse analysis is in [C2 Cross-project reuse matrix](https://./trace-lock-c2-combo-engineering-en.md) (next post in the same series branch).

Lifting these 5 artifacts wholesale **isn't worth it** when:

My setup (solo maintainer, many cross-layer dependencies, relatively stable business contracts, Claude Code pairing) happens to hit all the conditions. Your situation needs its own judgment.

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:

If a professional engineer spots misuse, or there's already a more standard name for any of these concepts, **I genuinely welcome corrections**.

*本文原載於我的部落格： Defensive Trace Lock, engineering edition — 5 artifacts in detail*
