[object Object]

← back to Ticket System

TK-11372: fault-injection A/B discriminates the fix, plus two discarded runs

299731eaddd5d69829f28af6de50ab02dfa2faa3 · 2026-09-11 12:01:22 -0700 · Steve Abrams

Injected a slow lsof on PATH (the seam both arms use, since each invokes lsof
by name) to exceed the code own 4000ms timeout without waiting for load 70.
Baseline stalled a request 3.512s behind execSync; the patched arm stayed
sub-millisecond across 12 probes. Discriminated.

Records two runs that did NOT count, rather than quietly dropping them:
- a first A/B was void because the baseline arm ran outside the repo and died
  on MODULE_NOT_FOUND, so its 14/14 failures were connection-refused from a
  dead process; a listening guard now gates every probe
- a second A/B at load 35 was non-discriminating, both arms 16/16 clean, which
  is the honest reading: the defect is load-dependent

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PkwT8hDyw7SCCkYBviZTbq

Files touched

Diff

commit 299731eaddd5d69829f28af6de50ab02dfa2faa3
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Fri Sep 11 12:01:22 2026 -0700

    TK-11372: fault-injection A/B discriminates the fix, plus two discarded runs
    
    Injected a slow lsof on PATH (the seam both arms use, since each invokes lsof
    by name) to exceed the code own 4000ms timeout without waiting for load 70.
    Baseline stalled a request 3.512s behind execSync; the patched arm stayed
    sub-millisecond across 12 probes. Discriminated.
    
    Records two runs that did NOT count, rather than quietly dropping them:
    - a first A/B was void because the baseline arm ran outside the repo and died
      on MODULE_NOT_FOUND, so its 14/14 failures were connection-refused from a
      dead process; a listening guard now gates every probe
    - a second A/B at load 35 was non-discriminating, both arms 16/16 clean, which
      is the honest reading: the defect is load-dependent
    
    Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01PkwT8hDyw7SCCkYBviZTbq
---
 verification/tk11372/ab-fault-injection.json | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/verification/tk11372/ab-fault-injection.json b/verification/tk11372/ab-fault-injection.json
new file mode 100644
index 00000000..8917033d
--- /dev/null
+++ b/verification/tk11372/ab-fault-injection.json
@@ -0,0 +1,22 @@
+{
+  "ticket": "TK-11372",
+  "purpose": "Break it on purpose, watch it go red, put it back (CLAUDE.md TK-11431 amendment 3). Discriminate the patched server from the baseline on the SAME box, without waiting for load 70.",
+  "testability_seam": "both arms invoke lsof BY NAME (baseline via /bin/sh, patched via execFile), so PATH injection needs no code change and no flag a plist could pass",
+  "fault": "a shim `lsof` on PATH that sleeps 10s then execs the real /usr/sbin/lsof — deliberately exceeding the code's own 4000ms timeout",
+  "arms": {
+    "baseline": {"port": 19795, "source": "server.pre-activation-backup.js (eaff7476)", "listening_confirmed": true},
+    "patched": {"port": 19794, "source": "server.js (57baae6e)", "listening_confirmed": true}
+  },
+  "result": {
+    "baseline": "12/12 answered 200 but probe 8 took 3.512707s — the request sat blocked behind execSync; 1/12 degraded",
+    "patched": "12/12 answered 200, slowest 0.003188s, all others sub-millisecond; 0/12 degraded",
+    "discriminated": true
+  },
+  "hit_rate_explained": "the baseline only spawns when its 5s cache TTL has expired, and the 4500ms interval fires just inside that TTL, so it blocks ~4s out of every ~9s (~44%). A ~13s probe run therefore crosses only 1-2 block windows, which matches 1 hit. On the real box at load 70 BOTH lsof AND ps overrun their 4s timeouts, giving ~8s of blocking per refresh — consistent with the 74% blocked measured by sample(1) on the live wedged process.",
+  "invalid_runs_discarded": [
+    "A first A/B was VOID: the baseline arm was copied outside the repo so it died instantly on MODULE_NOT_FOUND (require './lib.js'), and its 14/14 'failures' were connection-refused from a dead process in 0.3ms, not the bug. Added an explicit is-it-actually-LISTENING guard before any probe is allowed to count.",
+    "A second A/B at load ~35 was NON-DISCRIMINATING: both arms served 16/16 sub-millisecond. Reported as such rather than as a pass — the defect is load-dependent, which is exactly why the fault had to be injected."
+  ],
+  "cleanup": "both shadow instances killed, tmp-baseline-server.js removed, PATH shim deleted, git tree clean apart from an unrelated ledger file",
+  "cost_usd": 0
+}

← a5e9b14e TK-11372: prove the root cause and correct the pipe-EOF hypo  ·  back to Ticket System  ·  TK-11372: note the defect is load-dependent in the runbook 037b8e20 →