Diagnose artifact upload phases with opt-in bounded records

This commit is contained in:
Dotta committed 2026-10-11 03:27:30 -05:00
1 parent 086a9dbbd8
commit 4e489a1014
9 files changed
+226 -13

No files matched your search

@@ -1,3 +1,4 @@
import { reportCommandPhase } from "@paperclipai/adapter-utils/command-diagnostics";
import { generateKeyPairSync, randomUUID } from "node:crypto";
import {
createSshCommandManagedRuntimeRunner,
@@ -85,6 +86,7 @@ export function createBoatTransportAdmission() {
}
const withStdinUploadSlot: NonNullable<CommandManagedRuntimeRunner["withStdinUploadSlot"]> = (operation, options) => {
if (uploadQueue.length >= 128) return Promise.reject(new ComputerError("conflict", "Computer upload admission is busy"));
reportCommandPhase(options.onPhase, "upload_queued");
return new Promise((resolve, reject) => {
const cleanup = () => { clearTimeout(timer); options.signal?.removeEventListener("abort", aborted); };
const remove = (message: string) => {
@@ -98,8 +100,10 @@ export function createBoatTransportAdmission() {
const entry = { start: () => {
cleanup();
uploads++;
reportCommandPhase(options.onPhase, "upload_admitted");
void Promise.resolve().then(operation).then(resolve, reject).finally(() => {
uploads--;
reportCommandPhase(options.onPhase, "upload_released");
drainUploads();
});
} };
@@ -116,6 +116,19 @@ function fixture() {
};
}
describe("computer ownership", () => {
it("forwards opt-in phases through the existing owner guard without serializing the observer", async () => {
const f = fixture();
const phases: string[] = [];
vi.mocked(f.backend.runner).mockResolvedValue({ execute: async input => {
expect(input.stdin).not.toContain("onPhase");
await input.onPhase?.("ssh_spawned");
return { exitCode: 0, signal: null, timedOut: false, stdout: "", stderr: "", pid: null, startedAt: "" };
} });
await f.attach();
const binding = await f.admit();
await binding.process.runner.execute({ command: "fixture", onPhase: phase => { phases.push(phase); } });
expect(phases).toEqual(["owner_guard_start", "owner_guard_complete", "ssh_spawned"]);
});
it("rechecks owner authority after a shared upload slot wait before SSH dispatch", async () => {
const f = fixture();
const admission = createBoatTransportAdmission();
@@ -1,3 +1,4 @@
import { reportCommandPhase } from "@paperclipai/adapter-utils/command-diagnostics";
import { randomUUID } from "node:crypto";
import type { ComputerRepository, ComputerBackend } from "./ports.js";
import {
@@ -229,12 +230,14 @@ export function createComputerService(
): CommandManagedRuntimeRunner => ({
withStdinUploadSlot: (processScoped ? control : raw).withStdinUploadSlot,
execute: async (input) => {
reportCommandPhase(input.onPhase, "owner_guard_start");
const current = await (processScoped
? processScope()
: scoped({ ...record, owner: ownerRef }));
if ((!["active", "starting", "warm"].includes(current.owner.phase) || expired(current.owner, now())) &&
!(processScoped && pinnedProcess && processControlAllowed(current.owner)))
throw new ComputerError("conflict", "Computer owner is retired");
reportCommandPhase(input.onPhase, "owner_guard_complete");
// Start a command unit under the same owner slice while holding the remote tombstone lock.
// The lock is released only after systemd knows the unit, so retirement cannot miss a delayed launch.
return (processScoped ? control : raw).execute({
@@ -320,6 +323,7 @@ finally:
timeoutMs: input.timeoutMs,
onLog: input.onLog,
onSpawn: input.onSpawn,
onPhase: input.onPhase,
});
},
});