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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user