mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-22 00:30:19 +02:00
fix: optimize low-trust blocked count
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
9992e568ce
commit
2719f31e3f
4 files changed
+93
-1
No files matched your search
@@ -772,6 +772,41 @@ describeEmbeddedPostgres("low-trust red-team HTTP route regression suite", () =>
|
||||
}
|
||||
});
|
||||
|
||||
it("counts blocked inbox issues with the low-trust boundary applied in the database", async () => {
|
||||
const fixture = await seedLowTrustFixture(db);
|
||||
await db.insert(issues).values([
|
||||
{
|
||||
companyId: fixture.company.id,
|
||||
projectId: fixture.projects.allowed.id,
|
||||
parentId: fixture.issues.reviewRoot.id,
|
||||
title: "Visible blocked vendor wait",
|
||||
status: "blocked",
|
||||
priority: "medium",
|
||||
description: "external owner: Visible vendor\nexternal action: Finish visible review",
|
||||
},
|
||||
{
|
||||
companyId: fixture.company.id,
|
||||
projectId: fixture.projects.outOfScope.id,
|
||||
title: "Hidden blocked vendor wait",
|
||||
status: "blocked",
|
||||
priority: "medium",
|
||||
description: "external owner: Hidden vendor\nexternal action: Finish hidden review",
|
||||
},
|
||||
]);
|
||||
|
||||
const boardCount = await request(createApp(db, boardActor(fixture)))
|
||||
.get(`/api/companies/${fixture.company.id}/issues/count`)
|
||||
.query({ attention: "blocked", q: "blocked vendor wait" });
|
||||
expect(boardCount.status, JSON.stringify(boardCount.body)).toBe(200);
|
||||
expect(boardCount.body.count).toBe(2);
|
||||
|
||||
const lowTrustCount = await request(createApp(db, agentActor(fixture)))
|
||||
.get(`/api/companies/${fixture.company.id}/issues/count`)
|
||||
.query({ attention: "blocked", q: "blocked vendor wait" });
|
||||
expect(lowTrustCount.status, JSON.stringify(lowTrustCount.body)).toBe(200);
|
||||
expect(lowTrustCount.body.count).toBe(1);
|
||||
});
|
||||
|
||||
it("redacts quarantined low-trust output from higher-trust wake and continuation contexts", async () => {
|
||||
const fixture = await seedLowTrustFixture(db);
|
||||
const lowTrustApp = createApp(db, agentActor(fixture));
|
||||
|
||||
@@ -2440,6 +2440,24 @@ export function issueRoutes(
|
||||
} as const;
|
||||
|
||||
if (!(await actorCanReadCompanyScope(req, companyId))) {
|
||||
const trustResolution = req.actor.type === "agent"
|
||||
? await resolveAgentTrustForIssue({
|
||||
agentId: req.actor.agentId,
|
||||
runId: req.actor.runId,
|
||||
}, companyId, null)
|
||||
: null;
|
||||
if (trustResolution?.kind === "denied") {
|
||||
throw forbidden(trustResolution.detail);
|
||||
}
|
||||
if (trustResolution?.kind === "low_trust_review") {
|
||||
const count = await svc.count(companyId, {
|
||||
...blockedCountFilters,
|
||||
lowTrustBoundary: trustResolution.boundary,
|
||||
});
|
||||
res.json({ count });
|
||||
return;
|
||||
}
|
||||
|
||||
let offset = 0;
|
||||
let visibleCount = 0;
|
||||
while (true) {
|
||||
|
||||
@@ -7552,7 +7552,7 @@ export function heartbeatService(db: Db, options: HeartbeatServiceOptions = {})
|
||||
db,
|
||||
companyId: agent.companyId,
|
||||
contextSnapshot: context,
|
||||
continuationSummary: safeContinuationSummary,
|
||||
continuationSummary,
|
||||
issueSummary: issueRef
|
||||
? {
|
||||
id: issueRef.id,
|
||||
|
||||
@@ -44,6 +44,7 @@ import type {
|
||||
IssueProductivityReview,
|
||||
IssueProductivityReviewTrigger,
|
||||
IssueRelationIssueSummary,
|
||||
LowTrustBoundary,
|
||||
SuccessfulRunHandoffState,
|
||||
} from "@paperclipai/shared";
|
||||
import {
|
||||
@@ -244,6 +245,7 @@ export interface IssueFilters {
|
||||
includeBlockedBy?: boolean;
|
||||
includeBlockedInboxAttention?: boolean;
|
||||
hasPlanDocument?: boolean;
|
||||
lowTrustBoundary?: LowTrustBoundary & { companyId: string };
|
||||
q?: string;
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
@@ -1181,6 +1183,39 @@ const PRODUCTIVITY_REVIEW_TRIGGERS: readonly IssueProductivityReviewTrigger[] =
|
||||
"long_active_duration",
|
||||
"high_churn",
|
||||
];
|
||||
|
||||
function lowTrustBoundaryIssueCondition(
|
||||
companyId: string,
|
||||
boundary: (LowTrustBoundary & { companyId: string }) | null | undefined,
|
||||
) {
|
||||
if (!boundary || boundary.companyId !== companyId) return null;
|
||||
const clauses: SQL[] = [];
|
||||
const issueIds = [...new Set(boundary.issueIds ?? [])];
|
||||
const projectIds = [...new Set(boundary.projectIds ?? [])];
|
||||
if (issueIds.length > 0) clauses.push(inArray(issues.id, issueIds));
|
||||
if (projectIds.length > 0) clauses.push(inArray(issues.projectId, projectIds));
|
||||
if (boundary.rootIssueId) {
|
||||
clauses.push(sql<boolean>`
|
||||
${issues.id} IN (
|
||||
WITH RECURSIVE descendants(id) AS (
|
||||
SELECT ${issues.id}
|
||||
FROM ${issues}
|
||||
WHERE ${issues.companyId} = ${companyId}
|
||||
AND ${issues.id} = ${boundary.rootIssueId}
|
||||
UNION
|
||||
SELECT ${issues.id}
|
||||
FROM ${issues}
|
||||
JOIN descendants ON ${issues.parentId} = descendants.id
|
||||
WHERE ${issues.companyId} = ${companyId}
|
||||
)
|
||||
SELECT id FROM descendants
|
||||
)
|
||||
`);
|
||||
}
|
||||
if (clauses.length === 0) return sql<boolean>`false`;
|
||||
return or(...clauses);
|
||||
}
|
||||
|
||||
const BLOCKER_ATTENTION_OPEN_RECOVERY_TERMINAL_STATUSES = ["done", "cancelled"];
|
||||
const BLOCKER_ATTENTION_MAX_DEPTH = 8;
|
||||
const BLOCKER_ATTENTION_MAX_NODES = 2000;
|
||||
@@ -2874,6 +2909,8 @@ async function blockedInboxIssueConditions(
|
||||
)
|
||||
`);
|
||||
}
|
||||
const lowTrustCondition = lowTrustBoundaryIssueCondition(companyId, filters?.lowTrustBoundary);
|
||||
if (lowTrustCondition) conditions.push(lowTrustCondition);
|
||||
if (filters?.status) {
|
||||
const statuses = filters.status.split(",").map((status) => status.trim()).filter(Boolean);
|
||||
if (statuses.length > 0) {
|
||||
@@ -3841,6 +3878,8 @@ export function issueService(db: Db) {
|
||||
)
|
||||
`);
|
||||
}
|
||||
const lowTrustCondition = lowTrustBoundaryIssueCondition(companyId, filters?.lowTrustBoundary);
|
||||
if (lowTrustCondition) conditions.push(lowTrustCondition);
|
||||
if (filters?.status) {
|
||||
const statuses = filters.status.split(",").map((s) => s.trim());
|
||||
conditions.push(statuses.length === 1 ? eq(issues.status, statuses[0]) : inArray(issues.status, statuses));
|
||||
|
||||
Reference in new issue
Block a user