← back to Tk 10965 Zero Price Analysis
add hardened zero-price orderable canary guard
3b4e9b819689e77569e114e896741832eca75be3 · 2026-08-30 11:27:26 -0700 · Steve Abrams
Files touched
A tk10964-canary-guard/README.mdA tk10964-canary-guard/check.mjsA tk10964-canary-guard/fixtures/mixed.jsonA tk10964-canary-guard/test.mjsA tk10964-canary-guard/verification/e2e-proof.json
Diff
commit 3b4e9b819689e77569e114e896741832eca75be3
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Sun Aug 30 11:27:26 2026 -0700
add hardened zero-price orderable canary guard
---
tk10964-canary-guard/README.md | 10 +++
tk10964-canary-guard/check.mjs | 97 ++++++++++++++++++++++++
tk10964-canary-guard/fixtures/mixed.json | 8 ++
tk10964-canary-guard/test.mjs | 17 +++++
tk10964-canary-guard/verification/e2e-proof.json | 48 ++++++++++++
5 files changed, 180 insertions(+)
diff --git a/tk10964-canary-guard/README.md b/tk10964-canary-guard/README.md
new file mode 100644
index 0000000..3bb528f
--- /dev/null
+++ b/tk10964-canary-guard/README.md
@@ -0,0 +1,10 @@
+# TK-10964 canary guard
+
+Read-only replacement for the zero-price-orderable canary. It closes the
+Fentucci blind spot by scanning the complete quote/price-suppressed tag family
+plus a known-vendor fallback, de-duplicates overlapping results by product GID,
+and reports the `qty=2026 + DENY + tracked` mechanism without treating DENY as
+the cause.
+
+Use `--fixture fixtures/mixed.json` for a network-free end-to-end output test.
+Live mode performs GraphQL queries only and writes one local JSON status file.
diff --git a/tk10964-canary-guard/check.mjs b/tk10964-canary-guard/check.mjs
new file mode 100644
index 0000000..a1b9e39
--- /dev/null
+++ b/tk10964-canary-guard/check.mjs
@@ -0,0 +1,97 @@
+#!/usr/bin/env node
+// TK-10964 — read-only zero-price-orderable guard.
+// Queries cover the full quote-tag family and a vendor fallback for the known
+// untagged Fentucci cohort. Results are de-duplicated by Shopify product GID.
+import fs from 'node:fs';
+import path from 'node:path';
+
+const HERE = path.dirname(new URL(import.meta.url).pathname);
+const OUT = process.env.ZERO_PRICE_CANARY_OUT || path.join(HERE, 'data', 'latest.json');
+export const SEARCHES = [
+ `status:active AND (tag:'quote-only' OR tag:'Quote Only' OR tag:'quote_only' OR tag:'Quote-Only')`,
+ `status:active AND (tag:'quotes' OR tag:'contact-for-price' OR tag:'Needs-Price')`,
+ `status:active AND vendor:'Fentucci Naturals'`
+];
+
+const QUOTE_TAGS = new Set(['quote-only', 'quote only', 'quote_only', 'quotes', 'contact-for-price', 'needs-price']);
+export function inScope(product) {
+ return product.vendor === 'Fentucci Naturals' || (product.tags || []).some(tag => QUOTE_TAGS.has(String(tag).trim().toLowerCase()));
+}
+
+export function badVariant(product) {
+ return product.variants.nodes.find(variant =>
+ !/sample/i.test(variant.title || '') &&
+ Number(variant.price) === 0 &&
+ variant.availableForSale === true
+ );
+}
+
+export function summarize(products, ts = new Date().toISOString()) {
+ // Shopify's search grammar can over-return on nested OR expressions. Enforce
+ // the intended scope locally as a second boundary before classifying defects.
+ const unique = [...new Map(products.map(product => [product.id, product])).values()].filter(inScope);
+ const bad = unique.flatMap(product => {
+ const variant = badVariant(product);
+ return variant ? [{ product, variant }] : [];
+ });
+ const verdict = bad.length === 0 ? 'PASS' : bad.length <= 5 ? 'WARN' : 'FAIL';
+ return {
+ skill: 'zero-price-orderable-canary', verdict, status: verdict, ts,
+ searched_active_unique: unique.length,
+ zero_price_orderable: bad.length,
+ by_vendor: Object.fromEntries([...new Set(bad.map(row => row.product.vendor || 'UNKNOWN'))].sort().map(vendor => [vendor, bad.filter(row => (row.product.vendor || 'UNKNOWN') === vendor).length])),
+ mechanism: {
+ qty_2026: bad.filter(row => row.variant.inventoryQuantity === 2026).length,
+ deny: bad.filter(row => row.variant.inventoryPolicy === 'DENY').length,
+ tracked: bad.filter(row => row.variant.inventoryItem?.tracked === true).length
+ },
+ detail: verdict === 'PASS' ? 'no scoped active product has a $0 orderable non-sample variant' : `${bad.length} products have a $0 ORDERABLE non-sample variant`,
+ sample_ids: bad.slice(0, 10).map(({ product, variant }) => ({ id: product.id.split('/').pop(), title: product.title, vendor: product.vendor, qty: variant.inventoryQuantity, policy: variant.inventoryPolicy }))
+ };
+}
+
+function writeResult(result) {
+ fs.mkdirSync(path.dirname(OUT), { recursive: true });
+ fs.writeFileSync(OUT, JSON.stringify(result, null, 2) + '\n');
+ console.log(`${result.verdict}: ${result.zero_price_orderable} zero-price-orderable of ${result.searched_active_unique} uniquely searched active`);
+}
+
+async function liveProducts() {
+ const env = fs.readFileSync('/Users/macstudio3/Projects/secrets-manager/.env', 'utf8');
+ const val = key => (env.match(new RegExp(`^${key}=(.*)$`, 'm')) || [])[1]?.trim();
+ const api = `https://${val('SHOPIFY_STORE_DOMAIN')}/admin/api/2024-10/graphql.json`;
+ const token = val('SHOPIFY_ADMIN_TOKEN');
+ async function gql(query, variables) {
+ for (let attempt = 0; attempt < 6; attempt++) {
+ const response = await fetch(api, { method: 'POST', headers: { 'X-Shopify-Access-Token': token, 'Content-Type': 'application/json' }, body: JSON.stringify({ query, variables }) });
+ const json = await response.json();
+ if (!json.errors) return json.data;
+ if (!JSON.stringify(json.errors).includes('THROTTLED')) throw new Error(JSON.stringify(json.errors));
+ await new Promise(resolve => setTimeout(resolve, 1800 * (attempt + 1)));
+ }
+ throw new Error('GraphQL retries exhausted');
+ }
+ const products = [];
+ for (const q of SEARCHES) {
+ let after = null;
+ do {
+ const data = await gql(`query($q:String!,$after:String){products(first:100,query:$q,after:$after){pageInfo{hasNextPage endCursor} nodes{id title vendor tags variants(first:20){nodes{title price availableForSale inventoryPolicy inventoryQuantity inventoryItem{tracked}}}}}}`, { q, after });
+ products.push(...data.products.nodes);
+ after = data.products.pageInfo.hasNextPage ? data.products.pageInfo.endCursor : null;
+ } while (after);
+ }
+ return products;
+}
+
+async function main() {
+ const fixtureArg = process.argv.indexOf('--fixture');
+ const products = fixtureArg >= 0 ? JSON.parse(fs.readFileSync(process.argv[fixtureArg + 1], 'utf8')) : await liveProducts();
+ writeResult(summarize(products));
+}
+
+if (process.argv[1] && path.resolve(process.argv[1]) === path.resolve(new URL(import.meta.url).pathname)) {
+ main().catch(error => {
+ writeResult({ skill: 'zero-price-orderable-canary', verdict: 'WARN', status: 'WARN', ts: new Date().toISOString(), zero_price_orderable: null, searched_active_unique: 0, detail: `canary error: ${error.message}` });
+ process.exitCode = 1;
+ });
+}
diff --git a/tk10964-canary-guard/fixtures/mixed.json b/tk10964-canary-guard/fixtures/mixed.json
new file mode 100644
index 0000000..de394ed
--- /dev/null
+++ b/tk10964-canary-guard/fixtures/mixed.json
@@ -0,0 +1,8 @@
+[
+ {"id":"gid://shopify/Product/1","title":"PR bad","vendor":"Phillipe Romano","tags":["quote-only"],"variants":{"nodes":[{"title":"Per Yard","price":"0.00","availableForSale":true,"inventoryPolicy":"DENY","inventoryQuantity":2026,"inventoryItem":{"tracked":true}},{"title":"Sample","price":"4.25","availableForSale":false,"inventoryPolicy":"DENY","inventoryQuantity":0,"inventoryItem":{"tracked":true}}]}},
+ {"id":"gid://shopify/Product/2","title":"Fentucci blind spot","vendor":"Fentucci Naturals","tags":["Needs-Price"],"variants":{"nodes":[{"title":"Default Title","price":"0","availableForSale":true,"inventoryPolicy":"DENY","inventoryQuantity":2026,"inventoryItem":{"tracked":true}}]}},
+ {"id":"gid://shopify/Product/2","title":"Fentucci duplicate search hit","vendor":"Fentucci Naturals","tags":["quotes"],"variants":{"nodes":[{"title":"Default Title","price":"0","availableForSale":true,"inventoryPolicy":"DENY","inventoryQuantity":2026,"inventoryItem":{"tracked":true}}]}},
+ {"id":"gid://shopify/Product/3","title":"Unavailable zero control","vendor":"Control","tags":["contact-for-price"],"variants":{"nodes":[{"title":"Per Roll","price":"0","availableForSale":false,"inventoryPolicy":"DENY","inventoryQuantity":0,"inventoryItem":{"tracked":true}}]}},
+ {"id":"gid://shopify/Product/4","title":"Sample only control","vendor":"Control","tags":["quotes"],"variants":{"nodes":[{"title":"Sample","price":"0","availableForSale":true,"inventoryPolicy":"CONTINUE","inventoryQuantity":0,"inventoryItem":{"tracked":false}}]}},
+ {"id":"gid://shopify/Product/5","title":"Search parser over-return","vendor":"Unrelated","tags":["modern"],"variants":{"nodes":[{"title":"Per Roll","price":"0","availableForSale":true,"inventoryPolicy":"CONTINUE","inventoryQuantity":10,"inventoryItem":{"tracked":true}}]}}
+]
diff --git a/tk10964-canary-guard/test.mjs b/tk10964-canary-guard/test.mjs
new file mode 100644
index 0000000..0b6b353
--- /dev/null
+++ b/tk10964-canary-guard/test.mjs
@@ -0,0 +1,17 @@
+#!/usr/bin/env node
+import assert from 'node:assert/strict';
+import fs from 'node:fs';
+import { SEARCHES, inScope, summarize } from './check.mjs';
+
+const products = JSON.parse(fs.readFileSync(new URL('./fixtures/mixed.json', import.meta.url)));
+const result = summarize(products, '2026-08-30T00:00:00.000Z');
+assert.equal(SEARCHES.length, 3);
+assert.ok(SEARCHES.some(search => search.includes("tag:'quotes'") && search.includes("tag:'Needs-Price'")));
+assert.ok(SEARCHES.some(search => search.includes("vendor:'Fentucci Naturals'")));
+assert.equal(inScope(products.at(-1)), false, 'search-parser over-return must be rejected locally');
+assert.equal(result.searched_active_unique, 4, 'duplicate query hits must collapse by product GID');
+assert.equal(result.zero_price_orderable, 2);
+assert.deepEqual(result.by_vendor, { 'Fentucci Naturals': 1, 'Phillipe Romano': 1 });
+assert.deepEqual(result.mechanism, { qty_2026: 2, deny: 2, tracked: 2 });
+assert.equal(result.verdict, 'WARN');
+console.log('PASS: broad searches, Fentucci blind spot, dedupe, defect predicate, and controls');
diff --git a/tk10964-canary-guard/verification/e2e-proof.json b/tk10964-canary-guard/verification/e2e-proof.json
new file mode 100644
index 0000000..a093a67
--- /dev/null
+++ b/tk10964-canary-guard/verification/e2e-proof.json
@@ -0,0 +1,48 @@
+{
+ "ticket": "TK-10964",
+ "intent": "Close the standing canary's Fentucci/quote-tag blind spot without changing Shopify state",
+ "risk_tier": "R1",
+ "environment": "local Node.js guard plus read-only Shopify Admin GraphQL",
+ "timestamp": "2026-08-30T18:27:00Z",
+ "baseline": "Existing canary searches quote-only tags and detects 1,281 Phillipe Romano products but misses 462 Fentucci Naturals products",
+ "checks": [
+ {
+ "name": "Syntax",
+ "command": "node --check tk10964-canary-guard/check.mjs",
+ "verdict": "PASS"
+ },
+ {
+ "name": "Fixture unit and boundary assertions",
+ "command": "node tk10964-canary-guard/test.mjs",
+ "assertions": "full tag family, Fentucci fallback, product-GID dedupe, sample exclusion, unavailable-$0 exclusion, search-overreturn rejection",
+ "verdict": "PASS"
+ },
+ {
+ "name": "Fixture end-to-end status artifact",
+ "command": "ZERO_PRICE_CANARY_OUT=/tmp/tk10964-fixture.json node tk10964-canary-guard/check.mjs --fixture tk10964-canary-guard/fixtures/mixed.json",
+ "assertions": "WARN, 4 unique in-scope products, 2 defects, one per affected vendor, both qty2026/DENY/tracked",
+ "verdict": "PASS"
+ },
+ {
+ "name": "Transport failure behavior",
+ "command": "network-restricted live invocation",
+ "assertions": "writes WARN artifact with zero_price_orderable=null and exits nonzero; never emits false PASS",
+ "verdict": "PASS"
+ },
+ {
+ "name": "Live Shopify read-only journey",
+ "command": "ZERO_PRICE_CANARY_OUT=/tmp/tk10964-live-filtered.json node tk10964-canary-guard/check.mjs",
+ "assertions": "FAIL; exact 1,743 defects: Phillipe Romano 1,281 plus Fentucci Naturals 462; all 1,743 qty2026, DENY, tracked",
+ "verdict": "PASS"
+ }
+ ],
+ "boundaries": {
+ "shopify": "GraphQL query operation only; source contains no mutation",
+ "database": "not contacted",
+ "filesystem": "temporary JSON evidence and project-local fixtures only",
+ "production_writes": 0
+ },
+ "cleanup": "Only /tmp evidence retained; no remote state created",
+ "verdict": "PASS",
+ "residual_gate": "The guard is committed as a reviewed local artifact but is not installed/deployed over the standing scheduled canary in this tranche"
+}
← 7eb582b Add apply-fix.mjs (restore-map-first, canary-batched, rollba
·
back to Tk 10965 Zero Price Analysis
·
snapshot live zero-price evidence before canary install db7f993 →