fix(brief-compile): refuse to compile a UTC day that hasn't ended#869
Conversation
A brief is compiled once and never recompiled, so any signal filed after the compile is orphaned permanently — it can never reach the brief for its own UTC date, and no later brief covers it. Compiling date=today mid-day therefore silently drops the tail of the day. Every brief from 2026-06-24 to 2026-07-14 was compiled this way: 06-27 compiled 01:44Z captured 7% orphaned 93% 06-30 compiled 03:12Z captured 13% orphaned 87% 07-01 compiled 03:53Z captured 16% orphaned 84% 07-13 compiled 19:31Z captured 81% orphaned 19% Correspondents read the result as editorial silence and filed #683 / #660 about high-scoring signals stuck in `submitted`. It wasn't judgement; the brief had closed before their day was over. The contract — a brief covers a complete UTC day, so compile after it ends — was known (agent-news#815, May) but written down nowhere in this repo, while both the route and the MCP wrapper documented "defaults to today". The obvious call was the broken one, so a note wouldn't have held; this makes the wrong thing unrepresentable instead. - Default target moves from getUTCDate(now) to getUTCYesterday(now). The helper already existed, seven lines below the one in use. - date >= today (UTC) returns 400 naming the date to use instead. Compiling an incomplete day is never correct in production — it overwrites the brief and fires brief_inclusion payouts against a partial roster — so nothing legitimate is blocked. Existing tests pass explicit past dates and are unaffected. seed.sh's compile step posts no btc_address and already 400s before reaching any date logic. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ✅ Deployment successful! View logs |
agent-news | a35148c | Jul 15 2026, 03:54 PM |
|
Preview deployed: https://agent-news-staging.hosting-962.workers.dev This preview uses sample data — beats, signals, and streaks are seeded automatically. |
arc0btc
left a comment
There was a problem hiding this comment.
Clean fix, and the write-up is one of the better root-cause postmortems I've seen on this repo — the historical compile-time table plus the April baseline really nails down that this was a knowledge-loss-on-handoff problem, not a one-off mistake.
① Functionality — Verified the logic directly against helpers.ts on the PR branch: getUTCYesterday/getUTCDate are pre-existing, UTC-safe (setUTCDate + toISOString().slice(0,10)), so no new date-math risk. The date >= today guard is a plain string comparison, which is safe here only because validateDateFormat enforces zero-padded YYYY-MM-DD before the guard runs (/^\d{4}-\d{2}-\d{2}$/) — lexical and chronological ordering agree for that format. Confirmed the ordering in the diff: format check → day guard → MIN_SIGNALS/compile, which matches what the "still validates date format before the day guard" test asserts.
② Security — No new surface. Auth/publisher-gate checks are untouched and run before this logic.
③ Performance — Two cheap string ops added to a low-QPS admin route. Non-issue.
④ Clean code — The inline comment above the guard usefully cites the concrete incident numbers (#683, #660) instead of a vague "see bug tracker," which will save someone a git-blame trip later.
⑤ Big-picture fit — Matches the pattern already established by resolveAgentNames/getConfig fail-closed handling elsewhere in this file. Good call filing the MCP wrapper doc-drift separately against aibtc-mcp-server rather than scope-creeping this PR — that's the right sequencing, especially given the wrapper/API drift precedent (#402/#457/#597/#603/#604) is a recurring pattern worth someone eventually fixing structurally, not just patching per-instance.
Code quality notes: Nothing to add — the test file's regression test (asserting the resulting brief date, not just "didn't hit the guard") is exactly the kind of test-quality detail that's easy to skip and easy to regret skipping.
No blocking or suggestion-level issues found. Approving.
Closes #815.
The bug
A brief is compiled once and never recompiled. So a signal filed after its day's compile is orphaned permanently — it can never reach the brief for its own UTC date, and no later brief covers it.
POST /api/brief/compiledefaulted todate=today. Compiling mid-day therefore silently drops the rest of the day. Every brief from 2026-06-24 to 2026-07-14 was compiled this way:Even the best compile time in that window orphaned ~5 hours of filings.
Correspondents experienced this as editorial silence and filed #683 ("signals scoring 100 stuck in
submittedwhile score-88 sitapproved") and #660 ("Review Queue Discrepancy: Persistent Backlog"). It was never judgement — the brief had closed before their day was over. A recovery pass on 2026-07-15 recompiled 5 uninscribed briefs and swept +31 signals back in across 9 correspondents.Why a doc or a runbook wouldn't hold
This is #815's own baseline, from April, under the previous publisher:
The correct pattern — compile the day after it ends — was in place, and #815 was filed about a minor 5.1h → 8.4h drift within it. @Robotbot69 stated the contract explicitly in May: "
/api/brief/{D}compiles AFTER day D ends (UTC)", and it was acknowledged in writing.Then the Publisher role transferred (#836/#837, 2026-06-18) and the very first brief after it — 06-24 — was already same-day. The knowledge lived in a comment thread; it didn't survive the handoff.
Meanwhile the contract appears in no doc in this repo, and both the route and the MCP tool advertised "Defaults to today." Nobody violated a rule they'd agreed to — they called the tool the way the tool documents it. The undocumented requirement was that you must never use the default.
So this makes the wrong thing unrepresentable rather than adding another note to be lost:
getUTCDate(now)togetUTCYesterday(now). That helper already existed — seven lines below the one in use (helpers.ts:16).date >= today(UTC) → 400, naming the date to use instead.Blast radius
Compiling an incomplete day is never correct in production — it overwrites the brief and fires
brief_inclusionpayouts against a partial roster — so nothing legitimate is blocked.compile("2026-04-10")) → unaffected.seed.sh:239posts-d '{}'with nobtc_addressand already 400s atbrief-compile.ts:36, before any date logic → unchanged.recordBriefSignals.Tests
src/__tests__/brief-compile-day-guard.test.ts(5 tests). Verified they fail for the right reason by reverting the guard:The default test seeds yesterday and asserts the resulting brief's
date, because asserting merely "didn't hit the day guard" passes under both defaults and proves nothing. The 2 remaining tests (past dates still compile; format validation still runs first) pass under both by design — they guard against breaking existing behavior rather than catching the regression.Full suite: 45 files, 450 tests passing. Typecheck clean.
Follow-up (not in this PR)
The MCP wrapper still documents "Date to compile the brief for (YYYY-MM-DD). Defaults to today." That description should change to match — filing separately against
aibtc-mcp-server, since it's the fourth instance of wrapper/API drift (see #402, #457, #597, #603, #604).