Skip to content

Backend foundation for message-anchored comments - #1878

Open
simple-agent-manager[bot] wants to merge 4 commits into
mainfrom
sam/build-backendmultiplayer-constituent-pr-ys4449
Open

Backend foundation for message-anchored comments#1878
simple-agent-manager[bot] wants to merge 4 commits into
mainfrom
sam/build-backendmultiplayer-constituent-pr-ys4449

Conversation

@simple-agent-manager

@simple-agent-manager simple-agent-manager Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Builds the server-side foundation for SAM's message-anchored commenting MVP from idea 01M0JQB842XSJ3W172DYPB37HN.

This is a constituent PR for a coordinated multi-PR effort. It intentionally does not deploy to staging and must not be merged by this PR; the parent integrator will combine it into the primary integration PR.

Scope delivered

  • Append-only ProjectData DO migration 032-message-comment-threads for comment_threads, comment_replies, and comment_status_mutations.
  • Shared comment types/defaults and env-backed write/read limits.
  • ProjectData storage module with bounded validation, deterministic per-session/per-thread sequence ordering, idempotent create/reply/status mutations, missing/cross-session anchor rejection, row-parse isolation, and count limits.
  • Public ProjectData RPC delegates and typed services/project-data.ts wrappers.
  • HTTP routes under /api/projects/:projectId/sessions/:sessionId/comments* with project membership/capability authorization.
  • Server-authoritative WebSocket broadcast through existing ProjectData session/project fan-out.
  • API reference/configuration docs and targeted tests.

Out of scope by request: file comments, fuzzy file re-anchoring, mentions, reactions, notification inboxes, unrelated refactors, and actual agent prompt enqueueing for sent.

Exact HTTP contract

All routes require authenticated, approved users and are scoped by projectId through ProjectData. Reads require project task:read; writes require project task:write. HTTP writes derive the actor server-side from the authenticated user as { kind: "human", id, name }.

List threads

GET /api/projects/:projectId/sessions/:sessionId/comments

Query params:

  • messageId?: string — when present, must exist in the same session/project.
  • status?: "open" | "sent" | "resolved"
  • afterSequence?: number — non-negative integer cursor.
  • limit?: number — positive integer, clamped by COMMENT_LIST_LIMIT_MAX.

Response 200:

{
  threads: MessageCommentThread[];
  hasMore: boolean;
}

Create thread

POST /api/projects/:projectId/sessions/:sessionId/comments

Body:

{
  messageId: string;
  body: string;
  quote?: string | null;
  clientMutationId?: string | null;
}

Response: 201 for first write, 200 for idempotent replay.

{
  thread: MessageCommentThread;
  idempotent: boolean;
}

Create reply

POST /api/projects/:projectId/sessions/:sessionId/comments/:threadId/replies

Body:

{
  body: string;
  clientMutationId?: string | null;
}

Response: 201 for first write, 200 for idempotent replay.

{
  thread: MessageCommentThread;
  reply: MessageCommentReply;
  idempotent: boolean;
}

Status transitions

  • POST /api/projects/:projectId/sessions/:sessionId/comments/:threadId/send -> sent
  • POST /api/projects/:projectId/sessions/:sessionId/comments/:threadId/resolve -> resolved
  • POST /api/projects/:projectId/sessions/:sessionId/comments/:threadId/reopen -> open

Body:

{
  clientMutationId?: string | null;
}

Response 200:

{
  thread: MessageCommentThread;
  idempotent: boolean;
}

Error mapping

  • 400 validation failures, invalid status/query/body.
  • 404 missing project/session/message/thread, including cross-session message anchors.
  • 409 clientMutationId reuse with a different mutation intent.
  • 422 configured thread/reply/body/quote/idempotency limits exceeded.

Exact shared model

type CommentStatus = "open" | "sent" | "resolved";
type CommentAuthorKind = "human" | "agent";

type MessageCommentAnchor = {
  kind: "message";
  messageId: string;
  quote: string | null;
};

type CommentAuthor = {
  kind: CommentAuthorKind;
  id: string;
  name: string | null;
};

type MessageCommentReply = {
  id: string;
  threadId: string;
  sessionId: string;
  author: CommentAuthor;
  body: string;
  createdAt: number;
  sequence: number;
  clientMutationId: string | null;
};

type MessageCommentThread = {
  id: string;
  sessionId: string;
  anchor: MessageCommentAnchor;
  author: CommentAuthor;
  body: string;
  status: CommentStatus;
  createdAt: number;
  updatedAt: number;
  sequence: number;
  version: number;
  clientMutationId: string | null;
  sentAt: number | null;
  sentBy: CommentAuthor | null;
  resolvedAt: number | null;
  resolvedBy: CommentAuthor | null;
  reopenedAt: number | null;
  reopenedBy: CommentAuthor | null;
  replies: MessageCommentReply[];
};

Exact ProjectData RPC contract

listCommentThreads(input: {
  sessionId: string;
  messageId?: string | null;
  status?: CommentStatus | null;
  afterSequence?: number | null;
  limit?: number | null;
}): { threads: MessageCommentThread[]; hasMore: boolean };

createCommentThread(input: {
  sessionId: string;
  messageId: string;
  body: string;
  quote?: string | null;
  clientMutationId?: string | null;
  actor: CommentAuthor;
}): { thread: MessageCommentThread; idempotent: boolean };

createCommentReply(input: {
  sessionId: string;
  threadId: string;
  body: string;
  clientMutationId?: string | null;
  actor: CommentAuthor;
}): { thread: MessageCommentThread; reply: MessageCommentReply; idempotent: boolean };

updateCommentThreadStatus(input: {
  sessionId: string;
  threadId: string;
  status: CommentStatus;
  clientMutationId?: string | null;
  actor: CommentAuthor;
}): { thread: MessageCommentThread; idempotent: boolean };

Write RPCs are transactional inside the ProjectData DO. Service wrappers do not retry non-idempotent writes; clients should send clientMutationId for optimistic retries.

Exact WebSocket event contract

Project/session listeners on the existing ProjectData WebSocket route receive full authoritative thread payloads on first writes and real status changes:

{
  type: "comment.thread.changed";
  payload: {
    sessionId: string;
    thread: MessageCommentThread;
    reason: "thread_created" | "reply_created" | "marked_sent" | "resolved" | "reopened";
  };
}

Idempotent replays and no-op same-status updates do not rebroadcast.

Config contract

New env-backed defaults:

  • COMMENT_BODY_MAX_LENGTH=8000
  • COMMENT_QUOTE_MAX_LENGTH=2000
  • COMMENT_IDEMPOTENCY_KEY_MAX_LENGTH=200
  • COMMENT_LIST_LIMIT_DEFAULT=100
  • COMMENT_LIST_LIMIT_MAX=500
  • COMMENT_THREADS_PER_SESSION_MAX=1000
  • COMMENT_REPLIES_PER_THREAD_MAX=200

Tests / validation

  • pnpm --filter @simple-agent-manager/shared build
  • pnpm --filter @simple-agent-manager/api typecheck
  • pnpm --filter @simple-agent-manager/api test -- tests/unit/durable-objects/comments.test.ts tests/unit/durable-objects/project-data-comment-broadcast.test.ts tests/unit/routes/chat-comments.test.ts tests/unit/services/project-data-comments.test.ts
  • pnpm --filter @simple-agent-manager/api exec eslint src/durable-objects/project-data/comments.ts src/durable-objects/project-data/index.ts src/durable-objects/project-data/types.ts src/env.ts src/routes/chat.ts src/routes/chat-comments.ts src/schemas/comments.ts src/schemas/index.ts src/services/project-data.ts tests/unit/durable-objects/comments.test.ts tests/unit/durable-objects/project-data-comment-broadcast.test.ts tests/unit/routes/chat-comments.test.ts tests/unit/services/project-data-comments.test.ts
  • pnpm exec prettier --check .agents/skills/api-reference/SKILL.md .claude/skills/api-reference/SKILL.md apps/api/src/durable-objects/project-data/comments.ts apps/api/src/durable-objects/project-data/index.ts apps/api/src/durable-objects/project-data/types.ts apps/api/src/env.ts apps/api/src/routes/chat.ts apps/api/src/routes/chat-comments.ts apps/api/src/schemas/comments.ts apps/api/src/schemas/index.ts apps/api/src/services/project-data.ts apps/api/tests/unit/durable-objects/comments.test.ts apps/api/tests/unit/durable-objects/project-data-comment-broadcast.test.ts apps/api/tests/unit/routes/chat-comments.test.ts apps/api/tests/unit/services/project-data-comments.test.ts apps/www/src/content/docs/docs/reference/configuration.md packages/shared/src/constants/defaults.ts packages/shared/src/constants/index.ts packages/shared/src/types/comments.ts packages/shared/src/types/index.ts tasks/active/2026-08-21-message-anchored-commenting-backend.md
  • pnpm quality:do-migration-safety
  • pnpm quality:source-contract-tests
  • pnpm quality:wrangler-bindings
  • pnpm quality:type-boundaries
  • pnpm quality:file-sizes
  • git diff --check

Staging deployment: not run, per explicit instruction.

Specialist Review Evidence

Reviewer Status Outcome
constitution-validator PASS New limits are env-backed shared defaults with Worker/DO Env, Wrangler, .env.example, and docs entries; no hardcoded URLs/timeouts/identifiers added.
cloudflare-specialist PASS ProjectData migration is append-only/non-destructive and passes quality:do-migration-safety; write RPCs use synchronous DO transactions and existing WebSocket fan-out.
security-auditor PASS HTTP routes require project task:read/task:write; server derives human actor from auth; ProjectData rejects missing/cross-session message anchors and missing threads.
test-engineer PASS Added storage, route/auth/error, service-wrapper, and DO WebSocket event tests covering migration, CRUD, replies, status, limits, idempotency, ordering, and event payloads.
doc-sync-validator PASS API reference, shared types, env examples, Wrangler defaults, and configuration docs reflect the new HTTP/RPC/event contract and env knobs.
env-validator PASS COMMENT_* env vars are consistently named and present in Worker Env, ProjectData Env, Wrangler, .env.example, docs, and shared defaults.
task-completion-validator PASS Acceptance criteria checked against the diff and passing tests; staging and merge intentionally skipped by explicit task constraint.

Contract assumptions for sibling PRs

  • Only message anchors are productionized here: { kind: "message", messageId, quote }.
  • sent is persisted/broadcast as a status only. This PR does not enqueue a prompt or perform agent delivery.
  • HTTP writes create human-authored comments from the authenticated user. Agent-authored comments can use the same ProjectData RPC shape from MCP/tool work.
  • Authorization is project membership/capability based, not session-creator based.

Agent Preflight

  • Preflight completed before code changes

Classifications:

  • external-api-change
  • cross-component-change
  • business-logic-change
  • public-surface-change
  • docs-sync-change
  • security-sensitive-change
  • ui-change
  • infra-change

External References

N/A: no external API documentation was needed; implementation sources were the SAM idea 01M0JQB842XSJ3W172DYPB37HN, prototype branch behavior, repository rules, and ProjectData code.

Codebase Impact Analysis

Impacts apps/api ProjectData migrations/storage/RPC/service/HTTP routes/tests, packages/shared exported comment types/defaults, apps/www configuration docs, API reference skill docs, Wrangler/env examples, and the active task record under tasks/active.

Documentation & Specs

Updated .claude/skills/api-reference/SKILL.md, .agents/skills/api-reference/SKILL.md, apps/www/src/content/docs/docs/reference/configuration.md, apps/api/.env.example, apps/api/wrangler.toml, shared exported types/defaults, and the task file with validation/specialist evidence.

Constitution & Risk Check

Checked Principle XI no-hardcoded-values with env-backed comment limits, append-only ProjectData migration safety, tenant/IDOR authorization, bounded writes, idempotency conflict handling, and WebSocket event convergence without client-authored state trust.

@codspeed-hq

codspeed-hq Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 6 untouched benchmarks


Comparing sam/build-backendmultiplayer-constituent-pr-ys4449 (2c980b1) with main (221c48c)

Open in CodSpeed

@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant