mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-30 21:49:18 +01:00
fix(server): release settled tool health intent during drain
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
9ef6e4f4c8
commit
e685a0e7ef
3 files changed
+38
-2
No files matched your search
@@ -0,0 +1,31 @@
|
||||
# Configuration worker drain repairs
|
||||
|
||||
The voice drain audit exposed a pattern in three earlier independent workers:
|
||||
retained dirty scopes reported as pending work keep the coordinator's intent
|
||||
activity hold alive after the write has settled. No recurring SQL is necessary
|
||||
to block idle admission this way. The earlier tests checked traffic but did not
|
||||
check the idle counter or generation while paused.
|
||||
|
||||
Each owner must retain its hints and next deadline for resume, report dirty work
|
||||
as pending only while dispatch is allowed, and leave unresolved transaction
|
||||
fencing to the shared coordinator. No hint is acknowledged or discarded during
|
||||
drain. Business health/registration/lease deadlines and provider behavior stay
|
||||
unchanged. This is an idle-work boundary repair, not provider sleep qualification.
|
||||
|
||||
## Tool health — slice 18 / #17554
|
||||
|
||||
Baseline 9ef6e4f4c8 reproduces the bug: the strengthened existing drain case fails
|
||||
with active=1 after commit, recorded in /tmp/sleep18-drain-before.log. The repaired
|
||||
case asserts active=0, then a stable idle snapshot and no SQL/provider calls for a
|
||||
minute, then the saved work runs on resume without another producer.
|
||||
|
||||
All 23 lifecycle cases pass in /tmp/sleep18-drain-tests.log; production server
|
||||
typecheck passes in /tmp/sleep18-drain-typecheck.log. The test database is the
|
||||
fresh migrated paperclip_sleep18_drain_20261025_test. No test failures were retried
|
||||
blindly. Current master bf9a8551c3 has no changes to these three worker owners
|
||||
since the preceding aec86c794b inspection; full stack reconciliation remains open.
|
||||
|
||||
Discord command registration and receiver discovery require the same causal
|
||||
check at their own parents, then propagation through every dependent stack PR.
|
||||
The full original recurring-work inventory, milestone producer audit, older E2E
|
||||
failure and inbound cleanup/deletion policy finding remain outstanding.
|
||||
@@ -185,8 +185,11 @@ const support = external ? { supported: true } : await getEmbeddedPostgresTestSu
|
||||
});
|
||||
it("retains first writes through drain without querying", async () => {
|
||||
await start(); await quiet(); draining = true; await connection();
|
||||
await vi.waitFor(async () => { await vi.advanceTimersByTimeAsync(1000); expect(idleWorkSnapshot().active).toBe(0); });
|
||||
const drained = idleWorkSnapshot();
|
||||
const reads = vi.spyOn(db, "select"); await vi.advanceTimersByTimeAsync(60_000);
|
||||
expect(reads).not.toHaveBeenCalled(); reads.mockRestore(); expect(probe).not.toHaveBeenCalled();
|
||||
expect(reads).not.toHaveBeenCalled(); expect(idleWorkSnapshot()).toEqual(drained);
|
||||
reads.mockRestore(); expect(probe).not.toHaveBeenCalled();
|
||||
draining = false; await checked();
|
||||
});
|
||||
it("moves the deadline when an actual-use health observation commits", async () => {
|
||||
|
||||
@@ -82,7 +82,9 @@ export function registerToolConnectionHealthWork(
|
||||
if (due.length > batch.entries.length) earliest = Date.now() + 1_000;
|
||||
if (errors.length) throw new AggregateError(errors, "Tool connection health checks could not settle");
|
||||
},
|
||||
hasPending: async () => dirty.size > 0,
|
||||
// Keep the hints/deadlines for resume, but release settled intent while
|
||||
// paused. The coordinator independently fences unresolved transactions.
|
||||
hasPending: async () => canRun() && dirty.size > 0,
|
||||
nextRunAt: next,
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user