Files
PaperClipAI/server/src/__tests__/helpers/github-review-wait.ts
T
DottaandPaperclip 2cbfc35a07 perf(github): reuse the inherited review wait query template (#18965)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - External review waits check current GitHub review and publication
bindings.
> - The GitHub service rebuilds the same seven-table Drizzle query on
each check.
> - A sampled production window attributed 6.3% of SQL serialization CPU
to this query shape.
> - This pull request prepares that query once per exact database or
transaction object.
> - Each call still reads current provenance, review, connection and
publication state.
> - The local service benchmark reduces median client CPU per read by
24.35%.

## Linked Issues or Issue Description

**What existing behavior does this improve?**

Repeated `readInheritedGitHubReviewWait` calls from external review wait
reads and attention retention.

**Subsystem affected**

Server GitHub chat review waits.

**Current behavior**

Every call constructs and serializes the same joined SELECT. The
database driver already supports prepared statements. That setting does
not reuse the JavaScript query template.

**Proposed behavior**

Compile the existing selector once for each supplied database,
transaction or savepoint object. Bind company, issue, agent and comment
IDs on each execution. Keep every current database read and check.

**Reason and benefit**

Remove repeated query construction and serialization CPU. Do not cache
rows or authorization decisions. Exact open-PR searches found no
duplicate query-template change.

## What Changed

- Add an exact-handle `WeakMap` for the GitHub review selector and
execute it with fresh placeholders.
- Preserve the SQL projection, joins, JSON types, latest-review order
and all current authority checks.
- Extend real PostgreSQL tests for new bindings, warm-cache revocation,
latest heads and transaction/savepoint rollback.
- Capture prepared executions in the existing test helper.
- Add an actual-service benchmark and a report with every timing sample
and setup failure.

## Verification

- `github-review-wait.test.ts`: 16 tests passed on real PostgreSQL.
- `external-chat-wait.integration.test.ts -t 'reads small current
external review waits without saved prompts'`: four affected cases
passed; 269 unrelated cases skipped.
- Pinned baseline: `e54f9fbf828e06e385e0c62dc381ccb46190aa30`. Final
source SHA256:
`fe647e7fe2aed82609e8f1d66a0109e7a10ee28313ae2ef218ba3ab39b5cd105`.
- Exclusive-window benchmark: 500 warmups per version, nine alternating
pairs of 100 complete service reads on disposable PostgreSQL. Median
client CPU: 1.00336 → 0.75908 ms/read (24.35% less). Median wall time:
2.87089 → 2.38731 ms/read (16.84% less). CPU excludes the PostgreSQL
process.
- Every generated SQL statement, parameter, query count and complete
result matched. Warm joined-selector compilations per ten service reads:
10 → 0.
- See `doc/performance/2026-11-04-prepared-github-review-wait.md` for
reproduction, all samples and measurement limits.
- Setup record: loopback required execution outside the sandbox. The
fresh embedded-Postgres dependency lacked macOS dylib aliases; aliases
to existing identical bytes restored the fixture. The SDK import needed
a current workspace SDK/shared build. A broad external-chat suite run
was interrupted and replaced by the affected test filter. Those attempts
are not counted as passing suites.
- Greptile: 5/5 on current head
`d2e6ae8814c3a56c380bd5d60c47e6f98642f085`, with no actionable issues or
inline comments.
- Current-head CI is green at
`d2e6ae8814c3a56c380bd5d60c47e6f98642f085`: 52 successful checks, three
intentional skips and the successful Snyk status. This includes
typecheck, build, the full test matrix, end-to-end shards and the
aggregate gate.
- Combined local validation at `4ab58ff70b` includes both measured
production templates and final test fixes. Full recursive `pnpm -r
typecheck` passed in 39.864 seconds. Three real PostgreSQL files passed
35/35 tests (five chat cases, 16 GitHub review cases and 14 shared
prepared-query cases). The combined build passed earlier with the same
production template bytes.
- The initial combined typecheck failed on a separate chat test typing
error and build setup. The later passing run resolves that attempt. The
original full local `pnpm test:run` was deliberately interrupted after
six chat shards passed and partial general tests ran; it is not a
passing full local suite. Each PR has independent current-head CI for
broad coverage. The initial loopback sandbox restriction was resolved by
an escalated run of the same source.
- Local benchmark/test dependencies were locked at the baseline; the
rebase adds the upstream lockfile refresh and UI paste fix only.

## Risks

- A prepared query captures its session. The exact supplied handle is
the cache key so transactions and savepoints do not borrow the root
session.
- Placeholder mistakes could cross bindings. Tests alternate independent
company/issue/agent/comment fixtures on the same handle.
- The benchmark has one synthetic eligible fixture with warm sequential
reads. It does not predict production endpoint latency or whole-server
CPU savings.
- No schema, index, database driver setting or public API changes.

## Model Used

OpenAI `gpt-6.1-sol` through Codex. The context-window size is not
exposed by the session. Used reasoning, tool calls, source editing and
code execution. Independent Codex review found no correctness issues.

## 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-11-04 08:29:59 -06:00

86 lines
6.1 KiB
TypeScript

import { createHash, randomUUID } from "node:crypto";
import { and, eq, fillPlaceholders } from "drizzle-orm";
import { agents, chatActions, chatConversations, chatEndpointResources, chatEndpoints,
chatGitHubReviews, chatMessageLinks, companies, heartbeatRuns, issueComments, issues,
runIdentityContexts, toolApplications, toolConnections, type Db } from "@paperclipai/db";
import { defaultGitHubReviewPolicy } from "@paperclipai/shared";
export async function seedGitHubReviewWait(db: Db) {
const companyId = randomUUID(), agentId = randomUUID(), issueId = randomUUID(), runId = randomUUID();
const reviewRunId = randomUUID(), reviewId = randomUUID(), endpointId = randomUUID(), resourceId = randomUUID();
const conversationId = randomUUID(), commentId = randomUUID(), activeIdentityContextId = randomUUID();
const applicationId = randomUUID(), connectionId = randomUUID(), publicationId = randomUUID();
const headSha = "1".repeat(40), repository = "synthetic/repository", repositoryId = "42", pullNumber = 7;
await db.insert(companies).values({ id: companyId, name: "Synthetic review", issuePrefix: companyId });
await db.insert(agents).values({ id: agentId, companyId, name: "Reviewer", adapterType: "process" });
await db.insert(issues).values({ id: issueId, companyId, title: "Review", assigneeAgentId: agentId });
await db.insert(heartbeatRuns).values([
{ id: runId, companyId, agentId, nativeIssueId: issueId, responsibleUserId: "operator", activeIdentityContextId },
{ id: reviewRunId, companyId, agentId, nativeIssueId: issueId, status: "succeeded" },
]);
await db.insert(issueComments).values({ id: commentId, companyId, issueId, authorUserId: "operator", body: "Review" });
await db.insert(runIdentityContexts).values({ id: activeIdentityContextId, companyId, runId, revision: 1,
responsibleUserId: "operator", messageId: commentId, cause: "test", correlationId: "test", status: "accepted" });
await db.insert(toolApplications).values({ id: applicationId, companyId, name: "GitHub", type: "chat" });
await db.insert(toolConnections).values({ id: connectionId, companyId, applicationId, name: "GitHub",
uid: connectionId, transport: "chat_sdk", connectionPurpose: "channel", enabled: true, status: "active" });
await db.insert(chatEndpoints).values({ id: endpointId, companyId, connectionId, provider: "github",
publicId: endpointId, assignedAgentId: agentId });
await db.insert(chatEndpointResources).values({ id: resourceId, companyId, endpointId, type: "repository",
providerResourceId: repository, label: repository, metadata: { providerRepositoryId: repositoryId } });
await db.insert(chatConversations).values({ id: conversationId, companyId, endpointId, resourceId, issueId,
externalConversationId: repository, externalThreadId: `github:${repository}:${pullNumber}`, externalLabel: "Review" });
await db.insert(chatMessageLinks).values({ companyId, endpointId, conversationId, commentId,
providerMessageId: commentId, direction: "inbound" });
await db.insert(chatGitHubReviews).values({ id: reviewId, companyId, endpointId, issueId, runId: reviewRunId,
repositoryId, repository, pullNumber, headSha, deliveryId: reviewId, configurationRevision: 1,
state: "completed", policySnapshot: defaultGitHubReviewPolicy(),
event: { event: "mention", deliveryId: reviewId, repositoryId, repository, pullNumber, title: "Review", body: "",
baseSha: "0".repeat(40), headSha, baseBranch: "main", author: { id: "1", login: "author", isBot: false },
sender: { id: "1", login: "author" }, draft: false, labels: [] },
assessment: { reviewedCommit: headSha, score: 5, complete: true, summary: "Reviewed", rationale: "Reviewed",
coverage: { reviewedPaths: [], omittedPaths: [], limitations: [] }, findings: [] } });
await db.insert(chatActions).values({ id: publicationId, companyId, endpointId, kind: "github_review_publication",
providerActionId: `github_publication:${issueId}:assessment:${createHash("sha256").update(JSON.stringify(reviewId)).digest("hex")}`,
status: "processed" });
const [run] = await db.select().from(heartbeatRuns).where(eq(heartbeatRuns.id, runId));
return { run: run!, companyId, agentId, issueId, runId, reviewRunId, reviewId, endpointId, resourceId,
conversationId, commentId, activeIdentityContextId, connectionId, publicationId, headSha };
}
export function setGitHubReviewAssessment(db: Db, reviewId: string, assessment: unknown) {
return db.$client.unsafe("UPDATE chat_github_reviews SET assessment = $1::jsonb WHERE id = $2::uuid",
[JSON.stringify(assessment), reviewId]);
}
export type CapturedReviewWaitQuery = { sql: string; params: unknown[] };
export function captureReviewWaitQueries(db: Db, queries: CapturedReviewWaitQuery[]): Db {
return new Proxy(db, { get(target, key) {
if (key !== "select") return Reflect.get(target, key);
return (...fields: Parameters<Db["select"]>) => {
const builder = target.select(...fields), from = builder.from.bind(builder);
builder.from = ((...args: Parameters<typeof from>) => {
const selection = from(...args), execute = selection.execute.bind(selection);
const prepare = selection.prepare.bind(selection);
selection.prepare = ((...args: Parameters<typeof prepare>) => {
const prepared = prepare(...args), executePrepared = prepared.execute.bind(prepared);
prepared.execute = async (values = {}) => {
const query = prepared.getQuery();
queries.push({ sql: query.sql, params: fillPlaceholders(query.params, values) });
return executePrepared(values);
};
return prepared;
}) as typeof selection.prepare;
selection.execute = async () => { queries.push(selection.toSQL()); return execute(); };
return selection;
}) as typeof builder.from;
return builder;
};
}});
}
export async function revokeReviewWaitConnection(db: Db, fixture: Awaited<ReturnType<typeof seedGitHubReviewWait>>) {
await db.update(toolConnections).set({ enabled: false }).where(and(
eq(toolConnections.id, fixture.connectionId), eq(toolConnections.companyId, fixture.companyId)));
}