refactor(provenance): extract agentSourceFields write-stamp helper (#143 review #5)

The agent write-stamp idiom — `...(isAgent ? { <source>: 'agent', <chat>: aiChatId } : {})`
— was hand-reimplemented at every REST write site, so each new path risked a
wrong literal or a forgotten aiChatId. Extract a single
`agentSourceFields(provenance, sourceKey, chatKey)` next to AuthProvenanceData and
call it at the 5 uniform spread sites:

- comment.service create  -> createdSource / aiChatId
- page.service create/update/orphan-move/move -> lastUpdatedSource / lastUpdatedAiChatId

Sites that must CLEAR the source on a non-agent action keep their own conditional
(comment un-resolve writes an explicit null), and the collab persistence path keeps
its sticky-window logic — both noted in the helper's doc.

Behavior-preserving (the helper returns the identical object/`{}`). Typecheck
clean; server comment/page/auth/collab suites 246 pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
claude code agent 227
2026-06-24 00:05:54 +03:00
parent 0647faefcd
commit 1d54f8ed1c
3 changed files with 41 additions and 34 deletions

View File

@@ -13,6 +13,34 @@ export interface AuthProvenanceData {
aiChatId: string | null;
}
/**
* Agent-edit write-stamp fields for a repository insert/update (#143 review).
* Spread into the row being written: for an agent it stamps the `*Source`
* column 'agent' and the AI-chat id; for a normal user it returns `{}` so the
* column keeps its default ('user'). The only per-table variation is the column
* names, passed as `sourceKey`/`chatKey`, so the agent-stamp idiom lives in ONE
* place instead of being hand-reimplemented at every write site (where a wrong
* literal or a forgotten `aiChatId` could drift).
*
* insertComment({ ..., ...agentSourceFields(p, 'createdSource', 'aiChatId') })
* updatePage({ ..., ...agentSourceFields(p, 'lastUpdatedSource', 'lastUpdatedAiChatId') })
*
* Does NOT cover sites that must CLEAR the source on a non-agent action (e.g.
* comment un-resolve, which writes an explicit null) — those keep their own
* conditional; nor the collab persistence path (its own sticky-window logic).
*/
export function agentSourceFields<S extends string, C extends string>(
provenance: AuthProvenanceData | undefined,
sourceKey: S,
chatKey: C,
): Partial<Record<S, ProvenanceSource> & Record<C, string | null>> {
if (provenance?.actor !== 'agent') return {};
return {
[sourceKey]: 'agent',
[chatKey]: provenance.aiChatId,
} as Partial<Record<S, ProvenanceSource> & Record<C, string | null>>;
}
/**
* Resolve the request's provenance. Defaults to a 'user' actor when the claim
* is absent (e.g. an endpoint reached without going through the access-token