Files
PaperClipAI/server/src/modules/wake-queue/index.ts
T
Nicky LeachandPaperclip 86c2e0ac4a feat(server): log an activity row for each queued-comment queue mutation (#13159)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The server records actions that change issues and their queued
comments
> - The queued-comment edit, reorder, and discard routes changed queue
state without activity rows
> - Operators could not inspect these queue mutations in the activity
feed
> - This pull request adds one identifier-only activity row for each
successful queue mutation
> - The benefit is a durable audit trail with no comment text in the
activity log

## Linked Issues or Issue Description

**What existing behavior does this improve?**

The queued-comment edit, reorder, and discard routes now record their
successful mutations in the activity feed.

**Subsystem affected**

Cross-cutting (server and ui).

**Current behavior**

The three queue mutation routes change queued comments but do not write
an activity row. The activity feed has no label for these actions.

**Proposed behavior**

Each successful route writes one activity row with the actor fields,
entity fields, queue identifiers, and queue revision. The discard row
also includes the cancelled run identifier. The activity feed shows a
label for each action.

**Reason and benefit**

Operators need a durable record of queue changes. Identifier-only
details support audit and troubleshooting without storing comment text.

**Breaking changes**

None. The routes keep their existing response and authorization
behavior.

**Additional context**

Each mutation writes its activity row on the same locked transaction
that applies the mutation, so the two commit or roll back together. The
route publishes the live activity event only after that transaction
commits. The separate comment-cancel route opts out of this write and
keeps its existing single activity row.

## What Changed

- Add activity rows for queued-comment edit, reorder, and discard
mutations.
- Include queue identifiers, revisions, ordered comment identifiers, and
cancelled run identifiers as applicable.
- Add activity-feed labels for the three new actions.
- Add route and activity-format tests for the new behavior.
- Write each activity row on the same transaction as the mutation it
records, through a new port method that the adapter implements.
- Keep the comment-delete route opted out of that write, so a
cancellation does not log two rows.

## Verification

- [x] `npx vitest run
server/src/__tests__/issue-queued-comments-routes.test.ts` passes.
- [x] `npx vitest run ui/src/lib/activity-format.test.ts` passes.
- [x] `pnpm --filter @paperclipai/server typecheck` exits 0.
- [x] `pnpm --filter @paperclipai/ui typecheck` exits 0.
- [x] `node scripts/check-module-boundaries.mjs` passes.
- [x] The full CI suite is green.

## Risks

Low risk. The change adds activity rows after successful mutations and
does not change route responses, authorization, or stored comment text.

## Model Used

OpenAI Codex, GPT-5 Codex. The model used repository inspection, Git
operations, and command execution. The context window and reasoning mode
are not exposed by this runtime.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` /
`Closes: #` / `Refs: #` OR (b) described the issue in-PR following the
relevant issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-09-10 10:47:17 -07:00

130 lines
5.2 KiB
TypeScript

import type { Db } from "@paperclipai/db";
import {
createAdmissionTransactionScope as buildAdmissionTransactionScope,
createPostgresWakeQueueAdapter,
createWakeAdmissionReader,
createWakeAdmissionWriter,
} from "./adapters/postgres.js";
import { createQueuedCommentIssueLockWriter } from "./adapters/queued-comment-postgres.js";
import type { QueuedCommentQueuePostgresAdapterDeps } from "./adapters/queued-comment-postgres.js";
import { createAdmitWakeBehindIssueExecution, createReleaseIssueExecution } from "./application/use-cases.js";
import {
createDiscardQueuedComment,
createEditQueuedComment,
createReorderQueuedComments,
} from "./application/queued-comment-use-cases.js";
import type {
IssueSnapshot,
RecoveryEscalationPort,
RunSnapshot,
TransactionScope,
WakeAdmissionHeartbeatHelpers,
WakeQueueHost,
} from "./application/ports.js";
export type {
PostCommitEffect,
ReleaseOutcome,
RunSummary,
} from "./application/types.js";
export { WakeQueueApplicationError } from "./application/types.js";
export type {
IssueSnapshot,
RunSnapshot,
RecoveryEscalationPort,
ReleaseRecoveryBlockedNoticeKind,
TransactionScope,
} from "./application/ports.js";
export type { AdmitWakeBehindIssueExecutionInput, AdmitWakeBehindIssueExecutionResult, ReleaseIssueExecutionInput } from "./application/use-cases.js";
export {
QueuedCommentMutationError,
QueuedCommentMutationForbiddenError,
} from "./application/queued-comment-use-cases.js";
export type {
DiscardQueuedCommentInput,
DiscardQueuedCommentResult,
EditQueuedCommentInput,
EditQueuedCommentResult,
QueuedCommentMutationErrorCode,
ReorderQueuedCommentsInput,
ReorderQueuedCommentsResult,
} from "./application/queued-comment-use-cases.js";
export type {
QueuedCommentActivityPublication,
QueuedCommentActor,
QueuedCommentIssueContext,
QueuedCommentQueueSnapshot,
} from "./application/queued-comment-ports.js";
export type { QueuedCommentQueuePostgresAdapterDeps } from "./adapters/queued-comment-postgres.js";
export type WakeQueueDeps = {
/** Stays in `heartbeat.ts`; resolves the responsible user for a promoted or recovery run seed. */
resolveResponsibleUserId: WakeQueueHost["resolveResponsibleUserId"];
/** Stays in `heartbeat.ts`; reads the routine environment context for an execution issue. */
getRoutineEnv: WakeQueueHost["getRoutineEnv"];
/** Stays in `heartbeat.ts`; resolves the session-before display id for a wakeup. */
resolveSessionBeforeForWakeup: WakeQueueHost["resolveSessionBeforeForWakeup"];
/**
* The four wake-admission decision helpers stay in `heartbeat.ts` today;
* the module receives them here so it never imports the service it is
* extracted from.
*/
wakeAdmissionHelpers: WakeAdmissionHeartbeatHelpers;
/** `services/recovery`'s stranded-issue escalation, called only after the release transaction commits. */
recovery: RecoveryEscalationPort;
};
/**
* Composes the wake-queue module: the Postgres adapter (which owns the
* release transaction) and the release use case. `heartbeat.ts` holds the
* only caller: it builds one instance per process next to
* `createRunDispatch(db)` and delegates `releaseIssueExecutionAndPromote`'s
* body to `releaseIssueExecution`.
*
* The admission half is temporary: `heartbeat.ts` still opens and owns the
* transaction that admits a wake behind an active issue execution, so it
* builds a `TransactionScope` through `createAdmissionTransactionScope`
* before it calls `admitWakeBehindIssueExecution`.
*/
export function createWakeQueue(db: Db, deps: WakeQueueDeps) {
const issueLock = createPostgresWakeQueueAdapter(db, {
resolveResponsibleUserId: deps.resolveResponsibleUserId,
getRoutineEnv: deps.getRoutineEnv,
resolveSessionBeforeForWakeup: deps.resolveSessionBeforeForWakeup,
});
return {
releaseIssueExecution: createReleaseIssueExecution({ issueLock, recovery: deps.recovery }),
admitWakeBehindIssueExecution: createAdmitWakeBehindIssueExecution({
reader: createWakeAdmissionReader(),
writer: createWakeAdmissionWriter(),
helpers: deps.wakeAdmissionHelpers,
}),
createAdmissionTransactionScope(companyId: string, tx: Db): TransactionScope {
return buildAdmissionTransactionScope(companyId, tx);
},
};
}
export type WakeQueue = ReturnType<typeof createWakeQueue>;
/**
* Composes the three queued-comment queue mutations (edit, reorder,
* discard): the Postgres adapter, which owns the one transaction each
* mutation runs in, and the three use cases. This is a separate factory
* from `createWakeQueue` because these mutations need none of the release
* or admission host callbacks -- only the small set of comment-reference
* and external-object sync callbacks in `deps`, which a caller outside
* `heartbeat.ts` (the queued-comment route) can supply directly.
*/
export function createQueuedCommentQueue(db: Db, deps: QueuedCommentQueuePostgresAdapterDeps) {
const issueLock = createQueuedCommentIssueLockWriter(db, deps);
return {
editQueuedComment: createEditQueuedComment({ issueLock }),
reorderQueuedComments: createReorderQueuedComments({ issueLock }),
discardQueuedComment: createDiscardQueuedComment({ issueLock }),
};
}
export type QueuedCommentQueue = ReturnType<typeof createQueuedCommentQueue>;