[object Object]

← back to Tk10640 Redirect Prune

TK-10653: write-time freshness guard on apply + read-only freshness sampler (REPOINT 65.7% actionable, PRUNE 100%)

3b7f6f2c6b72c4e84541335d6553fe93b2965769 · 2026-08-20 00:00:01 -0700 · Steve Abrams

Files touched

Diff

commit 3b7f6f2c6b72c4e84541335d6553fe93b2965769
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Aug 20 00:00:01 2026 -0700

    TK-10653: write-time freshness guard on apply + read-only freshness sampler (REPOINT 65.7% actionable, PRUNE 100%)
---
 apply-remediation.mjs |  2 ++
 sample-freshness.mjs  | 38 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 40 insertions(+)

diff --git a/apply-remediation.mjs b/apply-remediation.mjs
index fa52fd6..707a3fa 100644
--- a/apply-remediation.mjs
+++ b/apply-remediation.mjs
@@ -15,6 +15,7 @@ const env={}; for(const l of txt.split('\n')){const m=l.match(/^([A-Z0-9_]+)=(.*
 const STORE=env.SHOPIFY_STORE_DOMAIN||'designer-laboratory-sandbox.myshopify.com', TOKEN=env.SHOPIFY_FULL_ACCESS_TOKEN, API='2024-10';
 const APPLY=process.argv.includes('--apply');
 const LIMIT=Number((process.argv.find(a=>a.startsWith('--limit='))||'').split('=')[1]||Infinity);
+const SCAN_LIMIT=Number((process.argv.find(a=>a.startsWith('--scan-limit='))||'').split('=')[1]||Infinity);
 const ONLY=(process.argv.find(a=>a.startsWith('--only='))||'').split('=')[1]||null;
 const sleep=ms=>new Promise(r=>setTimeout(r,ms));
 async function gql(q,v={}){for(let a=0;a<12;a++){try{
@@ -71,5 +72,6 @@ for(const r of rows){
   }
   if(scanned%1000===0) process.stderr.write(`  scanned ${scanned} repoint ${repointed} prune ${pruned} skip_live ${skipped_live}\n`);
   if(writes>=LIMIT){ process.stderr.write(`hit --limit=${LIMIT} (writes=${writes})\n`); break; }
+  if(scanned>=SCAN_LIMIT){ process.stderr.write(`hit --scan-limit=${SCAN_LIMIT} (scanned=${scanned})\n`); break; }
 }
 console.log(JSON.stringify({mode:APPLY?'APPLY':'DRY-RUN', only:ONLY||'ALL', total_rows:rows.length, scanned, repointed, pruned, skipped_live, skipped_stale, errors:err, writes},null,2));
diff --git a/sample-freshness.mjs b/sample-freshness.mjs
new file mode 100644
index 0000000..f3e0642
--- /dev/null
+++ b/sample-freshness.mjs
@@ -0,0 +1,38 @@
+// TK-10653 — READ-ONLY freshness sampler. For a random sample of map rows, checks LIVE state
+// (current redirect target by id + whether a handle is a live ACTIVE+published product) and
+// classifies each into the exact bucket the apply-guard would put it in. NO writes.
+// Usage: node sample-freshness.mjs <sample-file.jsonl>
+import { readFileSync } from 'node:fs';
+const HOME=process.env.HOME;
+const txt=readFileSync(`${HOME}/Projects/secrets-manager/.env`,'utf8');
+const env={}; for(const l of txt.split('\n')){const m=l.match(/^([A-Z0-9_]+)=(.*)$/); if(m) env[m[1]]=m[2].replace(/^["']|["']$/g,'');}
+const STORE=env.SHOPIFY_STORE_DOMAIN||'designer-laboratory-sandbox.myshopify.com', TOKEN=env.SHOPIFY_FULL_ACCESS_TOKEN, API='2024-10';
+const sleep=ms=>new Promise(r=>setTimeout(r,ms));
+async function gql(q,v={}){for(let a=0;a<12;a++){try{
+  const res=await fetch(`https://${STORE}/admin/api/${API}/graphql.json`,{method:'POST',headers:{'Content-Type':'application/json','X-Shopify-Access-Token':TOKEN},body:JSON.stringify({query:q,variables:v})});
+  const tx=await res.text();let j;try{j=JSON.parse(tx);}catch{await sleep(1200*(a+1));continue;}
+  if(j.errors){if(/THROTTLED/i.test(JSON.stringify(j.errors))){await sleep(1200*(a+1));continue;}throw new Error(JSON.stringify(j.errors));}
+  return j.data;
+}catch(e){if(a<11){await sleep(1200*(a+1));continue;}throw e;}}}
+const REDIR=`query($id:ID!){ urlRedirect(id:$id){ id path target } }`;
+const PBH=`query($h:String!){ productByHandle(handle:$h){ status onlineStoreUrl } }`;
+const handleOf=t=>(t||'').replace(/^\/products\//,'').split('?')[0].split('#')[0];
+async function isLiveProduct(target){ if(!/^\/products\//.test(target||'')) return false; const d=await gql(PBH,{h:handleOf(target)}); const p=d.productByHandle; return !!(p && p.status==='ACTIVE' && p.onlineStoreUrl); }
+const rows=readFileSync(process.argv[2],'utf8').trim().split('\n').filter(Boolean).map(JSON.parse);
+const b={}; const bump=k=>b[k]=(b[k]||0)+1;
+let i=0;
+for(const r of rows){
+  const d=await gql(REDIR,{id:r.id}); const cur=d.urlRedirect? d.urlRedirect.target : null;
+  if(cur===null){ bump('REDIRECT_GONE'); }
+  else if(r.action==='REPOINT'){
+    if(cur===r.new_target) bump('ALREADY_REPOINTED');
+    else if(cur!==r.old_target) bump('TARGET_DRIFTED');
+    else if(!(await isLiveProduct(r.new_target))) bump('REPLACEMENT_NOT_LIVE');
+    else bump('WOULD_REPOINT');
+  } else { // PRUNE
+    if(await isLiveProduct(cur)) bump('TARGET_NOW_LIVE');
+    else bump('WOULD_PRUNE');
+  }
+  if(++i%50===0) process.stderr.write(`  ${i}/${rows.length} ${JSON.stringify(b)}\n`);
+}
+console.log(JSON.stringify({sample:rows.length, action:rows[0].action, buckets:b},null,2));

← b0b115b TK-10653: wire write-time freshness guard into apply-remedia  ·  back to Tk10640 Redirect Prune  ·  TK-10653: applied 495-row reversible canary batch (195 repoi a94744d →