DEV Community

Cover image for Offensive audit, engineering edition — the 6-piece fix pattern in detail
Dexterlung
Dexterlung

Posted on • Originally published at coffeeshooters.com

Offensive audit, engineering edition — the 6-piece fix pattern in detail

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


This post is the engineering version of B1 Offensive audit.

B1 covered "why audit the whole chain" and "what the overall flow looks like." This post unpacks the 6-piece fix pattern (working name, my own tentative term) used to actually fix each gap that the audit surfaced: the code shape of each piece, why it is designed that way, what you can tune.

Written for engineers already familiar with governance rules, pinning tests, or incident-driven tests. If you have a non-engineering background, B1 is more suitable.

My stack: Vue 3 + Vite + Vitest + Supabase (PostgreSQL) + Node.js scripts. But the 6-piece pattern below is mostly independent of the frontend framework. It depends on filesystem conventions + regex parsers + CI-enforced execution (pre-push hook + GitHub Actions).


The 6 pieces at a glance

# Piece Role File type
1 Pure-function helper Extracts business logic into a side-effect-free function frontend-app/src/lib/business-rules/<feature>Logic.js
2 Trace test (fuse test) Pins the business contract + incident pinning cases frontend-app/src/__tests__/traces/T{NN}-*.trace.test.js
3 Governance rule Pre-push enforces callers route through the helper (or carry an explicit exemption) checkXxxCoverage() in scripts/governance-guard.mjs
4 Caller exemption comment Legitimate carve-out for thin wrapper / docstring scenarios @xxx-ok: <reason> comment in source
5 Registry entry The directory entry for this trace ## Critical Traces block in 文檔/data-source-registry.md
6 Iteration log The record of how this gap was fixed (for the future you) 文檔/iteration-logs/<sprint>/NN_Gap-N_<topic>.md

The set corresponds to the pipeline: a. offense surfaces gap → b. freeze business contract → c. extract pure function → d. pin behavior → e. force callers → f. record decision.

Mapping to A2's 5 artifacts

A2's 5 artifacts (registry + trace test + 2 governance rules + AI reminder skill) solve "rot protection for a single, known trace."

B2's 6 pieces solve "starting from zero, discover and fix N gaps in one sprint." Differences:

  • A2 does not need a helper (the trace usually already has an SSOT)
  • B2 must add a helper (gaps are gaps precisely because no SSOT exists yet; logic is scattered)
  • A2 does not need an iteration log (single trace, one-time setup)
  • B2 must add an iteration log (multi-gap repair process needs to record escalations, reverse verification, self-checks)

The two sets compose. B2 surfaces and locks new gaps; A2 prevents subsequent rot.

What breaks if you skip any one piece

Missing Consequence
Pure-function helper Business logic stays scattered; the next caller re-implements it
Trace test The frozen contract lives only in your head; in 3 months you forget why
Governance rule New callers bypass review and the helper is decorative
Caller exemption Some thin wrappers / cross-domain files get false-positive governance violations
Registry entry No central directory; AI does not know which files belong to which trace
Iteration log The context for "why cash-first instead of bonus-first" evaporates

No single piece is sufficient on its own. The closed loop forms only when all 6 are present.


Piece 1 (Pure-function helper): extraction logic

Once business logic is embedded in a Vue component, composable, or RPC handler, it carries side effects (DB reads/writes, store mutations, emits). The trace test cannot get a clean test target.

The design conventions for pure-function helpers:

// frontend-app/src/lib/business-rules/cancelOrderLogic.js

/**
 * Calculate refund allocation (cash first, bonus second)
 *
 * @param {object} params
 * @param {number} params.refundAmount - Total to refund
 * @param {number} params.originalDeductFromCash - Original cash deduction
 * @param {number} params.originalDeductFromBonus - Original bonus deduction
 * @returns {{ refundToCash: number, refundToBonus: number }}
 */
export function calculateRefundAllocation({
  refundAmount,
  originalDeductFromCash = 0,
  originalDeductFromBonus = 0,
}) {
  const refundToCash = Math.min(refundAmount, originalDeductFromCash)
  const refundToBonus = Math.min(refundAmount - refundToCash, originalDeductFromBonus)
  return { refundToCash, refundToBonus }
}
Enter fullscreen mode Exit fullscreen mode

A few engineering details.

Zero side effects + dependency injection

The helper does not call useUserStore(), supabase.from(...), or window.localStorage directly. All dependencies are passed via parameters:

// Counter-example (coupled to store)
export function calculateRefundAllocation(refundAmount) {
  const userStore = useUserStore()
  const cash = userStore.user.deductFromCash  // side effect
  ...
}

// Correct (pure function)
export function calculateRefundAllocation({ refundAmount, originalDeductFromCash }) { ... }
Enter fullscreen mode Exit fullscreen mode

Reason: trace tests can call the helper with fixture data without mocking the store or Supabase. Test cost drops by an order of magnitude.

Place under lib/business-rules/, not composables/

composables/ is a Vue convention; functions there return refs / reactives to components. lib/business-rules/ is plain JS, completely decoupled from Vue.

If in the future you want to:

  • Reuse the helper in an Edge Function (Deno runtime, cannot import Vue)
  • Reuse in SSR (no window)
  • Write unit tests (no need to spin up Vue test utils)

Placing it correctly makes reuse painless.

Naming convention: <feature>Logic.js

  • cancelOrderLogic.js / roastingLifecycle.js / packagingTaskState.js
  • cancelOrderUtils.js / refundHelper.js / roastingService.js

The Logic suffix lets a single grep find every SSOT helper. Utils / Helper / Service are too generic and hard to audit.

When not to extract a helper

  • Logic is called from only one place. Extracting scatters the reader's attention
  • Logic is purely a read or has no branches. Inlining is fine
  • Logic lives mostly in the DB (e.g., FIFO consumption). Go with the sql-only-trace variant (covered later)

Piece 2 (Trace test): the 5-section structure

A trace test is a vitest spec, but its structure is stricter than a typical unit test. Each describe block corresponds to a semantic facet. Here is the skeleton of T-019 order-cancel / refund:

// frontend-app/src/__tests__/traces/T019-cancel-refund.trace.test.js

import { describe, expect, it } from 'vitest'
import {
  calculateRefundAllocation,
  findRoastingOrdersToCancel,
} from '../../lib/business-rules/cancelOrderLogic'  // import anchor SSOT

describe('T-019 trace: refund allocation frozen contract (incident pinning)', () => {
  it('Original cash $30 + bonus $50 → refund $80 = cash 30 + bonus 50', () => {
    const result = calculateRefundAllocation({
      refundAmount: 80,
      originalDeductFromCash: 30,
      originalDeductFromBonus: 50,
    })
    expect(result).toEqual({ refundToCash: 30, refundToBonus: 50 })
  })

  it('Partial refund $40 = cash 30 + bonus 10', () => {
    expect(calculateRefundAllocation({
      refundAmount: 40,
      originalDeductFromCash: 30,
      originalDeductFromBonus: 50,
    })).toEqual({ refundToCash: 30, refundToBonus: 10 })
  })
})

describe('T-019 trace: legal paths', () => { /* ... */ })
describe('T-019 trace: illegal paths / edge cases', () => { /* ... */ })
describe('T-019 trace: cascading cancel rules for roasting_orders', () => { /* ... */ })
describe('T-019 trace: cross-layer structural contract', () => { /* ... */ })
Enter fullscreen mode Exit fullscreen mode

What each section is for

Section 1 (Business contract, incident pinning)

Take the conversation that froze the business contract and write it as test cases. Each BLOCKER gap's decision was made by the user on the spot (e.g., "refund fills cash first"); this section directly pins that decision. Three months later, anyone changing the algorithm flips the test red and is forced to see the decision context.

Each trace test gets at least 1 case here, often 5-10 (covering happy paths + boundaries).

Section 2 (Legal paths)

Variations of the full happy path. For example, T-022 packaging-task state machine has legal transitions pending → in_progress → completed and pending → cancelled. This section tests 4-6 legal paths.

Section 3 (Illegal paths / edge cases)

null / undefined / empty array / missing fields / negatives / very large numbers. This section guards against future AI edits regressing edge cases.

When writing this section, actively ask: "what does the helper do on dirty input? should it throw?" The answer is usually "return a safe fallback, do not throw" (because throws blow up the UI catch chain), but pin it in tests.

Section 4 (Role / permission / business rule limits)

If the gap involves role limits (e.g., T-022 specifies "rollback completed → cancelled is admin/super_admin only"), this section tests the allow/deny matrix per role.

Not every trace needs this section. Pure calculation logic can skip it.

Section 5 (Cross-layer structural contract)

The contact surface between the helper and the DB schema, other helpers, or other anchors. For example, the T-022 state string must align with valid values inside packaging_jobs.tasks JSONB on the DB. This section pins that the helper-known state set matches the DB expectation.

This section catches "helper drifted from DB schema."

Why the test must import the anchor

The first line of a trace test imports the function from lib/business-rules/<feature>Logic. Governance rule B (covered in A2) checks that every trace test really imports the anchor path declared in the registry.

A trace test that does not import the anchor is testing a copy-pasted copy of the logic. The anchor moves, the test does not see it, the trace rots while the test still goes green.

Target 15+ cases per trace

From the fulfillment-chain-fix sprint: T-019 / T-020 / T-022 averaged 30-38 cases. At least 1 pinning case in Section 1; 3-5 cases each in other sections.

Less than 15 usually means the business contract is not crisp enough. Go back to the audit map and refine.


Piece 3 (Governance rule): code template

Each gap gets a governance rule in scripts/governance-guard.mjs. The pre-push hook and GitHub Actions both run it.

A trimmed-down version of M5 (Cancel/Refund Helper Coverage):

// scripts/governance-guard.mjs
// M5 Cancel/Refund Helper Coverage (2026-05-25, T-019)
//
// Motivation: cancel-order + complete-refund both involve wallet reverse-deduction
//             and cascading cancel of roasting_orders. Callers must route through
//             cancelOrderLogic. No scattered re-implementation.
//
// Rule: any source file matching `fn_cancel_order_atomic` or `fn_admin_complete_refund_atomic`
//       must (a) import cancelOrderLogic OR
//            (b) carry `@cancel-refund-ok: <reason>` exemption

function checkCancelRefundHelperCoverage(violations) {
  const files = collectFiles('frontend-app/src', /\.(js|vue)$/)
  const RPC_PATTERN = /fn_cancel_order_atomic|fn_admin_complete_refund_atomic/

  for (const relativePath of files) {
    if (/(__tests__|\.test\.js|\.spec\.js)/.test(relativePath)) continue
    if (/(^|\/)dist\d?\//.test(relativePath)) continue

    const abs = path.join(repoRoot, relativePath)
    const src = fs.readFileSync(abs, 'utf8')
    if (!RPC_PATTERN.test(src)) continue

    const hasHelperImport = /cancelOrderLogic|calculateRefundAllocation/.test(src)
    const hasExemption = /@cancel-refund-ok\s*:\s*\S{5,}/.test(src)

    if (!hasHelperImport && !hasExemption) {
      violations.push({
        file: relativePath,
        line: 0,
        message: `M5 Cancel/Refund Coverage: this file calls cancel/refund RPC but does not import cancelOrderLogic or carry @cancel-refund-ok exemption`,
        excerpt: 'cancel/refund RPC call without helper / exemption',
      })
    }
  }
}

checkCancelRefundHelperCoverage(violations)  // call from footer
Enter fullscreen mode Exit fullscreen mode

Design points

Header docstring must contain 3 things: creation date + corresponding trace ID + motivation. The motivation must be a business motivation, not "to pass CI."

Use a disjunction regex for RPC_PATTERN: combine multiple RPC names with | instead of src.includes('fn_a') || src.includes('fn_b'). Cleaner and faster.

Skip __tests__ / dist: trace tests themselves import helpers and mention RPC names; without skipping they false-positive. dist is build output, wasted scan.

Write hasHelperImport loose: callers might import the whole module (import * as logic) or destructure (import { calculateRefundAllocation }). Disjunction matches both.

@cancel-refund-ok must satisfy \S{5,}: empty reasons are forbidden (covered as a trap below).

BLOCKER vs advisory

Sprint experience: trace-lock-related rules are all BLOCKER (pre-push exits 1, blocks push). Reasons:

  • Traces are, by design, "supposedly stable" relationships
  • Advisory equals not enforced. In 3 months it will be ignored
  • BLOCKER false positives are handled by exemption comments (low cost)

Exception: pure-text / docs / transition-period lint rules can be advisory. Trace lock is not in that category.


Piece 4 (Caller exemption comment): two traps

The exemption comment is a legitimate escape hatch around a governance rule. The format:

// useOrders.js
// @cancel-refund-ok: thin-wrapper-only — this composable only relays RPC results to the UI
export async function cancelOrder(orderId) {
  return supabase.rpc('fn_cancel_order_atomic', { p_order_id: orderId })
}
Enter fullscreen mode Exit fullscreen mode

Exemptions should be a minority. Two main categories:

  1. Thin wrapper: caller only relays results, no business logic
  2. Docstring mention: helper JSDoc references an RPC name but does not actually call it

Trap A: the \S{5,} regex and Chinese text

The exemption regex @xxx-ok\s*:\s*\S{5,} requires 5 consecutive non-whitespace characters after the colon. Chinese text like "此 helper 只透傳" has a space after the first character; regex sees one character then a break, fails to match.

Fixes:

// fails — Chinese chars + space
// @cancel-refund-ok: 此 helper 只透傳

// passes — dash-connected English
// @cancel-refund-ok: thin-wrapper-only

// passes — 5+ Chinese chars with no whitespace
// @cancel-refund-ok: 此處僅透傳上游結果不涉及計算
Enter fullscreen mode Exit fullscreen mode

In practice, the sprint adopted dash-connected English: short phrases parse easily, remain readable when audit:all output is truncated, and are unambiguous in regex.

Trap B: docstring mentions of RPC names false-positive

A helper's JSDoc may mention related RPC names (to help IDE jump-to-definition) without the helper actually calling them. The governance RPC_PATTERN regex cannot tell the semantic difference and false-positives.

Example:

/**
 * cancelOrderLogic
 *
 * Atomic RPCs: fn_cancel_order_atomic (migration 238)
 * Atomic RPCs: fn_admin_complete_refund_atomic (migration 239)
 */
// @cancel-refund-ok: docstring-mention-only — this helper only mentions RPC names in JSDoc for IDE jumps, does not call them

export function calculateRefundAllocation(...) { ... }
Enter fullscreen mode Exit fullscreen mode

Fix: add the exemption comment at the top of the helper file, reason docstring-mention-only. Governance sees the exemption and skips.

Sprint statistics: 4 gaps used 5 exemptions total (thin wrapper × 3 + docstring-mention × 2), under 10% of total callers. Exemptions should not become the norm; most callers should import the helper.


Piece 5 (Registry entry): align with existing format

Add a block for the new trace under ## Critical Traces in 文檔/data-source-registry.md. Format aligns with existing T-001 (covered in A2), plus a few sprint-discovered supplements:

### T-019: Order cancellation / refund SSOT

- **Type**: data-flow trace
- **Status**: locked (2026-05-25)
- **Anchor SSOT**: [`frontend-app/src/lib/business-rules/cancelOrderLogic.js`](../path)
- **Trace test**: [`frontend-app/src/__tests__/traces/T019-cancel-refund.trace.test.js`](../path)
- **Governance**: M5, Cancel/Refund Helper Coverage
- **Related migration**: 238, 239
- **Frozen business contract**:
  - Refund allocation: cash first, bonus second (frozen by user 2026-05-25)
  - Cascading cancel: source_orders contains only this order → cancel whole roasting; multiple orders share → remove share only
- **Trace nodes**:
  1. RPC `fn_cancel_order_atomic`
  2. RPC `fn_admin_complete_refund_atomic`
  3. helper `calculateRefundAllocation`
  4. helper `findRoastingOrdersToCancel`
  5. caller `useOrders.cancelOrder`
  6. caller `useRefunds.completeRefund`
- **Related incident**: half-year-old partial-refund wallet imbalance event
- **Last edited**: 2026-05-25
Enter fullscreen mode Exit fullscreen mode

Two extra fields compared to A2's T-001

Gap-fix-produced traces add two fields compared to the T-001 format in A2.

Governance: the governance rule ID for this trace. IDs M5 / M6 / M7 / M8 in the registry must match governance-guard.mjs exactly. Future audits scan the registry and immediately see which rules pair with which trace.

Frozen business contract: a bullet list of the decisions the user pinned on the spot. This is the biggest value-add over A2's 5 artifacts: every frozen decision is permanently traced.

Writing Trace nodes

Includes RPCs (DB write side) + helpers (pure-function SSOT) + callers (frontend composable / page). Order roughly follows "DB → UI," but in practice they interleave. This field mostly tells AI which files belong to this trace; ordering strictness is looser than the T-001 in A2.

sql-only-trace variant

When a gap has no frontend helper (pure DB logic, e.g., T-021 FIFO consume):

- **Type**: sql-only-trace
- **Trace test (SQL)**: [`scripts/integration-test/T021-fifo-consume-test.sql`](../path)
Enter fullscreen mode Exit fullscreen mode

Type becomes sql-only-trace. Trace test becomes Trace test (SQL). parseTraceRegistry sees both markers and skips the import check (covered later).


Piece 6 (Iteration log): 5-section structure

The iteration log records "why this gap was fixed this way." Three months later, the future you sees context here that commit messages cannot show. Filename format NN_Gap-N_<topic>.md, placed under 文檔/iteration-logs/<sprint>/:

# Iteration 1, Gap-1: Order cancel / refund (T-019)

**Completion time**: 2026-05-25 23:38

## (a) Files touched
- helper / trace test / registry / governance / caller exemption / migration with paths

## (b) Trace test result
- First run: 30/30 green
- Reverse verification A (helper anchor break): deliberately broke algorithm → N cases red → reverted → green
- Reverse verification B (governance anchor break): removed exemption → governance red → reverted → green
- Full trace suite: X files / Y cases all green
- audit:all: full PASS

## (c) Escalate log
- Traps stepped on / name alignment issues / Rule 27 signature change judgement / business contract micro-adjustment

## (d) Self-check
- Context usage estimate: ~X%
- Frozen Constraint drift check (per frozen point)
- Cross-gap consistency confirmation

## (e) Next gap
- Next gap ID + estimated time + main actions
Enter fullscreen mode Exit fullscreen mode

Why none of the 5 sections can be skipped

(a) Files touched: 3 months later, commit messages do not tell you which files were part of the same fix. This section is "the artifact list for this gap."

(b) Trace test result: not just positive-green; must record the result of reverse verification anchor breaks (covered later).

(c) Escalate log: traps, name misalignments, Rule 27 judgments, Edge Function vs DB trigger trade-offs. This section is "the decision log for why A and not B."

(d) Self-check: context usage + frozen Constraints have not drifted + cross-gap consistency. If context usage exceeds 80%, time to stop & handoff.

(e) Next gap: a cue for the autonomous loop to continue.

A full iteration log per gap runs 100-200 lines, which looks like a lot but is just raw facts, 5-10 minutes to write. Three months later you thank past-you for writing it.


Reverse verification anchor break (mandatory at gap closure)

Positive verification (trace test green + governance green) only proves "current state is not broken." It does not prove the lock has catching power. Reverse verification anchor break fills that gap:

  1. Helper anchor break: deliberately break the helper algorithm (e.g., swap cash first to bonus first) → the trace test should have at least 1 case red → revert → all green
  2. Governance anchor break: deliberately remove the caller's exemption comment → governance should turn red → revert → all green

Both passing means "the lock really has catching power." If reverse verification does not turn red, the trace test / governance rule is too lax and needs strengthening.

Reverse verification surprises

T-022 packaging-task's first reverse verification attempt was "add skipped as a legal transition," expected to break, but PACKAGING_TASK_STATES does not include skippedINVALID_TO_STATE still blocks → the test does not turn red.

The second attempt removed in_progress → completed legal path, which is a core transition, and the test finally broke.

Meaning: the reverse verification target must be a core contract the trace test actually protects, not an edge case. If reverse verification does not break, either pick a more central break point or backfill missing test coverage.


sql-only-trace special category (when DB logic stands alone)

When business logic lives purely inside the DB (complex triggers, FIFO RPCs, PL/pgSQL stored procedures), the 6 pieces become 5 pieces (no frontend helper) + 1 specialization (SQL test instead of vitest):

-- scripts/integration-test/T021-fifo-consume-test.sql

-- Set JWT so RPC passes is_admin / has_operator_capability
SELECT set_config(
  'request.jwt.claims',
  '{"sub":"test-user","system_role":"super_admin"}',
  false
) AS jwt_set;

DO $T021$
DECLARE
  v_errors TEXT[] := ARRAY[]::TEXT[];
  v_seed_prefix TEXT := 'T021-' || substr(md5(random()::text), 1, 8);
  -- ... seed IDs
BEGIN
  -- 1. SEED (build fixtures with unique prefix)
  -- 2. RUN (call the RPC under test, fn_consume_for_roasting)
  -- 3. ASSERTIONS (v_errors := array_append(v_errors, ...) on fail)
  -- 4. CLEANUP (explicit DELETE seed, do not rely on transaction)
  IF array_length(v_errors, 1) IS NULL THEN
    RAISE NOTICE 'T-021 ALL ASSERTIONS PASSED';
  ELSE
    RAISE EXCEPTION 'T-021 FAILED: %', array_to_string(v_errors, E'\n');
  END IF;
EXCEPTION WHEN OTHERS THEN
  -- best-effort cleanup + re-raise
  BEGIN DELETE FROM ... WHERE ... LIKE v_seed_prefix || '%'; EXCEPTION WHEN OTHERS THEN NULL; END;
  RAISE;
END
$T021$;

-- CLI db query only returns the last SELECT rows; print PASS message here so it shows
SELECT 'T-021 PASSED' AS status, 16 AS assertions, 'FIFO consume invariants' AS details;
Enter fullscreen mode Exit fullscreen mode

Engineering details

JWT via set_config('request.jwt.claims', ...): before calling an RLS-gated RPC, set the JWT to simulate super_admin; otherwise permission denied.

SEED with unique prefix: 'T021-' || substr(md5(random()::text), 1, 8) ensures multiple runs do not interfere. CLI db query cannot wrap in a transaction, so cleanup must be explicit DELETE.

Best-effort cleanup: cleanup inside EXCEPTION must swallow its own errors so the original error message is not masked.

Final SELECT prints PASS: npx supabase db query --linked -f only returns rows from the last SELECT. RAISE NOTICE is not shown by default. Put the PASS string in the final SELECT to see it.

RAISE EXCEPTION for full error output: on failure, EXCEPTION carries the v_errors array string.

Registry side

parseTraceRegistry() sees **Type**: sql-only-trace and **Trace test (SQL)** and marks the trace isSqlOnly = true. Governance rule checkTraceTestImportsAnchor checks isSqlOnly and skips the import check (SQL has no import semantics).

The whole parser extension is about 10 lines of code, done as part of the sprint.

Run command:

npx supabase db query --linked -f scripts/integration-test/T021-fifo-consume-test.sql
Enter fullscreen mode Exit fullscreen mode

On failure, EXCEPTION's v_errors print to CLI. On success, the final SELECT prints PASS. The pre-push hook runs this; non-zero exit blocks the push.


Engineering considerations

CLI db query limitations

Gotchas of supabase db query --linked -f:

  • By default does not display RAISE NOTICE, only returns the last SELECT rows
  • Each statement auto-commits, cannot wrap in BEGIN/ROLLBACK
  • Cleanup must be explicit DELETE
  • SEED must use a unique prefix to avoid pollution

Fix: every sql-only-trace test follows the engineering template above.

Rule 27 RPC overload risk

CREATE OR REPLACE does not remove other overloads. Changing the signature (including adding a default-NULL parameter) requires first DROP FUNCTION IF EXISTS public.foo(old signature).

Judgement:

  • Signature is fully equivalent (same parameter list, types, order) → no DROP needed
  • Any signature difference → must DROP

In the sprint, Gap-1 and Gap-2 RPC signatures stayed unchanged, no DROP. Gap-3 changed the FIFO RPC signature (added a new default-NULL parameter), so DROP before CREATE OR REPLACE.

Trigger: write warning, not RAISE

When the frozen business contract says "do not block, write a warning" (e.g., T-020 roast-level mismatch), the trigger function:

-- Old behavior (migration 177)
-- IF roast_level <> expected THEN
--   RAISE EXCEPTION '...'; -- workstation freezes
-- END IF;

-- New behavior (migration 240)
IF roast_level <> expected THEN
  UPDATE public.roasting_orders
  SET notes = CONCAT(COALESCE(notes, ''), E'\n[ROAST_LEVEL_MISMATCH] expected=', expected, ', got=', roast_level)
  WHERE id = NEW.related_id;

  INSERT INTO public.inventory_logs (action, note, ...)
  VALUES ('ROAST_LEVEL_WARNING', '...', ...);
END IF;

RETURN NEW;  -- continue write, workstation not frozen
Enter fullscreen mode Exit fullscreen mode

The trace test pins the warning prefix [ROAST_LEVEL_MISMATCH]. DB and frontend share the same identifier string.

Context window management

Fixing 4 gaps used about 60-70% context (reading migrations + helpers + callers + trace tests + running reverse verification twice).

Each gap's iteration log section (d) Self-check estimates context %. At the 80% red line:

  1. Immediately commit completed gaps
  2. Write a handoff doc to 補充資料/CLAUDE_HANDOFF_YYYY-MM-DD_<sprint>.md
  3. Use /clear to start a new session, run /goal --resume <sprint> to continue

Red line at 80% rather than 90% because of buffer for: writing commit messages, running full audit:all after push, finishing the handoff doc. Pushing through to 90% risks context anxiety mid-closure and partial artifacts.

Time estimate

Sprint statistics:

  • Audit map: 1-2h
  • Per gap 6-piece: 1-1.5h (including reverse verification)
  • Writing iteration log: 5-10 min
  • Sprint summary (only at ④): 1h
  • Skill extraction (only at ⑤): 1h

4 gaps full set: 1h audit + 4-6h fix + 1h summary = 7-9h.

Compared to the "fix one at a time" mode: 3-5h per bug × N. ROI ~5-8x (each gap blocks N future bugs).


Cross-project reusability

Piece Reusability Why
Pure-function helper convention (lib/business-rules/<feature>Logic.js + dependency injection) ★★★★★ Plain JS module convention, framework-agnostic
Trace test 5-section structure ★★★★☆ Works with Vitest / Jest / Mocha, just swap import syntax
Governance rule code ★★★★☆ Node.js script, swap RPC pattern + helper name
Caller exemption comment convention ★★★★★ Pure regex text convention, tool-agnostic
Registry entry format ★★★★★ Pure markdown convention
Iteration log 5 sections ★★★★★ Pure markdown record, tool-agnostic
Reverse verification anchor break process ★★★★☆ Pure process, but requires team / AI cooperation
sql-only-trace variant ★★★★☆ PostgreSQL-specific (DO block + JWT config), other DBs need adjustment
Concrete business contract content ★☆☆☆☆ 100% business-specific, every project reinvents

Framework layer 80-90% portable, business contract 0% portable. Same ratio as A2's 5 artifacts.

For the full cross-project reusability matrix see C2 cross-project reusability (same-branch next post).


When the 6-piece set does not fit

Cases where copy-pasting the 6-piece set across is not necessarily worth it:

  1. Single gap / single bug fix: 1h audit + 6-piece is over-engineering. Just fix and commit. The 6-piece is for sprints with 3+ gaps found in audit
  2. No governance script infrastructure: the 6-piece set depends on pre-push hooks + CI enforcement. Without that infrastructure, build A2's 5 artifacts first
  3. Business contracts churn very fast: when helper algorithms must change weekly, trace test pinning becomes a drag. Stabilize the business first, then lock
  4. Solo short-term project / first month after launch: the future-self-3-months-later does not exist; commit messages + PR descriptions are enough for pinning
  5. Multi-person maintenance + QA / code review pipeline: traditional code review already plays part of the governance role. The 6-piece set still works, but marginal value drops. Evaluate ROI

My environment (solo maintainer, many cross-layer dependencies, relatively stable business contracts, maintenance period of 6+ months, AI pair-programming) hits all the applicable conditions. Your context, you judge.


Related


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 (6-piece fix pattern / frozen business contract / reverse verification anchor break / sql-only-trace / incident pinning case / Trace Lock) are working names I gave them myself, not industry-standard terminology
  • 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.


本文原載於我的部落格:Offensive audit, engineering edition — the 6-piece fix pattern in detail

Top comments (0)