perf: skip conditions for unselected tool profile entries (#16556)

Check exact tool profile selectors before evaluating conditions. Matched entries retain fresh policy condition checks and existing approval, deny, binding, and decision-phase behavior.

Validation: 70 affected cases pass with zero skips. Full repository typecheck and build pass. A balanced same-database comparison over 47,931 entries preserves all decision JSON and reduces 200 decisions from about 1.05 seconds to 0.40 seconds. Fresh exact-head full CI, E2E, Superagent, legacy security, and Apex 5/5 passed with no unresolved findings.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip authored and GitHub committed 2026-10-16 05:59:23 -05:00
1 parent 8556539364
commit 43ffe8ce95
2 files changed
+50 -8

No files matched your search

@@ -18,7 +18,7 @@ import {
toolProfiles,
toolMcpGatewayTokens,
} from "@paperclipai/db";
import { eq } from "drizzle-orm";
import { and, eq } from "drizzle-orm";
import type { ToolAccessDecisionInput } from "@paperclipai/shared";
import {
createToolAccessDecisionCache,
@@ -178,6 +178,48 @@ describeEmbeddedPostgres("tool gateway listing memory", () => {
expect(largeListing.statements.length).toBeLessThan(100);
});
it("does not construct a condition clock for unmatched entries in a decision batch", async () => {
const fixture = await createListingFixture(db, 6);
const [binding] = await db.select().from(toolProfileBindings)
.where(and(eq(toolProfileBindings.companyId, fixture.company.id), eq(toolProfileBindings.targetId, fixture.namedGateway.id)));
expect(binding).toBeDefined();
for (let start = 0; start < 47_930; start += 1000) {
await db.insert(toolProfileEntries).values(Array.from({ length: Math.min(1000, 47_930 - start) }, (_, offset) => ({
companyId: fixture.company.id, profileId: binding!.profileId,
selectorType: "tool_name" as const, toolName: `not_selected_${start + offset}`, effect: "exclude" as const,
})));
}
const policy = toolAccessPolicyService(db);
const cache = createToolAccessDecisionCache();
const originalDate = Date;
let clocks = 0;
vi.stubGlobal("Date", new Proxy(originalDate, {
construct(target, args, constructor) {
if (args.length === 0) clocks++;
return Reflect.construct(target, args, constructor);
},
}));
const decisions = [];
try {
for (let index = 0; index < 10; index++) {
const entry = fixture.entries[4 + index % 2]!;
decisions.push(await policy.decide({
companyId: fixture.company.id,
actor: { actorType: "agent", actorId: fixture.agent.id, agentId: fixture.agent.id },
runContext: { heartbeatRunId: fixture.run.id, issueId: fixture.issue.id, projectId: fixture.project.id, gatewayId: fixture.namedGateway.id },
request: { toolName: entry.toolName!, applicationId: fixture.application.id, connectionId: fixture.connection.id,
catalogEntryId: entry.id, upstreamToolName: entry.toolName!, riskLevel: entry.riskLevel, arguments: {} },
consumeRateLimit: false,
}, { cache }));
}
} finally {
vi.unstubAllGlobals();
}
expect(decisions).toHaveLength(10);
expect(decisions.every(decision => decision.allowed && decision.reasonCode === "allow_profile")).toBe(true);
expect(clocks).toBeLessThan(5000);
}, 30_000);
it("reads the catalog without repeating the connection row for each tool", async () => {
const fixture = await createListingFixture(db, 20);
// An eligible connection without catalog tools adds nothing to a listing.
+7 -7
View File
@@ -591,14 +591,14 @@ function evaluatePolicyConditions(
}
function profileEntryMatches(entry: typeof toolProfileEntries.$inferSelect, ctx: ToolAccessContext): boolean {
const selected = entry.selectorType === "application" ? entry.applicationId === ctx.applicationId
: entry.selectorType === "connection" ? entry.connectionId === ctx.connectionId
: entry.selectorType === "catalog_entry" ? entry.catalogEntryId === ctx.catalogEntryId
: entry.selectorType === "tool_name" ? entry.toolName === ctx.toolName
: entry.selectorType === "risk_level" ? entry.riskLevel === ctx.riskLevel : false;
if (!selected) return false;
const conditions = isRecord(entry.conditions) ? entry.conditions as ToolPolicyConditions : null;
if (!evaluatePolicyConditions(conditions, ctx).matched) return false;
if (entry.selectorType === "application") return entry.applicationId === ctx.applicationId;
if (entry.selectorType === "connection") return entry.connectionId === ctx.connectionId;
if (entry.selectorType === "catalog_entry") return entry.catalogEntryId === ctx.catalogEntryId;
if (entry.selectorType === "tool_name") return entry.toolName === ctx.toolName;
if (entry.selectorType === "risk_level") return entry.riskLevel === ctx.riskLevel;
return false;
return evaluatePolicyConditions(conditions, ctx).matched;
}
function targetMatches(binding: typeof toolProfileBindings.$inferSelect, ctx: ToolAccessContext): boolean {