mattrguerra/meridian-ops-boardmainPrivate on GitHub, published here in full

test_brief.py

Language Python22.3 KB508 lines

analysis/test_brief.py

  1. #!/usr/bin/env python3
  2. """
  3. Tests for brief.py. Offline: no network, no credentials, no model.
  4. python3 -m unittest discover -s analysis -v
  5. Every case below states the failure it prevents rather than restating the
  6. rule. The failures that matter here are silent ones: a briefing that renders
  7. empty, or cut off, with nothing saying why.
  8. Following the same convention as the TypeScript suite, these assert properties
  9. (it raised, the message carries the stop_reason) rather than exact wording: a
  10. test that matches on message text can pin a wrong diagnosis in place.
  11. """
  12. from __future__ import annotations
  13. import io
  14. import json
  15. import sys
  16. import tempfile
  17. import unittest
  18. from pathlib import Path
  19. from unittest import mock
  20. sys.path.insert(0, str(Path(__file__).resolve().parent))
  21. import brief # noqa: E402
  22. def response(body: dict) -> mock.MagicMock:
  23. """A stand-in for the urlopen context manager."""
  24. res = mock.MagicMock()
  25. res.read.return_value = json.dumps(body).encode()
  26. res.__enter__.return_value = res
  27. res.__exit__.return_value = False
  28. return res
  29. def anthropic(**over) -> dict:
  30. base = {
  31. "content": [{"type": "text", "text": "A briefing."}],
  32. "stop_reason": "end_turn",
  33. "usage": {"input_tokens": 100, "output_tokens": 20},
  34. }
  35. base.update(over)
  36. return base
  37. def call(body: dict, max_tokens: int = 4000):
  38. with mock.patch("urllib.request.urlopen", return_value=response(body)):
  39. return brief.call_model(
  40. "https://api.anthropic.com/v1", "claude-opus-5", "sys", "usr",
  41. "key", 30, "anthropic", max_tokens,
  42. )
  43. PAYLOAD = {
  44. "schemaVersion": "1.0.0",
  45. "people": [{"name": "Nora Lindqvist"}, {"name": "Sam Whitaker"}],
  46. }
  47. class NoTextBlockRaises(unittest.TestCase):
  48. """
  49. Joining the text blocks of a response that has none returns "", which would
  50. be written to briefings.json and rendered as a heading with nothing under
  51. it: a silent failure in the one project built to eliminate them.
  52. """
  53. def test_raises_rather_than_returning_empty(self):
  54. with self.assertRaises(brief.EmptyResponseError):
  55. call(anthropic(content=[]))
  56. def test_raises_when_only_non_text_blocks_came_back(self):
  57. with self.assertRaises(brief.EmptyResponseError):
  58. call(anthropic(content=[{"type": "thinking", "thinking": "..."}]))
  59. def test_raises_on_whitespace_only_text(self):
  60. with self.assertRaises(brief.EmptyResponseError):
  61. call(anthropic(content=[{"type": "text", "text": " \n "}]))
  62. def test_message_names_the_model_and_the_stop_reason(self):
  63. # The handoff asked for both by name: without them the error says a
  64. # briefing failed but not what the API reported about it.
  65. with self.assertRaises(brief.EmptyResponseError) as caught:
  66. call(anthropic(content=[], stop_reason="max_tokens"))
  67. msg = str(caught.exception)
  68. self.assertIn("claude-opus-5", msg)
  69. self.assertIn("max_tokens", msg)
  70. def test_message_names_the_block_types_that_did_come_back(self):
  71. with self.assertRaises(brief.EmptyResponseError) as caught:
  72. call(anthropic(content=[{"type": "thinking", "thinking": "..."}]))
  73. self.assertIn("thinking", str(caught.exception))
  74. def test_does_not_assert_an_unobserved_cause(self):
  75. # Report what was observed, not a guessed cause (rule 1). Whether
  76. # reasoning consumed the budget is inferred, not observed, so it may be
  77. # offered as a candidate but never stated as the cause.
  78. with self.assertRaises(brief.EmptyResponseError) as caught:
  79. call(anthropic(content=[], stop_reason="max_tokens"))
  80. msg = str(caught.exception).lower()
  81. self.assertIn("candidates", msg)
  82. for claim in ("most likely", "must be", "because the model"):
  83. self.assertNotIn(claim, msg)
  84. class TruncationIsReportedNotDiscarded(unittest.TestCase):
  85. """
  86. A brief cut mid-word at the output limit must not be written to the page
  87. with no indication it is incomplete.
  88. Truncated-and-labelled beats absent. The text is kept and
  89. the caller is told, rather than the brief being thrown away.
  90. """
  91. def test_keeps_the_text(self):
  92. text, _, _ = call(anthropic(
  93. content=[{"type": "text", "text": "Half a sen"}], stop_reason="max_tokens"))
  94. self.assertEqual(text, "Half a sen")
  95. def test_surfaces_the_stop_reason_so_the_caller_can_label_it(self):
  96. _, _, stop = call(anthropic(stop_reason="max_tokens"))
  97. self.assertEqual(stop, "max_tokens")
  98. self.assertIn(stop, brief.TRUNCATED_STOPS)
  99. def test_a_complete_brief_is_not_flagged(self):
  100. _, _, stop = call(anthropic(stop_reason="end_turn"))
  101. self.assertNotIn(stop, brief.TRUNCATED_STOPS)
  102. class UsageIsReadFromTheResponse(unittest.TestCase):
  103. def test_exact_token_counts_not_estimates(self):
  104. _, usage, _ = call(anthropic(usage={"input_tokens": 1984, "output_tokens": 430}))
  105. self.assertEqual(usage, {"input": 1984, "output": 430})
  106. class OpenAiBranchFailsTheSameWay(unittest.TestCase):
  107. """
  108. The OpenAI-compatible path indexed ["choices"][0] directly, so a malformed
  109. response raised KeyError -- which says nothing about what went wrong. It gets
  110. the same contract as the Anthropic path: raise, and say what came back.
  111. """
  112. def openai(self, body: dict):
  113. with mock.patch("urllib.request.urlopen", return_value=response(body)):
  114. return brief.call_model("http://127.0.0.1:1234/v1", "qwen3", "s", "u",
  115. None, 30, "openai", 4000)
  116. def test_no_choices_raises_something_informative(self):
  117. with self.assertRaises(brief.EmptyResponseError):
  118. self.openai({"error": "model not loaded"})
  119. def test_empty_message_raises(self):
  120. with self.assertRaises(brief.EmptyResponseError):
  121. self.openai({"choices": [{"message": {"content": ""}}]})
  122. def test_length_finish_reason_counts_as_truncated(self):
  123. _, _, stop = self.openai({
  124. "choices": [{"message": {"content": "Half a sen"}, "finish_reason": "length"}],
  125. "usage": {"prompt_tokens": 10, "completion_tokens": 5},
  126. })
  127. self.assertIn(stop, brief.TRUNCATED_STOPS)
  128. class AllIncludesTheCompanyBrief(unittest.TestCase):
  129. """
  130. surfaces.ts gates the entire "Written briefing" card on the `company` key,
  131. with the leadership view nested inside it. An --all run without `company`
  132. would generate every brief while the board showed "Not generated for this
  133. sync", leadership included.
  134. """
  135. def test_all_generates_a_company_brief(self):
  136. keys = [j.key for j in brief.build_jobs(PAYLOAD, all_=True, person=None, view="standard")]
  137. self.assertIn("company", keys)
  138. def test_all_generates_exec_and_every_person(self):
  139. keys = [j.key for j in brief.build_jobs(PAYLOAD, all_=True, person=None, view="standard")]
  140. self.assertIn("exec", keys)
  141. for p in PAYLOAD["people"]:
  142. self.assertIn(p["name"], keys)
  143. def test_every_key_the_page_reads_is_covered(self):
  144. # The three shapes surfaces.ts looks up: `company` for the main card,
  145. # `exec` for the disclosure, and a person's roster name for "My day".
  146. keys = set(j.key for j in brief.build_jobs(PAYLOAD, all_=True, person=None, view="standard"))
  147. self.assertTrue({"company", "exec", "Nora Lindqvist"} <= keys)
  148. def test_no_duplicate_keys(self):
  149. keys = [j.key for j in brief.build_jobs(PAYLOAD, all_=True, person=None, view="standard")]
  150. self.assertEqual(len(keys), len(set(keys)))
  151. def test_a_single_person_run_writes_only_that_person(self):
  152. jobs = brief.build_jobs(PAYLOAD, all_=False, person="Sam Whitaker", view="standard")
  153. self.assertEqual([j.key for j in jobs], ["Sam Whitaker"])
  154. def test_exec_view_alone_keys_as_exec(self):
  155. jobs = brief.build_jobs(PAYLOAD, all_=False, person=None, view="exec")
  156. self.assertEqual([j.key for j in jobs], ["exec"])
  157. class BudgetsAreNotOneNumberForEverything(unittest.TestCase):
  158. """
  159. A single max_tokens of 800 covered a ~2k-token exec prompt and a ~6.7k-token
  160. company prompt alike, and both ran out. Each kind of brief carries its own.
  161. """
  162. def test_every_job_resolves_to_a_known_budget(self):
  163. for job in brief.build_jobs(PAYLOAD, all_=True, person=None, view="standard"):
  164. self.assertIn(job.budget, brief.MAX_TOKENS)
  165. def test_people_share_one_budget_rather_than_one_each(self):
  166. jobs = brief.build_jobs(PAYLOAD, all_=True, person=None, view="standard")
  167. people = [j for j in jobs if not j.key.startswith("company") and j.key != "exec"]
  168. self.assertTrue(people)
  169. for j in people:
  170. self.assertEqual(j.budget, "person")
  171. def test_the_all_hands_brief_is_priced_as_a_company_brief(self):
  172. # Same view over a slightly smaller payload. Priced as a person's brief it
  173. # would truncate: company measured 1715 output tokens against person 1029.
  174. jobs = brief.build_jobs(PAYLOAD, all_=True, person=None, view="standard")
  175. allhands = next(j for j in jobs if j.key == "company-allhands")
  176. self.assertEqual(allhands.budget, "company")
  177. def test_the_old_default_is_gone(self):
  178. # An 800-token ceiling truncates live briefings.
  179. for kind, budget in brief.MAX_TOKENS.items():
  180. self.assertGreater(budget, 800, kind)
  181. def test_every_budget_clears_what_was_measured_with_room_to_spare(self):
  182. # Live output_tokens from the run the budgets were set from.
  183. # A budget that merely equalled these would truncate on a busier day.
  184. measured = {"exec": 872, "company": 1715, "person": 1029}
  185. for kind, observed in measured.items():
  186. self.assertGreater(brief.MAX_TOKENS[kind], observed * 2, kind)
  187. def test_company_gets_the_largest_budget(self):
  188. # Exec, the most constrained prompt, might be expected to need the most.
  189. # Measured, company needs the most by a wide margin. This pins the
  190. # measurement, not the guess.
  191. self.assertEqual(max(brief.MAX_TOKENS, key=brief.MAX_TOKENS.get), "company")
  192. class NearCeilingWarnsBeforeItBreaks(unittest.TestCase):
  193. """
  194. stop_reason only reports truncation once a brief has already gone out
  195. half-finished. This is the warning one run earlier.
  196. """
  197. def test_the_threshold_leaves_usable_headroom(self):
  198. self.assertTrue(0.5 < brief.NEAR_CEILING < 1.0)
  199. def test_the_measured_worst_case_does_not_trip_it(self):
  200. # company at 1715 against a 4000 budget is 43% -- comfortable. If a
  201. # budget change made today's real data warn, the budget is wrong.
  202. for kind, observed in {"exec": 872, "company": 1715, "person": 1029}.items():
  203. self.assertLess(observed, brief.MAX_TOKENS[kind] * brief.NEAR_CEILING, kind)
  204. MIN_PAYLOAD = {
  205. "schemaVersion": "1.0.0",
  206. "generatedAt": "2026-08-25T18:38:31.943Z",
  207. "day": "2026-08-25",
  208. "timezone": "America/Chicago",
  209. "mode": "live",
  210. "sources": [{"name": "plinth", "ok": True, "error": None}],
  211. "rules": ["A rule that must reach the system prompt verbatim."],
  212. "execRules": ["Capacity, not performance."],
  213. "company": {"ourMove": 3},
  214. "exec": {"holding": 3},
  215. "dataHealth": {"unmeasurable": []},
  216. "people": [{"name": "Nora Lindqvist"}, {"name": "Sam Whitaker"}],
  217. }
  218. class RunEndToEnd(unittest.TestCase):
  219. """
  220. Drives main() with a stubbed transport. Covers the parts that only show up
  221. once the loop, the merge and the exit code are working together.
  222. """
  223. def setUp(self):
  224. self.dir = tempfile.TemporaryDirectory()
  225. self.addCleanup(self.dir.cleanup)
  226. self.payload = Path(self.dir.name) / "briefing-payload.json"
  227. self.payload.write_text(json.dumps(MIN_PAYLOAD), encoding="utf-8")
  228. self.out = Path(self.dir.name) / "briefings.json"
  229. def run_main(self, bodies: list[dict], argv: list[str]) -> int:
  230. full = ["brief.py", "--payload", str(self.payload), "--out", str(self.out),
  231. "--base-url", "https://api.anthropic.com/v1", "--api-key", "k",
  232. "--model", "claude-opus-5", *argv]
  233. with mock.patch.object(sys, "argv", full), \
  234. mock.patch("urllib.request.urlopen",
  235. side_effect=[response(b) for b in bodies]), \
  236. mock.patch.object(sys, "stdout", io.StringIO()), \
  237. mock.patch.object(sys, "stderr", io.StringIO()):
  238. return brief.main()
  239. def written(self) -> dict:
  240. return json.loads(self.out.read_text(encoding="utf-8"))
  241. def test_all_writes_the_company_key_the_page_reads(self):
  242. code = self.run_main([anthropic()] * 5, ["--all"])
  243. self.assertEqual(code, 0)
  244. self.assertIn("company", self.written()["briefings"])
  245. def test_all_writes_exec_company_and_every_person(self):
  246. self.run_main([anthropic()] * 5, ["--all"])
  247. got = set(self.written()["briefings"])
  248. self.assertEqual(got, {"exec", "company", "company-allhands", "Nora Lindqvist", "Sam Whitaker"})
  249. def test_one_empty_response_does_not_lose_the_other_briefs(self):
  250. # Making call_model raise turned a single bad response into a run that
  251. # wrote nothing at all. The rest of the briefs and a named failure beats none.
  252. bodies = [anthropic(content=[]), anthropic(), anthropic(), anthropic(), anthropic()]
  253. code = self.run_main(bodies, ["--all"])
  254. self.assertEqual(code, 1, "a failed brief must still fail the run")
  255. got = self.written()["briefings"]
  256. self.assertNotIn("exec", got, "the failed brief must be absent, not blank")
  257. self.assertEqual(set(got), {"company", "company-allhands", "Nora Lindqvist", "Sam Whitaker"})
  258. def test_a_failed_brief_is_absent_rather_than_empty_string(self):
  259. # Absent renders as "not generated"; an empty string renders as a blank
  260. # briefing, which is the failure this whole change is about.
  261. self.run_main([anthropic(content=[])] + [anthropic()] * 4, ["--all"])
  262. for key, text in self.written()["briefings"].items():
  263. self.assertTrue(text.strip(), key)
  264. def test_truncated_briefs_are_listed_for_the_page(self):
  265. bodies = [anthropic(stop_reason="max_tokens")] + [anthropic()] * 4
  266. self.run_main(bodies, ["--all"])
  267. self.assertEqual(self.written()["truncated"], ["exec"])
  268. def test_a_clean_run_lists_nothing_as_truncated(self):
  269. self.run_main([anthropic()] * 5, ["--all"])
  270. self.assertEqual(self.written()["truncated"], [])
  271. def test_rerunning_one_person_clears_only_their_stale_flag(self):
  272. # Union-ing the old and new lists would label a brief truncated forever,
  273. # so a successful re-run has to drop the key it rewrote and no other.
  274. self.run_main([anthropic(stop_reason="max_tokens")] * 5, ["--all"])
  275. self.assertEqual(self.written()["truncated"],
  276. ["Nora Lindqvist", "Sam Whitaker", "company", "company-allhands", "exec"])
  277. self.run_main([anthropic()], ["--person", "Nora Lindqvist"])
  278. self.assertEqual(self.written()["truncated"],
  279. ["Sam Whitaker", "company", "company-allhands", "exec"])
  280. def test_a_single_person_run_does_not_wipe_the_morning(self):
  281. self.run_main([anthropic()] * 5, ["--all"])
  282. self.run_main([anthropic()], ["--person", "Nora Lindqvist"])
  283. self.assertEqual(set(self.written()["briefings"]),
  284. {"exec", "company", "company-allhands", "Nora Lindqvist", "Sam Whitaker"})
  285. def test_ties_the_briefing_to_the_sync_it_was_written_from(self):
  286. # main.ts and briefingAge() compare this against snap.syncedAt to decide
  287. # whether the prose and the numbers describe the same moment.
  288. self.run_main([anthropic()] * 5, ["--all"])
  289. self.assertEqual(self.written()["payloadGeneratedAt"], MIN_PAYLOAD["generatedAt"])
  290. class RulesReachTheSystemPrompt(unittest.TestCase):
  291. """
  292. The payload ships its own `rules` array and brief.py's own docstring says
  293. they go in verbatim -- they are the traps that produce fluent, wrong
  294. sentences. A prompt built without them looks identical and is not.
  295. """
  296. def test_standard_view_carries_the_payload_rules(self):
  297. got = brief.system_prompt(MIN_PAYLOAD, "standard")
  298. self.assertIn(MIN_PAYLOAD["rules"][0], got)
  299. def test_exec_view_carries_both_rule_sets(self):
  300. got = brief.system_prompt(MIN_PAYLOAD, "exec")
  301. self.assertIn(MIN_PAYLOAD["execRules"][0], got)
  302. self.assertIn(MIN_PAYLOAD["rules"][0], got)
  303. def test_exec_rules_come_first(self):
  304. # Stated reason in the source: a model that runs out of attention should
  305. # lose the style guidance, not the limits.
  306. got = brief.system_prompt(MIN_PAYLOAD, "exec")
  307. self.assertLess(got.index(MIN_PAYLOAD["execRules"][0]),
  308. got.index(MIN_PAYLOAD["rules"][0]))
  309. if __name__ == "__main__":
  310. unittest.main()
  311. class RestrictedTodosNeverReachThePrompt(unittest.TestCase):
  312. """
  313. Leadership Team to-dos are readable only by the leadership team. The company briefing names
  314. to-dos by title, and prose cannot be redacted once a model has written it:
  315. there is no reliable way to strip a title from a paragraph that reasons about
  316. it, and asking the model not to mention it is a request, not a control.
  317. So the all-hands company brief is generated from a payload with those rows
  318. removed. These tests assert the removal, at the payload level, because that is
  319. the only place the guarantee is enforceable.
  320. """
  321. def payload(self):
  322. # Shaped like the real payload, which is the whole point.
  323. #
  324. # company.lateTodos is a count; the list lives under queues. A fixture
  325. # that put the list under company.lateTodos would pass against a shape
  326. # briefing-payload.ts never emits, while the filter removed nothing in
  327. # production. A fixture that does not match the contract tests the
  328. # fixture.
  329. return {
  330. **MIN_PAYLOAD,
  331. "company": {"ourMove": 3, "lateTodos": 2},
  332. "queues": {"lateTodos": [
  333. {"title": "SECRET leadership item", "restricted": True},
  334. {"title": "Open all-hands item", "restricted": False},
  335. ]},
  336. "people": [
  337. {"name": "Nora Lindqvist", "todos": [
  338. {"title": "SECRET leadership item", "restricted": True},
  339. {"title": "Open all-hands item", "restricted": False},
  340. ]},
  341. {"name": "Sam Whitaker", "todos": [
  342. {"title": "Another SECRET one", "restricted": True},
  343. ]},
  344. ],
  345. }
  346. def test_restricted_rows_are_gone(self):
  347. out = brief.without_restricted(self.payload())
  348. titles = [t["title"] for p in out["people"] for t in p["todos"]]
  349. self.assertEqual(titles, ["Open all-hands item"])
  350. def test_the_serialized_prompt_does_not_contain_the_titles(self):
  351. # The real guarantee: whatever ends up in the user turn must not carry
  352. # them. A filter that missed a nested copy would still pass the test above.
  353. out = brief.without_restricted(self.payload())
  354. prompt = brief.user_prompt(out, None, "standard")
  355. self.assertNotIn("SECRET leadership item", prompt)
  356. self.assertNotIn("Another SECRET one", prompt)
  357. self.assertIn("Open all-hands item", prompt)
  358. def test_the_queue_list_is_filtered_too(self):
  359. # queues.lateTodos is the company-level list of to-do TITLES. It was
  360. # never filtered, so every Leadership title reached the all-hands prompt
  361. # through it even once the rows were flagged.
  362. out = brief.without_restricted(self.payload())
  363. self.assertEqual([t["title"] for t in out["queues"]["lateTodos"]],
  364. ["Open all-hands item"])
  365. def test_a_count_that_looks_like_a_list_is_left_alone(self):
  366. # company.lateTodos is an int. Filtering must not touch it, and must not
  367. # crash on it either -- reading it as a list is what pointed the filter at
  368. # nothing for a week.
  369. out = brief.without_restricted(self.payload())
  370. self.assertEqual(out["company"]["lateTodos"], 2)
  371. def test_every_list_of_titles_in_the_payload_is_covered(self):
  372. # The assertion that would have caught the original bug without knowing
  373. # where the list lived: after filtering, NO to-do dict anywhere in the
  374. # payload may still carry restricted=True.
  375. out = brief.without_restricted(self.payload())
  376. def walk(node):
  377. if isinstance(node, dict):
  378. if node.get("restricted") is True:
  379. yield node
  380. for v in node.values():
  381. yield from walk(v)
  382. elif isinstance(node, list):
  383. for v in node:
  384. yield from walk(v)
  385. self.assertEqual(list(walk(out)), [])
  386. def test_the_original_payload_is_untouched(self):
  387. # The full company brief is generated from the same object in the same
  388. # run. Mutating it in place would silently redact that one too.
  389. src = self.payload()
  390. brief.without_restricted(src)
  391. self.assertEqual(len(src["people"][0]["todos"]), 2)
  392. def test_it_reports_how_many_it_removed(self):
  393. out = brief.without_restricted(self.payload())
  394. self.assertEqual(out["_restrictedTodosRemoved"], 3)
  395. def test_a_todo_with_no_restricted_flag_is_kept(self):
  396. # `restricted` is computed in TypeScript. An older payload without the
  397. # field must not silently drop every row -- that would empty the brief and
  398. # look like a quiet week.
  399. p = {**MIN_PAYLOAD, "people": [{"name": "X", "todos": [{"title": "No flag"}]}]}
  400. out = brief.without_restricted(p)
  401. self.assertEqual([t["title"] for t in out["people"][0]["todos"]], ["No flag"])
  402. class TheAllHandsBriefIsItsOwnGeneration(unittest.TestCase):
  403. def test_all_produces_both_company_briefs(self):
  404. keys = [j.key for j in brief.build_jobs(PAYLOAD, all_=True, person=None, view="standard")]
  405. self.assertIn("company", keys)
  406. self.assertIn("company-allhands", keys)
  407. def test_they_are_distinct_keys(self):
  408. # surfaces.ts picks one by permission. If they collided, everyone would
  409. # read whichever was written last.
  410. keys = [j.key for j in brief.build_jobs(PAYLOAD, all_=True, person=None, view="standard")]
  411. self.assertEqual(len(keys), len(set(keys)))