← back to Gmc Titlefix
Fix Canada shipping verification and fail closed on unreadable data
f62f718a384d322f2e15af534ac9e65d4f55c073 · 2026-09-04 22:52:23 -0700 · Steve Abrams
Files touched
A test/tk10993-shipping-verification.test.mjsM tk10993-ca-canary-verify.mjsA tk10993-shipping-verification.mjsM verification/e2e-proof.jsonA verification/tk10993-20260905-ca-final.jsonA verification/tk10993-20260905-ca-live.jsonA verification/tk10993-track-c-historical-e2e-proof.json
Diff
commit f62f718a384d322f2e15af534ac9e65d4f55c073
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri Sep 4 22:52:23 2026 -0700
Fix Canada shipping verification and fail closed on unreadable data
---
test/tk10993-shipping-verification.test.mjs | 71 ++++++++++++++
tk10993-ca-canary-verify.mjs | 108 ++++++++++-----------
tk10993-shipping-verification.mjs | 86 ++++++++++++++++
verification/e2e-proof.json | 90 +++++++++--------
verification/tk10993-20260905-ca-final.json | 105 ++++++++++++++++++++
verification/tk10993-20260905-ca-live.json | 105 ++++++++++++++++++++
.../tk10993-track-c-historical-e2e-proof.json | 56 +++++++++++
7 files changed, 524 insertions(+), 97 deletions(-)
diff --git a/test/tk10993-shipping-verification.test.mjs b/test/tk10993-shipping-verification.test.mjs
new file mode 100644
index 0000000..8daf121
--- /dev/null
+++ b/test/tk10993-shipping-verification.test.mjs
@@ -0,0 +1,71 @@
+import test from 'node:test';
+import assert from 'node:assert/strict';
+import { readJson, summarizeService, assessCanary } from '../tk10993-shipping-verification.mjs';
+
+const rate = { flatRate: { amountMicros: '27000000', currencyCode: 'CAD' } };
+const service = { serviceName: 'Canada', active: true, currencyCode: 'CAD', rateGroups: [{ singleValue: rate }] };
+const code = 'missing_shipping_mismatch_of_shipping_method_and_offer_currency';
+const baseline = { status: { CA: { active: 0, disapproved: 1112, codes: { [code]: 1027 } } } };
+const snapshot = () => ({ merchant: '146735262', errors: [], shipping: { CA: [summarizeService(service)] }, status: { CA: { active: 277, disapproved: 820, codes: { [code]: 755 } } } });
+const tableService = (weights, cells) => ({ ...service, rateGroups: [{ mainTable: { rowHeaders: { weights }, rows: cells.map(cell => ({ cells: [cell] })) } }] });
+
+test('CAD 27 flat service with no weight rows passes the real regression', () => {
+ assert.equal(summarizeService(service).ships_4lb, true);
+ assert.equal(assessCanary(snapshot(), baseline).state, 'PASS');
+ assert.match(assessCanary(snapshot(), baseline).message, /separately gated/);
+});
+test('a 1 lb noShipping cliff fails but a single 10 lb bracket covers rolls', () => {
+ // Actual Merchant API GB service shape from the retained shipping-settings snapshot.
+ assert.equal(summarizeService(tableService([{ amountMicros: '1000000', unit: 'POUND' }, { amountMicros: '-1', unit: 'POUND' }], [rate, { noShipping: true }])).ships_4lb, false);
+ assert.equal(summarizeService(tableService([{ amountMicros: '2000000', unit: 'KILOGRAM' }], [rate])).ships_4lb, true);
+ assert.equal(summarizeService(tableService([{ value: '1', unit: 'lb' }, { value: '-1', unit: 'lb' }], [rate, { noShipping: true }])).ships_4lb, false);
+ assert.equal(summarizeService(tableService([{ value: '10', unit: 'lb' }], [rate])).ships_4lb, true);
+ assert.equal(summarizeService(tableService([{ value: '2', unit: 'lb' }, { value: '3', unit: 'lb' }], [rate, rate])).ships_4lb, false);
+ assert.equal(summarizeService(tableService([{ value: '2', unit: 'kg' }], [rate])).ships_4lb, true);
+});
+test('inactive, label-only, minimum-order and unsupported carrier rates cannot pass', () => {
+ assert.equal(summarizeService({ ...service, active: false }).ships_4lb, false);
+ assert.equal(summarizeService({ ...service, minimumOrderValue: { amountMicros: '100000000' } }).ships_4lb, null);
+ assert.equal(summarizeService({ ...service, rateGroups: [{ applicableShippingLabels: ['sample'], singleValue: rate }] }).ships_4lb, null);
+ assert.equal(summarizeService({ ...service, rateGroups: [{ singleValue: { carrierRate: 'UPS' } }] }).ships_4lb, null);
+ assert.equal(summarizeService({ ...service, rateGroups: [] }).ships_4lb, null);
+});
+test('rate currency mismatch, missing money and malformed table fail closed', () => {
+ assert.equal(summarizeService({ ...service, currencyCode: 'USD' }).ships_4lb, null);
+ assert.equal(summarizeService({ ...service, rateGroups: [{ singleValue: { flatRate: { currencyCode: 'CAD' } } }] }).ships_4lb, null);
+ assert.equal(summarizeService(tableService([{ value: '5', unit: 'oz' }], [rate])).ships_4lb, null);
+ assert.equal(summarizeService(tableService([{ value: '10', unit: 'lb' }, { value: '2', unit: 'lb' }], [rate, rate])).ships_4lb, null);
+});
+test('all label groups must cover rolls, and the verifier searches all services', () => {
+ const partial = { ...service, rateGroups: [{ applicableShippingLabels: ['roll'], singleValue: { noShipping: true } }, { singleValue: rate }] };
+ assert.equal(summarizeService(partial).ships_4lb, false);
+ const s = snapshot();
+ s.shipping.CA.unshift(summarizeService({ ...service, active: false }));
+ assert.equal(assessCanary(s, baseline).state, 'PASS');
+});
+test('API and absent-status failures never look like zero disapprovals or recovery', () => {
+ const s = snapshot();
+ s.errors.push({ boundary: 'status', message: 'HTTP 410' });
+ assert.equal(assessCanary(s, baseline).state, 'UNKNOWN');
+ delete s.status.CA;
+ s.errors = [];
+ assert.equal(assessCanary(s, baseline).state, 'UNKNOWN');
+ assert.equal(assessCanary(snapshot(), { ...baseline, merchant: 'wrong' }).state, 'UNKNOWN');
+ assert.equal(assessCanary(snapshot(), null).state, 'UNKNOWN');
+});
+test('successful config with insufficient recovery stays reprocessing', () => {
+ const s = snapshot(); s.status.CA.codes[code] = 1020;
+ assert.equal(assessCanary(s, baseline).state, 'REPROCESSING');
+});
+test('HTTP/auth, GraphQL and invalid JSON failures are propagated; GET stays GET', async () => {
+ for (const [status, body] of [[401, { error: {} }], [410, {}], [503, {}], [200, { errors: [{ message: 'Access denied' }] }]]) {
+ await assert.rejects(readJson('https://example.invalid', {}, async () => new Response(JSON.stringify(body), { status })), /API read failed/);
+ }
+ await assert.rejects(readJson('https://example.invalid', {}, async () => new Response('not json')));
+ const result = await readJson('https://example.invalid', { headers: { test: 'read' } }, async (url, options) => {
+ assert.equal(options.method, undefined);
+ assert.equal(options.body, undefined);
+ return new Response('{"services":[]}');
+ });
+ assert.deepEqual(result, { services: [] });
+});
diff --git a/tk10993-ca-canary-verify.mjs b/tk10993-ca-canary-verify.mjs
index b92b38d..a3e0996 100644
--- a/tk10993-ca-canary-verify.mjs
+++ b/tk10993-ca-canary-verify.mjs
@@ -12,6 +12,7 @@
// node tk10993-ca-canary-verify.mjs # verify + diff against the baseline
import { createRequire } from 'module';
import fs from 'fs';
+import { readJson, summarizeService, assessCanary } from './tk10993-shipping-verification.mjs';
const require = createRequire(import.meta.url);
const { token, MERCHANT } = require('./_auth.js');
@@ -23,108 +24,97 @@ const shopTok = (fs.readFileSync(process.env.HOME + '/Projects/secrets-manager/.
.match(/^SHOPIFY_FULL_ACCESS_TOKEN=(.+)$/m) || [])[1]; // narrow admin token lacks read_markets
async function shopify(query) {
- const r = await fetch(`https://${SHOP}.myshopify.com/admin/api/2024-10/graphql.json`, {
+ return readJson(`https://${SHOP}.myshopify.com/admin/api/2026-07/graphql.json`, {
method: 'POST', headers: { 'X-Shopify-Access-Token': shopTok, 'Content-Type': 'application/json' },
- body: JSON.stringify({ query }), signal: AbortSignal.timeout(60000) });
- return r.json();
+ body: JSON.stringify({ query }) });
}
const tok = await token();
const H = { Authorization: 'Bearer ' + tok };
-const snap = { checked_at: new Date().toISOString() };
+const snap = { checked_at: new Date().toISOString(), merchant: MERCHANT, errors: [] };
// (1) Shopify markets
try {
- const j = await shopify(`{ markets(first:10){nodes{ name handle enabled catalogs(first:3){nodes{ status }}}}}`);
+ const j = await shopify(`{ markets(first:100){pageInfo{hasNextPage} nodes{ name handle enabled catalogs(first:100){pageInfo{hasNextPage} nodes{ status }}}}}`);
+ if (!j.data?.markets?.nodes || j.data.markets.pageInfo?.hasNextPage || j.data.markets.nodes.some(m => m.catalogs?.pageInfo?.hasNextPage)) throw new Error('Missing or incomplete market/catalog response');
snap.markets = Object.fromEntries((j.data?.markets?.nodes || []).map(m =>
- [m.handle, { enabled: m.enabled, catalogs: (m.catalogs?.nodes || []).length }]));
-} catch (e) { snap.markets = { error: e.message }; }
+ [m.handle, { enabled: m.enabled, catalogs: (m.catalogs?.nodes || []).length, active_catalogs: (m.catalogs?.nodes || []).filter(c => c.status === 'ACTIVE').length }]));
+} catch (e) { snap.errors.push({ boundary: 'markets', message: e.message }); }
// (2)+(3) shipping table for CA/GB
try {
- const j = await (await fetch(`https://shoppingcontent.googleapis.com/content/v2.1/${MERCHANT}/shippingsettings/${MERCHANT}`,
- { headers: H, signal: AbortSignal.timeout(60000) })).json();
+ const j = await readJson(`https://merchantapi.googleapis.com/accounts/v1/accounts/${MERCHANT}/shippingSettings`, { headers: H });
+ if (j.name !== `accounts/${MERCHANT}/shippingSettings` || !Array.isArray(j.services)) throw new Error('Missing or wrong-account shipping settings');
snap.shipping = {};
for (const c of ['CA', 'GB']) {
- const s = (j.services || []).filter(x => x.deliveryCountry === c);
- snap.shipping[c] = s.map(x => {
- const w = x.rateGroups?.[0]?.mainTable?.rowHeaders?.weights || [];
- const rows = x.rateGroups?.[0]?.mainTable?.rows || [];
- const maxBand = w.length ? w[w.length - 1] : null;
- const shipsHeavy = rows.some((r, i) => !r.cells?.[0]?.noShipping && i > 0);
- return { name: x.name, currency: x.currency, active: x.active,
- weight_bands: w.map(b => `${b.value}${b.unit || ''}`), maxBand, ships_over_1lb: shipsHeavy };
- });
+ snap.shipping[c] = j.services.filter(x => x.deliveryCountries?.includes(c)).map(x => summarizeService(x));
}
-} catch (e) { snap.shipping = { error: e.message }; }
+} catch (e) { snap.errors.push({ boundary: 'shipping', message: e.message }); }
// (4) THE CANARY — CA aggregate + item-level codes
try {
- const j = await (await fetch(`https://merchantapi.googleapis.com/issueresolution/v1/accounts/${MERCHANT}/aggregateProductStatuses?pageSize=100`,
- { headers: H, signal: AbortSignal.timeout(60000) })).json();
+ const all = [];
+ let page = '';
+ const seen = new Set();
+ do {
+ const j = await readJson(`https://merchantapi.googleapis.com/issueresolution/v1/accounts/${MERCHANT}/aggregateProductStatuses?pageSize=100${page ? `&pageToken=${encodeURIComponent(page)}` : ''}`, { headers: H });
+ if (!Array.isArray(j.aggregateProductStatuses)) throw new Error('Missing aggregate status response');
+ all.push(...j.aggregateProductStatuses);
+ page = j.nextPageToken || '';
+ if (page && seen.has(page)) throw new Error('Repeated aggregate page token');
+ seen.add(page);
+ } while (page);
snap.status = {};
- for (const a of (j.aggregateProductStatuses || [])) {
+ for (const a of all) {
if (a.reportingContext !== 'SHOPPING_ADS') continue;
+ if (!a.stats || !a.country || snap.status[a.country]) throw new Error('Missing or duplicate country status');
const s = a.stats || {};
const codes = {};
- for (const i of (a.itemLevelIssues || [])) if (i.severity === 'DISAPPROVED') codes[i.code] = +i.productCount || 0;
- snap.status[a.country] = { active: +s.activeCount || 0, disapproved: +s.disapprovedCount || 0, codes };
+ for (const i of (a.itemLevelIssues || [])) if (i.severity === 'DISAPPROVED') {
+ const count = Number(i.productCount ?? 0);
+ if (!Number.isFinite(count) || count < 0) throw new Error('Invalid issue count');
+ codes[i.code] = (codes[i.code] || 0) + count;
+ }
+ snap.status[a.country] = { active: Number(s.activeCount ?? 0), disapproved: Number(s.disapprovedCount ?? 0), pending: Number(s.pendingCount ?? 0), expiring: Number(s.expiringCount ?? 0), codes };
}
-} catch (e) { snap.status = { error: e.message }; }
+} catch (e) { snap.errors.push({ boundary: 'status', message: e.message }); }
if (SAVE) {
- fs.writeFileSync(BASELINE, JSON.stringify(snap, null, 2));
+ if (snap.errors.length || !snap.status?.CA || !snap.shipping?.CA?.length) throw new Error('Refusing incomplete baseline');
+ fs.writeFileSync(BASELINE, JSON.stringify(snap, null, 2), { flag: 'wx' });
console.log('BASELINE SAVED ->', BASELINE);
}
const base = (!SAVE && fs.existsSync(BASELINE)) ? JSON.parse(fs.readFileSync(BASELINE, 'utf8')) : null;
-const f = n => (n ?? 0).toLocaleString();
+const f = n => n === undefined || n === null ? 'UNKNOWN' : n.toLocaleString();
const ca = snap.status?.CA || {}, gb = snap.status?.GB || {};
const bca = base?.status?.CA || {};
console.log(`\n=== TK-10993 CA/GB verifier — ${snap.checked_at}${base ? ` (baseline ${base.checked_at})` : ''}`);
-console.log(`\n[1] UK market enabled ......... ${snap.markets?.gb?.enabled === true ? 'YES ✅' : 'NO ❌ (GB stays at 0 offers until enabled)'}`);
+console.log(`\n[1] UK market enabled ......... ${snap.markets?.gb ? `${snap.markets.gb.enabled}; active catalogs=${snap.markets.gb.active_catalogs}` : 'UNKNOWN'}`);
for (const c of ['CA', 'GB']) {
- const svc = snap.shipping?.[c]?.[0];
- if (!svc) { console.log(`[2/3] ${c} shipping ............ NO SERVICE ❌`); continue; }
+ const services = snap.shipping?.[c];
+ if (!services?.length) { console.log(`[2/3] ${c} shipping ............ ${services ? 'NO SERVICE' : 'UNKNOWN'}`); continue; }
+ for (const svc of services) {
const want = c === 'CA' ? 'CAD' : 'GBP';
console.log(`[2] ${c} currency ............. ${svc.currency}${svc.currency === want ? ' ✅' : ` ❌ (need ${want})`}`);
- console.log(`[3] ${c} ships over 1 lb ...... ${svc.ships_over_1lb ? 'YES ✅' : 'NO ❌ (~4 lb rolls unshippable)'} bands=[${svc.weight_bands}]`);
+ console.log(`[3] ${c} covers 4 lb roll ...... ${svc.ships_4lb === null ? 'UNKNOWN' : svc.ships_4lb ? 'YES ✅' : 'NO ❌'} active=${svc.active} type=${svc.rate_type} rate=${JSON.stringify(svc.flat_rate)} bands=[${svc.weight_bands}]`);
+ }
}
console.log(`\n[4] THE CANARY — SHOPPING_ADS Canada`);
console.log(` active ${f(bca.active)} -> ${f(ca.active)}`);
console.log(` disapproved ${f(bca.disapproved)} -> ${f(ca.disapproved)}`);
-const cur = ca.codes?.missing_shipping_mismatch_of_shipping_method_and_offer_currency || 0;
+const cur = ca.codes ? ca.codes.missing_shipping_mismatch_of_shipping_method_and_offer_currency || 0 : undefined;
const bcur = bca.codes?.missing_shipping_mismatch_of_shipping_method_and_offer_currency || 0;
console.log(` currency-mismatch ${f(bcur)} -> ${f(cur)}`);
console.log(` missing_shipping ${f(bca.codes?.missing_shipping)} -> ${f(ca.codes?.missing_shipping)}`);
console.log(` GB active ${f(gb.active)} disapproved ${f(gb.disapproved)}`);
-// Config gate: the canary numbers mean NOTHING until the console edits actually landed.
-// Small day-to-day drift in the feed is churn (products archived/added), not progress —
-// so require BOTH a landed config AND a material delta before claiming movement.
-const caSvc = snap.shipping?.CA?.[0] || {};
-const configLanded = caSvc.currency === 'CAD' && caSvc.ships_over_1lb === true;
-const currencyOnly = caSvc.currency === 'CAD' && caSvc.ships_over_1lb !== true;
-const MATERIAL = Math.max(50, Math.round((bcur || 0) * 0.05)); // noise floor
-const drop = (bcur || 0) - cur;
-
-let verdict;
-if (SAVE) verdict = 'BASELINE — pre-change snapshot only';
-else if (!base) verdict = 'NO BASELINE — run --baseline first';
-else if (!caSvc.currency) verdict = 'UNKNOWN — could not read the CA shipping service.';
-else if (!configLanded && !currencyOnly)
- verdict = `NO CHANGE — the console edits have NOT landed (CA still ${caSvc.currency}, still capped at 1 lb).`
- + (Math.abs(drop) > 0 ? ` The ${drop > 0 ? '-' : '+'}${Math.abs(drop)} movement in the counts is normal feed churn, not progress.` : '');
-else if (currencyOnly && (ca.codes?.missing_shipping || 0) > (bca.codes?.missing_shipping || 0))
- verdict = 'HALF-FIXED ❌ — currency landed but offers moved to missing_shipping. The >1 lb weight bracket was NOT raised. Go back to step 2.';
-else if (currencyOnly)
- verdict = 'HALF-APPLIED ⚠️ — currency is CAD but the 1 lb cap is still in place, so ~4 lb rolls stay unshippable. Raise the weight bracket.';
-else if (ca.active > 0 && drop >= MATERIAL)
- verdict = 'PASS ✅ — config landed AND the canary flipped. Shipping proven; safe to start the batched re-feed.';
-else if (ca.active > 0)
- verdict = 'MOSTLY ✅ — config landed and offers are serving, but the currency-error drop is small; re-check in a day.';
-else
- verdict = 'REPROCESSING ⏳ — config landed, nothing serving yet. Google typically takes ~1–3 days; re-run then.';
-console.log(`\nVERDICT: ${verdict}`);
+snap.verdict = SAVE ? { state: 'BASELINE', message: 'Pre-change snapshot only' } : assessCanary(snap, base);
+const output = process.argv.find(a => a.startsWith('--output='))?.slice(9);
+if (output) fs.writeFileSync(output, JSON.stringify(snap, null, 2), { flag: 'wx' });
+console.log(`\nVERDICT: ${snap.verdict.state} — ${snap.verdict.message}`);
+for (const error of snap.errors) console.log(`READ ERROR (${error.boundary}): ${error.message}`);
+console.log('UK recovery and all feed writes require their own approval; this verifier performs reads only.');
console.log('cost: $0 (read-only Google + Shopify reads)\n');
+process.exitCode = ['PASS', 'BASELINE'].includes(snap.verdict.state) ? 0 : snap.verdict.state === 'UNKNOWN' ? 2 : 1;
diff --git a/tk10993-shipping-verification.mjs b/tk10993-shipping-verification.mjs
new file mode 100644
index 0000000..118da4c
--- /dev/null
+++ b/tk10993-shipping-verification.mjs
@@ -0,0 +1,86 @@
+// Pure checks for the read-only Track B verifier. Unsupported rate shapes stay UNKNOWN.
+// https://developers.google.com/merchant/api/reference/rest/accounts_v1/accounts.shippingSettings
+export async function readJson(url, options = {}, fetchImpl = fetch) {
+ const response = await fetchImpl(url, { ...options, signal: AbortSignal.timeout(60000) });
+ const data = await response.json();
+ if (!response.ok || data.error || data.errors?.length) {
+ throw new Error(`API read failed (HTTP ${response.status})`);
+ }
+ return data;
+}
+
+function flatValue(value, currency) {
+ if (value?.noShipping === true) return false;
+ const rate = value?.flatRate;
+ if (!rate) return null;
+ const raw = rate.amountMicros ?? rate.value;
+ if (raw === undefined || raw === null || raw === '') return null;
+ const amount = Number(raw) / (rate.amountMicros !== undefined ? 1e6 : 1);
+ return Number.isFinite(amount) && amount >= 0 && (rate.currencyCode ?? rate.currency) === currency ? true : null;
+}
+
+function weightInPounds(weight) {
+ const raw = weight.amountMicros ?? weight.value;
+ const unit = String(weight.unit).toLowerCase();
+ if (!['lb', 'kg', 'pound', 'kilogram'].includes(unit) || raw === undefined || raw === null || raw === '') return NaN;
+ if (String(raw) === '-1' || String(raw) === 'infinity') return Infinity;
+ const value = Number(raw) / (weight.amountMicros !== undefined ? 1e6 : 1);
+ return value * (['kg', 'kilogram'].includes(unit) ? 2.2046226218 : 1);
+}
+
+function groupAtWeight(group, currency, pounds) {
+ if (group.singleValue && !group.mainTable) return flatValue(group.singleValue, currency);
+ const table = group.mainTable;
+ if (!table || group.singleValue || Object.keys(table.columnHeaders || {}).length) return null;
+ const weights = table.rowHeaders?.weights;
+ if (!weights?.length || Object.keys(table.rowHeaders).length !== 1 || table.rows?.length !== weights.length) return null;
+ const bounds = weights.map(weightInPounds);
+ if (bounds.some((n, i) => Number.isNaN(n) || n <= 0 || (i > 0 && n <= bounds[i - 1]))) return null;
+ const index = bounds.findIndex(n => pounds <= n);
+ if (index < 0) return false;
+ const cells = table.rows[index]?.cells;
+ return cells?.length === 1 ? flatValue(cells[0], currency) : null;
+}
+
+export function summarizeService(service, pounds = 4) {
+ const currency = service.currencyCode ?? service.currency;
+ const groups = service.rateGroups || [];
+ const checks = groups.map(g => groupAtWeight(g, currency, pounds));
+ // A general service must cover all label groups plus its unlabelled fallback.
+ const hasFallback = groups.some(g => !(g.applicableShippingLabels || []).length);
+ const restricted = service.minimumOrderValue || service.minimumOrderValueTable || service.loyaltyPrograms?.length || service.storeConfig ||
+ (service.shipmentType && !['DELIVERY', 'SHIPMENT_TYPE_UNSPECIFIED'].includes(service.shipmentType));
+ const ships = service.active !== true ? false : restricted || !hasFallback || !checks.length || checks.includes(null) ? null : checks.every(Boolean);
+ return {
+ name: service.serviceName ?? service.name, currency, active: service.active === true,
+ checked_weight_lb: pounds, ships_4lb: ships,
+ rate_type: groups.length === 1 && groups[0].singleValue?.flatRate ? 'flat' : 'table_or_other',
+ flat_rate: groups.length === 1 ? groups[0].singleValue?.flatRate ?? null : null,
+ weight_bands: groups.flatMap(g => (g.mainTable?.rowHeaders?.weights || []).map(w => `${weightInPounds(w)}lb`)),
+ reason: ships === null ? 'Unsupported, restricted, or incomplete rate definition; manual verification required.' : ships ? 'Active service covers a 4 lb roll across all rate groups.' : 'Inactive service or no rate for a 4 lb roll.'
+ };
+}
+
+export function assessCanary(snapshot, baseline) {
+ const services = snapshot.shipping?.CA;
+ const ca = snapshot.status?.CA;
+ const code = 'missing_shipping_mismatch_of_shipping_method_and_offer_currency';
+ if (snapshot.errors?.length || !Array.isArray(services) || !ca || !Number.isFinite(ca.active) || !Number.isFinite(ca.disapproved) || !ca.codes) {
+ return { state: 'UNKNOWN', message: 'Required API evidence is missing or failed. No readiness claim.' };
+ }
+ const landed = services.some(s => s.active && s.currency === 'CAD' && s.ships_4lb === true);
+ if (!landed) {
+ const cad = services.filter(s => s.active && s.currency === 'CAD');
+ return cad.some(s => s.ships_4lb === null)
+ ? { state: 'UNKNOWN', message: 'CAD service exists but its 4 lb rate could not be evaluated.' }
+ : { state: 'HOLD', message: cad.length ? 'CAD service exists but does not cover a 4 lb roll.' : 'No active CAD service covers a 4 lb roll.' };
+ }
+ const baseCA = baseline?.status?.CA;
+ if (!baseCA || !Number.isFinite(baseCA.codes?.[code]) || baseline.errors?.length || (baseline.merchant && baseline.merchant !== snapshot.merchant)) {
+ return { state: 'UNKNOWN', message: 'Config landed; a valid matching baseline is required to measure recovery.' };
+ }
+ const drop = baseCA.codes[code] - (ca.codes[code] || 0);
+ const material = Math.max(50, Math.round(baseCA.codes[code] * 0.05));
+ if (ca.active > 0 && drop >= material) return { state: 'PASS', message: 'Canada config landed and the existing-offer canary improved. Re-feed remains separately gated.', currency_error_drop: drop };
+ return { state: 'REPROCESSING', message: 'Canada config landed; canary recovery is not yet material.', currency_error_drop: drop };
+}
diff --git a/verification/e2e-proof.json b/verification/e2e-proof.json
index 4dd283d..2886430 100644
--- a/verification/e2e-proof.json
+++ b/verification/e2e-proof.json
@@ -1,56 +1,70 @@
{
- "intent": "Harden TK-10993 Track C before any live Merchant feed-rule PATCH",
- "risk_tier": "R3 (external integration, read-only proof; production mutation deliberately withheld)",
- "environment": "Mac2 local scripts against Merchant account 146735262",
- "build_commit": "2792808",
- "timestamp": "2026-08-31T09:10:00.000Z",
- "baseline": {
- "result": "FAIL_CLOSED",
- "artifact": "data/track-c-canary-baseline-attempt.json",
- "assertion": "A complete immutable baseline was refused because 12 of 400 offer reads failed"
+ "ticket": "TK-10993-gmc-degraded-remediation-durable-feed-co",
+ "agent": "codex-run-10993",
+ "intent": "Correct false Canada weight-cap diagnosis and verify recovery without live changes",
+ "risk_tier": "R3 read-only integration; local code mutation",
+ "environment": "Mac studio; merchant 146735262; Shopify designer-laboratory-sandbox",
+ "timestamp": "2026-09-05T05:52:23.169361+00:00",
+ "base_commit": "78a8c7bf8af7b1f4226398e0a657de27f240fdc8",
+ "source_sha256": {
+ "tk10993-ca-canary-verify.mjs": "056a5dd330e206d3c12fcb38d6cbc287aa5f238794a812046b024dd9d95208bc",
+ "tk10993-shipping-verification.mjs": "a9bf3688eb4fbc65b8cb39075c1ec563da0a8ec681458e4c2899f422e5186d08",
+ "test/tk10993-shipping-verification.test.mjs": "1b5d2c3a3ceda18a04c24eb53f43f93769134feb1df99fa6ff799a3202f30b3e",
+ "data/tk10993-ca-canary-baseline.json": "6eeee0311ae8ff86f684b05bb6b7d20c3cf1555605e6f3f2baa9388a629af641",
+ "verification/tk10993-20260905-ca-final.json": "991c62531daa55bf29e4126bfaacc3d68f07fefc832705a172cd3c32492c9fc1"
},
- "checks": [
+ "baseline": "Existing 2026-09-04T03:07:40.489Z snapshot retained byte-for-byte; Canada active 0, disapproved 1112, currency errors 1027",
+ "commands": [
+ "node --test test/tk10993-shipping-verification.test.mjs",
+ "node --check tk10993-ca-canary-verify.mjs",
+ "git diff --check",
+ "node tk10993-ca-canary-verify.mjs --output=verification/tk10993-20260905-ca-final.json",
+ "node ~/.claude/yolo-queue/tk10993-trackB-exec/4-verify-ca-service.mjs"
+ ],
+ "assertions": [
{
- "boundary": "unit",
- "command": "node --test test/track-c-safety.test.mjs",
+ "check": "Regression + failure-path tests",
"verdict": "PASS",
- "assertion": "5/5 tests cover snapshot immutability, URL paths, identity/hash rejection, approval delta and disapproval/read-error failure"
+ "evidence": "8/8 including real Merchant amountMicros/POUND table, flat rate, inactive/restricted services, HTTP 401/410/503, GraphQL errors, missing status/baseline"
},
{
- "boundary": "live Merchant read",
- "command": "node link-price-override-ds.mjs",
+ "check": "Real API journey",
"verdict": "PASS",
- "assertion": "Current rule remained 10677010255 > 10683334493 > SELF; dry-run created zero snapshots and sent no PATCH"
+ "evidence": "OAuth token -> Merchant shipping GET -> Shopify market query -> all aggregate status pages -> baseline comparison -> new exclusive-create evidence JSON; exit 0"
},
{
- "boundary": "target/data source evidence",
- "command": "node reconcile-track-c-targets.mjs",
- "verdict": "FAIL",
- "artifact": "data/track-c-target-reconciliation.json",
- "assertion": "10,352 clean exact candidates; 400-row canary subset; retained apply result 388 success/12 failure; fresh reads identify the same 12 missing offers; ready_to_link=false"
+ "check": "Independent legacy shipping read",
+ "verdict": "PASS",
+ "evidence": "2026-09-05T05:45:15Z separate script confirms CAD 27/no cap"
},
{
- "boundary": "rollback",
- "command": "node link-price-override-ds-ROLLBACK.mjs --manifest <test-manifest>",
+ "check": "Persisted output",
"verdict": "PASS",
- "assertion": "Dry-run validated merchant/feed identity and SHA-256 before constructing restore command"
+ "evidence": "verification/tk10993-20260905-ca-final.json: active 787; disapproved 296; currency errors 279; CAD 27 flat/active/covers 4lb"
},
{
- "boundary": "full override artifact",
- "command": "node apply-full.mjs",
+ "check": "External write boundary",
"verdict": "PASS",
- "assertion": "Unarmed preflight found 10,352 rows, zero <=$4.26, zero non-US, zero missing fields; no insert fired"
+ "evidence": "Only OAuth token mint and read-only GraphQL query POST; Merchant GET only; no shipping/product/channel mutation"
+ },
+ {
+ "check": "Overall feed remediation complete",
+ "verdict": "SKIP",
+ "reason": "Additional feed reprocessing/recovery and residual feed writes remain; ticket must not be marked done."
}
],
- "negative_checks": [
- "Rollback without --manifest aborts",
- "Tampered snapshot hash is rejected",
- "Wrong merchant/feed identity is rejected",
- "Baseline with any read error is refused",
- "Reconciliation exits 2 while datasource membership is not exact"
- ],
- "side_effects": "No Merchant PATCH, product insert, Shopify write, database write, publish, deploy, email, spend, DNS, or remote push",
- "cleanup": "Temporary rollback E2E fixture retained under /tmp/track-c-rollback-e2e only; no external test state created",
- "overall_verdict": "PARTIAL_BLOCKED",
- "remaining_gate": "Track C is not ready to execute until the 12 missing canary offers are removed/replaced and a complete immutable pre-link baseline is captured"
+ "dtd": {
+ "path": "/tmp/dtd-TK10993-20260905-verifier-network",
+ "votes": {
+ "Claude": "abstain: zero cost",
+ "Codex": "A (CLI gpt-6-astra)",
+ "Qwen": "A (qwen3:14b)",
+ "Grok": "abstain",
+ "Kimi": "abstain",
+ "Muse": "abstain"
+ },
+ "post_decision": "FINAL KEEP; require actual applicability, not merely flat-rate parsing"
+ },
+ "cleanup_rollback": "Local git revert available; no remote test records or live mutations; historical Track C proof retained at verification/tk10993-track-c-historical-e2e-proof.json",
+ "verdict": "VERIFIER_PASS; OVERALL_TICKET_PARTIAL_GATED"
}
diff --git a/verification/tk10993-20260905-ca-final.json b/verification/tk10993-20260905-ca-final.json
new file mode 100644
index 0000000..4b53cbb
--- /dev/null
+++ b/verification/tk10993-20260905-ca-final.json
@@ -0,0 +1,105 @@
+{
+ "checked_at": "2026-09-05T05:51:14.597Z",
+ "merchant": "146735262",
+ "errors": [],
+ "markets": {
+ "ca": {
+ "enabled": true,
+ "catalogs": 1,
+ "active_catalogs": 1
+ },
+ "international": {
+ "enabled": true,
+ "catalogs": 1,
+ "active_catalogs": 1
+ },
+ "gb": {
+ "enabled": false,
+ "catalogs": 0,
+ "active_catalogs": 0
+ },
+ "us": {
+ "enabled": true,
+ "catalogs": 1,
+ "active_catalogs": 1
+ }
+ },
+ "shipping": {
+ "CA": [
+ {
+ "name": "weight_based_POUND_54097543219_Custom_rate_weight_based",
+ "currency": "CAD",
+ "active": true,
+ "checked_weight_lb": 4,
+ "ships_4lb": true,
+ "rate_type": "flat",
+ "flat_rate": {
+ "amountMicros": "27000000",
+ "currencyCode": "CAD"
+ },
+ "weight_bands": [],
+ "reason": "Active service covers a 4 lb roll across all rate groups."
+ }
+ ],
+ "GB": [
+ {
+ "name": "weight_based_POUND_54097543219_Custom_rate_weight_based",
+ "currency": "USD",
+ "active": true,
+ "checked_weight_lb": 4,
+ "ships_4lb": false,
+ "rate_type": "table_or_other",
+ "flat_rate": null,
+ "weight_bands": [
+ "1lb",
+ "Infinitylb"
+ ],
+ "reason": "Inactive service or no rate for a 4 lb roll."
+ }
+ ]
+ },
+ "status": {
+ "US": {
+ "active": 63244,
+ "disapproved": 1830,
+ "pending": 14,
+ "expiring": 1469,
+ "codes": {
+ "image_link_broken": 12,
+ "image_too_generic": 43,
+ "image_link_internal_error": 2,
+ "image_too_small": 28,
+ "image_unwanted_overlays": 1,
+ "image_single_color": 4,
+ "landing_page_error": 1507,
+ "price_mismatch": 12,
+ "guns_parts_policy_violation": 4,
+ "illegal_drugs_policy_violation": 7,
+ "live_animals_policy_violation": 1,
+ "tobacco_policy_violation": 16,
+ "vehicles_policy_violation": 1,
+ "healthcare_pdt_policy_violation": 1,
+ "item_missing_required_attribute": 9,
+ "missing_shipping_weight": 187,
+ "image_link_pending_crawl": 67
+ }
+ },
+ "CA": {
+ "active": 787,
+ "disapproved": 296,
+ "pending": 0,
+ "expiring": 0,
+ "codes": {
+ "landing_page_error": 3,
+ "price_mismatch": 1,
+ "missing_shipping": 17,
+ "missing_shipping_mismatch_of_shipping_method_and_offer_currency": 279
+ }
+ }
+ },
+ "verdict": {
+ "state": "PASS",
+ "message": "Canada config landed and the existing-offer canary improved. Re-feed remains separately gated.",
+ "currency_error_drop": 748
+ }
+}
\ No newline at end of file
diff --git a/verification/tk10993-20260905-ca-live.json b/verification/tk10993-20260905-ca-live.json
new file mode 100644
index 0000000..d609a23
--- /dev/null
+++ b/verification/tk10993-20260905-ca-live.json
@@ -0,0 +1,105 @@
+{
+ "checked_at": "2026-09-05T05:49:45.715Z",
+ "merchant": "146735262",
+ "errors": [],
+ "markets": {
+ "ca": {
+ "enabled": true,
+ "catalogs": 1,
+ "active_catalogs": 1
+ },
+ "international": {
+ "enabled": true,
+ "catalogs": 1,
+ "active_catalogs": 1
+ },
+ "gb": {
+ "enabled": false,
+ "catalogs": 0,
+ "active_catalogs": 0
+ },
+ "us": {
+ "enabled": true,
+ "catalogs": 1,
+ "active_catalogs": 1
+ }
+ },
+ "shipping": {
+ "CA": [
+ {
+ "name": "weight_based_POUND_54097543219_Custom_rate_weight_based",
+ "currency": "CAD",
+ "active": true,
+ "checked_weight_lb": 4,
+ "ships_4lb": true,
+ "rate_type": "flat",
+ "flat_rate": {
+ "amountMicros": "27000000",
+ "currencyCode": "CAD"
+ },
+ "weight_bands": [],
+ "reason": "Active service covers a 4 lb roll across all rate groups."
+ }
+ ],
+ "GB": [
+ {
+ "name": "weight_based_POUND_54097543219_Custom_rate_weight_based",
+ "currency": "USD",
+ "active": true,
+ "checked_weight_lb": 4,
+ "ships_4lb": null,
+ "rate_type": "table_or_other",
+ "flat_rate": null,
+ "weight_bands": [
+ "undefinedPOUND",
+ "undefinedPOUND"
+ ],
+ "reason": "Unsupported, restricted, or incomplete rate definition; manual verification required."
+ }
+ ]
+ },
+ "status": {
+ "US": {
+ "active": 63244,
+ "disapproved": 1830,
+ "pending": 14,
+ "expiring": 1479,
+ "codes": {
+ "image_link_broken": 12,
+ "image_too_generic": 43,
+ "image_link_internal_error": 2,
+ "image_too_small": 28,
+ "image_unwanted_overlays": 1,
+ "image_single_color": 4,
+ "landing_page_error": 1507,
+ "price_mismatch": 12,
+ "guns_parts_policy_violation": 4,
+ "illegal_drugs_policy_violation": 7,
+ "live_animals_policy_violation": 1,
+ "tobacco_policy_violation": 16,
+ "vehicles_policy_violation": 1,
+ "healthcare_pdt_policy_violation": 1,
+ "item_missing_required_attribute": 9,
+ "missing_shipping_weight": 187,
+ "image_link_pending_crawl": 67
+ }
+ },
+ "CA": {
+ "active": 787,
+ "disapproved": 296,
+ "pending": 0,
+ "expiring": 0,
+ "codes": {
+ "landing_page_error": 3,
+ "price_mismatch": 1,
+ "missing_shipping": 17,
+ "missing_shipping_mismatch_of_shipping_method_and_offer_currency": 279
+ }
+ }
+ },
+ "verdict": {
+ "state": "PASS",
+ "message": "Canada config landed and the existing-offer canary improved. Re-feed remains separately gated.",
+ "currency_error_drop": 748
+ }
+}
\ No newline at end of file
diff --git a/verification/tk10993-track-c-historical-e2e-proof.json b/verification/tk10993-track-c-historical-e2e-proof.json
new file mode 100644
index 0000000..4dd283d
--- /dev/null
+++ b/verification/tk10993-track-c-historical-e2e-proof.json
@@ -0,0 +1,56 @@
+{
+ "intent": "Harden TK-10993 Track C before any live Merchant feed-rule PATCH",
+ "risk_tier": "R3 (external integration, read-only proof; production mutation deliberately withheld)",
+ "environment": "Mac2 local scripts against Merchant account 146735262",
+ "build_commit": "2792808",
+ "timestamp": "2026-08-31T09:10:00.000Z",
+ "baseline": {
+ "result": "FAIL_CLOSED",
+ "artifact": "data/track-c-canary-baseline-attempt.json",
+ "assertion": "A complete immutable baseline was refused because 12 of 400 offer reads failed"
+ },
+ "checks": [
+ {
+ "boundary": "unit",
+ "command": "node --test test/track-c-safety.test.mjs",
+ "verdict": "PASS",
+ "assertion": "5/5 tests cover snapshot immutability, URL paths, identity/hash rejection, approval delta and disapproval/read-error failure"
+ },
+ {
+ "boundary": "live Merchant read",
+ "command": "node link-price-override-ds.mjs",
+ "verdict": "PASS",
+ "assertion": "Current rule remained 10677010255 > 10683334493 > SELF; dry-run created zero snapshots and sent no PATCH"
+ },
+ {
+ "boundary": "target/data source evidence",
+ "command": "node reconcile-track-c-targets.mjs",
+ "verdict": "FAIL",
+ "artifact": "data/track-c-target-reconciliation.json",
+ "assertion": "10,352 clean exact candidates; 400-row canary subset; retained apply result 388 success/12 failure; fresh reads identify the same 12 missing offers; ready_to_link=false"
+ },
+ {
+ "boundary": "rollback",
+ "command": "node link-price-override-ds-ROLLBACK.mjs --manifest <test-manifest>",
+ "verdict": "PASS",
+ "assertion": "Dry-run validated merchant/feed identity and SHA-256 before constructing restore command"
+ },
+ {
+ "boundary": "full override artifact",
+ "command": "node apply-full.mjs",
+ "verdict": "PASS",
+ "assertion": "Unarmed preflight found 10,352 rows, zero <=$4.26, zero non-US, zero missing fields; no insert fired"
+ }
+ ],
+ "negative_checks": [
+ "Rollback without --manifest aborts",
+ "Tampered snapshot hash is rejected",
+ "Wrong merchant/feed identity is rejected",
+ "Baseline with any read error is refused",
+ "Reconciliation exits 2 while datasource membership is not exact"
+ ],
+ "side_effects": "No Merchant PATCH, product insert, Shopify write, database write, publish, deploy, email, spend, DNS, or remote push",
+ "cleanup": "Temporary rollback E2E fixture retained under /tmp/track-c-rollback-e2e only; no external test state created",
+ "overall_verdict": "PARTIAL_BLOCKED",
+ "remaining_gate": "Track C is not ready to execute until the 12 missing canary offers are removed/replaced and a complete immutable pre-link baseline is captured"
+}
← 78a8c7b auto-data-snapshot: 2026-09-04T14:13:19 (4 data files) — dat
·
back to Gmc Titlefix
·
Audit remaining GMC feed defects and consolidate approval ev 7a897c7 →