diff --git a/doc/plans/2026-10-24-recurring-server-sleep-stack.md b/doc/plans/2026-10-24-recurring-server-sleep-stack.md index 9a3deaee3e0..7c2756aebb8 100644 --- a/doc/plans/2026-10-24-recurring-server-sleep-stack.md +++ b/doc/plans/2026-10-24-recurring-server-sleep-stack.md @@ -2364,3 +2364,14 @@ unproven. Remote failures at `79bf36cbf8` include an early startup-settlement te assertion (repaired), a workspace staging timeout, and an E2E runner shutdown. See the response audit for logs. Fresh-head CI/review, earlier stack findings, master reconciliation, and all remaining original duties stay open. Nothing merged. + + +## Slice 48 full-history acceptance repair + +Fresh review of `c4beddad1e` is 5/5 with its scope finding resolved. Remote CI +exposed a second early startup assertion. Full local chat verification finished +1,595 passed / seven failed; all seven were new worker tests observing a transient +null deadline or unfinished startup batch. Their settlement predicate now includes +zero active work. All 18 affected cases pass against the full suite's retained +history. Fresh remote acceptance remains pending; these are recorded test repairs, +not a waiver of failed gates or a change to production batch behavior. diff --git a/doc/plans/2026-10-25-native-response-presentation-sleep-audit.md b/doc/plans/2026-10-25-native-response-presentation-sleep-audit.md index 12ea5b1fb71..b07359fd0e2 100644 --- a/doc/plans/2026-10-25-native-response-presentation-sleep-audit.md +++ b/doc/plans/2026-10-25-native-response-presentation-sleep-audit.md @@ -376,3 +376,25 @@ hit a five-second sandbox staging test timeout; E2E shard five received a runner shutdown signal. These failures remain recorded in `/tmp/sleep48-ci-chat3.log`, `/tmp/sleep48-ci-workspaces-b.log`, and `/tmp/sleep48-ci-e2e5.log`. Fresh-head CI and review are required after the repair; no failing gate is waived. + + +## Full-history test repair, 2026-10-26 + +At `c4beddad1e`, fresh Greptile review is 5/5 and the scope thread is resolved. +Remote CI passed 51 contexts but failed the chat shard and its aggregate gate: +a second startup assertion assumed `worker.ready` drained every fair batch. +The full local chat file finished with 1,595 passing and seven failing tests +(`/tmp/sleep48-chat-full.log`); all seven were in the newly added worker tests. + +The larger run exposed two related observation errors: startup can have another +batch scheduled, and `nextWakeAt() === null` can be transient while a batch runs. +The latter caused query instrumentation to count unfinished startup checks and +an outcome assertion to run before its response had been materialized. An early +future-deadline test failure then left its fixture's real deadline for later tests. + +Worker startup/settlement assertions now require both zero active work and the +expected next deadline. This changes test observation, not scheduler behavior or +batch limits. All 18 affected cases pass against the retained history database +from the full 1,602-case run (`/tmp/sleep48-settlement-history.log`), including +single-query unrelated wakes, future deadlines, and coordinator retry release. +The full-run failure remains recorded; fresh remote CI is still required. diff --git a/server/src/__tests__/chat-channels.integration.test.ts b/server/src/__tests__/chat-channels.integration.test.ts index 49d8efaca8c..1b3ae588acc 100644 --- a/server/src/__tests__/chat-channels.integration.test.ts +++ b/server/src/__tests__/chat-channels.integration.test.ts @@ -72641,7 +72641,8 @@ describeEmbeddedPostgres("chat channel control-plane integration", () => { const worker = work.register(coordinator, () => true); try { await worker.ready; - await expect.poll(() => coordinator.nextWakeAt(), { timeout: 5000 }).toBeNull(); + await expect.poll(() => ({ active: idleWorkSnapshot().active, next: coordinator.nextWakeAt() }), + { timeout: 10_000 }).toEqual({ active: 0, next: null }); const comments = await db.select().from(issueComments).where(eq(issueComments.createdByRunId, context.runId)); expect(comments).toHaveLength(1); expect(comments[0].body).toBe(context.result.summary); @@ -72666,11 +72667,13 @@ describeEmbeddedPostgres("chat channel control-plane integration", () => { let replacement: ReturnType | undefined; try { await worker.ready; - await expect.poll(() => coordinator.nextWakeAt(), { timeout: 5000 }).toBeNull(); + await expect.poll(() => ({ active: idleWorkSnapshot().active, next: coordinator.nextWakeAt() }), + { timeout: 10_000 }).toEqual({ active: 0, next: null }); expect(await readChatPublicationsByEndpointId(context.endpoint.id)).toHaveLength(0); replacement = createService(new FakeChatSdkRuntime(), fakeTelegramFetch() as typeof fetch); await expect.poll(async () => (await readChatPublicationsByEndpointId(context.endpoint.id)).length, { timeout: 5000 }).toBe(1); - await expect.poll(() => coordinator.nextWakeAt(), { timeout: 5000 }).toBeNull(); + await expect.poll(() => ({ active: idleWorkSnapshot().active, next: coordinator.nextWakeAt() }), + { timeout: 10_000 }).toEqual({ active: 0, next: null }); expect(errors).toEqual([]); } finally { worker.stop(); await coordinator.stop(); await replacement?.service.shutdown(); } }); @@ -72684,7 +72687,8 @@ describeEmbeddedPostgres("chat channel control-plane integration", () => { const worker = work.register(coordinator, () => true); try { await worker.ready; - await expect.poll(() => coordinator.nextWakeAt(), { timeout: 10_000 }).toBeNull(); + await expect.poll(() => ({ active: idleWorkSnapshot().active, next: coordinator.nextWakeAt() }), + { timeout: 10_000 }).toEqual({ active: 0, next: null }); expect(await work.load({ companyId: context.fixture.companyId, runId: context.runId })).toHaveLength(1); const previousDebug = db.$client.options.debug; const queries: string[] = []; @@ -72711,7 +72715,8 @@ describeEmbeddedPostgres("chat channel control-plane integration", () => { const worker = nativeResponsePresentationWork(db).register(coordinator, () => true); try { await worker.ready; - await expect.poll(() => coordinator.nextWakeAt(), { timeout: 5000 }).toBeNull(); + await expect.poll(() => ({ active: idleWorkSnapshot().active, next: coordinator.nextWakeAt() }), + { timeout: 10_000 }).toEqual({ active: 0, next: null }); const transaction = db.transaction(async tx => { await tx.transaction(async nested => { await notifyChatRunMilestones(nested, { companyId: context.fixture.companyId, issueId: context.issue.id, runId: randomUUID() }); @@ -72742,7 +72747,8 @@ describeEmbeddedPostgres("chat channel control-plane integration", () => { const worker = nativeResponsePresentationWork(db).register(coordinator, () => canRun); try { await worker.ready; - await expect.poll(() => coordinator.nextWakeAt(), { timeout: 5000 }).toBeNull(); + await expect.poll(() => ({ active: idleWorkSnapshot().active, next: coordinator.nextWakeAt() }), + { timeout: 10_000 }).toEqual({ active: 0, next: null }); canRun = false; await db.transaction(async tx => { await notifyChatRunMilestones(tx, { companyId: context.fixture.companyId, issueId: context.issue.id }); @@ -72818,7 +72824,8 @@ describeEmbeddedPostgres("chat channel control-plane integration", () => { const worker = nativeResponsePresentationWork(db).register(coordinator, () => true); try { await worker.ready; - expect(coordinator.nextWakeAt()).toBe(at.getTime()); + await expect.poll(() => ({ active: idleWorkSnapshot().active, next: coordinator.nextWakeAt() }), + { timeout: 10_000 }).toEqual({ active: 0, next: at.getTime() }); expect(await readChatPublicationsByEndpointId(context.endpoint.id)).toHaveLength(0); await vi.advanceTimersByTimeAsync(Math.max(0, at.getTime() - Date.now())); await vi.waitFor(async () => { @@ -72869,14 +72876,16 @@ describeEmbeddedPostgres("chat channel control-plane integration", () => { }); try { await worker.ready; - expect(coordinator.nextWakeAt()).toBeNull(); + await expect.poll(() => ({ active: idleWorkSnapshot().active, next: coordinator.nextWakeAt() }), + { timeout: 10_000 }).toEqual({ active: 0, next: null }); expect(await work.load({ companyId: context.fixture.companyId, agentId: randomUUID() })).toEqual([]); expect(await readChatPublicationsByEndpointId(context.endpoint.id)).toHaveLength(0); await createAgentLifecycle(db).resumeAgent(context.binding.agentId); await processAgentLifecycle(db, context.binding.agentId); await expect.poll(async () => (await db.select().from(agents).where(eq(agents.id, context.binding.agentId)))[0].status).toBe("idle"); await expect.poll(async () => (await readChatPublicationsByEndpointId(context.endpoint.id)).length, { timeout: 5000 }).toBe(1); - await expect.poll(() => coordinator.nextWakeAt(), { timeout: 5000 }).toBeNull(); + await expect.poll(() => ({ active: idleWorkSnapshot().active, next: coordinator.nextWakeAt() }), + { timeout: 10_000 }).toEqual({ active: 0, next: null }); expect(errors).toEqual([]); } finally { await lifecycle.stop(); worker.stop(); await coordinator.stop(); await context.service.shutdown(); } }); @@ -72891,7 +72900,8 @@ describeEmbeddedPostgres("chat channel control-plane integration", () => { const worker = nativeResponsePresentationWork(db).register(coordinator, () => true); try { await worker.ready; - expect(coordinator.nextWakeAt()).toBeNull(); + await expect.poll(() => ({ active: idleWorkSnapshot().active, next: coordinator.nextWakeAt() }), + { timeout: 10_000 }).toEqual({ active: 0, next: null }); await vi.advanceTimersByTimeAsync(2000); expect(await readChatPublicationsByEndpointId(context.endpoint.id)).toHaveLength(0); expect(coordinator.nextWakeAt()).toBeNull(); @@ -72914,7 +72924,8 @@ describeEmbeddedPostgres("chat channel control-plane integration", () => { const worker = nativeResponsePresentationWork(db).register(coordinator, () => true); try { await worker.ready; - await expect.poll(() => coordinator.nextWakeAt(), { timeout: 10_000 }).toBeNull(); + await expect.poll(() => ({ active: idleWorkSnapshot().active, next: coordinator.nextWakeAt() }), + { timeout: 10_000 }).toEqual({ active: 0, next: null }); expect(await readChatPublicationsByEndpointId(context.endpoint.id)).toHaveLength(0); const port = new PaperclipControlPlanePort(db, { companyId: context.binding.companyId, issueId: context.binding.issueId,