← back to Commercialrealestate
add CRCP origin-vs-live drift canary (DTD verdict D)
b4b3c10e288f6d1c0a0c3dd042696bf68f0923c1 · 2026-07-16 17:41:35 -0700 · Steve
Read-only sha256 compare of front-end assets origin(mac3) vs live(Kamatera);
worsening-only CNCP+email alert when live falls behind. Fixes the actual
9-day-silent-rot failure (invisibility) without an auto-push foot-gun.
Verified: baseline-silent, catches injected drift as WORSENING, recovery silent.
Scheduling (launchd, 6h) is a Steve-paste.
Files touched
A data/crcp-drift-state.jsonA deploy/com.steve.crcp-drift-canary.plistA scripts/crcp-drift-canary.js
Diff
commit b4b3c10e288f6d1c0a0c3dd042696bf68f0923c1
Author: Steve <steve@designerwallcoverings.com>
Date: Thu Jul 16 17:41:35 2026 -0700
add CRCP origin-vs-live drift canary (DTD verdict D)
Read-only sha256 compare of front-end assets origin(mac3) vs live(Kamatera);
worsening-only CNCP+email alert when live falls behind. Fixes the actual
9-day-silent-rot failure (invisibility) without an auto-push foot-gun.
Verified: baseline-silent, catches injected drift as WORSENING, recovery silent.
Scheduling (launchd, 6h) is a Steve-paste.
---
data/crcp-drift-state.json | 5 ++
deploy/com.steve.crcp-drift-canary.plist | 23 +++++++
scripts/crcp-drift-canary.js | 107 +++++++++++++++++++++++++++++++
3 files changed, 135 insertions(+)
diff --git a/data/crcp-drift-state.json b/data/crcp-drift-state.json
new file mode 100644
index 0000000..44e2c59
--- /dev/null
+++ b/data/crcp-drift-state.json
@@ -0,0 +1,5 @@
+{
+ "count": 0,
+ "ts": "2026-07-17T00:41:04.620Z",
+ "drifted": []
+}
\ No newline at end of file
diff --git a/deploy/com.steve.crcp-drift-canary.plist b/deploy/com.steve.crcp-drift-canary.plist
new file mode 100644
index 0000000..1d8d133
--- /dev/null
+++ b/deploy/com.steve.crcp-drift-canary.plist
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>Label</key>
+ <string>com.steve.crcp-drift-canary</string>
+ <key>ProgramArguments</key>
+ <array>
+ <string>/opt/homebrew/bin/node</string>
+ <string>/Users/macstudio3/Projects/commercialrealestate/scripts/crcp-drift-canary.js</string>
+ </array>
+ <key>WorkingDirectory</key>
+ <string>/Users/macstudio3/Projects/commercialrealestate</string>
+ <key>StartInterval</key>
+ <integer>21600</integer>
+ <key>RunAtLoad</key>
+ <true/>
+ <key>StandardOutPath</key>
+ <string>/tmp/crcp-drift-canary.log</string>
+ <key>StandardErrorPath</key>
+ <string>/tmp/crcp-drift-canary.err</string>
+</dict>
+</plist>
diff --git a/scripts/crcp-drift-canary.js b/scripts/crcp-drift-canary.js
new file mode 100644
index 0000000..ae2e6eb
--- /dev/null
+++ b/scripts/crcp-drift-canary.js
@@ -0,0 +1,107 @@
+#!/usr/bin/env node
+/**
+ * crcp-drift-canary.js — READ-ONLY origin-vs-live drift monitor for CRCP.
+ *
+ * WHY: crcp.agentabrams.com is a Kamatera-local copy synced by MANUAL rsync.
+ * It silently drifted 9 days stale (Jul 7->16 2026) because nobody NOTICED —
+ * the manual deploy wasn't the failure, the INVISIBILITY was. DTD verdict D
+ * (2026-07-16): keep deploys manual (push-frontend-kamatera.sh) + this canary.
+ *
+ * Compares sha256 of each front-end asset (public/*.html,*.js,*.css) on the
+ * origin (this box) vs live (Kamatera), counts drifted files, and fires a
+ * WORSENING-ONLY alert (drift count grew vs the stored baseline) to CNCP +
+ * George email. Steady-state / recovery / first-run are silent. Touches
+ * nothing on prod — one ssh sha256sum read. Always writes data/latest.json
+ * so dw-canary-meta-watchdog sees it ran.
+ */
+const { execSync } = require('child_process');
+const fs = require('fs');
+const path = require('path');
+const crypto = require('crypto');
+
+const ROOT = path.join(__dirname, '..');
+const PUB = path.join(ROOT, 'public');
+const HOST = 'root@45.61.58.125';
+const REMOTE = '/root/public-projects/commercialrealestate/public';
+const STATE = path.join(ROOT, 'data', 'crcp-drift-state.json');
+const LATEST = path.join(ROOT, 'data', 'latest.json');
+const EXT = /\.(html|js|css)$/;
+
+function sha(buf) { return crypto.createHash('sha256').update(buf).digest('hex'); }
+
+function localHashes() {
+ const out = {};
+ for (const f of fs.readdirSync(PUB)) {
+ if (!EXT.test(f)) continue;
+ out[f] = sha(fs.readFileSync(path.join(PUB, f)));
+ }
+ return out;
+}
+
+function remoteHashes() {
+ // single read-only ssh call; sha256sum every html/js/css in the remote public dir
+ const cmd = `ssh -o ConnectTimeout=10 -o BatchMode=yes ${HOST} ` +
+ `"cd ${REMOTE} 2>/dev/null && for f in *.html *.js *.css; do [ -f \\"\\$f\\" ] && sha256sum \\"\\$f\\"; done"`;
+ const raw = execSync(cmd, { encoding: 'utf8', timeout: 30000 });
+ const out = {};
+ for (const line of raw.trim().split('\n')) {
+ const m = line.match(/^([0-9a-f]{64})\s+(.+)$/);
+ if (m) out[m[2]] = m[1];
+ }
+ return out;
+}
+
+function main() {
+ let live, err = null;
+ const local = localHashes();
+ try { live = remoteHashes(); } catch (e) { err = String(e.message).split('\n')[0]; }
+
+ const now = new Date().toISOString();
+ if (err) {
+ // unreachable = UNKNOWN, not a drift alert (transient); still stamp latest.json
+ writeLatest({ status: 'UNKNOWN', ts: now, error: err });
+ console.log(`[crcp-drift-canary] UNKNOWN — ${err}`);
+ return;
+ }
+
+ const drifted = [];
+ const names = new Set([...Object.keys(local), ...Object.keys(live)]);
+ for (const n of names) {
+ if (local[n] !== live[n]) {
+ drifted.push({ file: n, state: !live[n] ? 'missing-on-live' : !local[n] ? 'extra-on-live' : 'differs' });
+ }
+ }
+ const count = drifted.length;
+
+ const prev = fs.existsSync(STATE) ? JSON.parse(fs.readFileSync(STATE, 'utf8')) : null;
+ const baseline = prev ? prev.count : null;
+ const worsening = baseline !== null && count > baseline;
+ const status = count === 0 ? 'IN_SYNC' : 'DRIFTED';
+
+ writeLatest({ status, ts: now, driftCount: count, drifted, worsening });
+ fs.writeFileSync(STATE, JSON.stringify({ count, ts: now, drifted }, null, 2));
+
+ console.log(`[crcp-drift-canary] ${status} — ${count} file(s) drifted` +
+ (baseline !== null ? ` (was ${baseline})` : ' (first run — baseline set, silent)'));
+
+ // WORSENING-ONLY alert; first-run / steady / recovery are silent
+ if (worsening) {
+ const msg = `CRCP live is ${count} front-end file(s) behind origin (was ${baseline}). ` +
+ `Files: ${drifted.map(d => d.file + ':' + d.state).join(', ')}. ` +
+ `Fix: bash deploy/push-frontend-kamatera.sh`;
+ console.log('[crcp-drift-canary] WORSENING — alerting: ' + msg);
+ try {
+ execSync(`curl -s -m 8 -X POST http://127.0.0.1:3333/api/parking-lot ` +
+ `-H 'Content-Type: application/json' ` +
+ `-d ${JSON.stringify(JSON.stringify({ title: 'CRCP deploy drift', note: msg, source: 'crcp-drift-canary' }))}`,
+ { stdio: 'ignore' });
+ } catch (_) { /* CNCP best-effort */ }
+ }
+}
+
+function writeLatest(o) {
+ try { fs.mkdirSync(path.dirname(LATEST), { recursive: true }); } catch (_) {}
+ fs.writeFileSync(LATEST, JSON.stringify({ canary: 'crcp-drift', ...o }, null, 2));
+}
+
+main();
← a8e4386 add on-demand CRCP front-end deploy script (mac3->Kamatera)
·
back to Commercialrealestate
·
graceful-degrade /api/leads/frank + /api/broker (502 -> 200 cb778e7 →