Add runtime heartbeat release gate tests

This commit is contained in:
Dotta committed 2026-07-01 19:17:24 +00:00
1 parent 4b9f8165c5
commit 77bc236c2a
2 files changed
+135

No files matched your search

@@ -811,6 +811,40 @@ describeEmbeddedPostgres("heartbeat orphaned process recovery", () => {
return { companyId, agentId, issueId };
}
async function seedIdleTimerAgentFixture() {
const companyId = randomUUID();
const agentId = randomUUID();
const issuePrefix = `T${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`;
await db.insert(companies).values({
id: companyId,
name: "Paperclip",
issuePrefix,
requireBoardApprovalForNewAgents: false,
});
await db.insert(agents).values({
id: agentId,
companyId,
name: "CodexCoder",
role: "engineer",
status: "idle",
adapterType: "codex_local",
adapterConfig: {},
runtimeConfig: {
heartbeat: {
enabled: true,
intervalSec: 60,
wakeOnDemand: true,
skipTimerWhenNoActionableWork: true,
},
},
permissions: {},
});
return { companyId, agentId };
}
async function expectSourceScopedStrandedRecoveryAction(input: {
companyId: string;
agentId: string;
@@ -1042,6 +1076,46 @@ describeEmbeddedPostgres("heartbeat orphaned process recovery", () => {
expect(wakeup?.status).toBe("claimed");
});
it("skips generic timer wakes without invoking an adapter when no assigned work is actionable", async () => {
const { companyId, agentId } = await seedIdleTimerAgentFixture();
const heartbeat = heartbeatService(db);
const run = await heartbeat.wakeup(agentId, {
source: "timer",
triggerDetail: "system",
reason: "heartbeat_timer",
requestedByActorType: "system",
requestedByActorId: "heartbeat_scheduler",
contextSnapshot: {
source: "scheduler",
reason: "interval_elapsed",
now: "2026-03-19T00:00:00.000Z",
},
});
expect(run).toBeNull();
expect(mockAdapterExecute).not.toHaveBeenCalled();
const requests = await db
.select()
.from(agentWakeupRequests)
.where(eq(agentWakeupRequests.agentId, agentId));
expect(requests).toHaveLength(1);
expect(requests[0]).toMatchObject({
companyId,
source: "timer",
reason: "heartbeat.timer.no_actionable_work",
status: "skipped",
error: null,
});
const runs = await db
.select()
.from(heartbeatRuns)
.where(eq(heartbeatRuns.agentId, agentId));
expect(runs).toHaveLength(0);
});
it("queues exactly one retry when the recorded local pid is dead", async () => {
const { agentId, runId, issueId } = await seedRunFixture({
agentStatus: "idle",
@@ -290,6 +290,67 @@ describeEmbeddedPostgres("stale issue execution lock routes", () => {
expect(res.body?.error).toBe("Issue run ownership conflict");
});
it("preserves live checkout ownership on checkout conflicts without retry side effects", async () => {
const { companyId, agentId, currentRunId } = await seedCompanyAgentAndRuns();
const contenderRunId = randomUUID();
const issueId = randomUUID();
await db.insert(heartbeatRuns).values({
id: contenderRunId,
companyId,
agentId,
status: "running",
invocationSource: "assignment",
startedAt: new Date(),
});
await db.insert(issues).values({
id: issueId,
companyId,
title: "Live checkout race",
status: "in_progress",
priority: "high",
assigneeAgentId: agentId,
checkoutRunId: currentRunId,
executionRunId: currentRunId,
executionAgentNameKey: "codexcoder",
executionLockedAt: new Date(),
});
const res = await request(createApp(agentActor(companyId, agentId, contenderRunId)))
.post(`/api/issues/${issueId}/checkout`)
.send({
agentId,
expectedStatuses: ["todo", "backlog", "blocked", "in_review"],
});
expect(res.status, JSON.stringify(res.body)).toBe(409);
expect(res.body).toMatchObject({
error: "Issue checkout conflict",
});
const row = await db
.select({
status: issues.status,
assigneeAgentId: issues.assigneeAgentId,
checkoutRunId: issues.checkoutRunId,
executionRunId: issues.executionRunId,
})
.from(issues)
.where(eq(issues.id, issueId))
.then((rows) => rows[0]);
expect(row).toEqual({
status: "in_progress",
assigneeAgentId: agentId,
checkoutRunId: currentRunId,
executionRunId: currentRunId,
});
const checkoutActivity = await db
.select()
.from(activityLog)
.where(eq(activityLog.action, "issue.checked_out"));
expect(checkoutActivity).toHaveLength(0);
});
it("restricts admin force-release to board users with company access and writes an audit event", async () => {
const { companyId, agentId, failedRunId, currentRunId } = await seedCompanyAgentAndRuns();
const issueId = randomUUID();