[object Object]

← back to Dw Signup Fulfillment

add Option C go-live readiness preflight (PASS/WARN/FAIL, no-send George auth probe)

927545c05a1ab04ca4dd875545ef6a0d250a1829 · 2026-08-14 13:40:44 -0700 · steve

Files touched

Diff

commit 927545c05a1ab04ca4dd875545ef6a0d250a1829
Author: steve <steve@designerwallcoverings.com>
Date:   Fri Aug 14 13:40:44 2026 -0700

    add Option C go-live readiness preflight (PASS/WARN/FAIL, no-send George auth probe)
---
 .gitignore                  |  1 +
 scripts/golive-preflight.js | 90 +++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 91 insertions(+)

diff --git a/.gitignore b/.gitignore
index e7e11d3..be78ded 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,3 +19,4 @@ __pycache__/
 data/minted-cards-honor-worklist.jsonl
 data/honor-reissue-ledger*.jsonl
 data/*.bak-*
+data/latest.json
diff --git a/scripts/golive-preflight.js b/scripts/golive-preflight.js
new file mode 100644
index 0000000..28e1fff
--- /dev/null
+++ b/scripts/golive-preflight.js
@@ -0,0 +1,90 @@
+#!/usr/bin/env node
+'use strict';
+// golive-preflight.js — read-only readiness check for the Option C go-live
+// (verify email → verified-sample tag → Regios). Verifies every dependency WITHOUT
+// sending an email or writing anything, so it's safe to run anytime. Emits PASS/WARN/FAIL
+// (the fleet-health-rollup vocabulary) and writes data/latest.json for the rollup panel.
+//
+//   PASS = every hard dependency green, DRY_RUN already off → safe to serve live.
+//   WARN = launch-blocking config still pending (e.g. DRY_RUN=1, no PUBLIC_URL, Regios manual).
+//   FAIL = a hard dependency is broken (George auth down, missing secret/token).
+//
+// Run: node scripts/golive-preflight.js
+const fs = require('fs');
+const path = require('path');
+const http = require('http');
+const config = require('../lib/config');
+
+const checks = [];
+const add = (name, level, detail) => checks.push({ name, level, detail });
+
+// Small authenticated GET helper (used only for George's health probe — no email send).
+function getJSON(url, headers) {
+  return new Promise((resolve) => {
+    let u;
+    try { u = new URL(url); } catch (e) { return resolve({ status: 0, error: e.message }); }
+    const req = http.request({ hostname: u.hostname, port: u.port || 80, path: u.pathname, method: 'GET', headers }, (res) => {
+      let d = ''; res.on('data', (c) => (d += c));
+      res.on('end', () => resolve({ status: res.statusCode, body: d.slice(0, 200) }));
+    });
+    req.on('error', (e) => resolve({ status: 0, error: e.message }));
+    req.setTimeout(8000, () => { req.destroy(); resolve({ status: 0, error: 'timeout' }); });
+    req.end();
+  });
+}
+
+async function main() {
+  // 1) George Basic-auth — the exact thing that caused the outage. Authenticated GET to
+  //    George's health endpoint: 200 = auth good, 401 = the empty-password bug is back.
+  const auth = config.GEORGE_BASIC_AUTH || 'admin:';
+  const basic = auth.startsWith('Basic ') ? auth : 'Basic ' + Buffer.from(auth.includes(':') ? auth : 'admin:' + auth).toString('base64');
+  const gh = await getJSON((config.GEORGE_URL || 'http://127.0.0.1:9850') + '/api/health', { Authorization: basic });
+  if (gh.status === 200) add('george_basic_auth', 'PASS', 'George reachable + Basic-auth accepted');
+  else if (gh.status === 401) add('george_basic_auth', 'FAIL', 'George 401 — Basic-auth wrong (empty-password bug?). Set GEORGE_AUTH in secrets.');
+  else add('george_basic_auth', 'FAIL', `George unreachable/error (status ${gh.status}${gh.error ? ' ' + gh.error : ''})`);
+
+  // 2) External-send approval token (needed for external welcome emails).
+  add('george_send_token', config.GEORGE_EXTERNAL_SEND_TOKEN ? 'PASS' : 'FAIL',
+    config.GEORGE_EXTERNAL_SEND_TOKEN ? 'set (…' + String(config.GEORGE_EXTERNAL_SEND_TOKEN).slice(-4) + ')' : 'missing — external sends will be blocked');
+
+  // 3) Verify-token signing secret (a dev fallback exists ONLY in DRY_RUN; live needs a real one).
+  add('verify_secret', config.VERIFY_SECRET ? 'PASS' : (config.DRY_RUN ? 'WARN' : 'FAIL'),
+    config.VERIFY_SECRET ? 'DW_SIGNUP_VERIFY_SECRET set' : 'missing — set DW_SIGNUP_VERIFY_SECRET before live (dev fallback is DRY-only)');
+
+  // 4) Shopify token (needed to write the verified-sample tag).
+  add('shopify_token', config.SHOPIFY_FULFILLMENT_TOKEN ? 'PASS' : 'FAIL',
+    config.SHOPIFY_FULFILLMENT_TOKEN ? 'set' : 'missing — the tag write (read_customers+write_customers) will no-op');
+
+  // 5) Public URL (verify links + webhook must resolve off localhost).
+  const pu = config.PUBLIC_URL;
+  add('public_url', pu && /^https:\/\//.test(pu) && !/localhost|127\.0\.0\.1/.test(pu) ? 'PASS' : 'WARN',
+    pu ? `PUBLIC_URL=${pu}` : 'PUBLIC_URL unset — verify links + webhook won\'t resolve for customers');
+
+  // 6) Reward tag name (informational).
+  add('verified_tag', 'PASS', `VERIFIED_TAG=${config.VERIFIED_TAG} (must equal the Regios rule tag)`);
+
+  // 7) DRY_RUN state — must be OFF to actually serve live.
+  add('dry_run', config.DRY_RUN ? 'WARN' : 'PASS', config.DRY_RUN ? 'DRY_RUN=1 — service is inert; flip to 0 to go live' : 'DRY_RUN=0 (live)');
+
+  // 8) Webhook token (only if using the customers/create webhook entry).
+  add('webhook_url_token', config.WEBHOOK_URL_TOKEN ? 'PASS' : 'WARN',
+    config.WEBHOOK_URL_TOKEN ? 'set' : 'unset — only needed if using the webhook trigger (the /claim form works without it)');
+
+  // 9) Local service health.
+  const sh = await getJSON(`http://127.0.0.1:${config.PORT}/healthz`, {});
+  add('service_health', sh.status === 200 ? 'PASS' : 'WARN', sh.status === 200 ? `:${config.PORT}/healthz 200` : `:${config.PORT}/healthz status ${sh.status}`);
+
+  // 10) Regios rule — not API-verifiable; always a manual gate.
+  add('regios_rule', 'WARN', 'MANUAL: confirm a Regios rule makes samples free for tag ' + config.VERIFIED_TAG + ', scoped to the Sample VARIANT only (roll stays full price).');
+
+  const worst = checks.some((c) => c.level === 'FAIL') ? 'FAIL' : checks.some((c) => c.level === 'WARN') ? 'WARN' : 'PASS';
+  const out = { skill: 'dw-signup-golive-preflight', verdict: worst, status: worst, checked_at: new Date().toISOString(), checks };
+  try { fs.mkdirSync(path.join(__dirname, '..', 'data'), { recursive: true }); fs.writeFileSync(path.join(__dirname, '..', 'data', 'latest.json'), JSON.stringify(out, null, 2)); } catch (e) {}
+
+  console.log(`\nOption C go-live preflight — verdict: ${worst}\n`);
+  for (const c of checks) console.log(`  ${c.level === 'PASS' ? '✅' : c.level === 'WARN' ? '🟠' : '❌'} ${c.name.padEnd(18)} ${c.detail}`);
+  console.log(`\n${worst === 'PASS' ? 'Ready to serve live.' : worst === 'WARN' ? 'Launch-blocking items remain (WARN) — resolve, then flip DRY_RUN=0.' : 'A hard dependency is broken (FAIL) — fix before launch.'}`);
+  console.log('wrote data/latest.json');
+  process.exit(worst === 'FAIL' ? 1 : 0);
+}
+main();

← 7169240 auto-data-snapshot: 2026-08-14T12:54:45 (1 data files) — pac  ·  back to Dw Signup Fulfillment  ·  ecosystem: set PUBLIC_URL for Option C verify links + webhoo ce1c7d7 →