Attribute evidence freshness in compiled Context briefs

SI
5d ago
+370-9

Summary

Adds evidence-freshness attribution to compiled Context briefs and a fixture-tested two-song checkpoint. There is no recollection, model call, provider spend or database change. read_context_documents already returns sources[].retrievedAt per document, but compileContextBrief dropped it. The compiler now adds an additive input_manifest.freshness object:

  • oldestRetrievedAt/newestRetrievedAt across every dated source of the selected documents
  • documentsWithoutRetrievalDate
  • per-document oldestRetrievedAt/newestRetrievedAt

Every document section of the Markdown text now ends with a Retrieved: line: one timestamp, <oldest> to <newest> when that document's sources differ, or unknown. Source locations never enter the text or the manifest.

The new twoSongBriefCheckpoint.test.ts drives the shared processContextOperation surface, the same one HTTP and MCP call per transports.test.ts. Its fixture has two same-artist track requests, an out-of-recipe customer supporting-text document and a withdrawn revision. It asserts:

  • artist reuse and separate per-song documents
  • task-specific creative-direction vs playlist-pitch output
  • honest lyric gaps
  • exclusion of withdrawn, other-workspace and out-of-recipe evidence
  • freshness bounds
  • identical brief/save_brief/read_brief snapshots with no dispatch

Because compiled output changed, the Sites save_brief idempotency suffix moves from brief-v3 to brief-v4 (saveSiteContextBrief.ts). save_context_brief raises Brief key already used for different output when a key is replayed with different output. Without the bump, a site whose current revision already had a v3 brief would conflict after deploy on concepts → generate and on every retry. The workflow then reports "stopped during context brief" and the revision does not change. albumContextKeys.test.ts now locks the exact keys.

Plan deviation (validated against the deployed RPC): the scoping plan proposed bumping compilerVersion to context-brief-v2 and adding retrievedAt inside each input_manifest.documents[] entry. The live save_context_brief function (20261010010000_context_company_assessment.sql) raises Invalid compiled brief unless compilerVersion is exactly context-brief-v1. It raises Brief manifest does not match evidence unless input_manifest.documents equals a six-key shape it rebuilds itself. This PR keeps both pinned values stable and carries freshness in a new manifest key that the RPC tolerates. The checkpoint test asserts the six-key shape and v1, so a regression would fail the test.

Refs recoupable/app#2130 (child of epic recoupable/app#2116).

Merge sequencing

docs → api. Merge the companion docs PR recoupable/docs#360 first; it now documents the freshness contract only. The company_onboarding docs catch-up belongs to docs#365/#358. There is no database or app PR in this slice. This PR does not touch processContextOperation.ts or the MCP registration files, so it does not overlap the open api#981/#983/#985 dispatcher PRs. The one-line saveSiteContextBrief.ts change is not touched by any other open api PR. api#1018 edits only the company_onboarding recipe in compileContextBrief.ts, which is a different hunk.

Verification

Final pass, head 393dcb91:

  • Ran: pnpm exec vitest run lib/context → 68 files passed, 8 skipped (live/local suites skip without CONTEXT_LIVE_TEST/CONTEXT_PAID_TEST); 332 tests passed, 8 skipped, 0 failed.
  • Ran: pnpm exec vitest run lib/sites → 50 files passed, 3 skipped; 226 tests passed, 4 skipped, 0 failed. The ticket's lib/sites/production path has no test files; Sites tests live in lib/sites/__tests__.
  • Ran: pnpm exec tsc --noEmit → 199 errors, all pre-existing and none in changed files or lib/context. The two in lib/sites are in untouched buildProgress.test.ts/publishAction.test.ts. An untouched origin/main checkout at c72887a reported 202, with 0 in lib/context.
  • Ran: pnpm exec eslint on the 7 changed TS files → clean. pnpm exec prettier --check on them plus README.md → clean.
  • Red → green, first commit: before implementation the two new files reported 4 failed / 6 passed. Two failures were genuine RED, input_manifest.freshness being undefined. The other two were fixture mistakes corrected alongside the implementation.
  • Red → green, review fixes: the updated freshness assertions failed against the old newest-only summary, 2 failed / 8 passed: the oldest bound was masked and per-document entries had no range. Both pass after the fix (10/10). The new exact Sites key assertion failed with the brief-v3 suffix (1 failed / 1 passed) and passes with brief-v4.
  • Not verified: hosted deployment, live providers (no Music Flamingo, Perplexity, GPT/Astra, Chartmetric, Luminate or Spotify calls), production database, RPC behavior executed against PostgreSQL (read from migration SQL), a real two-track same-artist run, Sites workflow end to end.

Out of scope / known gaps

  • Freshness is reported, not enforced. No age limit drops stale evidence yet.
  • There is no evidence-kind private-input rule in the compiler. A customer_assertion on a recipe topic for a request subject would be selected. company_onboarding intentionally keeps labeled assertions. The Sites handoff already drops customer assertions in readSiteContextBrief, but playlist_pitch has no consumer-side filter. The checkpoint test's supporting-text exclusion proves topic scoping only and is named accordingly.
  • Source locations stay out of text and input_manifest only. brief.documents[].sources[].url is still returned and saved, which is pre-existing behavior that Sites relies on.
  • Old save_brief keys conflict after deploy. Replaying a pre-deploy key from another caller returns the documented conflict, and callers use a new key. The chat company-assessment Save mints a fresh random key after each preview.
  • Acceptance items 4–6 of #2130 are not advanced here. The live two-track evaluation, injected failure/resume with provider-expense reconciliation, and the human-review pass/fail dossier are gated on #2127/#2128/#2129 and approval.
  • Other tickets own the remaining changes. This PR adds no DB migration or chat UI. Withdrawal logic belongs to #2124, and provider and coverage changes belong to #2127/#2128/#2129.

🤖 Generated with Claude Code