refactor: remove obsolete external object batch refresh entrypoints

Exercise lease, selection, and recovery behavior through the registered scheduler owner.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-30 18:09:41 -05:00
1 parent 4771986772
commit 4e97daafa0
5 files changed
+43 -95

No files matched your search

@@ -100,14 +100,6 @@ const active: ExternalObjectResolveResult = { ok: true, snapshot: {
});
}
it("captures the old configured-empty aggregate SQL cost", async () => {
const svc = service(); await fixture();
const selects = vi.spyOn(db, "select");
for (let i = 0; i < 3; i++) await svc.refreshDueObjectsForActiveCompanies();
expect(selects).toHaveBeenCalledTimes(6);
expect(provider).not.toHaveBeenCalled();
});
it("activates saved work when the ordinary feature and company settings enable it", async () => {
clock(); const settings = instanceSettingsService(db);
await settings.updateExperimental({ enableExternalObjects: true });
@@ -1,4 +1,5 @@
import { randomUUID } from "node:crypto";
import { createDeliveryWorkCoordinator } from "../services/delivery-work-coordinator.js";
import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest";
import { eq } from "drizzle-orm";
import {
@@ -733,6 +734,18 @@ describeEmbeddedPostgres("externalObjectService", () => {
await tempDb?.cleanup();
});
async function runRefreshWork(service: ReturnType<typeof externalObjectService>) {
const errors: unknown[] = [];
const coordinator = createDeliveryWorkCoordinator({ owner: db, canRun: () => true,
onError: error => errors.push(error) });
try {
await service.registerRefreshWork(coordinator, () => true).ready;
expect(errors).toEqual([]);
} finally {
await coordinator.stop();
}
}
async function createIssue(companyId = randomUUID()) {
const issueId = randomUUID();
await db.insert(companies).values({
@@ -882,9 +895,9 @@ describeEmbeddedPostgres("externalObjectService", () => {
expect(object.nextRefreshAt).toBeInstanceOf(Date);
const refreshed = await svc.refreshDueObjects(companyId, 50, new Date(Date.now() + 1_000));
await runRefreshWork(svc);
expect(refreshed).toHaveLength(1);
expect((await db.select().from(externalObjects))[0]?.lastResolvedAt).toBeInstanceOf(Date);
expect(resolve).toHaveBeenCalledTimes(1);
});
@@ -921,15 +934,13 @@ describeEmbeddedPostgres("externalObjectService", () => {
await svc.syncIssue(issueId);
const object = await db.select().from(externalObjects).then((rows) => rows[0]!);
const dueRefresh = svc.refreshDueObjects(companyId, 50, new Date(Date.now() + 1_000));
const dueRefresh = runRefreshWork(svc);
await resolverStarted;
const manualRefresh = svc.refreshObject(object.id, { companyId, force: true });
finishResolver();
const [dueResults, manualResult] = await Promise.all([dueRefresh, manualRefresh]);
const [, manualResult] = await Promise.all([dueRefresh, manualRefresh]);
expect(dueResults).toHaveLength(1);
expect(dueResults[0]?.refreshed).toBe(true);
expect(manualResult.refreshed).toBe(true);
expect(manualResult.object.statusLabel).toBe("Open");
expect(resolve).toHaveBeenCalledTimes(1);
@@ -969,14 +980,12 @@ describeEmbeddedPostgres("externalObjectService", () => {
await scheduledService.syncIssue(issueId);
const object = await db.select().from(externalObjects).then((rows) => rows[0]!);
const dueRefresh = scheduledService.refreshDueObjects(companyId, 50, new Date(Date.now() + 1_000));
const dueRefresh = runRefreshWork(scheduledService);
await resolverStarted;
const manualRefresh = await manualService.refreshObject(object.id, { companyId, force: true });
finishResolver();
const dueResults = await dueRefresh;
await dueRefresh;
expect(dueResults).toHaveLength(1);
expect(dueResults[0]?.refreshed).toBe(true);
expect(manualRefresh.refreshed).toBe(false);
expect(manualRefresh.reason).toBe("refresh_in_progress");
expect(manualRefresh.object).not.toHaveProperty("refreshToken");
@@ -1032,7 +1041,7 @@ describeEmbeddedPostgres("externalObjectService", () => {
const object = await db.select().from(externalObjects).then((rows) => rows[0]!);
const claimedAt = new Date(Date.now() + 1_000);
const dueRefresh = scheduledService.refreshDueObjects(companyId, 50, claimedAt);
const dueRefresh = runRefreshWork(scheduledService);
await resolverStarted;
const replacementResult = await replacementService.refreshObject(object.id, {
companyId,
@@ -1040,11 +1049,10 @@ describeEmbeddedPostgres("externalObjectService", () => {
now: new Date(claimedAt.getTime() + 301_000),
});
finishResolver();
const dueResults = await dueRefresh;
await dueRefresh;
const finalObject = await db.select().from(externalObjects).then((rows) => rows[0]!);
expect(replacementResult.refreshed).toBe(true);
expect(dueResults[0]).toMatchObject({ refreshed: false, reason: "refresh_superseded" });
expect(finalObject.statusLabel).toBe("Closed");
expect(finalObject.refreshStartedAt).toBeNull();
expect(finalObject.refreshToken).toBeNull();
@@ -1078,9 +1086,7 @@ describeEmbeddedPostgres("externalObjectService", () => {
await svc.syncIssue(active.issueId);
await svc.syncIssue(paused.issueId);
const result = await svc.refreshDueObjectsForActiveCompanies(50, new Date(Date.now() + 1_000));
expect(result).toEqual({ companies: 1, checked: 1, refreshed: 1 });
await runRefreshWork(svc);
expect(resolve).toHaveBeenCalledTimes(1);
const rows = await db.select().from(externalObjects);
const activeObject = rows.find((row) => row.companyId === active.companyId);
@@ -1139,9 +1145,8 @@ describeEmbeddedPostgres("externalObjectService", () => {
.set({ nextRefreshAt: new Date(0) })
.where(eq(externalObjects.id, object.id));
const refreshed = await svc.refreshDueObjects(companyId);
await runRefreshWork(svc);
expect(refreshed).toEqual([]);
expect(resolve).toHaveBeenCalledTimes(1);
});
@@ -1184,7 +1189,7 @@ describeEmbeddedPostgres("externalObjectService", () => {
});
it("re-checks previously frozen GitHub not-found objects so they can self-heal", async () => {
const { companyId } = await createIssue();
const { companyId, issueId } = await createIssue();
const canonical = canonicalizeExternalObjectUrl("https://github.com/acme/app/pull/42");
if (!canonical) throw new Error("expected canonical url");
const inserted = await db.insert(externalObjects).values({
@@ -1225,9 +1230,10 @@ describeEmbeddedPostgres("externalObjectService", () => {
const svc = externalObjectService(db, { resolvers: [resolver], github: false });
expect(object.isTerminal).toBe(true);
const refreshed = await svc.refreshDueObjects(companyId);
expect(refreshed).toHaveLength(1);
expect(refreshed[0]?.refreshed).toBe(true);
const [linked] = await db.select().from(externalObjects).where(eq(externalObjects.companyId, companyId));
await db.insert(externalObjectMentions).values({ companyId, sourceIssueId: issueId,
sourceKind: "description", objectId: linked!.id });
await runRefreshWork(svc);
const updated = await db.select().from(externalObjects).then((rows) => rows[0]!);
expect(updated.statusLabel).toBe("Open");
@@ -1236,7 +1242,7 @@ describeEmbeddedPostgres("externalObjectService", () => {
});
it("does not re-check probe-verified GitHub not-found objects (deleted references are terminal)", async () => {
const { companyId } = await createIssue();
const { companyId, issueId } = await createIssue();
const canonical = canonicalizeExternalObjectUrl("https://github.com/acme/app/pull/42");
if (!canonical) throw new Error("expected canonical url");
await db.insert(externalObjects).values({
@@ -1273,14 +1279,16 @@ describeEmbeddedPostgres("externalObjectService", () => {
};
const svc = externalObjectService(db, { resolvers: [resolver], github: false });
const refreshed = await svc.refreshDueObjects(companyId);
const [linked] = await db.select().from(externalObjects).where(eq(externalObjects.companyId, companyId));
await db.insert(externalObjectMentions).values({ companyId, sourceIssueId: issueId,
sourceKind: "description", objectId: linked!.id });
await runRefreshWork(svc);
expect(refreshed).toEqual([]);
expect(resolver.resolve).not.toHaveBeenCalled();
});
it("does not re-check terminal objects that are not GitHub not-found verdicts", async () => {
const { companyId } = await createIssue();
const { companyId, issueId } = await createIssue();
const canonical = canonicalizeExternalObjectUrl("https://github.com/acme/app/pull/42");
if (!canonical) throw new Error("expected canonical url");
await db.insert(externalObjects).values({
@@ -1316,9 +1324,11 @@ describeEmbeddedPostgres("externalObjectService", () => {
};
const svc = externalObjectService(db, { resolvers: [resolver], github: false });
const refreshed = await svc.refreshDueObjects(companyId);
const [linked] = await db.select().from(externalObjects).where(eq(externalObjects.companyId, companyId));
await db.insert(externalObjectMentions).values({ companyId, sourceIssueId: issueId,
sourceKind: "description", objectId: linked!.id });
await runRefreshWork(svc);
expect(refreshed).toEqual([]);
expect(resolver.resolve).not.toHaveBeenCalled();
});
@@ -209,7 +209,6 @@ const {
const externalObjectsServiceMock = {
registerRefreshWork: vi.fn((coordinator: { register: typeof deliveryWorkRegisterMock }, _canRun: () => boolean) =>
coordinator.register("external-object-refresh", { run: async () => undefined, hasPending: async () => false })),
refreshDueObjectsForActiveCompanies: vi.fn(async () => ({ companies: 0, checked: 0, refreshed: 0 })),
};
const externalObjectsServiceFactoryMock = vi.fn(() => externalObjectsServiceMock);
const routineServiceMock = {
@@ -1404,7 +1403,6 @@ describe("startServer feedback export wiring", () => {
expect(externalObjectsServiceMock.registerRefreshWork).toHaveBeenCalledTimes(1);
expect(externalObjectsServiceMock.registerRefreshWork).toHaveBeenCalledWith(expect.any(Object), expect.any(Function));
expect(deliveryWorkRegisterMock.mock.calls.some(([queue]) => queue === DELIVERY_QUEUES.externalObjectRefresh)).toBe(true);
expect(externalObjectsServiceMock.refreshDueObjectsForActiveCompanies).not.toHaveBeenCalled();
expect(issueThreadInteractionServiceMock.sweepMergedPullRequestConfirmations).toHaveBeenCalledTimes(1);
expect(executionWorkspaceServiceMock.sweepTerminalWorkspaces).toHaveBeenCalledTimes(1);
expect(routineServiceMock.tickScheduledTriggers).toHaveBeenCalledTimes(1);
@@ -1434,7 +1432,6 @@ describe("startServer feedback export wiring", () => {
});
await startServer();
expect(externalObjectsServiceMock.registerRefreshWork).toHaveBeenCalledOnce();
expect(externalObjectsServiceMock.refreshDueObjectsForActiveCompanies).not.toHaveBeenCalled();
});
it("keeps external object refresh active when heartbeat scheduling is disabled", async () => {
@@ -1467,7 +1464,6 @@ describe("startServer feedback export wiring", () => {
expect(externalObjectsServiceMock.registerRefreshWork).toHaveBeenCalledTimes(1);
expect(externalObjectsServiceMock.registerRefreshWork).toHaveBeenCalledWith(expect.any(Object), expect.any(Function));
expect(deliveryWorkRegisterMock.mock.calls.some(([queue]) => queue === DELIVERY_QUEUES.externalObjectRefresh)).toBe(true);
expect(externalObjectsServiceMock.refreshDueObjectsForActiveCompanies).not.toHaveBeenCalled();
expect(routineServiceMock.tickScheduledTriggers).not.toHaveBeenCalled();
expect(heartbeatServiceMock.reconcileCostAccounting).toHaveBeenCalledTimes(1);
expect(environmentCustomImagesServiceMock.cleanupExpiredSetupSessions).not.toHaveBeenCalled();
-52
View File
@@ -1109,56 +1109,6 @@ export function externalObjectService(
return results;
}
async function refreshDueObjectsUnchecked(companyId: string, limit = 50, now = new Date()) {
const staleRefreshStartedBefore = new Date(now.getTime() - DEFAULT_REFRESH_LEASE_SECONDS * 1000);
const refreshable = refreshableObjectWhere();
const due = await db
.select({ id: externalObjects.id })
.from(externalObjects)
.where(
and(
eq(externalObjects.companyId, companyId),
refreshable,
lte(externalObjects.nextRefreshAt, now),
or(
isNull(externalObjects.refreshStartedAt),
lte(externalObjects.refreshStartedAt, staleRefreshStartedBefore),
),
),
)
.limit(limit);
const results = [];
for (const row of due) {
results.push(await refreshObject(row.id, {
companyId,
actor: { actorType: "system", actorId: "external-object-resolver", agentId: null, runId: null },
now,
}));
}
return results;
}
async function refreshDueObjects(companyId: string, limit = 50, now = new Date()) {
if (!(await isEnabled())) return [];
return refreshDueObjectsUnchecked(companyId, limit, now);
}
async function refreshDueObjectsForActiveCompanies(limitPerCompany = 50, now = new Date()) {
if (!(await isEnabled())) return { companies: 0, checked: 0, refreshed: 0 };
const activeCompanies = await db
.select({ id: companies.id })
.from(companies)
.where(eq(companies.status, "active"));
let checked = 0;
let refreshed = 0;
for (const company of activeCompanies) {
const results = await refreshDueObjectsUnchecked(company.id, limitPerCompany, now);
checked += results.length;
refreshed += results.filter((result) => result.refreshed).length;
}
return { companies: activeCompanies.length, checked, refreshed };
}
function registerRefreshWork(
coordinator: ReturnType<typeof createDeliveryWorkCoordinator>,
canRun: () => boolean,
@@ -1234,8 +1184,6 @@ export function externalObjectService(
getProjectSummary,
refreshObject,
refreshIssueObjects,
refreshDueObjects,
refreshDueObjectsForActiveCompanies,
registerRefreshWork,
};
}