feat(comments): attribute MCP agent comments as AI (unspoofable provenance) #143
Reference in New Issue
Block a user
Delete Branch "feat/mcp-comments-ai-attribution"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Implements the design in
docs/backlog/mcp-comments-ai-attribution.md: comments (and pages) created via MCP under a flagged agent service-account are attributed as AI — unspoofable (derived from server identity), additive (the author stays, an AI badge is shown next to it).Backend (B1) — unspoofable agent identity
20260623T120000-user-is-agent(users.is_agentboolean, default false) +db.d.ts/user.repobaseFields so the strategy actually receives it.jwt.strategy.tsderives provenance from the signed identity:req.raw.actor = user.isAgent ? 'agent' : (payload.actor ?? 'user'),aiChatId = payload.aiChatId ?? null. A flagged service-account stamps every write'agent'; external MCP has no internalai_chatsrow soaiChatIdis null. The existingcomment.servicestamping (created_source='agent'/resolved_source='agent') needed no change — a nullaiChatIdalready flows into the nullable column. ProvenanceaiChatIdtype loosened tostring | null(the internal AI-chat path still passes a real id).actoris never read from the request body; the onlyactor='agent'sources are a flagged identity or a server-signed token claim (internal AI chat) — a plain login can't forge either. Defense-in-depth: the provenance decorator hard-defaults to'user'.Frontend (B2) — AI badge in the comments sidebar
AiAgentBadgefrom page-history into a sharedcomponents/ui/ai-agent-badge.tsx(clickable deep-link whenaiChatIdpresent, plain label when null). page-history behavior unchanged.ICommentgainscreatedSource/aiChatId/resolvedSource(backend already returns them);comment-list-itemrenders the badge whencreatedSource==='agent'.Tests + verification
created_source='agent'); clientAiAgentBadge+comment-list-itemrendering branches. All green; client+servertscclean; migration applies.is_agent=truecomment →created_source='agent', ai_chat_id=null;is_agent=false→created_source='user'. Confirms unspoofability (provenance from identity, not request).Skipped (doc marks optional): the "Resolved by AI" marker —
resolved_sourceis already stamped +resolvedSourcewired intoIComment, so it's a small follow-up.🤖 Generated with Claude Code
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>После исправления можно вливать
Ghost referenced this pull request2026-06-25 12:00:53 +03:00