From 8bd6659ec295f7a24185128d3b5fc763b02b4219 Mon Sep 17 00:00:00 2001 From: Dotta Date: Fri, 5 Jun 2026 00:42:59 +0000 Subject: [PATCH] fix: align low-trust count and ancestry limits Co-Authored-By: Paperclip --- server/src/routes/issues.ts | 70 ++++++++----------- server/src/services/authorization.ts | 2 +- .../services/low-trust-runtime-containment.ts | 6 +- server/src/services/trust-preset-resolver.ts | 2 + 4 files changed, 38 insertions(+), 42 deletions(-) diff --git a/server/src/routes/issues.ts b/server/src/routes/issues.ts index 90033a30049..32a2d2c222f 100644 --- a/server/src/routes/issues.ts +++ b/server/src/routes/issues.ts @@ -130,7 +130,11 @@ import { resolveActorSourceTrustForIssue, sanitizeQuarantinedCommentForHigherTrust, } from "../services/source-trust.js"; -import { resolveCoreTrustPreset, type TrustPresetResolution } from "../services/trust-preset-resolver.js"; +import { + LOW_TRUST_ISSUE_ANCESTRY_MAX_DEPTH, + resolveCoreTrustPreset, + type TrustPresetResolution, +} from "../services/trust-preset-resolver.js"; const MAX_ISSUE_COMMENT_LIMIT = 500; const updateIssueRouteSchema = updateIssueSchema.extend({ @@ -1094,7 +1098,7 @@ export function issueRoutes( if (row.id !== input.issueId) { let cursor = row.parentId; let isDescendant = false; - for (let depth = 0; cursor && depth < 20; depth += 1) { + for (let depth = 0; cursor && depth < LOW_TRUST_ISSUE_ANCESTRY_MAX_DEPTH; depth += 1) { if (cursor === input.issueId) { isDescendant = true; break; @@ -2395,43 +2399,7 @@ export function issueRoutes( return; } - const companyScopeDecision = await access.decide({ - actor: req.actor, - action: "company_scope:read", - resource: { type: "company", companyId }, - }); - if (!companyScopeDecision.allowed) { - const rows = await svc.list(companyId, { - attention: "blocked", - status: req.query.status as string | undefined, - assigneeAgentId: req.query.assigneeAgentId as string | undefined, - participantAgentId: req.query.participantAgentId as string | undefined, - assigneeUserId: req.query.assigneeUserId as string | undefined, - projectId: req.query.projectId as string | undefined, - workspaceId: req.query.workspaceId as string | undefined, - executionWorkspaceId: req.query.executionWorkspaceId as string | undefined, - parentId: req.query.parentId as string | undefined, - descendantOf: req.query.descendantOf as string | undefined, - labelId: req.query.labelId as string | undefined, - originKind: req.query.originKind as string | undefined, - originKindPrefix: req.query.originKindPrefix as string | undefined, - originId: req.query.originId as string | undefined, - includeRoutineExecutions: - req.query.includeRoutineExecutions === "true" || req.query.includeRoutineExecutions === "1", - excludeRoutineExecutions: - req.query.excludeRoutineExecutions === "true" || req.query.excludeRoutineExecutions === "1", - includePluginOperations: - req.query.includePluginOperations === "true" || req.query.includePluginOperations === "1", - includeBlockedBy: true, - includeBlockedInboxAttention: true, - q: req.query.q as string | undefined, - limit: ISSUE_LIST_MAX_LIMIT, - }); - res.json({ count: (await filterIssuesForActor(req, rows)).length }); - return; - } - - const count = await svc.count(companyId, { + const blockedCountFilters = { attention: "blocked", status: req.query.status as string | undefined, assigneeAgentId: req.query.assigneeAgentId as string | undefined, @@ -2456,7 +2424,31 @@ export function issueRoutes( includeBlockedInboxAttention: true, hasPlanDocument, q: req.query.q as string | undefined, + } as const; + + const companyScopeDecision = await access.decide({ + actor: req.actor, + action: "company_scope:read", + resource: { type: "company", companyId }, }); + if (!companyScopeDecision.allowed) { + let offset = 0; + let visibleCount = 0; + while (true) { + const rows = await svc.list(companyId, { + ...blockedCountFilters, + limit: ISSUE_LIST_MAX_LIMIT, + offset, + }); + visibleCount += (await filterIssuesForActor(req, rows)).length; + if (rows.length < ISSUE_LIST_MAX_LIMIT) break; + offset += rows.length; + } + res.json({ count: visibleCount }); + return; + } + + const count = await svc.count(companyId, blockedCountFilters); res.json({ count }); }); diff --git a/server/src/services/authorization.ts b/server/src/services/authorization.ts index 870184cd7d5..1c7da80167e 100644 --- a/server/src/services/authorization.ts +++ b/server/src/services/authorization.ts @@ -12,6 +12,7 @@ import { import type { PermissionKey, PrincipalType } from "@paperclipai/shared"; import { LOW_TRUST_REVIEW_PRESET, type LowTrustBoundary } from "@paperclipai/shared"; import { + LOW_TRUST_ISSUE_ANCESTRY_MAX_DEPTH, isIssueWithinLowTrustBoundary, resolveCoreTrustPreset, type TrustPresetResolution, @@ -183,7 +184,6 @@ type AssignmentPolicyEffect = type AgentHierarchyRow = { id: string; reportsTo: string | null }; type LowTrustBoundaryWithCompany = LowTrustBoundary & { companyId: string }; -const LOW_TRUST_ISSUE_ANCESTRY_MAX_DEPTH = 12; type AgentAuthorizationRow = { id: string; companyId: string; diff --git a/server/src/services/low-trust-runtime-containment.ts b/server/src/services/low-trust-runtime-containment.ts index a1013e8f830..22ce04d6d9d 100644 --- a/server/src/services/low-trust-runtime-containment.ts +++ b/server/src/services/low-trust-runtime-containment.ts @@ -3,10 +3,12 @@ import type { Db } from "@paperclipai/db"; import { issues } from "@paperclipai/db"; import { unprocessable } from "../errors.js"; import type { TrustPresetResolution } from "./trust-preset-resolver.js"; -import { isIssueWithinLowTrustBoundary } from "./trust-preset-resolver.js"; +import { + LOW_TRUST_ISSUE_ANCESTRY_MAX_DEPTH, + isIssueWithinLowTrustBoundary, +} from "./trust-preset-resolver.js"; export const LOW_TRUST_RUNTIME_MANAGEMENT_TOOL_CLASS = "runtime.manage"; -const LOW_TRUST_ISSUE_ANCESTRY_MAX_DEPTH = 12; export function isLowTrustRuntimeManagementAllowed(resolution: TrustPresetResolution) { return resolution.kind === "low_trust_review" && diff --git a/server/src/services/trust-preset-resolver.ts b/server/src/services/trust-preset-resolver.ts index e7c9b482383..97529840bcd 100644 --- a/server/src/services/trust-preset-resolver.ts +++ b/server/src/services/trust-preset-resolver.ts @@ -11,6 +11,8 @@ import { type JsonRecord = Record; +export const LOW_TRUST_ISSUE_ANCESTRY_MAX_DEPTH = 12; + export type TrustPresetPolicySource = "agent" | "project" | "issue" | "run"; export type ResolveCoreTrustPresetInput = {