[object Object]

← back to Tk10640 Redirect Prune

TK-10653: wire write-time freshness guard into apply-remediation (skip stale/drifted/already-done rows; declare skipped_stale)

b0b115bf2a2153df320f3cc54a1652795745db9c · 2026-08-19 23:52:00 -0700 · Steve Abrams

Files touched

Diff

commit b0b115bf2a2153df320f3cc54a1652795745db9c
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Aug 19 23:52:00 2026 -0700

    TK-10653: wire write-time freshness guard into apply-remediation (skip stale/drifted/already-done rows; declare skipped_stale)
---
 apply-remediation.mjs | 22 ++++++++++++++++++++--
 1 file changed, 20 insertions(+), 2 deletions(-)

diff --git a/apply-remediation.mjs b/apply-remediation.mjs
index c26dead..fa52fd6 100644
--- a/apply-remediation.mjs
+++ b/apply-remediation.mjs
@@ -27,9 +27,17 @@ const DONE=`${HOME}/Projects/tk10640-redirect-prune/data/full-scan/done-remediat
 const done=new Set(); if(existsSync(DONE)) for(const l of readFileSync(DONE,'utf8').split('\n')) if(l.trim()) done.add(JSON.parse(l).id||JSON.parse(l).path);
 const UPD=`mutation($id:ID!,$r:UrlRedirectInput!){ urlRedirectUpdate(id:$id, urlRedirect:$r){ userErrors{ message } } }`;
 const DEL=`mutation($id:ID!){ urlRedirectDelete(id:$id){ deletedUrlRedirectId userErrors{ message } } }`;
+// WRITE-TIME FRESHNESS GUARD (dump is captured hours/days earlier; the live redirect may have
+// been repointed since). Read the redirect's CURRENT live target by id + whether a handle is a
+// live ACTIVE+published product. A row is STALE and SKIPPED if reality no longer matches the map.
+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 liveTarget(id){ const d=await gql(REDIR,{id}); return d.urlRedirect? d.urlRedirect.target : null; }
+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(`${HOME}/Projects/tk10640-redirect-prune/data/full-scan/remediation-map.jsonl`,'utf8').trim().split('\n').filter(Boolean).map(JSON.parse)
   .filter(r=> ONLY? r.action===ONLY : true);
-let scanned=0,repointed=0,pruned=0,skipped_live=0,err=0,writes=0;
+let scanned=0,repointed=0,pruned=0,skipped_live=0,skipped_stale=0,err=0,writes=0;
 for(const r of rows){
   const key=r.id||r.path;
   if(done.has(key)) continue;
@@ -40,6 +48,16 @@ for(const r of rows){
     const p=d.productByHandle;
     if(p && p.status==='ACTIVE' && p.onlineStoreUrl){ skipped_live++; appendFileSync(DONE,JSON.stringify({id:r.id,path:r.path,skipped:'LIVE_ACTIVE_PUBLISHED'})+'\n'); continue; }
   }
+  // Freshness guard vs LIVE state (skip stale rows even in DRY-RUN so the dry-run count is honest)
+  const curTarget = await liveTarget(r.id);
+  if(curTarget===null){ skipped_stale++; appendFileSync(DONE,JSON.stringify({id:r.id,path:r.path,skipped:'REDIRECT_GONE'})+'\n'); continue; }
+  if(r.action==='REPOINT'){
+    if(curTarget===r.new_target){ skipped_stale++; appendFileSync(DONE,JSON.stringify({id:r.id,path:r.path,skipped:'ALREADY_REPOINTED'})+'\n'); continue; }
+    if(curTarget!==r.old_target){ skipped_stale++; appendFileSync(DONE,JSON.stringify({id:r.id,path:r.path,skipped:'TARGET_DRIFTED',live:curTarget})+'\n'); continue; }
+    if(!(await isLiveProduct(r.new_target))){ skipped_stale++; appendFileSync(DONE,JSON.stringify({id:r.id,path:r.path,skipped:'REPLACEMENT_NOT_LIVE'})+'\n'); continue; }
+  } else { // PRUNE
+    if(await isLiveProduct(curTarget)){ skipped_live++; appendFileSync(DONE,JSON.stringify({id:r.id,path:r.path,skipped:'TARGET_NOW_LIVE'})+'\n'); continue; }
+  }
   if(APPLY){
     if(r.action==='REPOINT'){
       const d=await gql(UPD,{id:r.id, r:{path:r.path, target:r.new_target}});
@@ -54,4 +72,4 @@ 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; }
 }
-console.log(JSON.stringify({mode:APPLY?'APPLY':'DRY-RUN', only:ONLY||'ALL', total_rows:rows.length, scanned, repointed, pruned, skipped_live, errors:err, writes},null,2));
+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));

← 65994e0 TK-10653: rate-safe canary (curl-L) + gitignore data dir; ca  ·  back to Tk10640 Redirect Prune  ·  TK-10653: write-time freshness guard on apply + read-only fr 3b7f6f2 →