mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-09 16:02:36 +01:00
Merge master and check registered failover cursors
Use the registered controller state when checkpoint persistence replaces the local journal with an external marker. The new regression follows registration and real checkpoint conversion before remote preparation, reproducing the stale-copy bypass on the prior guard. Current backups retain the existing integrity checks. Resolve the workflow fixture conflict by keeping each normal-route input once. Verified 1,192 native session/execution-driver tests, ten workflow checks, fresh server declarations and the workflow project typecheck. Maintained preflight, module boundaries, whitespace and secrets/PII checks pass. Fresh CI and exact-head Apex review remain required. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
commit
cd8c9cd07c
17 files changed
+537
-120
No files matched your search
@@ -275,7 +275,7 @@ const state = vi.hoisted(() => ({
|
||||
release: null as null | (() => void),
|
||||
}));
|
||||
|
||||
vi.mock("../../runtime-api.js", () => ({ resolveLocalRuntimeApiUrl: state.localRuntimeApiUrl }));
|
||||
vi.mock("../../runtime-api.js", async importOriginal => ({ ...await importOriginal<typeof import("../../runtime-api.js")>(), resolveLocalRuntimeApiUrl: state.localRuntimeApiUrl }));
|
||||
|
||||
const accountingEvents = vi.hoisted(() => ({ committed: undefined as undefined | ((event: PrpEvent) => Promise<void>), duplicate: undefined as undefined | ((event: PrpEvent) => Promise<void>) }));
|
||||
vi.mock("./paperclip-control-plane-port.js", async importOriginal => {
|
||||
@@ -18090,10 +18090,12 @@ describe("runnerd provider runtime wiring", () => {
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ backup: "stale", nextSourceSeq: 120, controlSchema: "paperclip.runner.durable.control-plane-state.v1" },
|
||||
{ backup: "stale", nextSourceSeq: 120, controlSchema: "paperclip.runner.durable.control-plane-state.v2" },
|
||||
{ backup: "current", nextSourceSeq: 1518, controlSchema: "paperclip.runner.durable.control-plane-state.v2" },
|
||||
])("checks the controller cursor before restoring a $backup failover backup ($controlSchema)", async ({ backup, nextSourceSeq, controlSchema }) => {
|
||||
{ backup: "stale", nextSourceSeq: 120, controlSchema: "paperclip.runner.durable.control-plane-state.v1", registered: false },
|
||||
{ backup: "stale", nextSourceSeq: 120, controlSchema: "paperclip.runner.durable.control-plane-state.v2", registered: false },
|
||||
{ backup: "current", nextSourceSeq: 1518, controlSchema: "paperclip.runner.durable.control-plane-state.v2", registered: false },
|
||||
{ backup: "stale", nextSourceSeq: 120, controlSchema: "paperclip.runner.durable.control-plane-state.v2", registered: true },
|
||||
{ backup: "current", nextSourceSeq: 1518, controlSchema: "paperclip.runner.durable.control-plane-state.v2", registered: true },
|
||||
])("checks the controller cursor before restoring a $backup failover backup ($controlSchema, registered=$registered)", async ({ backup, nextSourceSeq, controlSchema, registered }) => {
|
||||
const remoteCwd = join(isolatedStateDirectory, "remote");
|
||||
await mkdir(join(remoteCwd, ".paperclip-runtime", "paperclip-runner"), { recursive: true });
|
||||
const syncIn = vi.fn(async () => undefined);
|
||||
@@ -18114,14 +18116,29 @@ describe("runnerd provider runtime wiring", () => {
|
||||
};
|
||||
return { exitCode: 0, timedOut: false, stdout: "", stderr: "" };
|
||||
});
|
||||
const checkpointSink = vi.fn(async () => undefined);
|
||||
const checkpointRegistration = registered ? vi.spyOn(runnerCheckpoint, "nativeRunnerCheckpointRegistration")
|
||||
.mockReturnValue(async authority => {
|
||||
authority.setCheckpointPersistence(checkpointSink);
|
||||
await authority.flushCheckpoint();
|
||||
}) : undefined;
|
||||
let controller: DurablePrpControlPlane | undefined;
|
||||
let registration: Awaited<ReturnType<NonNullable<import("../../vendor/paperclip-runner/index.js").RunnerExecutionDriver["controlPlaneRegistration"]>>> | undefined;
|
||||
onTestFinished(async () => { await registration?.release(); await controller?.stop(); checkpointRegistration?.mockRestore(); });
|
||||
const controllerArtifact = join(isolatedStateDirectory, "failover-cursor-controller");
|
||||
if (registered) await writeFile(controllerArtifact, "fixture bootstrap artifact");
|
||||
const runnerInstanceId = "runner-failover-cursor";
|
||||
await createRunnerdBackend({
|
||||
db: leaseDb(execution), execution, runnerInstanceId,
|
||||
...(registered ? { controllerPersistence: { bootId: "failover-cursor-boot" },
|
||||
runnerRemoteBinaryPath: controllerArtifact, runnerRemoteCodexPath: controllerArtifact } : {}),
|
||||
runnerIngressAuthorized: true,
|
||||
runnerExecutionTarget: {
|
||||
kind: "remote", transport: "sandbox", remoteCwd, environmentId: "environment",
|
||||
leaseId: "lease-replacement", providerKey: "daytona",
|
||||
effectiveCapabilities: { runnerWebSocketIngress: true },
|
||||
getRunnerIngressEndpoint: async () => ({ kind: "authenticated_websocket", websocketUrl: "wss://sandbox.invalid/runner",
|
||||
secretHeaders: [{ name: "Authorization", value: "fixture-ingress" }], generation: "1", refresh: vi.fn(), close: vi.fn(async () => undefined) }),
|
||||
sandboxLeaseAcquisition: { outcome: "replacement", providerLeaseId: "sandbox-replacement", reason: "not_found" },
|
||||
runner: { execute: remoteExecute, syncIn },
|
||||
} as never,
|
||||
@@ -18129,10 +18146,11 @@ describe("runnerd provider runtime wiring", () => {
|
||||
state.createBackend.mock.calls.at(-1)![1].codexTransportFactory!();
|
||||
const options = state.createTransport.mock.calls.at(-1)![0] as RunnerTransportOptions & {
|
||||
prepareExternalRunnerState: () => Promise<void>;
|
||||
controlPlaneRegistration: NonNullable<import("../../vendor/paperclip-runner/index.js").RunnerExecutionDriver["controlPlaneRegistration"]>;
|
||||
};
|
||||
const root = options.stateDirectory!;
|
||||
await mkdir(join(root, "control-plane"), { recursive: true });
|
||||
await writeFile(join(root, "control-plane", "control-plane-state.json"), JSON.stringify({
|
||||
if (!registered) await writeFile(join(root, "control-plane", "control-plane-state.json"), JSON.stringify({
|
||||
schema: controlSchema,
|
||||
identity: options.prpIdentity, ackedSourceSeq: 1517, connectionCount: 1, committedEvents: [], commands: [],
|
||||
}));
|
||||
@@ -18150,6 +18168,20 @@ describe("runnerd provider runtime wiring", () => {
|
||||
sourceProviderLeaseId: "sandbox-previous",
|
||||
})));
|
||||
|
||||
if (registered) {
|
||||
controller = new DurablePrpControlPlane({ stateDirectory: join(root, "control-plane"),
|
||||
identity: options.prpIdentity! as ConstructorParameters<typeof DurablePrpControlPlane>[0]["identity"],
|
||||
expectedRunnerVersion: "failover-cursor-fixture", expectedRunnerDigest: `sha256:${"a".repeat(64)}` });
|
||||
controller.store.state.ackedSourceSeq = 1517;
|
||||
await controller.flushCheckpoint();
|
||||
registration = await options.controlPlaneRegistration(controller);
|
||||
expect(checkpointRegistration).toHaveBeenCalled();
|
||||
expect(checkpointSink).toHaveBeenCalled();
|
||||
const marker = JSON.parse(await readFile(join(root, "control-plane", "control-plane-state.json"), "utf8"));
|
||||
expect(marker.schema).toBe("paperclip.runner.external-control-plane-state.v1");
|
||||
expect(marker.ackedSourceSeq).toBeUndefined();
|
||||
syncIn.mockClear();
|
||||
}
|
||||
const prepared = options.prepareExternalRunnerState();
|
||||
if (backup === "stale") {
|
||||
await expect(prepared).rejects.toThrow(
|
||||
|
||||
Reference in new issue
Block a user