Expose stored comment recency through authorized fan reads
Weekly fan reports need a real Last heard source. The authenticated GET /api/artists/{id}/fans query already joins each relationship’s latest comment, but response mapping discards it. This adds nullable last_comment_at from that stored comment when its commenter ID matches the returned fan.
Existing profile fields, engagement ordering, pagination, authentication and artist-access checks remain unchanged. Missing/mismatched evidence returns null; profile refresh or generic engagement timestamps are never substituted. Existing object/array join shapes are retained. No query, provider call, MCP tool, database schema/trigger, migration or write path is added.
This delivers the bounded supported fan-recency alternative called for in recoupable/mono#327 and recoupable/app#2023; it does not restore GET /api/comments, complete unrelated stale operations or close either tracker. The owning API verification contract documents the additive response and public ArtistFan schema follow-up for canonical Marketing/retained Docs publication. Existing valuation history work stays with #795.
Validation: nine new mocked regression/access cases; five domain assertions failed before the fix. All 22 fans cases (four files), full ESLint, scoped formatting and whitespace checks pass. Public-handler tests retain the real validator/domain projection and prove denied requests stop before comment reads. Repository TypeScript has diagnostics outside changed files; none reported in changed files. No customer comments were read, and no provider, production or customer write occurred. Synthetic tests do not establish a live weekly report or deployed schema.
Independent Records review, CI and coordinator qualifying merge remain required. See docs/verification/fan-comment-recency.md for exact evidence semantics and remaining publication/live acceptance.
Summary by cubic
Exposes the stored latest comment timestamp on each fan read so weekly fan reports have a real Last heard source. GET /api/artists/{id}/fans already joined each relationship's newest comment, but the response mapping discarded it.
Adds a nullable last_comment_at to each returned fan profile, taken from the stored comment only when its commenter ID matches that fan. Missing or mismatched evidence returns null; profile refresh times, engagement timestamps, and current time are never substituted. No query, schema, trigger, migration, provider call, or write path changes. Existing authentication, artist-access checks, ordering, pagination, and object/array join shapes stay unchanged.
Verification
- Adds nine mocked regression and access cases; five assertions failed before the fix.
- Public-handler cases run the real validator and projection and prove denied requests stop before reading fan or comment data.
- Tests use synthetic fixtures only; no live customer comments were read.