personal memory agent

refactor(talent): audit fixes for the JSON morning briefing master

render_briefing_markdown no longer borrows talent_outputs.ABSENT_TEXT, whose "Not specified in this document" copy is wrong for a briefing and reaches the owner through sol call ... briefing as well as the search index. Empty sections now render a briefing-specific "Nothing to report.", and an absent coverage_preamble omits the blockquote entirely rather than printing filler, matching render_briefing_sections' omit-empty behavior. All five ## headings still render. _clean stays duplicated across briefing.py and talent_outputs.py on purpose: importing an underscore-prefixed private name across module boundaries is worse than a one-line duplicate. briefing_cmd's newest-first fallback dropped its exists() / st_size > 0 precheck, which was dead once load_briefing began validating shape, and stopped shadowing the name briefing between a Path in the loop and a dict in the --day branch. morning_briefing_path is no longer imported there. A new test covers a malformed briefing at the newest day falling through to an older valid one, behavior that is now load-driven rather than size-driven. tests/eval_schemas.py::load_cases runs schema_path cases through hydrate_runtime_enums, but make ci never invokes that harness, so the call was uncovered. A unit test now asserts the morning_briefing case's reading.items.properties.facet carries no __RUNTIME_FACETS__ sentinel after loading, either replaced with live facet slugs or with the enum key dropped on a zero-facet journal. The stats/stats API-baseline diff reported during audit was not a regression and no baseline changed. make verify-api at ce4409b7 reported ten failing endpoints against nine at base dbd036e7. The extra one was traced to a stale tests/fixtures/journal/stats.json runtime artifact left behind by an earlier sandbox run: git-ignored, but read by the sandbox as source. Removing it makes the HEAD failure set exactly equal to the base failure set: config/convey, sol/run-detail, sol/stats-month, sol/badge-count, search/search, thinking/providers, tokens/usage, tokens/stats-month, tokens/daily. Those nine are pre-existing drift on clean main and are out of scope here. make update-api-baselines was deliberately not used to fix this commit, because it rewrites every baseline and would have laundered that pre-existing drift into this commit. The two baselines updated in ce4409b7 were already correct. The Home briefing card was observed end-to-end against a seeded sandbox journal: briefing_phase: active, briefing_summary: "Morning briefing — 3 meetings, 2 items need attention", briefing_needs_badge: "1 item also in Pulse needs", briefing_needs_deduped an array of two strings, and briefing_sections five markdown bodies. No browser screenshot was taken because Playwright is not installed on this machine (ModuleNotFoundError: No module named 'playwright'); the API payloads above are the wire contract home.js consumes, and no visual pass is claimed. Declined during audit: _briefing_summary keeps both its briefing and sections arguments because the structured meeting count and the first-non-empty-line fallback genuinely need each. _briefing_freshness's second path resolution stays because _briefing_output_format() is lru_cached and a request-scoped context refactor is not worth it. No defensive handling was added around get_journal() raising SolstoneNotConfigured on an unwritable journal, per CLAUDE.md §8; that failure mode is identical for every get_journal() caller. The narrative frontmatter prose in docs/design/yesterdays-processing-card.md and docs/design/voice-server.md remains a follow-up, as ce4409b7 already recorded. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>