From fe619aa13a3f454e7f6c678d69ed77dd26beb239 Mon Sep 17 00:00:00 2001 From: Dotta Date: Sat, 10 Oct 2026 12:08:58 -0500 Subject: [PATCH] fix(computers): prune retired desktop viewers during reconciliation Keep active viewer leases and retired runner process proof, while removing expired or disconnected viewer records from the host ledger. Co-Authored-By: Paperclip --- .../computers/application/service.test.ts | 18 ++++++++++++++++++ .../modules/computers/application/service.ts | 3 ++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/server/src/modules/computers/application/service.test.ts b/server/src/modules/computers/application/service.test.ts index 1de6b30a7dd..bf3cdfb3f8c 100644 --- a/server/src/modules/computers/application/service.test.ts +++ b/server/src/modules/computers/application/service.test.ts @@ -259,6 +259,24 @@ describe("computer ownership", () => { await f.service.reconcile(); expect(f.backend.stop).toHaveBeenCalledOnce(); }); + it("prunes retired viewers while preserving active viewers and runner stop proof", async () => { + const f = fixture(); + await f.attach(); + const runner = await f.admit(); + await f.service.retire({ ...f.scope, owner: runner.owner }); + const expired = await f.service.connect({ ...f.scope, userId: "alice", idleTimeoutMs: 1000 }); + const active = await f.service.connect({ ...f.scope, userId: "bob", idleTimeoutMs: 60_000 }); + const disconnected = await f.service.connect({ ...f.scope, userId: "alice", idleTimeoutMs: 60_000 }); + await f.service.disconnectViewer({ ...f.scope, owner: disconnected.owner, userId: "alice" }); + f.advance(1001); + await f.service.reconcile(); + const owners = (await f.repository.get(f.scope)).ledger.owners; + expect(owners.map(owner => owner.id).sort()).toEqual([runner.owner.ownerId, active.owner.ownerId].sort()); + expect(owners.some(owner => [expired.owner.ownerId, disconnected.owner.ownerId].includes(owner.id))).toBe(false); + expect(await f.service.isRetired({ ...f.scope, owner: runner.owner, agentId: "agent", runId: "run" })).toBe(true); + await expect(f.service.renewViewer({ ...f.scope, owner: active.owner, userId: "bob" })).resolves.toHaveProperty("viewerUrl"); + expect(f.backend.stop).not.toHaveBeenCalled(); + }); it("a failed exact-process retirement keeps the machine held", async () => { const f = fixture(); await f.attach(); diff --git a/server/src/modules/computers/application/service.ts b/server/src/modules/computers/application/service.ts index 5ad160d8370..cb63c1b3c19 100644 --- a/server/src/modules/computers/application/service.ts +++ b/server/src/modules/computers/application/service.ts @@ -942,7 +942,8 @@ finally: await retire({ ...record, owner: ref(record, owner) }); await repository.update(record, (current) => { current.ledger.owners = current.ledger.owners.filter( - (owner) => owner.kind !== "file-operation" || owner.phase !== "retired", + // Only runner tombstones are needed for durable process-stop proof. + (owner) => owner.kind === "runner" || owner.phase !== "retired", ); }); record = await repository.get(record);