← back to Designerwallcoverings
scripts/c34-osborne-gaston/APPLY-blank-reverify-guard.sh
109 lines
#!/usr/bin/env bash
# ============================================================================
# APPLY the drain-time live-blank re-verify guard to the Kamatera worker.
# RUN ON KAMATERA (operates on the local /root path). Steve-gated deploy.
#
# scp ~/Projects/designerwallcoverings/scripts/c34-osborne-gaston/APPLY-blank-reverify-guard.sh my-server:/tmp/
# ssh my-server "bash /tmp/APPLY-blank-reverify-guard.sh"
#
# Idempotent: if the guard is already present it exits 0 without touching the
# file. Anchored: refuses to edit if either insertion anchor isn't found
# (so a future worker.js refactor can't cause a silent mis-splice).
# Backs up to worker.js.pre-blankreverify.<ts>, syntax-checks (node -c),
# and restarts via pm2. On syntax failure it auto-restores the backup.
# DTD verdict A (2026-06-18, 2/3) — vp-dw-commerce + vp-engineering APPROVE.
# ============================================================================
set -euo pipefail
W=/root/DW-Agents/shopify-queue-worker/worker.js
TS=$(date +%Y%m%d-%H%M%S)
BAK="${W}.pre-blankreverify.${TS}"
[ -f "$W" ] || { echo "FATAL: $W not found"; exit 1; }
if grep -q 'parseMetafieldCreateJob' "$W"; then
echo "Already applied (parseMetafieldCreateJob present) — no-op."
exit 0
fi
cp -p "$W" "$BAK"
echo "Backup → $BAK"
node <<'NODE'
const fs = require('fs');
const W = '/root/DW-Agents/shopify-queue-worker/worker.js';
let s = fs.readFileSync(W, 'utf8');
const HELPERS = `
// --- DRAIN-TIME LIVE BLANK RE-VERIFY (officer-required for c34 specs push) ---
function parseMetafieldCreateJob(job) {
if (job.method !== 'POST') return null;
const m = /^\\/products\\/(\\d+)\\/metafields\\.json$/.exec(job.endpoint || '');
if (!m) return null;
const payload = typeof job.payload === 'string' ? JSON.parse(job.payload) : job.payload;
const mf = payload && payload.metafield;
if (!mf || mf.namespace !== 'custom' || !mf.key) return null;
return { productId: m[1], namespace: mf.namespace, key: mf.key };
}
async function liveMetafieldAlreadyFilled(productId, key, token) {
const res = await shopifyRequest('GET', \`/products/\${productId}/metafields.json?namespace=custom&key=\${encodeURIComponent(key)}\`, null, token);
const list = (res.body && res.body.metafields) || [];
const hit = list.find(x => x.namespace === 'custom' && x.key === key);
const val = hit && hit.value;
return typeof val === 'string' ? val.trim().length > 0 : (val != null);
}
`;
const GUARD = `
// --- DRAIN-TIME LIVE BLANK RE-VERIFY (blank-fill-only invariant at the drain) ---
const mfJob = (() => { try { return parseMetafieldCreateJob(job); } catch { return null; } })();
if (mfJob) {
try {
const filled = await liveMetafieldAlreadyFilled(mfJob.productId, mfJob.key, token);
if (filled) {
await pool.query(
\`UPDATE shopify_api_queue SET status = 'done', response_body = $2, error = $3, completed_at = NOW() WHERE id = $1\`,
[job.id,
JSON.stringify({ blank_reverify_skip: true, product_id: mfJob.productId, key: mfJob.key }),
\`BLANK-REVERIFY SKIP: custom.\${mfJob.key} already non-null live\`]
);
await pool.query(\`SELECT pg_notify('shopify_queue_done', $1)\`, [String(job.id)]);
console.log(\`[blank-reverify] #\${job.id} custom.\${mfJob.key} already filled on product \${mfJob.productId} — skipped write\`);
return;
}
} catch (rvErr) {
console.error(\`[blank-reverify] #\${job.id} guard error (proceeding with write): \${rvErr.message}\`);
}
}
`;
// Anchor 1: top-level helpers go immediately before processJob's declaration.
const A1 = 'async function processJob(job) {';
if (s.indexOf(A1) === -1) { console.error('FATAL: anchor A1 (processJob declaration) not found'); process.exit(2); }
if (s.indexOf(A1) !== s.lastIndexOf(A1)) { console.error('FATAL: anchor A1 not unique'); process.exit(2); }
s = s.replace(A1, HELPERS + A1);
// Anchor 2: the guard block goes immediately before the main shopifyRequest write.
const A2 = ' try {\n const res = await shopifyRequest(job.method, job.endpoint, job.payload, token);';
if (s.indexOf(A2) === -1) { console.error('FATAL: anchor A2 (main shopifyRequest write) not found'); process.exit(2); }
if (s.indexOf(A2) !== s.lastIndexOf(A2)) { console.error('FATAL: anchor A2 not unique'); process.exit(2); }
s = s.replace(A2, GUARD + A2);
fs.writeFileSync(W, s);
console.log('Inserted helpers + guard block.');
NODE
if node -c "$W"; then
echo "Syntax OK."
else
echo "SYNTAX FAILED — restoring backup."
cp -p "$BAK" "$W"
echo "Restored $W from $BAK. No restart performed."
exit 3
fi
pm2 restart shopify-queue-worker --update-env
echo "Done. Guard live. Rollback if needed: cp -p $BAK $W && pm2 restart shopify-queue-worker"