← back to Rentv
harden(agents review): spend-safety + robustness pass on this session's code (2 read-only reviewers, findings verified before applying). gen-deal-brief: (1) ledger write wrapped so a post-TTS ENOSPC surfaces LOUD instead of silently under-counting the cap; (2) --force now bypasses dedupe ONLY, cap needs explicit --force-overcap; (3) lockfile prevents two overlapping runs double-spending; (4) DRY_RUN=0 no longer treated as dry. parseAmount: (?![a-z]) kills the '$5 billionaire'→$5B fabrication (0/30 corpus regressions; now feeds paid TTS). brief.html: scheme-guard t.href (no javascript: URLs). +1 test, 65/0.
3e79a22dd96574103482f47d75e1f6cc8ac7f18c · 2026-08-06 17:00:31 -0700 · Steve
Files touched
M public/brief.htmlM scripts/gen-deal-brief.mjsM scripts/lib/deal-parse.mjsM test/deals/parse.test.mjs
Diff
commit 3e79a22dd96574103482f47d75e1f6cc8ac7f18c
Author: Steve <steve@designerwallcoverings.com>
Date: Thu Aug 6 17:00:31 2026 -0700
harden(agents review): spend-safety + robustness pass on this session's code (2 read-only reviewers, findings verified before applying). gen-deal-brief: (1) ledger write wrapped so a post-TTS ENOSPC surfaces LOUD instead of silently under-counting the cap; (2) --force now bypasses dedupe ONLY, cap needs explicit --force-overcap; (3) lockfile prevents two overlapping runs double-spending; (4) DRY_RUN=0 no longer treated as dry. parseAmount: (?![a-z]) kills the '$5 billionaire'→$5B fabrication (0/30 corpus regressions; now feeds paid TTS). brief.html: scheme-guard t.href (no javascript: URLs). +1 test, 65/0.
---
public/brief.html | 3 ++-
scripts/gen-deal-brief.mjs | 47 +++++++++++++++++++++++++++++-----------------
scripts/lib/deal-parse.mjs | 5 ++++-
test/deals/parse.test.mjs | 7 +++++++
4 files changed, 43 insertions(+), 19 deletions(-)
diff --git a/public/brief.html b/public/brief.html
index 863fed17..2f665926 100644
--- a/public/brief.html
+++ b/public/brief.html
@@ -127,7 +127,8 @@
fetch('/audio/deal-brief.json',{cache:'no-store'}).then(function(r){return r.ok?r.json():null;}).then(function(m){
if(!m||m.dry_run) return;
var el=document.getElementById('boomcast'); if(!el) return;
- var t=document.getElementById('bc-title'); t.textContent=m.title||'Today’s top deal'; if(m.url)t.href=m.url;
+ var t=document.getElementById('bc-title'); t.textContent=m.title||'Today’s top deal';
+ if(m.url && /^(https?:\/\/|\/)/.test(m.url)) t.href=m.url; // scheme-guard: only http(s)/relative, never javascript:
document.getElementById('bc-audio').src='/audio/deal-brief.mp3';
el.style.display='block';
}).catch(function(){});
diff --git a/scripts/gen-deal-brief.mjs b/scripts/gen-deal-brief.mjs
index 32ea27d6..14f18ce3 100644
--- a/scripts/gen-deal-brief.mjs
+++ b/scripts/gen-deal-brief.mjs
@@ -9,7 +9,7 @@
//
// Boomer is the on-air PERSONA ("This is Boom…") — never "Bloom" in any customer-facing text. The script is
// factual-only (no editorializing) so an auto-published brief can't misstate a deal.
-import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
+import { readFileSync, writeFileSync, existsSync, mkdirSync, unlinkSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
@@ -19,7 +19,8 @@ const AUDIO = join(ROOT, 'public', 'audio');
const VOICE_ID = process.env.RENTV_VOICE_ID || 'czUPGx5phxE6qpQ46nRS';
const RATE_PER_1K = 0.30; // ElevenLabs Creator-tier estimate, $/1000 chars
const MONTHLY_CAP = parseInt(process.env.BRIEF_MONTHLY_CAP || '40', 10); // hard spend backstop (~$6/mo)
-const FORCE = process.argv.includes('--force');
+const FORCE = process.argv.includes('--force'); // bypass the top-deal-change dedupe only
+const FORCE_OVERCAP = process.argv.includes('--force-overcap'); // ALSO bypass the hard $ cap (dangerous, explicit)
function elevenKey() {
if (process.env.ELEVENLABS_API_KEY) return process.env.ELEVENLABS_API_KEY.trim().replace(/^["']|["']$/g, '');
@@ -85,23 +86,35 @@ export async function generateBrief() {
console.log(`est cost: $${est.toFixed(3)} (${chars} chars @ $${RATE_PER_1K}/1k)`);
const meta = { deal_id: top.id, title: top.title, acquirer: top.acquirer || null, amount_label: top.amount_label || null, url: top.url || null, script: text, chars, generated_at: new Date().toISOString() };
- if (process.env.DRY_RUN) { meta.dry_run = true; writeFileSync(metaPath, JSON.stringify(meta, null, 1)); console.log('DRY_RUN=1 → wrote metadata, NO TTS call, $0'); return; }
+ // DRY_RUN=0 must NOT count as dry (a non-empty string is truthy in JS) — only a set, non-"0" value.
+ if (process.env.DRY_RUN && process.env.DRY_RUN !== '0') { meta.dry_run = true; writeFileSync(metaPath, JSON.stringify(meta, null, 1)); console.log('DRY_RUN → wrote metadata, NO TTS call, $0'); return; }
- // HARD MONTHLY SPEND CAP — a dollar backstop beyond the top-deal-change dedupe. Even if the top deal
- // flaps, this bounds spend to MONTHLY_CAP generations/month (default 40 ≈ $6/mo). Ledger lives in the
- // rsync-excluded public/audio (prod-owned, survives deploys). --force overrides.
- const ledgerPath = join(AUDIO, 'brief-ledger.json');
- const month = new Date().toISOString().slice(0, 7); // YYYY-MM
- let ledger = {}; try { ledger = JSON.parse(readFileSync(ledgerPath, 'utf8')); } catch {}
- if ((ledger[month] || 0) >= MONTHLY_CAP && !FORCE) {
- console.log(`monthly cap reached (${ledger[month] || 0}/${MONTHLY_CAP} for ${month}) — SKIP, no spend`); return;
- }
+ // Lock so two overlapping runs (cron + a manual invocation) can't both pass the cap check and double-spend.
+ const lockPath = join(AUDIO, 'brief-gen.lock');
+ try { writeFileSync(lockPath, String(process.pid), { flag: 'wx' }); }
+ catch { console.log('another brief-gen in progress (lock held) — SKIP, no spend'); return; }
+ try {
+ // HARD MONTHLY SPEND CAP — a dollar backstop beyond the top-deal-change dedupe (bounds spend to
+ // MONTHLY_CAP/month ≈ $6). Ledger in rsync-excluded public/audio (prod-owned, survives deploys).
+ // Only --force-overcap bypasses the CAP; plain --force only bypasses the dedupe (the cap is a hard $ ceiling).
+ const ledgerPath = join(AUDIO, 'brief-ledger.json');
+ const month = new Date().toISOString().slice(0, 7); // YYYY-MM
+ let ledger = {}; try { ledger = JSON.parse(readFileSync(ledgerPath, 'utf8')); } catch {}
+ if ((ledger[month] || 0) >= MONTHLY_CAP && !FORCE_OVERCAP) {
+ console.log(`monthly cap reached (${ledger[month] || 0}/${MONTHLY_CAP} for ${month}) — SKIP, no spend`); return;
+ }
- await tts(text, join(AUDIO, 'deal-brief.mp3'));
- ledger[month] = (ledger[month] || 0) + 1;
- writeFileSync(ledgerPath, JSON.stringify(ledger, null, 1));
- writeFileSync(metaPath, JSON.stringify(meta, null, 1));
- console.log(`✓ /public/audio/deal-brief.mp3 written · actual ~$${est.toFixed(3)} · ${ledger[month]}/${MONTHLY_CAP} this month`);
+ await tts(text, join(AUDIO, 'deal-brief.mp3'));
+ // Count the spend that already happened. If the ledger write fails (ENOSPC etc.), the spend still
+ // occurred — surface it LOUDLY so the cap isn't silently under-enforced, rather than swallowing it.
+ ledger[month] = (ledger[month] || 0) + 1;
+ try { writeFileSync(ledgerPath, JSON.stringify(ledger, null, 1)); }
+ catch (le) { console.error(`WARN: spend occurred but ledger write FAILED (${le.message}) — monthly cap may under-count this month`); }
+ writeFileSync(metaPath, JSON.stringify(meta, null, 1));
+ console.log(`✓ /public/audio/deal-brief.mp3 written · actual ~$${est.toFixed(3)} · ${ledger[month]}/${MONTHLY_CAP} this month`);
+ } finally {
+ try { unlinkSync(lockPath); } catch {}
+ }
}
// Run standalone only when invoked directly (not when imported by pull-deals.mjs).
diff --git a/scripts/lib/deal-parse.mjs b/scripts/lib/deal-parse.mjs
index 1976115f..8cbec2a1 100644
--- a/scripts/lib/deal-parse.mjs
+++ b/scripts/lib/deal-parse.mjs
@@ -19,7 +19,10 @@ export function parseAmount(text) {
// regressing legit inputs): (a) a shared-magnitude range "$40-50 million" reads only the low bound
// unscaled ($40) — needs true range semantics; (b) the spelled word matches inside a larger word, so
// "$5 billionaire" would fabricate $5B — can't be tightened without breaking "$1.5MM" finance notation.
- const re = /\$\s?([\d,]+(?:\.\d+)?)\s*(bil|billion|mil|million|[mb]|k)?/gi;
+ // (?![a-z]) anchors the magnitude so a spelled unit can't match inside a longer word: "$5 billionaire"
+ // no longer fabricates $5B (degrades to $5). Verified 0/30 corpus-amount regressions. Higher-stakes now
+ // that parseAmount feeds pickTopDeal() → the paid Boomer audio brief.
+ const re = /\$\s?([\d,]+(?:\.\d+)?)\s*(bil|billion|mil|million|[mb]|k)?(?![a-z])/gi;
let m;
while ((m = re.exec(text)) !== null) {
const after = text.slice(m.index + m[0].length);
diff --git a/test/deals/parse.test.mjs b/test/deals/parse.test.mjs
index ae7cfa58..d28b162a 100644
--- a/test/deals/parse.test.mjs
+++ b/test/deals/parse.test.mjs
@@ -94,6 +94,13 @@ test('parseAmount: bare $NM / $NB shorthand', () => {
// "per <non-unit word>" must NOT be skipped as a per-X metric
assert.equal(parseAmount('$90M per SEC filing').amount, 90000000);
});
+test('parseAmount: a spelled unit inside a longer word does not fabricate a magnitude (billionaire)', () => {
+ // "$5 billionaire" used to match "billion" → $5,000,000,000. The (?![a-z]) anchor degrades it to $5.
+ assert.equal(parseAmount('$5 billionaire investor buys tower').amount, 5);
+ assert.equal(parseAmount('backed by a $3 millionaire family office').amount, 3);
+ // real magnitudes still scale
+ assert.equal(parseAmount('$5 billion fund').amount, 5000000000);
+});
test('parseAmount: broadened per-sf spellings (psf / sqft / per sq ft / per foot) are skipped', () => {
for (const t of ['$153 psf', '$153 per sq ft', '$153 sqft', '$153 per foot'])
assert.equal(parseAmount(t).amount, null, t);
← 9c83657e fix(firm): profile KPIs drill deeper (every-datum-hrefs rule
·
back to Rentv
·
harden(brief): stale-lock TTL — a hard-killed run (SIGKILL/O 04680821 →