P0 fix: content/ not in image crashed API boot; harden garnish + preflight

ROOT CAUSE: the Dockerfile copied src/poller/scripts/supabase but NOT
content/. mediaEngine.js read content/stark-lines.json with an unguarded
module-load readFileSync; ENOENT in the image threw at require time, and
via app.js → routes/desk → deskService → mediaEngine that crashed the
ENTIRE API at boot. The Coolify healthcheck rolled back to the last
healthy image (4d2b27d), so every deploy since 219167e silently served a
14-hour-old build — S11 live tracking, S6 API code, the settlement boot
line, and SNAPSHOT_EXPECTED_INTERVAL were all merged but NOT running.

FIX (one train):
1. Dockerfile COPYs content/ into the runner image.
2. mediaEngine: stark-lines.json is OPTIONAL (garnish, never load-bearing)
   — loadStark() try/catch → {} → posts render without the Stark kicker,
   never a crash. Belt AND suspenders with #1.
3. src/preflight.js (§A4): boot prints '[preflight] OK' or 'DEGRADED'
   naming exactly what content/env is missing — before the healthcheck
   can fail silently. Run first in server.js.
4. Full fragility sweep: mediaEngine was the ONLY unguarded module-load
   file read; coachSignals (config/coaches.json) was already lazy +
   try/catch + copied. No others.

Verified: requiring app.js + deskService + mediaEngine with
stark-lines.json ABSENT now boots clean (reproduced the exact prod
failure). 2757 -> 2763 tests (tests/unit/bootResilience.test.js).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kev
2026-07-12 13:42:18 -04:00
parent 4dc22c9bc9
commit 17fb981f99
5 changed files with 203 additions and 1 deletions
+69
View File
@@ -0,0 +1,69 @@
'use strict';
/**
* preflight — boot visibility (Session 65, Standard §A4).
*
* A healthcheck that only pings /api/health can't tell "booted degraded"
* from "crashed and rolled back" — which is exactly how a missing garnish
* file (content/stark-lines.json) served a 14-hour-old image silently. This
* runs once at boot and prints ONE unambiguous line: `[preflight] OK` when
* everything required is present, or `[preflight] DEGRADED` naming exactly
* what is missing. It NEVER throws and NEVER blocks boot — its only job is
* to make the container's state legible in the logs.
*
* Philosophy (matches the app's own doctrine): content garnish missing is
* NOTED, not fatal (absent beats crashed). Missing critical env is named so
* an operator sees it immediately instead of debugging a symptom later.
*/
const fs = require('fs');
const path = require('path');
// Optional content — the app runs fine without these; we just name them so a
// missing one is visible (and never silently crashes a require chain again).
const OPTIONAL_CONTENT = [
{ label: 'stark-lines.json', file: path.join(__dirname, '..', 'content', 'stark-lines.json'), note: 'media posts drop the Stark kicker' },
{ label: 'coaches.json', file: path.join(__dirname, 'config', 'coaches.json'), note: 'coach-impact feature uses no seed' },
];
// Critical env — the app boots without these but key surfaces degrade; named
// loudly so an operator fixes them before hunting a downstream symptom.
const CRITICAL_ENV = ['SUPABASE_URL', 'REDIS_URL', 'VYNDR_INTERNAL_KEY'];
// Either service-role name satisfies the Supabase write path.
const ENV_EITHER = [['SUPABASE_SERVICE_ROLE_KEY', 'SUPABASE_SERVICE_KEY']];
function runPreflight(log = console) {
try {
const missingContent = [];
for (const c of OPTIONAL_CONTENT) {
let ok = false;
try { fs.accessSync(c.file, fs.constants.R_OK); ok = true; } catch { ok = false; }
if (!ok) missingContent.push(`${c.label} (${c.note})`);
}
const missingEnv = CRITICAL_ENV.filter((k) => !process.env[k]);
for (const group of ENV_EITHER) {
if (!group.some((k) => process.env[k])) missingEnv.push(group.join('|'));
}
// Operational visibility for the pipeline knobs (not failures — states).
const cron = process.env.SNAPSHOT_CRON === '1' ? 'armed' : 'off';
const interval = Number(process.env.SNAPSHOT_EXPECTED_INTERVAL) > 0
? `${process.env.SNAPSHOT_EXPECTED_INTERVAL}s` : 'default(18000s)';
if (missingContent.length === 0 && missingEnv.length === 0) {
log.log(`[preflight] OK — content present, critical env present, snapshot cron ${cron}, expected-interval ${interval}`);
} else {
log.warn(`[preflight] DEGRADED — snapshot cron ${cron}, expected-interval ${interval}`);
if (missingEnv.length) log.warn(`[preflight] missing env: ${missingEnv.join(', ')}`);
if (missingContent.length) log.warn(`[preflight] missing content (non-fatal): ${missingContent.join(', ')}`);
}
return { ok: missingContent.length === 0 && missingEnv.length === 0, missingContent, missingEnv };
} catch (e) {
// Preflight itself must never break boot.
try { log.warn(`[preflight] check errored (non-fatal): ${e.message}`); } catch { /* ignore */ }
return { ok: false, error: e.message };
}
}
module.exports = { runPreflight, __internals: { OPTIONAL_CONTENT, CRITICAL_ENV, ENV_EITHER } };