mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-09 16:35:27 +02:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - The server coordinates issue execution and agent wake events > - Queued comment mutations belong to the wake queue that owns their state > - Route-local database writes split queue rules across two layers > - This pull request moves those mutations into the wake-queue module and keeps route authorization and response mapping > - The benefit is one transaction boundary with company-scoped writes and a shared checked response contract ## Linked Issues or Issue Description **What existing behavior does this improve?** The queued-comment edit, reorder, and discard endpoints write queue state directly from the route layer. **Subsystem affected** server/ — REST API and orchestration services. **Current behavior** The route layer owns database transactions, locks, queue writes, and wake-row writes for queued comments. **Proposed behavior** The wake-queue module owns these operations. The routes keep authorization, input checks, error mapping, and response mapping. **Reason and benefit** The module gives all queued-comment callers one transaction boundary and applies company predicates to every adapter read and write. **Breaking changes** None. The endpoints keep their existing paths and response behavior. ## What Changed - Move queued-comment edit, reorder, and discard operations into the wake-queue module. - Add company predicates to seven queue writes. - Use the shared queue contract type for mutation responses. - Add module tests and route tests for the moved operations. ## Verification - `server/src/modules/wake-queue`: 128 tests pass across 6 files. - `server/src/__tests__/issue-queued-comments-routes.test.ts`: 19 tests pass. - The server TypeScript check reports the same 141 pre-existing errors before and after this change. - GitHub Actions must pass the required pull-request checks. ## Risks The change moves transaction and lock ownership across module boundaries. The new adapter, use-case, and route tests cover the moved behavior. No database schema changes occur. ## Model Used OpenAI Codex, GPT-5, current agent runtime, tool use and code review support. ## 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>
118 lines
4.3 KiB
TypeScript
118 lines
4.3 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { buildQueuedCommentQueueSnapshot, decideQueuedCommentQueueSteering } from "./issue-queued-comment-queue.js";
|
|
|
|
describe("decideQueuedCommentQueueSteering", () => {
|
|
it("answers unsupported on the legacy protocol", () => {
|
|
const decision = decideQueuedCommentQueueSteering({
|
|
state: "deferred",
|
|
queueRunRuntimeMode: null,
|
|
activeRun: { id: "run-1", runtimeMode: "legacy" },
|
|
assignedAgentAdapterType: "codex_local",
|
|
queuedCommentCount: 1,
|
|
});
|
|
|
|
expect(decision).toEqual({ protocol: "legacy", kind: "unsupported" });
|
|
});
|
|
|
|
it("answers temporarily_unavailable for a promoted native queue with no deferred run", () => {
|
|
const decision = decideQueuedCommentQueueSteering({
|
|
state: "queued",
|
|
queueRunRuntimeMode: "native",
|
|
activeRun: null,
|
|
assignedAgentAdapterType: "paperclip_runner",
|
|
queuedCommentCount: 1,
|
|
});
|
|
|
|
expect(decision).toEqual({ protocol: "paperclip_runner_v1", kind: "temporarily_unavailable" });
|
|
});
|
|
|
|
it("answers temporarily_unavailable when the queue holds no live comments", () => {
|
|
const decision = decideQueuedCommentQueueSteering({
|
|
state: "deferred",
|
|
queueRunRuntimeMode: null,
|
|
activeRun: { id: "run-1", runtimeMode: "native" },
|
|
assignedAgentAdapterType: "paperclip_runner",
|
|
queuedCommentCount: 0,
|
|
});
|
|
|
|
expect(decision).toEqual({ protocol: "paperclip_runner_v1", kind: "temporarily_unavailable" });
|
|
});
|
|
|
|
it("tells the caller it may probe a running deferred turn on the native protocol", () => {
|
|
const decision = decideQueuedCommentQueueSteering({
|
|
state: "deferred",
|
|
queueRunRuntimeMode: null,
|
|
activeRun: { id: "run-1", runtimeMode: "native" },
|
|
assignedAgentAdapterType: "paperclip_runner",
|
|
queuedCommentCount: 1,
|
|
});
|
|
|
|
expect(decision).toEqual({ protocol: "paperclip_runner_v1", kind: "probe", steeringRunId: "run-1" });
|
|
});
|
|
|
|
// Acceptance-criterion fact pattern: a deferred queue whose active run
|
|
// has not resolved a runtime mode yet, for an agent on the
|
|
// `paperclip_runner` adapter. The protocol resolves to
|
|
// `paperclip_runner_v1` through the adapter-type fallback, and the
|
|
// decision hands the run to the caller to probe live — it never answers
|
|
// the flat "unsupported" value a duplicated, unshared rule can drift to.
|
|
it("resolves the protocol through the adapter-type fallback and asks the caller to probe", () => {
|
|
const decision = decideQueuedCommentQueueSteering({
|
|
state: "deferred",
|
|
queueRunRuntimeMode: null,
|
|
activeRun: { id: "run-1", runtimeMode: null },
|
|
assignedAgentAdapterType: "paperclip_runner",
|
|
queuedCommentCount: 1,
|
|
});
|
|
|
|
expect(decision).toEqual({ protocol: "paperclip_runner_v1", kind: "probe", steeringRunId: "run-1" });
|
|
});
|
|
});
|
|
|
|
describe("buildQueuedCommentQueueSnapshot entry permissions", () => {
|
|
const baseFacts = {
|
|
issueId: "issue-1",
|
|
queueId: "queue-1",
|
|
state: "queued" as const,
|
|
activeRunId: null,
|
|
protocol: "legacy" as const,
|
|
steeringDisposition: "unsupported" as const,
|
|
};
|
|
|
|
it("grants edit and discard to the user who authored the queued comment", () => {
|
|
const queue = buildQueuedCommentQueueSnapshot({
|
|
...baseFacts,
|
|
actorType: "user",
|
|
actorId: "user-1",
|
|
comments: [{ id: "comment-1", updatedAt: new Date(), authorUserId: "user-1" }],
|
|
});
|
|
|
|
expect(queue.entries[0]?.canEdit).toBe(true);
|
|
expect(queue.entries[0]?.canDiscard).toBe(true);
|
|
});
|
|
|
|
it("denies edit and discard to a user who did not author the queued comment", () => {
|
|
const queue = buildQueuedCommentQueueSnapshot({
|
|
...baseFacts,
|
|
actorType: "user",
|
|
actorId: "user-1",
|
|
comments: [{ id: "comment-1", updatedAt: new Date(), authorUserId: "user-2" }],
|
|
});
|
|
|
|
expect(queue.entries[0]?.canEdit).toBe(false);
|
|
expect(queue.entries[0]?.canDiscard).toBe(false);
|
|
});
|
|
|
|
it("denies edit and discard to an agent actor even when the comment carries a matching author id", () => {
|
|
const queue = buildQueuedCommentQueueSnapshot({
|
|
...baseFacts,
|
|
actorType: "agent",
|
|
actorId: "user-1",
|
|
comments: [{ id: "comment-1", updatedAt: new Date(), authorUserId: "user-1" }],
|
|
});
|
|
|
|
expect(queue.entries[0]?.canEdit).toBe(false);
|
|
expect(queue.entries[0]?.canDiscard).toBe(false);
|
|
});
|
|
});
|