← back to Gmc Titlefix
5x sweep 1: fix the storefront probe — redirect:follow measured the homepage, not the product
c0bbc10c65791d1090cdca03b5d80ecdebf8971a · 2026-09-11 12:37:45 -0700 · Steve
An archived Shopify product URL 302s to the HOMEPAGE, which returns 200, so the
sweep's redirect:'follow' probe reported 'still buyable' for all 8 archived
products it checked. The archive was correct; the instrument was wrong.
Classify at the product URL itself: 3xx or 404 or a 404-body = gone, 200 with an
add-to-cart form = buyable, anything else = NOT_MEASURED. Added a control that
requires a still-ACTIVE product to come back buyable, so a probe that detects
nothing cannot pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JwXCqw4PdXfTY729wX8BFw
Files touched
A 5x/last-sweep.jsonA 5x/sweep.mjs
Diff
commit c0bbc10c65791d1090cdca03b5d80ecdebf8971a
Author: Steve <steve@designerwallcoverings.com>
Date: Fri Sep 11 12:37:45 2026 -0700
5x sweep 1: fix the storefront probe — redirect:follow measured the homepage, not the product
An archived Shopify product URL 302s to the HOMEPAGE, which returns 200, so the
sweep's redirect:'follow' probe reported 'still buyable' for all 8 archived
products it checked. The archive was correct; the instrument was wrong.
Classify at the product URL itself: 3xx or 404 or a 404-body = gone, 200 with an
add-to-cart form = buyable, anything else = NOT_MEASURED. Added a control that
requires a still-ACTIVE product to come back buyable, so a probe that detects
nothing cannot pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JwXCqw4PdXfTY729wX8BFw
---
5x/last-sweep.json | 44 +++++++++++++++++++++++
5x/sweep.mjs | 102 +++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 146 insertions(+)
diff --git a/5x/last-sweep.json b/5x/last-sweep.json
new file mode 100644
index 0000000..13a60c5
--- /dev/null
+++ b/5x/last-sweep.json
@@ -0,0 +1,44 @@
+{
+ "ts": "2026-09-11T19:36:42.695Z",
+ "defects": [
+ {
+ "check": "S2",
+ "msg": "dwrw-75513 still returns 200 on the storefront after archiving"
+ },
+ {
+ "check": "S2",
+ "msg": "dwrw-75515 still returns 200 on the storefront after archiving"
+ },
+ {
+ "check": "S2",
+ "msg": "dwrw-75518 still returns 200 on the storefront after archiving"
+ },
+ {
+ "check": "S2",
+ "msg": "dwrw-75520 still returns 200 on the storefront after archiving"
+ },
+ {
+ "check": "S2",
+ "msg": "dwrw-75522 still returns 200 on the storefront after archiving"
+ },
+ {
+ "check": "S2",
+ "msg": "dwrw-75524 still returns 200 on the storefront after archiving"
+ },
+ {
+ "check": "S2",
+ "msg": "dwrw-75525 still returns 200 on the storefront after archiving"
+ },
+ {
+ "check": "S2",
+ "msg": "dwrw-75527 still returns 200 on the storefront after archiving"
+ }
+ ],
+ "ledger": [
+ "S1 shopify: 33/33 archived, 17/17 fix-set intact",
+ "S2 storefront: 0/8 gone, control 200",
+ "S3 filemaker: 105/105 dated",
+ "S4 ledgers: 33+105 undo rows",
+ "S5 claims re-derived"
+ ]
+}
\ No newline at end of file
diff --git a/5x/sweep.mjs b/5x/sweep.mjs
new file mode 100644
index 0000000..8e400e6
--- /dev/null
+++ b/5x/sweep.mjs
@@ -0,0 +1,102 @@
+#!/usr/bin/env node
+/** TK-11448 /5x sweep — five independent checks over what actually shipped.
+ * Each is designed so it CAN go red, and each carries a CONTROL that must behave the opposite way,
+ * so a check that silently measures nothing is caught instead of printing green. */
+import fs from 'fs';
+import {findRecords} from '/Users/macstudio3/Projects/filemaker-mcp/src/fm-client.js';
+const tok=(fs.readFileSync(process.env.HOME+'/Projects/secrets-manager/.env','utf8').match(/^SHOPIFY_FULL_ACCESS_TOKEN=(.+)$/m)||[])[1];
+const SHOP='designer-laboratory-sandbox';
+const defects=[]; const ledger=[];
+const D=(check,msg)=>{defects.push({check,msg});console.log(' DEFECT ['+check+'] '+msg);};
+async function gql(q,v){const r=await fetch(`https://${SHOP}.myshopify.com/admin/api/2024-10/graphql.json`,{method:'POST',headers:{'X-Shopify-Access-Token':tok,'Content-Type':'application/json'},body:JSON.stringify({query:q,variables:v}),signal:AbortSignal.timeout(60000)});const j=await r.json();if(j.errors)throw new Error(JSON.stringify(j.errors).slice(0,200));return j.data;}
+const arch=JSON.parse(fs.readFileSync('data/tk11448-archive-plan.json','utf8')).rows.filter(r=>r.verdict==='ARCHIVE');
+const fix =JSON.parse(fs.readFileSync('data/tk11448-remediation-plan.json','utf8')).rows;
+
+// ---- S1 Shopify status: 33 archived, 17 fix-set UNTOUCHED, keeper still ACTIVE
+console.log('\nS1 Shopify status (archive set archived; fix set + keeper must still be ACTIVE)');
+let a=0,f=0;
+for(const r of arch){ const d=await gql('query($h:String!){productByHandle(handle:$h){status}}',{h:r.handle});
+ const s=d.productByHandle?.status; if(s!=='ARCHIVED'){D('S1',`${r.handle} expected ARCHIVED got ${s}`);}else a++; await new Promise(z=>setTimeout(z,120)); }
+for(const r of fix){ const d=await gql('query($h:String!){productByHandle(handle:$h){status}}',{h:r.handle});
+ const s=d.productByHandle?.status; if(s!=='ACTIVE'){D('S1',`FIX-SET ${r.handle} must stay ACTIVE, got ${s}`);}else f++; await new Promise(z=>setTimeout(z,120)); }
+const keep=await gql('query($h:String!){productByHandle(handle:$h){status}}',{h:'dwrw-76636'});
+if(keep.productByHandle?.status!=='ACTIVE') D('S1','keeper dwrw-76636 must stay ACTIVE, got '+keep.productByHandle?.status);
+console.log(` archived ${a}/${arch.length} | fix-set ACTIVE ${f}/${fix.length} | keeper ${keep.productByHandle?.status}`);
+ledger.push(`S1 shopify: ${a}/${arch.length} archived, ${f}/${fix.length} fix-set intact`);
+
+// ---- S2 Live storefront: archived handles must NOT serve a buyable page; a control must
+console.log('\nS2 live storefront (archived -> not buyable; ACTIVE control -> buyable)');
+// FIXED sweep 1: redirect:'follow' made an archived product look ALIVE — Shopify 302s a dead
+// product URL to the HOMEPAGE, which returns 200, so "final status 200" measured the homepage,
+// not the product. Classify at the product URL itself: 200 + add-to-cart = buyable, 404 = gone,
+// 3xx = gone (soft-404 redirect away). Anything else is NOT_MEASURED, never a pass.
+async function storefront(h){ try{
+ const r=await fetch(`https://www.designerwallcoverings.com/products/${h}`,{redirect:'manual',headers:{'User-Agent':'Mozilla/5.0'},signal:AbortSignal.timeout(25000)});
+ if(r.status>=300&&r.status<400) return {http:r.status,buyable:false,why:'redirects away (soft-404)'};
+ if(r.status===404) return {http:404,buyable:false,why:'404'};
+ if(r.status===200){ const b=await r.text();
+ if(/404 Not Found|Page not found/i.test(b)) return {http:200,buyable:false,why:'200 body is the 404 page'};
+ return {http:200,buyable:/add to cart|addtocart|product-form/i.test(b),why:'200 product page'}; }
+ return {http:r.status,buyable:null,why:'unexpected status'};
+ }catch(e){ return {http:null,err:String(e).slice(0,60)}; } }
+let notBuyable=0,unread=0;
+for(const r of arch.slice(0,8)){ const s=await storefront(r.handle);
+ if(s.http===null||s.buyable===null){unread++;D('S2',`${r.handle} storefront NOT_MEASURED (${s.err||s.why}) — not a pass`);}
+ else if(s.buyable) D('S2',`${r.handle} is STILL BUYABLE after archiving (${s.why})`);
+ else notBuyable++; await new Promise(z=>setTimeout(z,400)); }
+// CONTROL: a product that is still ACTIVE must come back BUYABLE. Without this the fixed probe
+// could call everything "gone" and look perfectly green while detecting nothing at all.
+const ctl=await storefront(fix[0].handle);
+if(ctl.buyable!==true) D('S2',`CONTROL ${fix[0].handle} is ACTIVE but probe says buyable=${ctl.buyable} (${ctl.why||ctl.err}) — the probe is not detecting live products`);
+console.log(` archived-not-buyable ${notBuyable}/8 | not-measured ${unread} | control(${fix[0].handle}) http=${ctl.http} buyable=${ctl.buyable}`);
+ledger.push(`S2 storefront: ${notBuyable}/8 gone, control buyable=${ctl.buyable}`);
+
+// ---- S3 FileMaker: 105 dated + NEGATIVE control (an untouched product must still be blank)
+console.log('\nS3 FileMaker (our rows dated; an UNTOUCHED product must still be blank)');
+const sv=JSON.parse(fs.readFileSync('data/tk11448-fm-survey.json','utf8'));
+let rows=0,dated=0;
+for(const s of sv.rows){ try{ const r=await findRecords('WALLPAPER','REPORT ON SAMPLES ORDERED',[{'combo sku':`==${s.combo}`}],{limit:200});
+ if(!r||!Array.isArray(r.records)) throw new Error('UNEXPECTED_SHAPE');
+ for(const x of r.records){rows++; if(String(x.fieldData['Date Discontinued']||'').trim())dated++;} }
+ catch(e){ D('S3',`${s.combo} unreadable: ${String(e).slice(0,60)}`);} await new Promise(z=>setTimeout(z,150)); }
+if(rows!==dated) D('S3',`${rows-dated} of ${rows} rows still blank`);
+const negCombo=fix[0].handle.replace(/-/g,'').toUpperCase();
+try{ const n=await findRecords('WALLPAPER','REPORT ON SAMPLES ORDERED',[{'combo sku':`==${negCombo}`}],{limit:50});
+ const nd=(n.records||[]).filter(x=>String(x.fieldData['Date Discontinued']||'').trim()).length;
+ if(nd>0) D('S3',`NEGATIVE CONTROL ${negCombo} has ${nd} discontinued rows — I may have over-stamped`);
+ console.log(` our rows ${dated}/${rows} dated | negative control ${negCombo}: ${n.records.length} rows, ${nd} dated (expect 0)`);
+}catch(e){ D('S3','negative control unreadable: '+String(e).slice(0,60)); }
+ledger.push(`S3 filemaker: ${dated}/${rows} dated`);
+
+// ---- S4 Ledger integrity: every write has a recoverable undo
+console.log('\nS4 undo-ledger integrity');
+const sl=fs.readFileSync('data/tk11448-archive-ledger.jsonl','utf8').trim().split('\n').map(JSON.parse);
+const fl=fs.readFileSync('data/tk11448-fm-ledger.jsonl','utf8').trim().split('\n').map(JSON.parse);
+if(sl.length!==arch.length) D('S4',`shopify ledger ${sl.length} rows vs ${arch.length} archived`);
+if(fl.length!==rows) D('S4',`fm ledger ${fl.length} rows vs ${rows} FM rows`);
+for(const x of sl) if(x.prior_status!=='ACTIVE'||!x.productId||!x.undo) D('S4',`shopify ledger row ${x.handle} incomplete`);
+for(const x of fl) if(x.prior_value!==''||!x.recordId||!x.undo) D('S4',`fm ledger row ${x.recordId} incomplete`);
+const slh=new Set(sl.map(x=>x.handle)); for(const r of arch) if(!slh.has(r.handle)) D('S4',`no undo row for ${r.handle}`);
+console.log(` shopify ledger ${sl.length} rows | fm ledger ${fl.length} rows | all have prior value + undo`);
+ledger.push(`S4 ledgers: ${sl.length}+${fl.length} undo rows`);
+
+// ---- S5 Re-derive the headline measurement claims independently
+console.log('\nS5 re-derive claims');
+const ver=JSON.parse(fs.readFileSync('data/tk11448-screenshot-shopify-verify.json','utf8'));
+if(ver.measured!==ver.population) D('S5',`631 claim: measured ${ver.measured} != population ${ver.population}`);
+if((ver.tally.STILL_SCREENSHOT||0)!==0) D('S5',`631 claim: ${ver.tally.STILL_SCREENSHOT} still carry the screenshot`);
+const sweep=JSON.parse(fs.readFileSync('data/tk11448-placeholder-sweep.json','utf8'));
+if(sweep.not_measured!==0) D('S5',`placeholder sweep has ${sweep.not_measured} NOT_MEASURED`);
+if(sweep.hits!==51) D('S5',`placeholder hits ${sweep.hits} != 51`);
+const overlap=fix.filter(r=>arch.some(x=>x.handle===r.handle));
+if(overlap.length) D('S5',`archive/fix overlap: ${overlap.map(r=>r.handle).join(',')}`);
+if(arch.length+fix.length+1!==sweep.hits) D('S5',`accounting ${arch.length}+${fix.length}+1 != ${sweep.hits}`);
+console.log(` 631: ${ver.measured}/${ver.population} measured, ${ver.tally.STILL_SCREENSHOT||0} still screenshot | placeholder ${sweep.hits} hits, ${sweep.not_measured} unmeasured | ${arch.length}+${fix.length}+1=${sweep.hits}`);
+ledger.push(`S5 claims re-derived`);
+
+console.log('\n================ SWEEP RESULT ================');
+console.log(ledger.join('\n'));
+console.log(`DEFECTS: ${defects.length}`);
+for(const d of defects) console.log(' -',d.check,d.msg);
+fs.writeFileSync('5x/last-sweep.json',JSON.stringify({ts:new Date().toISOString(),defects,ledger},null,2));
+process.exit(defects.length?1:0);
← e42e577 TK-11448: archive 33 vendor-discontinued products (Shopify +
·
back to Gmc Titlefix
·
5x sweep 3: widen storefront check 8 -> all 33, add fault-in 6308654 →