[object Object]

← 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

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 →