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
+41
View File
@@ -0,0 +1,41 @@
'use strict';
/**
* /api/desk (Session 63 / A1-S4) — Kev's copy-paste arsenal.
*
* GET /pack — the assembled daily media pack + DATA BRIEF.
* Auth: requireAuth + email allowlist (env DESK_OWNERS, comma-separated).
* Unset allowlist → 403 for everyone (deny by default; the desk is the
* founder's surface, not a tier feature).
*/
const express = require('express');
const { requireAuth } = require('../middleware/auth');
const { createRateLimit } = require('../middleware/rateLimit');
const { assembleDeskPack } = require('../services/deskService');
const router = express.Router();
router.use(createRateLimit({ windowMs: 60_000, max: 20 }));
function isOwner(req) {
const owners = String(process.env.DESK_OWNERS || '')
.split(',').map((s) => s.trim().toLowerCase()).filter(Boolean);
const email = String(req.user?.email || '').toLowerCase();
return owners.length > 0 && email && owners.includes(email);
}
router.get('/pack', requireAuth, async (req, res) => {
if (!isOwner(req)) {
return res.status(403).json({ error: 'The desk is the publishers surface. Set DESK_OWNERS to grant access.' });
}
try {
const pack = await assembleDeskPack();
res.set('Cache-Control', 'private, max-age=60');
return res.json(pack);
} catch (err) {
console.error('[desk/pack]', err.message);
return res.status(200).json({ generated_at: null, formats: {}, data_brief: null, error: 'assembly failed' });
}
});
module.exports = router;
+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