← back to Rebel Walls Push

scripts/revalidate-TK11248.js

134 lines

'use strict';
/* READ-ONLY revalidation for the TK-11248 batch (TK-11238 + TK-10405).
 * Confirms both live targets still match their characterization and builds
 * restore maps. NO WRITES. $0. Exit 0 only if BOTH targets are exactly as
 * expected and safe to execute once approval is logged.
 *
 * Usage: node revalidate-TK11248.js
 * Writes: data/TK11248-revalidation-<ts>.json  (evidence + restore maps)
 */
const fs = require('fs');
const path = require('path');
const { gqlRetry, DOMAIN } = require('./_shop');

// ---- TK-11238: Aries title fix ------------------------------------------------
const ARIES_ID = '7800009752627';
const ARIES_GID = `gid://shopify/Product/${ARIES_ID}`;
const ARIES_OLD_TITLE = 'Aries - Single Color Commercial Wallcovering | DW Commercial Surfaces';
const ARIES_NEW_TITLE = 'Aries - Akoya Type II Vinyl Wallcovering | Hollywood Wallcoverings';

// ---- TK-10405: 12 Rebel Walls Default-Title variant deletes -------------------
const RW12 = [
  ['6679729078323', 'A City Rises (Class 2 $80.48 anomaly)'],
  ['6679739629619', 'A Priori'],
  ['7851164303411', '3D Art - Multi'],
  ['7851164336179', '3D Boxes - Gray'],
  ['7851164368947', '3D Wave - Gray'],
  ['7851164401715', '3D Wave - Marble'],
  ['7851164434483', '3D Wave - Sand'],
  ['7851164500019', '3D Wave - White'],
  ['7851164532787', '70S Retro - Beige'],
  ['7851164565555', 'A City Rises - Dark Blue'],
  ['7851164598323', 'A Priori - Taupe'],
  ['7851164631091', 'A Priori - Soft'],
];

const PRODUCT_Q = `query($id: ID!) {
  product(id: $id) {
    id legacyResourceId title handle status vendor
    variants(first: 20) {
      nodes { id legacyResourceId title sku price selectedOptions { name value } }
    }
  }
}`;

async function main() {
  const ts = new Date().toISOString().replace(/[:.]/g, '-');
  const report = { ts, store: DOMAIN, tk11238: null, tk10405: null, gate: { ready_11238: false, ready_10405: false } };

  // ---- TK-11238 revalidation ----
  const ar = await gqlRetry(PRODUCT_Q, { id: ARIES_GID }, 'aries');
  const ap = ar.json && ar.json.data && ar.json.data.product;
  if (!ap) {
    report.tk11238 = { error: 'product not found', raw: ar.json };
  } else {
    const titleMatches = ap.title === ARIES_OLD_TITLE;
    const isActive = ap.status === 'ACTIVE';
    report.tk11238 = {
      id: ap.legacyResourceId, gid: ap.id, handle: ap.handle, vendor: ap.vendor, status: ap.status,
      current_title: ap.title,
      expected_old_title: ARIES_OLD_TITLE,
      proposed_new_title: ARIES_NEW_TITLE,
      title_matches_expected: titleMatches,
      is_active: isActive,
      undo: { field: 'title', restore_to: ap.title },
    };
    report.gate.ready_11238 = titleMatches && isActive;
  }

  // ---- TK-10405 revalidation ----
  const rows = [];
  for (const [pid, label] of RW12) {
    const r = await gqlRetry(PRODUCT_Q, { id: `gid://shopify/Product/${pid}` }, `rw:${pid}`);
    const p = r.json && r.json.data && r.json.data.product;
    if (!p) { rows.push({ pid, label, error: 'not found', raw: r.json }); continue; }
    const vs = p.variants.nodes;
    const byTitle = t => vs.filter(v => v.title === t);
    const dt = byTitle('Default Title');
    const mural = vs.find(v => /Mural \(per m²\)|Mural \(per m2\)/.test(v.title));
    const sample = vs.find(v => v.title === 'Sample' || /-Sample$/i.test(v.sku || ''));
    rows.push({
      pid, label, product_status: p.status, handle: p.handle,
      variant_count: vs.length,
      default_title_variants: dt.map(v => ({ id: v.id, legacyId: v.legacyResourceId, sku: v.sku, price: v.price,
        options: v.selectedOptions })),
      has_mural_survivor: !!mural,
      mural: mural ? { id: mural.id, sku: mural.sku, price: mural.price } : null,
      has_sample_survivor: !!sample,
      sample: sample ? { id: sample.id, sku: sample.sku, price: sample.price } : null,
      all_variants: vs.map(v => ({ id: v.id, title: v.title, sku: v.sku, price: v.price })),
    });
  }
  // safe iff: each product has exactly one Default Title variant to delete, AND both
  // Mural + Sample survivors remain (so the delete cannot orphan the sellable/sample).
  const perProductSafe = rows.map(row => ({
    pid: row.pid, label: row.label,
    ok: !row.error && Array.isArray(row.default_title_variants) && row.default_title_variants.length === 1 &&
        row.has_mural_survivor && row.has_sample_survivor && row.variant_count === 3,
    dt_count: row.default_title_variants ? row.default_title_variants.length : null,
    variant_count: row.variant_count,
  }));
  report.tk10405 = { products: rows, per_product_safe: perProductSafe };
  report.gate.ready_10405 = perProductSafe.length === 12 && perProductSafe.every(x => x.ok);

  const outDir = path.join(__dirname, '..', 'data');
  const outFile = path.join(outDir, `TK11248-revalidation-${ts}.json`);
  fs.writeFileSync(outFile, JSON.stringify(report, null, 2));

  // ---- console summary ----
  console.log(`\n=== TK-11248 REVALIDATION (READ-ONLY, $0) @ ${ts} ===`);
  console.log(`store: ${DOMAIN}`);
  console.log(`\n[TK-11238 Aries title]`);
  if (report.tk11238.error) console.log(`  ERROR: ${report.tk11238.error}`);
  else {
    console.log(`  product ${report.tk11238.id}  status=${report.tk11238.status}  handle=${report.tk11238.handle}  vendor="${report.tk11238.vendor}"`);
    console.log(`  current title : "${report.tk11238.current_title}"`);
    console.log(`  title matches expected old? ${report.tk11238.title_matches_expected}`);
    console.log(`  is active?                  ${report.tk11238.is_active}`);
    console.log(`  -> proposed new title: "${report.tk11238.proposed_new_title}"`);
    console.log(`  READY_11238=${report.gate.ready_11238}`);
  }
  console.log(`\n[TK-10405 Rebel Walls 12 Default-Title deletes]`);
  for (const s of perProductSafe) {
    const row = rows.find(r => r.pid === s.pid);
    const dt = row && row.default_title_variants && row.default_title_variants[0];
    console.log(`  ${s.pid}  ${s.ok ? 'OK ' : 'HOLD'}  variants=${s.variant_count} dt=${s.dt_count}  ${dt ? `[del ${dt.sku} @ $${dt.price}]` : ''}  mural=${row&&row.has_mural_survivor} sample=${row&&row.has_sample_survivor}  ${s.label}`);
  }
  console.log(`  READY_10405=${report.gate.ready_10405}  (${perProductSafe.filter(x=>x.ok).length}/12 safe)`);
  console.log(`\nEvidence -> ${outFile}`);
  console.log(`\nGATE: ready_11238=${report.gate.ready_11238}  ready_10405=${report.gate.ready_10405}`);
  console.log(`NO WRITES FIRED. Awaiting explicit exact approval logged on TK-11248.\n`);
}

main().catch(e => { console.error('FATAL', e); process.exit(2); });