From 31f2a4757955282206e7318bc29f9fdfae740b54 Mon Sep 17 00:00:00 2001 From: CTO Date: Thu, 27 Aug 2026 05:48:46 -0500 Subject: [PATCH 1/2] fix: align exact blocker attention projection --- .../__tests__/issue-blocker-attention.test.ts | 68 ++++++++++++++++++- server/src/services/issues.ts | 7 +- 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/server/src/__tests__/issue-blocker-attention.test.ts b/server/src/__tests__/issue-blocker-attention.test.ts index 1e6c00631e..4fc4d68afe 100644 --- a/server/src/__tests__/issue-blocker-attention.test.ts +++ b/server/src/__tests__/issue-blocker-attention.test.ts @@ -18,7 +18,10 @@ import { getEmbeddedPostgresTestSupport, startEmbeddedPostgresTestDatabase, } from "./helpers/embedded-postgres.js"; -import { issueService } from "../services/issues.js"; +import { + BLOCKER_ATTENTION_MAX_DEPTH, + issueService, +} from "../services/issues.js"; import { buildIssueGraphLivenessIncidentKey } from "../services/recovery/origins.js"; const embeddedPostgresSupport = await getEmbeddedPostgresTestSupport(); @@ -333,6 +336,69 @@ describeEmbeddedPostgres("issue blocker attention", () => { }); }); + it("keeps singleton and collection projections aligned through a maintained review descendant", async () => { + const { companyId, agentId } = await createCompany("PBP"); + const rootId = await insertIssue({ + companyId, + identifier: "PBP-1", + title: "Blocked source", + status: "blocked", + }); + + let blockedIssueId = rootId; + for (let index = 0; index < BLOCKER_ATTENTION_MAX_DEPTH; index += 1) { + const blockerId = await insertIssue({ + companyId, + identifier: `PBP-${index + 2}`, + title: `Blocked dependency ${index + 1}`, + status: "blocked", + }); + await block({ companyId, blockerIssueId: blockerId, blockedIssueId }); + blockedIssueId = blockerId; + } + + const reviewLeafId = await insertIssue({ + companyId, + identifier: `PBP-${BLOCKER_ATTENTION_MAX_DEPTH + 2}`, + title: "Maintained review leaf", + status: "in_review", + assigneeAgentId: agentId, + }); + await block({ companyId, blockerIssueId: reviewLeafId, blockedIssueId }); + await db.insert(issueThreadInteractions).values({ + companyId, + issueId: reviewLeafId, + kind: "request_confirmation", + status: "pending", + continuationPolicy: "wake_assignee", + payload: { version: 1, prompt: "Confirm the maintained candidate?" }, + }); + + const collectionIssue = (await svc.list(companyId, { status: "blocked" })) + .find((issue) => issue.id === rootId); + const exactIssue = await svc.getById(rootId); + expect(exactIssue).not.toBeNull(); + const singletonAttention = await svc.listBlockerAttention(companyId, [exactIssue!]); + const exactAttention = singletonAttention.get(rootId); + + expect(collectionIssue?.blockerAttention).toMatchObject({ + state: "covered", + reason: "active_dependency", + unresolvedBlockerCount: 1, + coveredBlockerCount: 1, + stalledBlockerCount: 0, + attentionBlockerCount: 0, + sampleBlockerIdentifier: `PBP-${BLOCKER_ATTENTION_MAX_DEPTH + 2}`, + terminalBlockerIssueId: reviewLeafId, + terminalBlocker: { + id: reviewLeafId, + identifier: `PBP-${BLOCKER_ATTENTION_MAX_DEPTH + 2}`, + title: "Maintained review leaf", + }, + }); + expect(exactAttention).toEqual(collectionIssue?.blockerAttention); + }); + it("does not let another company's active run cover the blocker", async () => { const { companyId, agentId } = await createCompany("PBS"); const other = await createCompany("PBT"); diff --git a/server/src/services/issues.ts b/server/src/services/issues.ts index e1c035527d..85ca6b188e 100644 --- a/server/src/services/issues.ts +++ b/server/src/services/issues.ts @@ -2353,7 +2353,12 @@ async function listIssueBlockerAttentionMap( let frontier = roots.map((root) => root.id); let truncated = false; const pendingFinalizeBlockerIssueIds = new Set(); - for (let depth = 0; frontier.length > 0 && depth < BLOCKER_ATTENTION_MAX_DEPTH; depth += 1) { + // Expand until the reachable graph is exhausted. A depth cap makes the + // result depend on the caller's batch: collection reads can seed an + // intermediate blocker as another root, while an exact read starts only at + // the requested issue. nodesById prevents cycles from re-entering the + // frontier, and BLOCKER_ATTENTION_MAX_NODES remains the fail-closed bound. + while (frontier.length > 0) { const nextFrontier = new Set(); for (const chunk of chunkList([...new Set(frontier)], ISSUE_LIST_RELATED_QUERY_CHUNK_SIZE)) {