[object Object]

← back to Designerwallcoverings

TK-11357: track zero-price investigation tooling (process-snapshot watch3, coverage probe, >25-variant truncation closer)

9f80218945b3c0807cb82a14f859d0a5f9e60053 · 2026-09-10 12:58:03 -0700 · Steve Abrams

Empirical instruments from the 6th-recurrence hunt: watch3 snapshots the full
process table at the instant the sentinel SKU's inventory changes; coverage-probe
+ trunc-closer prove the fleet-wide 0-hit claim including the 254 products whose
variant page truncates past 25.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VuDPyPwDnRAtJupE67UyPn

Files touched

Diff

commit 9f80218945b3c0807cb82a14f859d0a5f9e60053
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Sep 10 12:58:03 2026 -0700

    TK-11357: track zero-price investigation tooling (process-snapshot watch3, coverage probe, >25-variant truncation closer)
    
    Empirical instruments from the 6th-recurrence hunt: watch3 snapshots the full
    process table at the instant the sentinel SKU's inventory changes; coverage-probe
    + trunc-closer prove the fleet-wide 0-hit claim including the 254 products whose
    variant page truncates past 25.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01VuDPyPwDnRAtJupE67UyPn
---
 scripts/tk11357-zero-price-stopgap/_watch2.mjs     | 32 ++++++++++++
 scripts/tk11357-zero-price-stopgap/_watch3.mjs     | 38 +++++++++++++++
 .../tk11357-zero-price-stopgap/coverage-probe.mjs  | 57 ++++++++++++++++++++++
 .../tk11357-zero-price-stopgap/trunc-closer.mjs    | 37 ++++++++++++++
 4 files changed, 164 insertions(+)

diff --git a/scripts/tk11357-zero-price-stopgap/_watch2.mjs b/scripts/tk11357-zero-price-stopgap/_watch2.mjs
new file mode 100644
index 0000000..61c3bfe
--- /dev/null
+++ b/scripts/tk11357-zero-price-stopgap/_watch2.mjs
@@ -0,0 +1,32 @@
+/** TK-11357 watch2 — self-reporting revert watch.
+ * Fix vs _holdwatch.mjs: that one died silently 2 min before the 17:44:16Z revert,
+ * and a dead watcher is indistinguishable from an all-clear. This one (a) writes a
+ * heartbeat line EVERY cycle with a cycle counter, (b) traps exit/uncaught errors and
+ * logs WHY it died, (c) tracks the authoritative inventoryLevel.updatedAt on the watch
+ * SKU so a change is caught even if on_hand lands back on the same number.
+ * READ-ONLY. Predicted next event ~23:44:57Z (17:44:16Z + 6h0m41s).
+ */
+import fs from 'node:fs';
+import { gql } from './lib.mjs';
+const IID='gid://shopify/InventoryItem/46699390599219';
+const Q=`query($id:ID!){inventoryItem(id:$id){sku inventoryLevels(first:3){nodes{location{name} quantities(names:["on_hand"]){quantity} updatedAt}}}}`;
+const log=s=>{const l=`${new Date().toISOString()} ${s}\n`;fs.appendFileSync('/tmp/tk11357-watch2.log',l);console.log(l.trim());};
+process.on('exit',c=>log(`[WATCH2 EXIT code=${c}] — if this was not cycle 360, the instrument DIED`));
+process.on('uncaughtException',e=>{log(`[WATCH2 CRASH] ${e.message}`);process.exit(1);});
+log(`[WATCH2 START] watching ${IID} every 60s; predicted next revert ~23:44:57Z`);
+let prev=null;
+for(let c=0;c<360;c++){
+  try{
+    const d=await gql(Q,{id:IID});
+    const L=d?.inventoryItem?.inventoryLevels?.nodes||[];
+    const oh=L.reduce((a,x)=>a+((x.quantities||[])[0]?.quantity||0),0);
+    const u=L.map(x=>x.updatedAt).sort().pop()||'?';
+    const cur=`${oh}@${u}`;
+    if(prev===null) log(`cyc${c} BASELINE on_hand=${oh} updatedAt=${u}`);
+    else if(cur!==prev) log(`*** CHANGE cyc${c} on_hand=${oh} updatedAt=${u} (was ${prev}) ***`);
+    else if(c%10===0) log(`cyc${c} heartbeat on_hand=${oh} updatedAt=${u}`);
+    prev=cur;
+  }catch(e){ log(`cyc${c} READ-ERROR ${e.message}`); }
+  await new Promise(r=>setTimeout(r,60000));
+}
+log('[WATCH2 COMPLETE 360 cycles]');
diff --git a/scripts/tk11357-zero-price-stopgap/_watch3.mjs b/scripts/tk11357-zero-price-stopgap/_watch3.mjs
new file mode 100644
index 0000000..6c2b225
--- /dev/null
+++ b/scripts/tk11357-zero-price-stopgap/_watch3.mjs
@@ -0,0 +1,38 @@
+/** TK-11357 watch3 — CATCH THE CALLER IN THE ACT.
+ * Static analysis identified the app (ImportNewSkufromURL-12-10-25) and its credential
+ * (SHOPIFY_ADMIN_ACCESS_TOKEN …75b9 in Designer-Wallcoverings/.env) but NOT which process
+ * calls it. The write is a ~6s burst on a ~6h cadence, so the only way to name the caller
+ * is to snapshot the process table AT the transition. Polls every 30s; on a change it dumps
+ * every node/python process, recent launchd starts and online pm2 apps at that instant.
+ * READ-ONLY: never writes to Shopify. Self-reports its own death.
+ */
+import fs from 'node:fs'; import { execSync } from 'node:child_process';
+import { gql } from './lib.mjs';
+const IID='gid://shopify/InventoryItem/46699390599219';
+const Q=`query($id:ID!){inventoryItem(id:$id){inventoryLevels(first:3){nodes{quantities(names:["on_hand"]){quantity} updatedAt}}}}`;
+const LOG='/tmp/tk11357-watch3.log';
+const log=s=>{fs.appendFileSync(LOG,`${new Date().toISOString()} ${s}\n`);};
+const snap=()=>{ try{
+  const ps=execSync("ps -Ao pid,etime,command | grep -Ei 'node|python|tsx' | grep -v grep | head -60").toString();
+  const pm=execSync("pm2 jlist 2>/dev/null | python3 -c \"import sys,json;d=json.load(sys.stdin);print(chr(10).join(p['name'] for p in d if p.get('pm2_env',{}).get('status')=='online'))\" | head -60").toString();
+  fs.appendFileSync(LOG,`--- PROCESS SNAPSHOT ---\n${ps}\n--- PM2 ONLINE ---\n${pm}\n--- END SNAPSHOT ---\n`);
+}catch(e){ log(`snapshot failed: ${e.message}`); } };
+process.on('exit',c=>log(`[WATCH3 EXIT code=${c}]`));
+process.on('uncaughtException',e=>{log(`[WATCH3 CRASH] ${e.message}`);process.exit(1);});
+log('[WATCH3 START] 30s poll; snapshots the process table on any inventory change. Predicted event ~23:44:57Z');
+let prev=null;
+for(let c=0;c<900;c++){
+  try{
+    const d=await gql(Q,{id:IID});
+    const L=d?.inventoryItem?.inventoryLevels?.nodes||[];
+    const oh=L.reduce((a,x)=>a+((x.quantities||[])[0]?.quantity||0),0);
+    const u=L.map(x=>x.updatedAt).sort().pop()||'?';
+    const cur=`${oh}@${u}`;
+    if(prev===null) log(`cyc${c} BASELINE on_hand=${oh} updatedAt=${u}`);
+    else if(cur!==prev){ log(`*** CHANGE cyc${c} on_hand=${oh} updatedAt=${u} (was ${prev}) — SNAPSHOTTING ***`); snap(); }
+    else if(c%20===0) log(`cyc${c} heartbeat on_hand=${oh} updatedAt=${u}`);
+    prev=cur;
+  }catch(e){ log(`cyc${c} READ-ERROR ${e.message}`); }
+  await new Promise(r=>setTimeout(r,30000));
+}
+log('[WATCH3 COMPLETE]');
diff --git a/scripts/tk11357-zero-price-stopgap/coverage-probe.mjs b/scripts/tk11357-zero-price-stopgap/coverage-probe.mjs
new file mode 100644
index 0000000..92f4360
--- /dev/null
+++ b/scripts/tk11357-zero-price-stopgap/coverage-probe.mjs
@@ -0,0 +1,57 @@
+#!/usr/bin/env node
+/**
+ * TK-11357 coverage-probe.mjs — READ-ONLY, $0, zero writes.
+ *
+ * The zero-price-orderable-canary searches only:
+ *   status:active AND (quote-tag family OR vendor:'Fentucci Naturals')
+ * so its PASS is a PASS on that TAGGED SLICE, not the fleet. This probe scans
+ * ALL status:active products and reports $0 non-sample orderable variants that
+ * fall OUTSIDE the canary's inScope() — i.e. the population the canary is
+ * structurally blind to. It reuses the canary's own badVariant/inScope so the
+ * predicate is provably identical.
+ */
+import fs from 'node:fs';
+import path from 'node:path';
+import { pathToFileURL } from 'node:url';
+import { gql } from './lib.mjs';
+const CANARY = pathToFileURL(path.join(process.env.HOME, '.claude/skills/zero-price-orderable-canary/check.mjs')).href;
+const { inScope, badVariant } = await import(CANARY);
+
+const Q = `query($c:String){products(first:100,after:$c,query:"status:active"){
+  pageInfo{hasNextPage endCursor}
+  nodes{ id title vendor tags
+    variants(first:25){ nodes{ title price availableForSale inventoryPolicy inventoryQuantity } }
+    variantsCount{count} } }}`;
+
+let cursor=null, scanned=0, outOfScope=[], inScopeHits=0, truncated=0, pages=0;
+while(true){
+  const d = await gql(Q,{c:cursor});
+  if(d?.__err){ console.error('GQL ERR', JSON.stringify(d.__err).slice(0,300)); process.exit(1); }
+  const pg = d.products;
+  for(const p of pg.nodes){
+    scanned++;
+    if((p.variantsCount?.count||0) > 25) truncated++;
+    const bad = badVariant(p);
+    if(!bad) continue;
+    if(inScope(p)) { inScopeHits++; continue; }
+    outOfScope.push({ id:p.id.split('/').pop(), title:p.title, vendor:p.vendor,
+      variant:bad.title, price:bad.price, policy:bad.inventoryPolicy, qty:bad.inventoryQuantity,
+      tags:(p.tags||[]).slice(0,6) });
+  }
+  pages++;
+  if(pages%40===0) console.log(`  ...${scanned} active scanned, ${outOfScope.length} out-of-scope hits so far`);
+  if(!pg.pageInfo.hasNextPage) break;
+  cursor = pg.pageInfo.endCursor;
+}
+const byVendor={}; for(const r of outOfScope) byVendor[r.vendor]=(byVendor[r.vendor]||0)+1;
+const out={ ts:new Date().toISOString(), scanned_active:scanned,
+  variant_page_truncated_products:truncated,
+  in_scope_hits:inScopeHits, out_of_scope_hits:outOfScope.length,
+  out_of_scope_by_vendor:byVendor, sample:outOfScope.slice(0,25) };
+fs.writeFileSync('coverage-probe.json', JSON.stringify(out,null,2));
+console.log('\n=== COVERAGE PROBE ===');
+console.log(`active scanned                : ${scanned}`);
+console.log(`canary IN-scope $0-orderable  : ${inScopeHits}   (canary should equal this)`);
+console.log(`OUT-of-scope $0-orderable     : ${outOfScope.length}   <-- canary is BLIND to these`);
+console.log(`products w/ >25 variants (trunc): ${truncated}`);
+console.log('by vendor:', JSON.stringify(byVendor));
diff --git a/scripts/tk11357-zero-price-stopgap/trunc-closer.mjs b/scripts/tk11357-zero-price-stopgap/trunc-closer.mjs
new file mode 100644
index 0000000..d0b0aab
--- /dev/null
+++ b/scripts/tk11357-zero-price-stopgap/trunc-closer.mjs
@@ -0,0 +1,37 @@
+#!/usr/bin/env node
+/** TK-11357 trunc-closer.mjs — READ-ONLY. Closes coverage-probe.mjs's honest gap:
+ * it read only variants(first:25), so 254 active products with >25 variants had
+ * variants #26+ unexamined. Pass 1 = cheap id+variantsCount sweep to find them;
+ * Pass 2 = FULL variant paging on just those, applying the canary's own badVariant. */
+import fs from 'node:fs';
+import path from 'node:path';
+import { pathToFileURL } from 'node:url';
+import { gql } from './lib.mjs';
+const { inScope, badVariant } = await import(pathToFileURL(path.join(process.env.HOME,'.claude/skills/zero-price-orderable-canary/check.mjs')).href);
+
+const Q1=`query($c:String){products(first:250,after:$c,query:"status:active"){pageInfo{hasNextPage endCursor} nodes{id title vendor tags variantsCount{count}}}}`;
+const Q2=`query($id:ID!,$c:String){product(id:$id){id title vendor tags variants(first:100,after:$c){pageInfo{hasNextPage endCursor} nodes{title price availableForSale inventoryPolicy inventoryQuantity}}}}`;
+
+let c=null,n=0,big=[];
+while(true){
+  const d=await gql(Q1,{c}); if(d?.__err){console.error('ERR',JSON.stringify(d.__err).slice(0,200));process.exit(1);}
+  for(const p of d.products.nodes){ n++; if((p.variantsCount?.count||0)>25) big.push(p); }
+  if(!d.products.pageInfo.hasNextPage) break; c=d.products.pageInfo.endCursor;
+}
+console.log(`pass1: ${n} active scanned, ${big.length} products with >25 variants`);
+const hits=[];
+for(const p of big){
+  let vc=null, all=[];
+  while(true){
+    const d=await gql(Q2,{id:p.id,c:vc}); const v=d?.product?.variants; if(!v) break;
+    all.push(...v.nodes); if(!v.pageInfo.hasNextPage) break; vc=v.pageInfo.endCursor;
+  }
+  const bad=badVariant({variants:{nodes:all}});
+  if(bad) hits.push({id:p.id.split('/').pop(),title:p.title,vendor:p.vendor,inScope:inScope(p),
+    variant:bad.title,price:bad.price,policy:bad.inventoryPolicy,qty:bad.inventoryQuantity,variants_read:all.length});
+}
+const out={ts:new Date().toISOString(),active_scanned:n,products_over_25_variants:big.length,
+  fully_paged:big.length,hits_found:hits.length,hits};
+fs.writeFileSync('trunc-closer.json',JSON.stringify(out,null,2));
+console.log(`\n=== TRUNCATION CLOSER ===\nproducts >25 variants fully paged: ${big.length}\n$0-orderable found among them  : ${hits.length}`);
+if(hits.length) console.log(JSON.stringify(hits.slice(0,10),null,2));

← 509210d TK-11404: add GATED position-1 sample-reorder fix + rollback  ·  back to Designerwallcoverings  ·  auto-data-snapshot: 2026-09-10T13:02:09 (1 data files) — scr 6cea993 →