perf(runner): narrow current tool authority reads (#17587)

Select the current run fields used by Native tool authority without transferring unused saved profiles or output. Preserve full context, receipts, authorization predicates, and locked mutation reads.

Verified with full build and typecheck, 2,358 affected tests across 20 suites, a counterfactual real PostgreSQL byte regression, complete exact-head CI, and Greptile 5/5 Medium with no unresolved comments.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Devin FoleyandPaperclip authored and GitHub committed 2026-10-25 03:45:25 -07:00
1 parent c4c4f7eecb
commit 35d5b0686a
3 files changed
+64 -2

No files matched your search

+6
View File
@@ -123,6 +123,12 @@ launch context does not travel with each policy or parent-report check. JSON
types, task-key precedence, and company and agent identity checks are preserved.
Each decision still reads current data; this is not an authorization cache.
Native tool authority lookups select current run identity, status, invocation,
responsible user, context, and receipt data. Saved profiles and output excerpts
stay in storage and are omitted from that read. Task and agent binding, current
status, budget, and task privacy checks retain their existing predicates.
Mutation transactions still lock and read complete run records.
WebSocket delivery uses one fresh authorization batch for an event's task and
run bindings. When both bindings name the same task, the batch reads its current
authority once. Different task bindings must both pass. The batch is discarded
@@ -20,6 +20,7 @@ import {
issues,
} from "@paperclipai/db";
import { startEmbeddedPostgresTestDatabase } from "../../__tests__/helpers/embedded-postgres.js";
import { createDatabaseResponseCapture, installDatabaseResponseCapture } from "@paperclipai/db/response-capture";
import { initializeRunIdentity, reserveSteeredIdentity, reconcileSteeredIdentity } from "../run-identity.js";
import { documentService } from "../documents.js";
import { issueService } from "../issues.js";
@@ -198,6 +199,51 @@ describe("PaperclipRunnerToolAuthority", () => {
).rejects.toThrow("paperclip_runner_tool_not_advertised");
});
it("reads current tool authority without transferring saved profiles or output", async () => {
const binding = { companyId, agentId, issueId, runId };
const authority = new PaperclipRunnerToolAuthority(db, binding);
const contextCall = { tool: "get_task_context", callId: "context-read-width", arguments: {} };
const apiCall = { tool: "search_api", callId: "api-read-width", arguments: { query: "read task" } };
const expectedContext = await authority.execute(contextCall);
const expectedApi = await authority.execute(apiCall);
const [original] = await db.select().from(heartbeatRuns).where(eq(heartbeatRuns.id, runId));
const [actor] = await db.select({ status: agents.status }).from(agents).where(eq(agents.id, agentId));
const payload = "saved-profile-and-output🙂".repeat(32_768);
await db.update(heartbeatRuns).set({
runnerProfileJson: { ...original.runnerProfileJson, retainedPayload: payload },
stdoutExcerpt: payload,
stderrExcerpt: payload,
}).where(eq(heartbeatRuns.id, runId));
const [expanded] = await db.select().from(heartbeatRuns).where(eq(heartbeatRuns.id, runId));
const observed = createDb(temporary!.connectionString), events: Array<Record<string, unknown>> = [], now = Date.now();
const capture = createDatabaseResponseCapture({ start: now - 100, end: now + 59_000 }, event => events.push(event));
installDatabaseResponseCapture(observed, { heartbeatRuns, issues, agents, companies }, capture);
try {
const reader = new PaperclipRunnerToolAuthority(observed, binding);
const context = await reader.execute(contextCall), api = await reader.execute(apiCall);
expect(context).toEqual(expectedContext);
expect(api).toEqual(expectedApi);
expect(JSON.stringify([context, api])).not.toContain("saved-profile-and-output");
capture.flush();
const reads = events.filter(event => event.table === "heartbeat_runs" && event.operation === "select");
expect(reads.length).toBeGreaterThan(0);
expect(reads.reduce((bytes, event) => bytes + Number(event.responseValueBytes), 0)).toBeLessThan(16 * 1024);
expect((await db.select().from(heartbeatRuns).where(eq(heartbeatRuns.id, runId)))[0]).toEqual(expanded);
await db.update(agents).set({ status: "paused" }).where(eq(agents.id, agentId));
await expect(reader.execute(contextCall)).rejects.toThrow("paperclip_runner_tool_binding_not_authorized");
await expect(reader.execute(apiCall)).rejects.toThrow("paperclip_runner_tool_binding_not_authorized");
} finally {
capture.flush();
await observed.$client.end();
await db.update(agents).set({ status: actor.status }).where(eq(agents.id, agentId));
await db.update(heartbeatRuns).set({
runnerProfileJson: original.runnerProfileJson,
stdoutExcerpt: original.stdoutExcerpt,
stderrExcerpt: original.stderrExcerpt,
}).where(eq(heartbeatRuns.id, runId));
}
});
it("preserves platform receipt provenance in both mixed task history readers", async () => {
const at = new Date("2026-10-10T12:00:00Z");
const commentIds = Array.from({ length: 4 }, () => randomUUID());
@@ -1227,7 +1227,17 @@ export class PaperclipRunnerToolAuthority {
async #boundContext() {
if (this.binding.museRuntime && !this.binding.externalAuthorizingUserId) throw forbidden("Muse authorizer is unavailable");
const [row] = await this.db.select({ issue: issues, actor: agents, run: heartbeatRuns })
const [row] = await this.db.select({ issue: issues, actor: agents, run: {
id: heartbeatRuns.id,
companyId: heartbeatRuns.companyId,
agentId: heartbeatRuns.agentId,
runtimeMode: heartbeatRuns.runtimeMode,
status: heartbeatRuns.status,
invocationSource: heartbeatRuns.invocationSource,
responsibleUserId: heartbeatRuns.responsibleUserId,
contextSnapshot: heartbeatRuns.contextSnapshot,
resultJson: heartbeatRuns.resultJson,
} })
.from(heartbeatRuns)
.innerJoin(issues, eq(issues.id, this.binding.issueId))
.innerJoin(agents, eq(agents.id, this.binding.agentId))
@@ -2168,7 +2178,7 @@ export class PaperclipRunnerToolAuthority {
});
}
async #sourcePlanApproval(context: { issue: typeof issues.$inferSelect; run: typeof heartbeatRuns.$inferSelect }) {
async #sourcePlanApproval(context: { issue: typeof issues.$inferSelect; run: Pick<typeof heartbeatRuns.$inferSelect, "responsibleUserId"> }) {
const plan = await handoffPlanContext(this.db, context.issue, this.#privacyActor(context.run));
if (!plan || !this.binding.museRuntime) return plan;
const document = await documentService(this.db).getIssueDocumentByKey(plan.sourceIssueId, "plan");