← 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; });