S4 (a1): the media engine — VOICE templates, /desk, Ghost drafts

4a VOICE v1.1 committed (board start); lint is EXECUTABLE — banned list +
   no-exclamation law enforced in the engine (throws in test, drops in
   prod) and locked by tests. Curly-apostrophe variants covered.
4b mediaEngine: deterministic templates (MORNING WIRE, SIGNAL, STREAK
   WATCH, THE SETTLE, RECEIPTS, ARCHETYPE WATCH, LINE DISPATCH) filled
   ONLY from snapshot/ledger/streaks JSON. Record percentages never render
   under n>=20 (counts + 'Record building' below). Stark layer = curated
   committed library (content/stark-lines.json), day-rotated selection —
   selected, never generated.
4c /desk (founder-only: requireAuth + DESK_OWNERS email allowlist,
   deny-by-default): all formats as text + <=280-char pre-segmented tweets
   with per-tweet copy buttons + char counts, wire/numbers-only variants,
   DATA BRIEF block (structured day numbers) with copy-for-claude.ai.
   ntfy ping after the day's first snapshot: 'Desk pack ready'.
4d ghostPublisher: DRAFTS ONLY (status:'draft' test-locked), env-gated
   no-op, HS256 JWT via node crypto (zero new deps). POST
   /api/internal/ghost/drafts saves slate preview + settle drafts.
   Nothing anywhere auto-posts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Kev
2026-07-11 14:31:35 -04:00
parent d242b11b4b
commit 0e7871ac0e
12 changed files with 912 additions and 0 deletions
+19
View File
@@ -180,6 +180,25 @@ router.post('/outcomes/all', async (req, res) => {
}
});
/**
* POST /api/internal/ghost/drafts (Session 63 / A1-S4d) — assemble today's
* desk pack and save the slate-preview + settle drafts to Ghost. DRAFTS
* ONLY; Kev publishes from the Ghost admin. Env-gated no-op without Ghost.
*/
router.post('/ghost/drafts', async (req, res) => {
try {
const { assembleDeskPack } = require('../services/deskService');
const ghost = require('../services/ghostPublisher');
const pack = await assembleDeskPack();
const result = await ghost.saveDailyDrafts(pack);
return res.json({ ok: result.ok, result });
} catch (err) {
const message = err && err.message ? err.message : String(err);
console.error('[internal/ghost/drafts] failed:', message);
return res.status(500).json({ ok: false, error: message });
}
});
/**
* POST /api/internal/refresh/all (Session 60, Phase 2.5) — manual intraday
* odds-only refresh: STEAM/VALUE movement, public revisions, closing