Merge commit '295f4f82722d92b93d8b5135399bca937116b536' into codex/sleep-stack-01

* commit '295f4f82722d92b93d8b5135399bca937116b536':
  test(server): preserve attention feed beyond blocker root budget
This commit is contained in:
Dotta committed 2026-10-30 20:51:04 -05:00
commit a91c4280aa
2 files changed
+21

No files matched your search

@@ -48,3 +48,5 @@ Local embedded PostgreSQL could not start on the test host because system semaph
Review the source parity fixtures and producer hints together. The intended outcome is unchanged archive state and notifications with no recurring retention SQL when the sources remain unchanged. No merge or deployment is part of this change.
Issue producer invalidation has its own finite discovery budget. Reverse parent/blocker traversal returns at most 101 distinct UUIDs in one recursive query, with each relation adjacency capped at 101 unique edges before materialization. At most 100 affected issues produce one sorted canonical batch hint; an overflow sentinel produces one company hint and the existing complete paged reconstruction. UNION deduplication terminates cycles. A PostgreSQL EXPLAIN ANALYZE on 2,100 reverse edges proves the outer limit stops at 101 CTE rows while the indexed relation branch returns only 100 rows. Repeated identical writes coalesce by canonical key. The shared scheduler is unchanged: a broader company hint does not cancel already pending narrower scopes; those remain finite batches of at most 100 IDs, processed at the existing 25-scope pass limit. This permits redundant bounded reconstruction instead of discarding invalidations or introducing cursor/subsumption policy across other owners. A 151-node cyclic chain proves overflow still registers the far ancestor's exact archive deadline and settles quietly afterward.
The ordinary feed retains conservative `needs_attention` entries for roots outside the shared graph budget. The batch finalization visits every input root; a root without explored edges points attention at itself. A real PostgreSQL regression with 2,001 blocked roots verifies the nonempty deferred set, bounded graph size, ordinary issue-list metadata, and all 2,001 attention-feed subject IDs. This is a consumer parity proof; it does not increase graph traversal or change archive eligibility.
@@ -318,6 +318,25 @@ const delay = DEFAULT_DECISION_ARCHIVE_DAYS * 86_400_000;
}
});
it("keeps deferred roots visible in the ordinary attention feed above the root cap", async () => {
await db.execute(sql`insert into issues (id, company_id, title, status, updated_at)
select gen_random_uuid(), ${companyId}::uuid, 'overflow-root-' || n, 'blocked', ${new Date().toISOString()}::timestamptz
from generate_series(1, ${BLOCKER_ATTENTION_MAX_NODES + 1}) n`);
const roots = await db.select().from(issues).where(eq(issues.companyId, companyId));
const batch = await issueService(db).listBlockerAttentionBatch(companyId, roots);
expect(batch.deferredRootIds.size).toBeGreaterThan(0);
expect(batch.nodeCount).toBeLessThanOrEqual(BLOCKER_ATTENTION_MAX_NODES);
for (const rootId of batch.deferredRootIds) expect(batch.attention.get(rootId)).toMatchObject({
state: "needs_attention", reason: "attention_required", terminalBlockerIssueId: rootId,
});
const listed = await issueService(db).list(companyId, { status: "blocked", includeBlockedBy: true });
expect(listed).toHaveLength(roots.length);
for (const root of listed) expect(root.blockerAttention).toMatchObject({ state: "needs_attention", terminalBlockerIssueId: root.id });
const feed = await attentionService(db).list(companyId, { all: true, allowUnscopedAll: true });
const visible = new Set(feed.items.filter(item => item.sourceKind === "blocker_attention").map(item => item.subject.id));
expect(visible).toEqual(new Set(roots.map(root => root.id)));
});
it("bounds every graph query when independent root trees contain over 65535 unfinished nodes", async () => {
await db.execute(sql`insert into issues (id, company_id, title, status, updated_at)
select gen_random_uuid(), ${companyId}::uuid, 'parameter-root-' || n, 'blocked', ${old().toISOString()}::timestamptz