← 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
A scripts/tk11357-zero-price-stopgap/_watch2.mjsA scripts/tk11357-zero-price-stopgap/_watch3.mjsA scripts/tk11357-zero-price-stopgap/coverage-probe.mjsA scripts/tk11357-zero-price-stopgap/trunc-closer.mjs
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 →