[object Object]

← back to Gmc Titlefix

Harden GMC Track C preflight and rollback

279280894c8190e06cd810a10b215c8b7a503130 · 2026-08-31 02:08:55 -0700 · Steve

Files touched

Diff

commit 279280894c8190e06cd810a10b215c8b7a503130
Author: Steve <steve@designerwallcoverings.com>
Date:   Mon Aug 31 02:08:55 2026 -0700

    Harden GMC Track C preflight and rollback
---
 data/track-c-canary-baseline-attempt.json | 60 +++++++++++++++++++
 data/track-c-target-reconciliation.json   | 40 +++++++++++++
 link-price-override-ds-ROLLBACK.mjs       | 19 +++---
 link-price-override-ds.mjs                | 14 +++--
 reconcile-track-c-targets.mjs             | 51 ++++++++++++++++
 test/track-c-safety.test.mjs              | 46 +++++++++++++++
 track-c-safety.mjs                        | 77 +++++++++++++++++++++++++
 verify-canary.mjs                         | 96 ++++++++++++++++++++++---------
 8 files changed, 363 insertions(+), 40 deletions(-)

diff --git a/data/track-c-canary-baseline-attempt.json b/data/track-c-canary-baseline-attempt.json
new file mode 100644
index 0000000..d37a8bd
--- /dev/null
+++ b/data/track-c-canary-baseline-attempt.json
@@ -0,0 +1,60 @@
+{
+  "generated_at": "2026-08-31T09:04:52.333Z",
+  "total": 400,
+  "approved": 385,
+  "disapproved": 3,
+  "other": 0,
+  "flipped": 350,
+  "still425": 38,
+  "readErrors": 12,
+  "errors": [
+    {
+      "offerId": "shopify_US_7421471817779_42654206787635",
+      "error": "HTTP 404 https://shoppingcontent.googleapis.com/content/v2.1/146735262/products/online%3Aen%3AUS%3Ashopify_US_7421471817779_42654206787635"
+    },
+    {
+      "offerId": "shopify_US_7810571173939_44170603298867",
+      "error": "HTTP 404 https://shoppingcontent.googleapis.com/content/v2.1/146735262/products/online%3Aen%3AUS%3Ashopify_US_7810571173939_44170603298867"
+    },
+    {
+      "offerId": "shopify_US_6811234533427_40875215519795",
+      "error": "HTTP 404 https://shoppingcontent.googleapis.com/content/v2.1/146735262/products/online%3Aen%3AUS%3Ashopify_US_6811234533427_40875215519795"
+    },
+    {
+      "offerId": "shopify_US_7810436300851_44170214211635",
+      "error": "HTTP 404 https://shoppingcontent.googleapis.com/content/v2.1/146735262/products/online%3Aen%3AUS%3Ashopify_US_7810436300851_44170214211635"
+    },
+    {
+      "offerId": "shopify_US_7810578677811_44170611327027",
+      "error": "HTTP 404 https://shoppingcontent.googleapis.com/content/v2.1/146735262/products/online%3Aen%3AUS%3Ashopify_US_7810578677811_44170611327027"
+    },
+    {
+      "offerId": "shopify_US_7506089508915_42949341741107",
+      "error": "HTTP 404 https://shoppingcontent.googleapis.com/content/v2.1/146735262/productstatuses/online%3Aen%3AUS%3Ashopify_US_7506089508915_42949341741107"
+    },
+    {
+      "offerId": "shopify_US_7552701497395_43243527340083",
+      "error": "HTTP 404 https://shoppingcontent.googleapis.com/content/v2.1/146735262/products/online%3Aen%3AUS%3Ashopify_US_7552701497395_43243527340083"
+    },
+    {
+      "offerId": "shopify_US_7822321745971_44223552225331",
+      "error": "HTTP 404 https://shoppingcontent.googleapis.com/content/v2.1/146735262/productstatuses/online%3Aen%3AUS%3Ashopify_US_7822321745971_44223552225331"
+    },
+    {
+      "offerId": "shopify_US_7552741834803_43243618959411",
+      "error": "HTTP 404 https://shoppingcontent.googleapis.com/content/v2.1/146735262/products/online%3Aen%3AUS%3Ashopify_US_7552741834803_43243618959411"
+    },
+    {
+      "offerId": "shopify_US_7855821946931_44417989279795",
+      "error": "HTTP 404 https://shoppingcontent.googleapis.com/content/v2.1/146735262/productstatuses/online%3Aen%3AUS%3Ashopify_US_7855821946931_44417989279795"
+    },
+    {
+      "offerId": "shopify_US_7810545025075_44170472357939",
+      "error": "HTTP 404 https://shoppingcontent.googleapis.com/content/v2.1/146735262/productstatuses/online%3Aen%3AUS%3Ashopify_US_7810545025075_44170472357939"
+    },
+    {
+      "offerId": "shopify_US_7430004637747_42683857797171",
+      "error": "HTTP 404 https://shoppingcontent.googleapis.com/content/v2.1/146735262/products/online%3Aen%3AUS%3Ashopify_US_7430004637747_42683857797171"
+    }
+  ]
+}
diff --git a/data/track-c-target-reconciliation.json b/data/track-c-target-reconciliation.json
new file mode 100644
index 0000000..d6bce4a
--- /dev/null
+++ b/data/track-c-target-reconciliation.json
@@ -0,0 +1,40 @@
+{
+  "schema": 1,
+  "generated_at": "2026-08-31T09:08:11.113Z",
+  "datasource": "accounts/146735262/dataSources/10693978453",
+  "exact_current_target_count": 10352,
+  "datasource_seed_evidence": {
+    "source": "/Users/macstudio3/.claude/yolo-queue/gmc-canary-apply-result.json",
+    "successful_inserts": 388,
+    "failed_inserts": 12,
+    "exact_successful_offer_ids_recorded": false
+  },
+  "known_datasource_canary_count": 400,
+  "artifact_intersection_count": 400,
+  "proven_datasource_intersection_count": null,
+  "proven_current_served_intersection_count": 388,
+  "current_unreadable_offer_ids": [
+    "shopify_US_6811234533427_40875215519795",
+    "shopify_US_7421471817779_42654206787635",
+    "shopify_US_7430004637747_42683857797171",
+    "shopify_US_7506089508915_42949341741107",
+    "shopify_US_7552701497395_43243527340083",
+    "shopify_US_7552741834803_43243618959411",
+    "shopify_US_7810436300851_44170214211635",
+    "shopify_US_7810545025075_44170472357939",
+    "shopify_US_7810571173939_44170603298867",
+    "shopify_US_7810578677811_44170611327027",
+    "shopify_US_7822321745971_44223552225331",
+    "shopify_US_7855821946931_44417989279795"
+  ],
+  "target_outside_current_full": 0,
+  "full_target_duplicates": 0,
+  "canary_duplicates": 0,
+  "invalid_target_rows": 0,
+  "artifact_clean": true,
+  "datasource_membership_exact": false,
+  "immutable_baseline_captured": false,
+  "ready_to_link": false,
+  "historical_466_claim": "obsolete served-leak count from a different observation; not the current target or datasource row count",
+  "interpretation": "10352 is the current exact candidate set and all 400 canary IDs are a clean subset. The retained apply result records 388 successes and 12 failures without IDs. A fresh read-only baseline attempt independently found 12 unreadable IDs, proving 388 currently served canary rows and identifying the missing IDs. Because a complete immutable baseline cannot be captured, Track C remains HOLD. The historical 466 must not be used as blast radius."
+}
diff --git a/link-price-override-ds-ROLLBACK.mjs b/link-price-override-ds-ROLLBACK.mjs
index 9af0ed6..c025ffa 100644
--- a/link-price-override-ds-ROLLBACK.mjs
+++ b/link-price-override-ds-ROLLBACK.mjs
@@ -1,22 +1,27 @@
 // ROLLBACK for link-price-override-ds.mjs — restores primary feed 180695450's
-// defaultRule to the EXACT pre-change snapshot saved at /tmp/gmc-primary-180695450-CURRENT.json.
-// Dry-run by default; --apply --yes-i-am-steve to fire.
-import fs from 'fs';
+// defaultRule to an immutable, SHA-256-verified pre-change snapshot.
+// Dry-run by default; requires --manifest plus --apply --yes-i-am-steve to fire.
 import { createRequire } from 'module';
+import { loadValidatedSnapshot } from './track-c-safety.mjs';
 const require = createRequire(import.meta.url);
 const { token, MERCHANT } = require('./_auth.js');
 
 const PRIMARY = '180695450';
-const SNAP = '/tmp/gmc-primary-180695450-CURRENT.json';
 const args = new Set(process.argv.slice(2));
 const ARMED = args.has('--apply') && args.has('--yes-i-am-steve');
+const manifestFlag = process.argv.indexOf('--manifest');
+const manifestPath = manifestFlag >= 0 ? process.argv[manifestFlag + 1] : '';
 
-if (!fs.existsSync(SNAP)) { console.error('No snapshot at', SNAP, '— cannot roll back safely. Abort.'); process.exit(1); }
-const snap = JSON.parse(fs.readFileSync(SNAP, 'utf8'));
+if (!manifestPath) { console.error('Missing --manifest <immutable-manifest.json> — abort.'); process.exit(1); }
+let loaded;
+try { loaded = loadValidatedSnapshot(manifestPath); }
+catch (e) { console.error('Snapshot validation failed:', e.message); process.exit(1); }
+const { snapshot: snap, manifest } = loaded;
 const rule = snap.primaryProductDataSource?.defaultRule?.takeFromDataSources || [];
+console.log('Validated snapshot SHA-256:', manifest.snapshot_sha256);
 console.log('Will RESTORE takeFromDataSources to:', rule.map(x => x.self ? 'SELF' : x.supplementalDataSourceName.split('/').pop()).join(' > '));
 
-if (!ARMED) { console.log('\nDRY-RUN. To apply: node ~/Projects/gmc-titlefix/link-price-override-ds-ROLLBACK.mjs --apply --yes-i-am-steve'); process.exit(0); }
+if (!ARMED) { console.log(`\nDRY-RUN. To apply: node ~/Projects/gmc-titlefix/link-price-override-ds-ROLLBACK.mjs --manifest ${manifestPath} --apply --yes-i-am-steve`); process.exit(0); }
 
 const tok = await token(); const H = { Authorization: 'Bearer ' + tok, 'Content-Type': 'application/json' };
 const base = `https://merchantapi.googleapis.com/datasources/v1/accounts/${MERCHANT}/dataSources/${PRIMARY}`;
diff --git a/link-price-override-ds.mjs b/link-price-override-ds.mjs
index 252489a..9590a09 100644
--- a/link-price-override-ds.mjs
+++ b/link-price-override-ds.mjs
@@ -11,9 +11,9 @@
 // This is a CUSTOMER-FACING GMC FEED STRUCTURE CHANGE (changes what price wins for the
 // whole catalog on the live Google feed) => GATED. Dry-run by default; requires
 // --apply --yes-i-am-steve to fire. Reversible via link-price-override-ds-ROLLBACK.mjs
-// (restores the exact pre-change rule saved to /tmp/gmc-primary-180695450-CURRENT.json).
-import fs from 'fs';
+// (restores the exact immutable pre-change snapshot written only by an armed run).
 import { createRequire } from 'module';
+import { writeImmutableSnapshot } from './track-c-safety.mjs';
 const require = createRequire(import.meta.url);
 const { token, MERCHANT } = require('./_auth.js');
 
@@ -30,9 +30,6 @@ const rule = cur.primaryProductDataSource?.defaultRule?.takeFromDataSources || [
 const ids = rule.map(x => x.self ? 'SELF' : x.supplementalDataSourceName.split('/').pop());
 console.log('CURRENT takeFromDataSources:', ids.join(' > '));
 
-// Save an exact rollback snapshot every run.
-fs.writeFileSync('/tmp/gmc-primary-180695450-CURRENT.json', JSON.stringify(cur, null, 2));
-
 if (rule.some(x => x.supplementalDataSourceName === PRICE_DS)) {
   console.log('ALREADY LINKED — price-override DS 10693978453 is present. No change needed.');
   process.exit(0);
@@ -55,10 +52,15 @@ const body = {
 if (!ARMED) {
   console.log('\nDRY-RUN (not armed). To apply the live feed-link change:');
   console.log('  node ~/Projects/gmc-titlefix/link-price-override-ds.mjs --apply --yes-i-am-steve');
-  console.log('Rollback after apply: node ~/Projects/gmc-titlefix/link-price-override-ds-ROLLBACK.mjs --apply --yes-i-am-steve');
+  console.log('An armed run writes an immutable manifest; rollback requires that exact --manifest path.');
   process.exit(0);
 }
 
+const saved = writeImmutableSnapshot(cur, new URL('./data/track-c-snapshots', import.meta.url));
+console.log('IMMUTABLE PRE-CHANGE SNAPSHOT:', saved.snapshotPath);
+console.log('ROLLBACK MANIFEST:', saved.manifestPath);
+console.log('SNAPSHOT SHA-256:', saved.manifest.snapshot_sha256);
+
 const url = `${base}?updateMask=primaryProductDataSource.defaultRule`;
 const r = await fetch(url, { method: 'PATCH', headers: H, body: JSON.stringify(body) });
 const j = await r.json();
diff --git a/reconcile-track-c-targets.mjs b/reconcile-track-c-targets.mjs
new file mode 100644
index 0000000..27cba8e
--- /dev/null
+++ b/reconcile-track-c-targets.mjs
@@ -0,0 +1,51 @@
+// Local-only evidence builder. Reconciles the current full target artifact, the
+// applied supplemental canary artifact, and the historical "466" memo claim.
+import fs from 'fs';
+const FULL = '/Users/macstudio3/.claude/yolo-queue/gmc-fresh-override-full.json';
+const CANARY = '/Users/macstudio3/.claude/yolo-queue/gmc-fresh-override-canary.json';
+const APPLY = '/Users/macstudio3/.claude/yolo-queue/gmc-canary-apply-result.json';
+const ATTEMPT = new URL('./data/track-c-canary-baseline-attempt.json', import.meta.url);
+const OUT = new URL('./data/track-c-target-reconciliation.json', import.meta.url);
+
+const fullDoc = JSON.parse(fs.readFileSync(FULL));
+const canaryDoc = JSON.parse(fs.readFileSync(CANARY));
+const apply = JSON.parse(fs.readFileSync(APPLY));
+const attempt = fs.existsSync(ATTEMPT) ? JSON.parse(fs.readFileSync(ATTEMPT)) : null;
+const full = fullDoc.overrides || [], canary = canaryDoc.overrides || [];
+const fullIds = new Set(full.map(x => x.offerId));
+const canaryIds = new Set(canary.map(x => x.offerId));
+const duplicateFull = full.length - fullIds.size;
+const duplicateCanary = canary.length - canaryIds.size;
+const intersection = [...canaryIds].filter(id => fullIds.has(id));
+const outside = [...canaryIds].filter(id => !fullIds.has(id));
+const invalid = full.filter(x => !x.offerId || x.feedLabel !== 'US' || !(x.realPrice > 4.26));
+const artifactClean = !duplicateFull && !duplicateCanary && !outside.length && !invalid.length;
+const datasourceMembershipExact = apply.fail === 0 && apply.ok === canary.length;
+const failedReadIds = new Set((attempt?.errors || []).map(x => x.offerId));
+const readFailureIdsExact = !!attempt && attempt.readErrors === failedReadIds.size && [...failedReadIds].every(id => canaryIds.has(id));
+const servedIntersectionCount = readFailureIdsExact ? canary.length - failedReadIds.size : null;
+const report = {
+  schema: 1,
+  generated_at: new Date().toISOString(),
+  datasource: canaryDoc.datasource,
+  exact_current_target_count: full.length,
+  datasource_seed_evidence: { source: APPLY, successful_inserts: apply.ok, failed_inserts: apply.fail, exact_successful_offer_ids_recorded: false },
+  known_datasource_canary_count: canary.length,
+  artifact_intersection_count: intersection.length,
+  proven_datasource_intersection_count: datasourceMembershipExact ? intersection.length : null,
+  proven_current_served_intersection_count: servedIntersectionCount,
+  current_unreadable_offer_ids: readFailureIdsExact ? [...failedReadIds].sort() : null,
+  target_outside_current_full: outside.length,
+  full_target_duplicates: duplicateFull,
+  canary_duplicates: duplicateCanary,
+  invalid_target_rows: invalid.length,
+  artifact_clean: artifactClean,
+  datasource_membership_exact: datasourceMembershipExact,
+  immutable_baseline_captured: false,
+  ready_to_link: false,
+  historical_466_claim: 'obsolete served-leak count from a different observation; not the current target or datasource row count',
+  interpretation: `${full.length} is the current exact candidate set and all ${canary.length} canary IDs are a clean subset. The retained apply result records ${apply.ok} successes and ${apply.fail} failures without IDs. A fresh read-only baseline attempt independently found ${attempt?.readErrors ?? 'unknown'} unreadable IDs, proving ${servedIntersectionCount ?? 'unknown'} currently served canary rows and identifying the missing IDs. Because a complete immutable baseline cannot be captured, Track C remains HOLD. The historical 466 must not be used as blast radius.`,
+};
+fs.writeFileSync(OUT, JSON.stringify(report, null, 2) + '\n');
+console.log(JSON.stringify(report, null, 2));
+if (!report.ready_to_link) process.exitCode = 2;
diff --git a/test/track-c-safety.test.mjs b/test/track-c-safety.test.mjs
new file mode 100644
index 0000000..69f0c51
--- /dev/null
+++ b/test/track-c-safety.test.mjs
@@ -0,0 +1,46 @@
+import test from 'node:test';
+import assert from 'node:assert/strict';
+import fs from 'fs';
+import os from 'os';
+import path from 'path';
+import { evaluateCanary, loadValidatedSnapshot, writeImmutableSnapshot } from '../track-c-safety.mjs';
+
+const snapshot = { name: 'accounts/146735262/dataSources/180695450', primaryProductDataSource: { defaultRule: { takeFromDataSources: [{ self: true }] } } };
+
+test('immutable snapshot is hash-bound and cannot be overwritten', () => {
+  const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'track-c-'));
+  const now = new Date('2026-08-31T08:00:00.000Z');
+  const saved = writeImmutableSnapshot(snapshot, dir, now);
+  assert.equal(loadValidatedSnapshot(saved.manifestPath).snapshot.name, snapshot.name);
+  assert.throws(() => writeImmutableSnapshot(snapshot, dir, now), /EEXIST/);
+  fs.appendFileSync(saved.snapshotPath, 'tamper');
+  assert.throws(() => loadValidatedSnapshot(saved.manifestPath), /SHA-256 mismatch/);
+});
+
+test('immutable snapshot accepts a file URL directory', () => {
+  const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'track-c-url-'));
+  const saved = writeImmutableSnapshot(snapshot, new URL(`file://${dir}/`), new Date('2026-08-31T08:01:00.000Z'));
+  assert.equal(fs.existsSync(saved.snapshotPath), true);
+});
+
+test('rollback validation rejects wrong account/feed', () => {
+  const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'track-c-'));
+  assert.throws(() => writeImmutableSnapshot({ ...snapshot, name: 'accounts/9/dataSources/8' }, dir), /identity mismatch/);
+});
+
+test('canary passes only with approval floor and no worsening', () => {
+  const baseline = { total: 100, approved: 94, disapproved: 6, readErrors: 0 };
+  const result = evaluateCanary({ total: 100, approved: 96, disapproved: 4, readErrors: 0 }, baseline);
+  assert.equal(result.pass, true);
+  assert.equal(result.approvalDelta, 2);
+  assert.equal(result.disapprovalDelta, -2);
+});
+
+test('canary fails closed on read error, approval loss, or new disapproval', () => {
+  const baseline = { total: 100, approved: 96, disapproved: 4, readErrors: 0 };
+  const result = evaluateCanary({ total: 100, approved: 94, disapproved: 5, readErrors: 1 }, baseline);
+  assert.equal(result.pass, false);
+  assert.match(result.reasons.join(';'), /read errors/);
+  assert.match(result.reasons.join(';'), /approved fell/);
+  assert.match(result.reasons.join(';'), /new disapprovals/);
+});
diff --git a/track-c-safety.mjs b/track-c-safety.mjs
new file mode 100644
index 0000000..392240c
--- /dev/null
+++ b/track-c-safety.mjs
@@ -0,0 +1,77 @@
+import crypto from 'crypto';
+import fs from 'fs';
+import path from 'path';
+import { fileURLToPath } from 'url';
+
+export const EXPECTED_MERCHANT = '146735262';
+export const EXPECTED_PRIMARY = '180695450';
+
+export function sha256(data) {
+  return crypto.createHash('sha256').update(data).digest('hex');
+}
+
+export function assertPrimaryIdentity(snapshot, merchant = EXPECTED_MERCHANT, primary = EXPECTED_PRIMARY) {
+  const expected = `accounts/${merchant}/dataSources/${primary}`;
+  if (!snapshot || snapshot.name !== expected) {
+    throw new Error(`snapshot identity mismatch: expected ${expected}, got ${snapshot?.name || '<missing>'}`);
+  }
+  if (!snapshot.primaryProductDataSource?.defaultRule?.takeFromDataSources) {
+    throw new Error('snapshot missing primaryProductDataSource.defaultRule.takeFromDataSources');
+  }
+  return expected;
+}
+
+export function writeImmutableSnapshot(snapshot, dir, now = new Date()) {
+  assertPrimaryIdentity(snapshot);
+  if (dir instanceof URL) dir = fileURLToPath(dir);
+  fs.mkdirSync(dir, { recursive: true });
+  const stamp = now.toISOString().replace(/[:.]/g, '-');
+  const snapshotPath = path.join(dir, `primary-${EXPECTED_PRIMARY}-pre-link-${stamp}.json`);
+  const body = JSON.stringify(snapshot, null, 2) + '\n';
+  fs.writeFileSync(snapshotPath, body, { flag: 'wx', mode: 0o600 });
+  const manifest = {
+    schema: 1,
+    created_at: now.toISOString(),
+    merchant: EXPECTED_MERCHANT,
+    primary_data_source: EXPECTED_PRIMARY,
+    snapshot_path: snapshotPath,
+    snapshot_sha256: sha256(body),
+  };
+  const manifestPath = snapshotPath.replace(/\.json$/, '.manifest.json');
+  fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n', { flag: 'wx', mode: 0o600 });
+  return { snapshotPath, manifestPath, manifest };
+}
+
+export function loadValidatedSnapshot(manifestPath) {
+  const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
+  if (String(manifest.merchant) !== EXPECTED_MERCHANT || String(manifest.primary_data_source) !== EXPECTED_PRIMARY) {
+    throw new Error('rollback manifest merchant/feed mismatch');
+  }
+  const body = fs.readFileSync(manifest.snapshot_path, 'utf8');
+  if (sha256(body) !== manifest.snapshot_sha256) throw new Error('rollback snapshot SHA-256 mismatch');
+  const snapshot = JSON.parse(body);
+  assertPrimaryIdentity(snapshot, manifest.merchant, manifest.primary_data_source);
+  return { manifest, snapshot };
+}
+
+export function evaluateCanary(current, baseline, thresholds = {}) {
+  const minApprovalPct = thresholds.minApprovalPct ?? 95;
+  const maxReadErrors = thresholds.maxReadErrors ?? 0;
+  const n = current.total;
+  if (!Number.isInteger(n) || n <= 0) throw new Error('current total must be positive');
+  if (!baseline || baseline.total !== n) throw new Error('baseline missing or population mismatch');
+  for (const [label, row] of [['current', current], ['baseline', baseline]]) {
+    for (const key of ['approved', 'disapproved', 'readErrors']) {
+      if (!Number.isInteger(row[key]) || row[key] < 0) throw new Error(`${label}.${key} invalid`);
+    }
+  }
+  const approvalPct = current.approved / n * 100;
+  const approvalDelta = current.approved - baseline.approved;
+  const disapprovalDelta = current.disapproved - baseline.disapproved;
+  const reasons = [];
+  if (current.readErrors > maxReadErrors) reasons.push(`read errors ${current.readErrors} > ${maxReadErrors}`);
+  if (approvalPct < minApprovalPct) reasons.push(`approval ${approvalPct.toFixed(1)}% < ${minApprovalPct}%`);
+  if (approvalDelta < 0) reasons.push(`approved fell by ${-approvalDelta}`);
+  if (disapprovalDelta > 0) reasons.push(`new disapprovals +${disapprovalDelta}`);
+  return { pass: reasons.length === 0, approvalPct, approvalDelta, disapprovalDelta, reasons };
+}
diff --git a/verify-canary.mjs b/verify-canary.mjs
index 0d165b0..6ea7e66 100644
--- a/verify-canary.mjs
+++ b/verify-canary.mjs
@@ -1,32 +1,74 @@
-// READ-ONLY canary verification: run 24-48h after apply. Reads the 400 canary offers'
-// processed price + approval status; reports % flipped to real price and % approved.
-// GATE: release the remaining US overrides only if approved >=95% and no NEW disapprovals.
+// READ-ONLY canary verifier. First capture a pre-link baseline, then compare the
+// same fixed population after reprocessing. Fails closed on every read error.
 import fs from 'fs';
 import { createRequire } from 'module';
+import { evaluateCanary } from './track-c-safety.mjs';
 const require = createRequire(import.meta.url);
 const { token, MERCHANT } = require('./_auth.js');
-const canary = JSON.parse(fs.readFileSync('/Users/macstudio3/.claude/yolo-queue/gmc-fresh-override-canary.json','utf8')).overrides;
-const tok = await token(); const H={Authorization:'Bearer '+tok};
-let flipped=0, still425=0, approved=0, disapproved=0, other=0, err=0;
-const bad=[];
-for (let i=0;i<canary.length;i++){
-  const row=canary[i];
-  const rid=`online:${row.contentLanguage}:${row.feedLabel}:${row.offerId}`;
-  try{
-    const st=await (await fetch(`https://shoppingcontent.googleapis.com/content/v2.1/${MERCHANT}/productstatuses/${encodeURIComponent(rid)}`,{headers:H})).json();
-    const pr=await (await fetch(`https://shoppingcontent.googleapis.com/content/v2.1/${MERCHANT}/products/${encodeURIComponent(rid)}`,{headers:H})).json();
-    const price=parseFloat(pr.price?.value||'0');
-    if (price>4.26) flipped++; else still425++;
-    const ds=(st.destinationStatuses||[]);
-    const s=(ds.find(d=>/Shopping/i.test(d.destination))||ds[0]||{}).status||'';
-    if(s==='disapproved'){disapproved++; if(bad.length<15) bad.push(`${row.offerId} price=${price} DISAPPROVED`);}
-    else if(s==='approved'){approved++;} else other++;
-  }catch(e){err++;}
-  if(i%50===0) process.stderr.write(`  ${i}/${canary.length}\n`);
+const CANARY = '/Users/macstudio3/.claude/yolo-queue/gmc-fresh-override-canary.json';
+const BASELINE = new URL('./data/track-c-canary-baseline.json', import.meta.url);
+const BASELINE_ATTEMPT = new URL('./data/track-c-canary-baseline-attempt.json', import.meta.url);
+const RESULT = new URL('./data/track-c-canary-verification.json', import.meta.url);
+const capture = process.argv.includes('--capture-baseline');
+const canary = JSON.parse(fs.readFileSync(CANARY, 'utf8')).overrides;
+
+async function getJson(url, headers) {
+  const response = await fetch(url, { headers });
+  if (!response.ok) throw new Error(`HTTP ${response.status} ${url}`);
+  const value = await response.json();
+  if (value.error) throw new Error(`API error ${JSON.stringify(value.error).slice(0, 180)}`);
+  return value;
 }
-const pctFlip=(flipped/canary.length*100).toFixed(1), pctAppr=(approved/canary.length*100).toFixed(1);
-console.log(`\n=== CANARY VERIFY (n=${canary.length}) ===`);
-console.log(`  price flipped to real: ${flipped} (${pctFlip}%)   still $4.25: ${still425}`);
-console.log(`  approved: ${approved} (${pctAppr}%)   disapproved: ${disapproved}   other/pending: ${other}   err: ${err}`);
-console.log(`  GATE (release remaining US if approved>=95% AND disapproved not rising): ${pctAppr>=95?'PASS':'HOLD'}`);
-if(bad.length){console.log('--- disapproved sample ---'); bad.forEach(b=>console.log('  '+b));}
+
+async function scan() {
+  const tok = await token(); const headers = { Authorization: 'Bearer ' + tok };
+  const out = { generated_at: new Date().toISOString(), total: canary.length, approved: 0, disapproved: 0, other: 0, flipped: 0, still425: 0, readErrors: 0, errors: [] };
+  for (let i = 0; i < canary.length; i++) {
+    const row = canary[i], rid = `online:${row.contentLanguage}:${row.feedLabel}:${row.offerId}`;
+    try {
+      const [status, product] = await Promise.all([
+        getJson(`https://shoppingcontent.googleapis.com/content/v2.1/${MERCHANT}/productstatuses/${encodeURIComponent(rid)}`, headers),
+        getJson(`https://shoppingcontent.googleapis.com/content/v2.1/${MERCHANT}/products/${encodeURIComponent(rid)}`, headers),
+      ]);
+      const price = Number(product.price?.value);
+      if (!Number.isFinite(price)) throw new Error('missing/non-numeric served price');
+      if (price > 4.26) out.flipped++; else out.still425++;
+      const destinations = status.destinationStatuses || [];
+      const state = (destinations.find(d => /Shopping/i.test(d.destination)) || destinations[0] || {}).status;
+      if (state === 'approved') out.approved++;
+      else if (state === 'disapproved') out.disapproved++;
+      else out.other++;
+    } catch (error) {
+      out.readErrors++;
+      if (out.errors.length < 20) out.errors.push({ offerId: row.offerId, error: error.message });
+    }
+    if (i % 50 === 0) process.stderr.write(`  ${i}/${canary.length}\n`);
+  }
+  return out;
+}
+
+const current = await scan();
+if (capture) {
+  fs.writeFileSync(BASELINE_ATTEMPT, JSON.stringify(current, null, 2) + '\n');
+  if (current.readErrors) {
+    console.error(`BASELINE REFUSED: ${current.readErrors} read errors; diagnostics: ${BASELINE_ATTEMPT.pathname}`);
+    process.exit(1);
+  }
+  fs.writeFileSync(BASELINE, JSON.stringify(current, null, 2) + '\n', { flag: 'wx' });
+  console.log(`Immutable baseline captured: ${BASELINE.pathname}`);
+  console.log(JSON.stringify(current, null, 2));
+  process.exit(0);
+}
+
+if (!fs.existsSync(BASELINE)) {
+  console.error(`HOLD: no immutable baseline at ${BASELINE.pathname}; run --capture-baseline before linking.`);
+  process.exit(1);
+}
+const baseline = JSON.parse(fs.readFileSync(BASELINE));
+let gate;
+try { gate = evaluateCanary(current, baseline); }
+catch (error) { console.error('HOLD:', error.message); process.exit(1); }
+const result = { schema: 1, baseline, current, gate };
+fs.writeFileSync(RESULT, JSON.stringify(result, null, 2) + '\n');
+console.log(JSON.stringify(result, null, 2));
+if (!gate.pass) process.exit(1);

← 50a1f6e auto-data-snapshot: 2026-08-28T00:19:23 (5 data files) — dat  ·  back to Gmc Titlefix  ·  auto-data-snapshot: 2026-08-31T02:00:54 (3 data files) — dat b2e9868 →