← back to Dw Activation Debug TK11314
scripts/verify-canary-independent.js
89 lines
#!/usr/bin/env node
'use strict';
// Independent verifier: no activation modules, gate functions or mutations.
// Before/after assertions use fresh Shopify queries, public HTTP and ledger reads.
const fs = require('node:fs');
const path = require('node:path');
const os = require('node:os');
const assert = require('node:assert/strict');
const [phase, productSnapshot, evidenceDirectory] = process.argv.slice(2);
if (!['before', 'after'].includes(phase) || !evidenceDirectory) throw new Error('Usage: verifier before|after <product-snapshot> <evidence-dir>');
const env = fs.readFileSync(path.join(os.homedir(), 'Projects/secrets-manager/.env'), 'utf8');
const token = (env.match(/^SHOPIFY_ADMIN_TOKEN=(.*)$/m) || [])[1]?.trim().replace(/^['"]|['"]$/g, '');
const day = new Date().toISOString().slice(0, 10);
const out = path.join(os.homedir(), 'Projects/dw-rotation-activator/out');
const ledgerPath = path.join(out, `activation-ledger-${day}.json`);
const auditPath = path.join(out, `rotation-activations-${day}.jsonl`);
function ledger() { return fs.existsSync(ledgerPath) ? JSON.parse(fs.readFileSync(ledgerPath)) : { date: day, used: 0, file_absent: true }; }
const query = `query($ids:[ID!]!){nodes(ids:$ids){... on Product{
id title handle vendor status tags publishedAt onlineStoreUrl
googlePublished:publishedOnPublication(publicationId:"gid://shopify/Publication/29646651457")
variants(first:100){nodes{id sku title price} pageInfo{hasNextPage}}
images(first:5){nodes{url}}
metafields(first:100){nodes{namespace key value} pageInfo{hasNextPage}}
resourcePublicationsV2(first:50){nodes{isPublished publishDate publication{id name}} pageInfo{hasNextPage}}
}}}`;
async function readProducts(ids) {
const response = await fetch('https://designer-laboratory-sandbox.myshopify.com/admin/api/2026-07/graphql.json', {
method: 'POST', headers: { 'X-Shopify-Access-Token': token, 'Content-Type': 'application/json' },
body: JSON.stringify({ query, variables: { ids } }), signal: AbortSignal.timeout(30000),
});
const result = await response.json();
assert.equal(response.status, 200); assert.equal(result.errors, undefined, JSON.stringify(result.errors));
assert.equal(result.data.nodes.length, ids.length);
assert.ok(result.data.nodes.every(Boolean));
return result.data.nodes;
}
async function main() {
fs.mkdirSync(evidenceDirectory, { recursive: true });
if (phase === 'before') {
const snapshot = JSON.parse(fs.readFileSync(productSnapshot));
const ids = snapshot.products.slice(0, 50).map(p => p.id);
const products = await readProducts(ids);
assert.ok(products.every(p => p.status === 'DRAFT'));
const firstDesigntex = products.find(p => p.id.endsWith('/7867560886323'));
assert.ok(firstDesigntex, 'Expected canary must have a before-state');
assert.equal(firstDesigntex.googlePublished, false, 'Canary must not already be enrolled in Google publication');
const result = { timestamp: new Date().toISOString(), products, ledger: ledger(),
audit_path: auditPath, audit_offset: fs.statSync(auditPath).size, mutations: 0 };
fs.writeFileSync(path.join(evidenceDirectory, 'canary-before.json'), JSON.stringify(result, null, 2)+'\n');
console.log(JSON.stringify({ phase, products: products.length, expected_canary: firstDesigntex.id,
expected_status: firstDesigntex.status, google_published: firstDesigntex.googlePublished, ledger: result.ledger }));
return;
}
const before = JSON.parse(fs.readFileSync(path.join(evidenceDirectory, 'canary-before.json')));
const appended = fs.readFileSync(before.audit_path).subarray(before.audit_offset).toString().trim();
const rows = appended ? appended.split('\n').map(JSON.parse) : [];
const activated = rows.filter(r => r.action === 'activated');
assert.equal(activated.length, 1, `Expected exactly one activation, got ${activated.length}`);
const activation = activated[0];
const previous = before.products.find(p => p.id === activation.shopify_id);
assert.ok(previous, 'No before-state for activated product');
const [product] = await readProducts([activation.shopify_id]);
assert.equal(previous.status, 'DRAFT'); assert.equal(product.status, 'ACTIVE');
assert.equal(product.googlePublished, false);
assert.ok(product.tags.includes('New Arrival'));
assert.deepEqual(product.variants, previous.variants, 'Activation changed variant prices/SKUs');
assert.ok(product.resourcePublicationsV2.nodes.some(p => p.isPublished && p.publication.name === 'Online Store'));
assert.ok(!product.resourcePublicationsV2.pageInfo.hasNextPage, 'Incomplete publication read');
const currentLedger = ledger();
assert.equal(currentLedger.used, before.ledger.used + 1);
assert.equal(activation.passes, true); assert.equal(activation.settlement.verdict, 'PASS');
const storefrontUrl = `https://www.designerwallcoverings.com/products/${product.handle}`;
const response = await fetch(storefrontUrl, { signal: AbortSignal.timeout(30000) });
const html = await response.text();
assert.equal(response.status, 200, 'Canary storefront URL failed');
assert.ok(html.includes(product.id.split('/').pop()), 'Storefront response does not identify the actual product');
const report = { timestamp: new Date().toISOString(), verdict: 'PASS', product, activation,
before_status: previous.status, ledger_before: before.ledger, ledger_after: currentLedger,
storefront: { url: storefrontUrl, status: response.status, product_id_found: true },
independent_verifier: 'Separate read-only implementation using Admin API 2026-07 and public storefront HTTP',
mutations: 0, audit_rows_after_offset: rows.length };
fs.writeFileSync(path.join(evidenceDirectory, 'canary-verified.json'), JSON.stringify(report, null, 2)+'\n');
console.log(JSON.stringify({ verdict: report.verdict, sku: product.variants.nodes[0]?.sku, id: product.id,
status: product.status, googlePublished: product.googlePublished,
published: product.resourcePublicationsV2.nodes.filter(p=>p.isPublished).map(p=>p.publication.name),
ledger: currentLedger.used, storefront: report.storefront }));
}
main().catch(error => { console.error(error.message); process.exitCode = 1; });