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 <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-10 12:08:58 -05:00
1 parent 6726f78238
commit fe619aa13a
2 files changed
+20 -1

No files matched your search

@@ -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();
@@ -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);