Integrate routine status with scoped execution and source notifications

Keep all owner subscriptions, restore issue identity, and remove only the replaced routine and retention sweeps.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-30 18:18:44 -05:00
commit bc3559eb5e
23 files changed
+696 -67

No files matched your search

@@ -1,3 +1,4 @@
import { resolveExternalTestDatabaseUrl } from "../../__tests__/helpers/external-test-database.js";
import { randomUUID } from "node:crypto";
import { eq } from "drizzle-orm";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
@@ -8,13 +9,14 @@ import { routineService } from "../routines.js";
import { commitNativeStatusDecision } from "./status-decision-committer.js";
import { NATIVE_STATUS_ARBITER_POLICY_VERSION, type NativeStatusDecision } from "./status-arbiter.js";
const support = await getEmbeddedPostgresTestSupport();
const external = resolveExternalTestDatabaseUrl("PAPERCLIP_NATIVE_ROUTINE_STATUS_TEST_DATABASE_URL");
const support = external ? { supported: true, reason: undefined } : await getEmbeddedPostgresTestSupport();
if (!support.supported) console.warn(`Skipping native routine status tests: ${support.reason}`);
(support.supported ? describe : describe.skip)("native routine history status projection", () => {
let db: ReturnType<typeof createDb>, database: Awaited<ReturnType<typeof startEmbeddedPostgresTestDatabase>>;
const companyId = randomUUID(), agentId = randomUUID();
beforeAll(async () => {
database = await startEmbeddedPostgresTestDatabase("native-routine-status-"); db = createDb(database.connectionString);
if (!external) database = await startEmbeddedPostgresTestDatabase("native-routine-status-"); db = createDb(external ?? database.connectionString, { maxConnections: 6 });
await db.insert(companies).values({ id: companyId, name: "Routine status fixture", issuePrefix: "NRS" });
await db.insert(agents).values({ id: agentId, companyId, name: "Routine worker", adapterType: "paperclip_runner", status: "running" });
}, 60_000);
@@ -38,7 +38,7 @@ describe("reconstruction after confirmed native workspace loss", () => {
const external = resolveExternalTestDatabaseUrl("PAPERCLIP_NATIVE_RECONSTRUCTION_TEST_DATABASE_URL");
if (external) {
await applyPendingMigrations(external);
db = createDb(external);
db = createDb(external, { maxConnections: 6 });
return;
}
temporary = await startEmbeddedPostgresTestDatabase("workspace-reconstruction-");
@@ -144,17 +144,21 @@ describe("reconstruction after confirmed native workspace loss", () => {
expect(await queue.nextTimerHeartbeatAt(agent)).toBeNull();
const events: string[] = [];
const unsubscribe = subscribeDatabaseWork(db, DELIVERY_QUEUES.agentTimers, event => events.push(event));
const routineEvents: string[] = [];
const unsubscribeRoutine = subscribeDatabaseWork(db, DELIVERY_QUEUES.routineExecutionStatus, event => routineEvents.push(event));
try {
await db.transaction(async tx => {
await reconstructNativeWorkspace({ ...f, db: tx as unknown as Db });
expect(events).toContain("intent");
expect(events).not.toContain("settled");
expect(routineEvents).toContain("intent"); expect(routineEvents).not.toContain("settled");
expect((await db.select().from(issues).where(eq(issues.id, f.issueId)))[0].status).toBe("blocked");
});
expect(events).toContain("settled");
expect(routineEvents).toContain("settled");
expect((await db.select().from(issues).where(eq(issues.id, f.issueId)))[0].status).toBe("todo");
expect(await queue.nextTimerHeartbeatAt(agent)).toBe(agent.createdAt.getTime() + 600_000);
} finally { unsubscribe(); }
} finally { unsubscribe(); unsubscribeRoutine(); }
});
it("pins a historical cancelled workspace through public heartbeat startup without replaying its turn", async () => {
@@ -1,5 +1,6 @@
import { notifyStatusCardIssueChange } from "../status-card-notifications.js";
import { notifyAttentionRetentionWork } from "../retention-work-notifications.js";
import { notifyRoutineExecutionStatus } from "../routine-execution-status-notifications.js";
import { DELIVERY_QUEUES, notifyDeliveryWork } from "../delivery-work-notifications.js";
import { notifyUnsafeWorkspaceExportRecovery } from "../unsafe-workspace-export-notifications.js";
import { and, eq, gt, inArray, ne, notInArray, or, sql } from "drizzle-orm";
@@ -191,6 +192,7 @@ export async function reconstructNativeWorkspace(input: {
await notifyDeliveryWork(tx, DELIVERY_QUEUES.issueMonitors);
await notifyDeliveryWork(tx, DELIVERY_QUEUES.agentTimers);
await notifyStatusCardIssueChange(tx, input.companyId);
await notifyRoutineExecutionStatus(tx, input.issueId, input.companyId);
await tx.update(issues).set({ status: "todo", unblockDescriptor: null, blockedTransitionAt: null,
blockedOwnerNotifiedAt: null, updatedAt: now }).where(eq(issues.id, issue.id));
const publication = await persistActivity(db, { companyId: input.companyId, actorType: "user", actorId: input.actorId,