[object Object]

← back to Tk 10965 Zero Price Analysis

chore: syntax-validate + VERSION 1.0.0 (session close)

7f8dbb29b629779abde78cdfc605d28500595e07 · 2026-08-30 15:02:03 -0700 · steve@designerwallcoverings.com

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 7f8dbb29b629779abde78cdfc605d28500595e07
Author: steve@designerwallcoverings.com <steve@designerwallcoverings.com>
Date:   Sun Aug 30 15:02:03 2026 -0700

    chore: syntax-validate + VERSION 1.0.0 (session close)
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 VERSION                                            |  1 +
 tk10964-canary-guard/data/latest.json              |  2 +-
 .../rollback/TK-10974-preinstall/check.mjs         | 41 ++++++++++++++++++++++
 3 files changed, 43 insertions(+), 1 deletion(-)

diff --git a/VERSION b/VERSION
new file mode 100644
index 0000000..3eefcb9
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+1.0.0
diff --git a/tk10964-canary-guard/data/latest.json b/tk10964-canary-guard/data/latest.json
index 75c521b..09436da 100644
--- a/tk10964-canary-guard/data/latest.json
+++ b/tk10964-canary-guard/data/latest.json
@@ -2,7 +2,7 @@
   "skill": "zero-price-orderable-canary",
   "verdict": "WARN",
   "status": "WARN",
-  "ts": "2026-08-30T21:27:37.393Z",
+  "ts": "2026-08-30T22:01:44.279Z",
   "searched_active_unique": 4,
   "zero_price_orderable": 2,
   "by_vendor": {
diff --git a/tk10964-canary-guard/rollback/TK-10974-preinstall/check.mjs b/tk10964-canary-guard/rollback/TK-10974-preinstall/check.mjs
new file mode 100644
index 0000000..c6f885f
--- /dev/null
+++ b/tk10964-canary-guard/rollback/TK-10974-preinstall/check.mjs
@@ -0,0 +1,41 @@
+#!/usr/bin/env node
+// zero-price-orderable-canary — sibling of discontinued-orderable-canary (same root cause:
+// UI hides the buy button but the /cart/add endpoint still accepts the variant).
+// READ-ONLY: flags ACTIVE products with a NON-Sample variant priced $0 that is ORDERABLE
+// (availableForSale OR inventoryPolicy=CONTINUE) — a customer can check out at $0 (revenue loss)
+// or a quote-only item can be bought without a quote. Emits data/latest.json PASS/WARN/FAIL for
+// fleet-health-rollup. $0 (live Shopify reads unmetered). Ref incident: pending-approval/_approved/
+// 2026-07-29-quote-only-zero-price-checkout-exposure.md
+import fs from 'node:fs';
+import path from 'node:path';
+const HERE=path.dirname(new URL(import.meta.url).pathname);
+const OUT=path.join(HERE,'data','latest.json');
+const env=fs.readFileSync('/Users/macstudio3/Projects/secrets-manager/.env','utf8');
+const val=k=>(env.match(new RegExp('^'+k+'=(.*)$','m'))||[])[1]?.trim();
+const DOM=val('SHOPIFY_STORE_DOMAIN'), TOK=val('SHOPIFY_ADMIN_TOKEN');
+const API=`https://${DOM}/admin/api/2024-10/graphql.json`;
+async function gql(q,v){for(let a=0;a<6;a++){const r=await fetch(API,{method:'POST',headers:{'X-Shopify-Access-Token':TOK,'Content-Type':'application/json'},body:JSON.stringify({query:q,variables:v})});const j=await r.json();if(j.errors){if(JSON.stringify(j.errors).includes('THROTTLED')){await new Promise(s=>setTimeout(s,1800*(a+1)));continue;}throw new Error(JSON.stringify(j.errors));}return j.data;}throw new Error('retries');}
+// Search quote-only tagged AND (separately) sample-priced tags; also catch any active with a $0 real variant.
+const SEARCH=`status:active AND (tag:'quote-only' OR tag:'Quote Only' OR tag:'quote_only' OR tag:'Quote-Only')`;
+// availableForSale===true is Shopify's authoritative "this variant is purchasable" signal.
+// (Do NOT OR-in inventoryPolicy=CONTINUE — that overcounts non-buyable rows.)
+function isBad(p){
+  return p.variants.nodes
+    .filter(v=>!/sample/i.test(v.title||''))
+    .some(v=> Number(v.price)===0 && v.availableForSale===true);
+}
+async function run(){
+  let after=null,all=[],pages=0;
+  do{ const d=await gql(`query($q:String!,$after:String){products(first:100,query:$q,after:$after){pageInfo{hasNextPage endCursor} nodes{id title variants(first:20){nodes{title price availableForSale inventoryPolicy}}}}}`,{q:SEARCH,after});
+    all.push(...d.products.nodes); after=d.products.pageInfo.hasNextPage?d.products.pageInfo.endCursor:null; pages++;
+  }while(after&&pages<40);
+  const bad=all.filter(isBad);
+  const verdict = bad.length===0 ? 'PASS' : (bad.length<=5 ? 'WARN' : 'FAIL');
+  const out={ skill:'zero-price-orderable-canary', verdict, status:verdict, ts:new Date().toISOString(),
+    quote_only_active: all.length, zero_price_orderable: bad.length,
+    detail: verdict==='PASS'?'no quote-only/active product has a $0 orderable non-sample variant':`${bad.length} products have a $0 ORDERABLE non-sample variant (checkout at $0 / quote bypass)`,
+    sample_ids: bad.slice(0,10).map(p=>({id:p.id.split('/').pop(),title:p.title})) };
+  fs.mkdirSync(path.dirname(OUT),{recursive:true}); fs.writeFileSync(OUT,JSON.stringify(out,null,2));
+  console.log(`${verdict}: ${bad.length} zero-price-orderable of ${all.length} quote-only active`);
+}
+run().catch(e=>{const out={skill:'zero-price-orderable-canary',verdict:'WARN',status:'WARN',ts:new Date().toISOString(),detail:'canary error: '+e.message};fs.mkdirSync(path.dirname(OUT),{recursive:true});fs.writeFileSync(OUT,JSON.stringify(out,null,2));console.error('ERR',e.message);process.exit(0);});

← 7e1dd31 auto-data-snapshot: 2026-08-30T14:42:48 (4 data files) — tk1  ·  back to Tk 10965 Zero Price Analysis  ·  auto-data-snapshot: 2026-08-30T15:20:13 (1 data files) — tk1 ae6d884 →