← back to Interiordesignershowroom
add tracked-link coverage canary: samples live /go redirects, alerts on worsening CJ-tracking coverage (CNCP), daily 3:15am
325e3999d24740d6243cc62133dc295114afd0ed · 2026-08-02 00:46:18 -0700 · Steve Abrams
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
A scripts/tracked-coverage-canary.js
Diff
commit 325e3999d24740d6243cc62133dc295114afd0ed
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Sun Aug 2 00:46:18 2026 -0700
add tracked-link coverage canary: samples live /go redirects, alerts on worsening CJ-tracking coverage (CNCP), daily 3:15am
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
scripts/tracked-coverage-canary.js | 91 ++++++++++++++++++++++++++++++++++++++
1 file changed, 91 insertions(+)
diff --git a/scripts/tracked-coverage-canary.js b/scripts/tracked-coverage-canary.js
new file mode 100644
index 0000000..5be3442
--- /dev/null
+++ b/scripts/tracked-coverage-canary.js
@@ -0,0 +1,91 @@
+#!/usr/bin/env node
+// Tracked-link coverage canary for Interior Designer's Showroom.
+// The whole affiliate model earns $0 if /go/:id redirects to RAW merchant URLs
+// instead of CJ tracked click-links (which only mint once advertisers are JOINED
+// in CJ). This canary samples the LIVE site's /go redirects and measures what
+// fraction land on a CJ tracked domain. HTTP-only, read-only — no DB, no tunnel.
+//
+// Classifies OK (>=50% tracked) / WARN (1-49%) / CRITICAL (0%). Alerts (CNCP
+// parking-lot card) only on a worsening transition, never on steady-state or
+// recovery. Writes data/coverage-canary.json every run (heartbeat + last state).
+'use strict';
+const https = require('https');
+const fs = require('fs');
+const path = require('path');
+
+const SITE = 'https://interiordesignershowroom.com';
+const CNCP = 'http://127.0.0.1:3333/api/parking-lot';
+const STATE = path.join(__dirname, '..', 'data', 'coverage-canary.json');
+const CJ_TRACKED = /dpbolvw|anrdoezrs|kqzyfj|tkqlhce|jdoqocy|cj\.com|emjcd|ftjcd|awltovhc/i;
+const SAMPLE = 20;
+
+function get(url, { method = 'GET', followRedirect = true } = {}) {
+ return new Promise((resolve) => {
+ const req = https.request(url, { method, timeout: 10000 }, (res) => {
+ let body = '';
+ res.on('data', (c) => { body += c; });
+ res.on('end', () => resolve({ status: res.statusCode, headers: res.headers, body }));
+ });
+ req.on('error', () => resolve({ status: 0, headers: {}, body: '' }));
+ req.on('timeout', () => { req.destroy(); resolve({ status: 0, headers: {}, body: '' }); });
+ req.end();
+ });
+}
+
+async function main() {
+ // 1. harvest product ids off the live /shop grid
+ const shop = await get(`${SITE}/shop`);
+ const ids = [...new Set((shop.body.match(/data-id="(\d+)"/g) || [])
+ .map((m) => m.match(/\d+/)[0]))];
+ if (!ids.length) { return finish('UNKNOWN', 0, 0, 'could not read product ids from /shop'); }
+
+ // 2. sample /go/:id redirects, inspect the Location header (302 -> merchant)
+ const pick = ids.sort(() => 0.5 - Math.random()).slice(0, SAMPLE);
+ let tracked = 0, checked = 0;
+ for (const id of pick) {
+ const r = await get(`${SITE}/go/${id}`, { method: 'HEAD' });
+ const loc = r.headers.location || '';
+ if (!loc) continue;
+ checked++;
+ if (CJ_TRACKED.test(loc)) tracked++;
+ }
+ if (!checked) return finish('UNKNOWN', 0, 0, 'no /go redirects resolved');
+
+ const pct = Math.round((tracked / checked) * 100);
+ const status = pct === 0 ? 'CRITICAL' : pct < 50 ? 'WARN' : 'OK';
+ return finish(status, tracked, checked, `${pct}% tracked (${tracked}/${checked})`);
+}
+
+function rank(s) { return { OK: 0, WARN: 1, CRITICAL: 2, UNKNOWN: -1 }[s] ?? -1; }
+
+async function finish(status, tracked, checked, detail) {
+ let prev = {};
+ try { prev = JSON.parse(fs.readFileSync(STATE, 'utf8')); } catch (e) {}
+ const now = { status, tracked, checked, detail, ts: new Date().toISOString() };
+ try { fs.mkdirSync(path.dirname(STATE), { recursive: true }); fs.writeFileSync(STATE, JSON.stringify(now, null, 2)); } catch (e) {}
+
+ // alert ONLY on a worsening transition (e.g. OK->WARN, WARN->CRITICAL)
+ const worsened = rank(status) > rank(prev.status || 'OK');
+ console.log(`[tracked-coverage] ${status} — ${detail}${worsened ? ' (WORSENED, alerting)' : ''}`);
+ if (worsened && (status === 'WARN' || status === 'CRITICAL')) {
+ const title = `IDC affiliate tracking ${status}: ${detail}`;
+ const note = `interiordesignershowroom.com /go redirects are ${detail}. `
+ + `Untracked links earn $0 commission. Fix = approve CJ advertiser programs (Wayfair, Herman Miller, etc.) `
+ + `in members.cj.com, then re-run scripts/ingest-cj-catalog.js. Prev: ${prev.status || 'n/a'}.`;
+ await postCncp(title, note);
+ }
+ process.exit(0);
+}
+
+function postCncp(title, note) {
+ return new Promise((resolve) => {
+ const data = JSON.stringify({ project: 'interiordesignershowroom', title, note, source: 'tracked-coverage-canary' });
+ const req = require('http').request(CNCP, {
+ method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data) }, timeout: 5000,
+ }, (res) => { res.on('data', () => {}); res.on('end', resolve); });
+ req.on('error', resolve); req.on('timeout', () => { req.destroy(); resolve(); });
+ req.write(data); req.end();
+ });
+}
+
+main().catch((e) => { console.error(e); process.exit(1); });
← 64a5449 feat: affiliate cart — collect pieces, checkout opens each p
·
back to Interiordesignershowroom
·
feat: auto guide generator — gen-guide.js builds a room (sha 213ca8b →