From 2bc8266b64d9a356744a947760d56b145fb9aab7 Mon Sep 17 00:00:00 2001 From: Aaron Erickson Date: Fri, 7 Aug 2026 06:33:42 -0700 Subject: [PATCH 1/6] fix(inference): honor declared llama.cpp host port Signed-off-by: Aaron Erickson --- .../llama-cpp/managed-installer.test.ts | 1 + .../inference/llama-cpp/managed-installer.ts | 20 +++++++------- ...docker-llama-cpp-managed-lifecycle.test.ts | 27 ++++++++++++++----- .../docker-llama-cpp-managed-lifecycle.ts | 24 ++++++++++++++--- 4 files changed, 52 insertions(+), 20 deletions(-) diff --git a/src/lib/inference/llama-cpp/managed-installer.test.ts b/src/lib/inference/llama-cpp/managed-installer.test.ts index f037dbfe96f..10a107f390f 100644 --- a/src/lib/inference/llama-cpp/managed-installer.test.ts +++ b/src/lib/inference/llama-cpp/managed-installer.test.ts @@ -735,6 +735,7 @@ describe("managed llama.cpp installer", () => { expect(createLifecycle).toHaveBeenCalledWith( expect.objectContaining({ bindings: expect.objectContaining({ + hostPort: selected.recipe.spec.serve.port, imageReference: selected.recipe.spec.runtime.image, }), contract: expect.objectContaining({ diff --git a/src/lib/inference/llama-cpp/managed-installer.ts b/src/lib/inference/llama-cpp/managed-installer.ts index ca1fb588f03..6007cb52ea2 100644 --- a/src/lib/inference/llama-cpp/managed-installer.ts +++ b/src/lib/inference/llama-cpp/managed-installer.ts @@ -24,7 +24,7 @@ import { isLlamaCppServingRecipe } from "../serving/adapter-registry"; import { loadManagedInferenceCatalog } from "../serving/catalog-loader"; import type { ResolvedLlamaCppInferenceSelection } from "../serving/types"; import { buildVllmDockerEnv } from "../vllm-docker-env"; -import { LLAMA_CPP_CREDENTIAL_ENV, LLAMA_CPP_PORT } from "./contract"; +import { LLAMA_CPP_CREDENTIAL_ENV } from "./contract"; import { acquireVerifiedLlamaCppGguf, verifyLlamaCppGgufCacheEntry } from "./gguf-acquisition"; import { compileLlamaCppGgufCachePlan } from "./gguf-cache-plan"; import type { @@ -395,7 +395,7 @@ function lifecycleFor(input: { bindings: { apiKeyHostPath: input.paths.apiKeyPath, containerName: MANAGED_LLAMA_CPP_CONTAINER_NAME, - hostPort: LLAMA_CPP_PORT, + hostPort: recipe.spec.serve.port, imageReference: recipe.spec.runtime.image, model: input.artifact, network: { isolation: "docker-internal", name: MANAGED_LLAMA_CPP_NETWORK_NAME }, @@ -488,6 +488,7 @@ export async function installManagedLlamaCpp( const checkPort = options.checkPort ?? checkPortAvailable; const log = options.log ?? ((message: string) => console.log(message)); const recipe = selection.recipe; + const hostPort = recipe.spec.serve.port; let engine: ContainerEngine | null = null; let operation: HostLocalInferenceOperation | null = null; let owner: ReturnType["owner"] | null = null; @@ -523,10 +524,10 @@ export async function installManagedLlamaCpp( assertContainerNameAvailable(engine, persistedReceipt); if (persistedReceipt === null && pending.length === 0) { - const port = await checkPort(LLAMA_CPP_PORT); + const port = await checkPort(hostPort); if (!port.ok) { throw new Error( - `Managed llama.cpp port ${String(LLAMA_CPP_PORT)} is unavailable: ${port.reason}`, + `Managed llama.cpp port ${String(hostPort)} is unavailable: ${port.reason}`, ); } } @@ -608,10 +609,10 @@ export async function installManagedLlamaCpp( if (receipt !== null) { receipt = lifecycle.resume(receipt); } else { - const port = await checkPort(LLAMA_CPP_PORT); + const port = await checkPort(hostPort); if (!port.ok) { throw new Error( - `Managed llama.cpp port ${String(LLAMA_CPP_PORT)} is unavailable: ${port.reason}`, + `Managed llama.cpp port ${String(hostPort)} is unavailable: ${port.reason}`, ); } const transactionId = randomBytes(32).toString("hex"); @@ -664,6 +665,7 @@ export async function resumeManagedLlamaCppRuntime( const engine = operation.engine; const verify = options.verifyGguf ?? verifyLlamaCppGgufCacheEntry; const checkPort = options.checkPort ?? checkPortAvailable; + const hostPort = selection.recipe.spec.serve.port; const plan = compileLlamaCppGgufCachePlan(selection.recipe); const cacheRoot = ensureSharedHuggingFaceCache(homeDir); const artifact = await verify(plan, cacheRoot); @@ -706,11 +708,9 @@ export async function resumeManagedLlamaCppRuntime( receipt = loadManagedLlamaCppReceipt(paths); } if (receipt === null) { - const port = await checkPort(LLAMA_CPP_PORT); + const port = await checkPort(hostPort); if (!port.ok) { - throw new Error( - `Managed llama.cpp port ${String(LLAMA_CPP_PORT)} is unavailable: ${port.reason}`, - ); + throw new Error(`Managed llama.cpp port ${String(hostPort)} is unavailable: ${port.reason}`); } loadOrCreateManagedLlamaCppApiKey(paths); const transactionId = randomBytes(32).toString("hex"); diff --git a/src/lib/onboard/runtime-provider/docker-llama-cpp-managed-lifecycle.test.ts b/src/lib/onboard/runtime-provider/docker-llama-cpp-managed-lifecycle.test.ts index bc9c2b0ee89..4a106fb946d 100644 --- a/src/lib/onboard/runtime-provider/docker-llama-cpp-managed-lifecycle.test.ts +++ b/src/lib/onboard/runtime-provider/docker-llama-cpp-managed-lifecycle.test.ts @@ -387,7 +387,7 @@ interface DockerFixture { readonly seed: (journal: HostLocalCreateJournalRecord, running: boolean) => void; } -function dockerFixture(): DockerFixture { +function dockerFixture(configuredHostPort = ""): DockerFixture { let networkId = NETWORK_ID; let networkPresent = false; let networkTransactionId = TRANSACTION_ID; @@ -435,7 +435,9 @@ function dockerFixture(): DockerFixture { HostConfig: { NetworkMode: "nemoclaw-llama-cpp-internal", RestartPolicy: { Name: "unless-stopped", MaximumRetryCount: 0 }, - PortBindings: { "8081/tcp": [{ HostIp: "127.0.0.1", HostPort: "" }] }, + PortBindings: { + "8081/tcp": [{ HostIp: "127.0.0.1", HostPort: configuredHostPort }], + }, ReadonlyRootfs: !hardeningDrift, CapDrop: ["ALL"], SecurityOpt: ["no-new-privileges:true"], @@ -463,7 +465,9 @@ function dockerFixture(): DockerFixture { NetworkSettings: { Networks: { "nemoclaw-llama-cpp-internal": { NetworkID: networkId } }, Ports: { - "8081/tcp": startedOnce ? [{ HostIp: "127.0.0.1", HostPort: "49152" }] : null, + "8081/tcp": startedOnce + ? [{ HostIp: "127.0.0.1", HostPort: configuredHostPort || "49152" }] + : null, }, }, Mounts: [ @@ -771,15 +775,17 @@ function preparedJournal(): HostLocalCreateJournalRecord { } describe("dormant Docker llama.cpp managed lifecycle", () => { - it("journals create/start/finalize and serves the provider-neutral lifecycle in a test-only bundle (#8395)", () => { - const fixture = dockerFixture(); + it("journals a product install on its declared loopback host port (#8544)", () => { + const fixture = dockerFixture("8081"); const store = journalStore(); - const lifecycle = controller(fixture, store); + const lifecycle = createDockerLlamaCppManagedLifecycle( + options(fixture, store, { ...bindings(), hostPort: 8081 }), + ); const writer = receiptWriter(); const receipt = lifecycle.start(writer); const serialized = serializeHostLocalInferenceReceipt(receipt); - expect(receipt.endpoint.port).toBe(49152); + expect(receipt.endpoint.port).toBe(8081); expect(receipt.runtime).toMatchObject({ kind: "container", runtimeId: RUNTIME_ID, @@ -806,6 +812,13 @@ describe("dormant Docker llama.cpp managed lifecycle", () => { expect(lifecycle.runtime.destroy(receipt).status).toBe("already-absent"); }); + it("rejects a Docker binding that differs from the declared product port (#8544)", () => { + const lifecycle = createDockerLlamaCppManagedLifecycle( + options(dockerFixture("8082"), journalStore(), { ...bindings(), hostPort: 8081 }), + ); + expect(() => lifecycle.start(receiptWriter())).toThrow("does not match its loopback binding"); + }); + it("uses the declarative readiness timeout as both curl retry budget and capture budget", () => { const fixture = dockerFixture(); const lifecycle = createDockerLlamaCppManagedLifecycle({ diff --git a/src/lib/onboard/runtime-provider/docker-llama-cpp-managed-lifecycle.ts b/src/lib/onboard/runtime-provider/docker-llama-cpp-managed-lifecycle.ts index 5d9c87b6a1b..630697ea905 100644 --- a/src/lib/onboard/runtime-provider/docker-llama-cpp-managed-lifecycle.ts +++ b/src/lib/onboard/runtime-provider/docker-llama-cpp-managed-lifecycle.ts @@ -275,6 +275,7 @@ function parseInspection( output: string, contract: LlamaCppHostLocalLaunchContract, networkName: string, + hostPort: number | undefined, ): DockerContainerInspection { let parsed: unknown; try { @@ -308,8 +309,9 @@ function parseInspection( throw new Error("Docker llama.cpp container has unexpected configured ports."); } const configuredPort = record(configuredBindings[0], "Docker llama.cpp configured port"); - if (configuredPort.HostIp !== "127.0.0.1" || configuredPort.HostPort !== "") { - throw new Error("Docker llama.cpp configured host port is not loopback-only."); + const expectedHostPort = hostPort === undefined ? "" : String(hostPort); + if (configuredPort.HostIp !== "127.0.0.1" || configuredPort.HostPort !== expectedHostPort) { + throw new Error("Docker llama.cpp configured host port does not match its loopback binding."); } const bindings = ports[portKey]; const published = @@ -424,6 +426,7 @@ function inspectContainer( target: string, contract: LlamaCppHostLocalLaunchContract, networkName: string, + hostPort: number | undefined, ): DockerContainerInspection | null { const result = engine.capture(["container", "inspect", target], INSPECT_TIMEOUT_MS); const escapedTarget = target.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&"); @@ -434,7 +437,12 @@ function inspectContainer( if (!result.error && result.status === 1 && exactAbsent.test(result.stderr.trim())) { return null; } - return parseInspection(requireSuccess("container inspection", result), contract, networkName); + return parseInspection( + requireSuccess("container inspection", result), + contract, + networkName, + hostPort, + ); } function currentUid(): bigint { @@ -920,6 +928,7 @@ function rollbackExact( target, options.contract, options.bindings.network.name, + options.bindings.hostPort, ); if (container === null && record.phase === "creating" && uncertainRecoveryUnixMs !== undefined) { if ( @@ -935,6 +944,7 @@ function rollbackExact( target, options.contract, options.bindings.network.name, + options.bindings.hostPort, ); } if (container !== null) { @@ -949,6 +959,7 @@ function rollbackExact( owned.id, options.contract, options.bindings.network.name, + options.bindings.hostPort, ) !== null ) { throw new Error("Docker llama.cpp exact rollback left the owned runtime present."); @@ -1206,6 +1217,7 @@ export function createDockerLlamaCppManagedLifecycle( authorized.receipt.runtime.runtimeId, options.contract, options.bindings.network.name, + options.bindings.hostPort, ); if (inspected === null) throw new Error("Docker llama.cpp owned runtime is absent."); const container = requireOwnedContainer(inspected, options, authorized.journal); @@ -1323,6 +1335,7 @@ export function createDockerLlamaCppManagedLifecycle( normalized.runtime.runtimeId, options.contract, options.bindings.network.name, + options.bindings.hostPort, ); const journal = options.journalStore.load(normalized.runtime.model.generation); if (existing !== null || journal !== null) authorizeReceipt(normalized, true); @@ -1338,6 +1351,7 @@ export function createDockerLlamaCppManagedLifecycle( normalized.runtime.runtimeId, options.contract, options.bindings.network.name, + options.bindings.hostPort, ); if (existing === null) { const journal = options.journalStore.load(normalized.runtime.model.generation); @@ -1377,6 +1391,7 @@ export function createDockerLlamaCppManagedLifecycle( inspected.container.id, options.contract, options.bindings.network.name, + options.bindings.hostPort, ) !== null ) { throw new Error("Docker llama.cpp removal left the exact runtime present."); @@ -1473,6 +1488,7 @@ export function createDockerLlamaCppManagedLifecycle( options.bindings.containerName, options.contract, options.bindings.network.name, + options.bindings.hostPort, ) !== null ) { throw new Error("Docker llama.cpp container name is already in use."); @@ -1554,6 +1570,7 @@ export function createDockerLlamaCppManagedLifecycle( options.bindings.containerName, options.contract, options.bindings.network.name, + options.bindings.hostPort, ); if (create.error || create.status !== 0 || created === null) { throw new Error( @@ -1580,6 +1597,7 @@ export function createDockerLlamaCppManagedLifecycle( created.id, options.contract, options.bindings.network.name, + options.bindings.hostPort, ); if (started === null || !started.running) { throw new Error("Docker llama.cpp start did not leave the exact runtime running."); From 20bfcb19fd2df1158ce9c94c7f647e8486fa60a0 Mon Sep 17 00:00:00 2001 From: Aaron Erickson Date: Fri, 7 Aug 2026 08:04:30 -0700 Subject: [PATCH 2/6] fix(inference): bind published llama.cpp port authority Signed-off-by: Aaron Erickson --- ...docker-llama-cpp-managed-lifecycle.test.ts | 26 +++++++++---------- .../docker-llama-cpp-managed-lifecycle.ts | 6 ++++- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/lib/onboard/runtime-provider/docker-llama-cpp-managed-lifecycle.test.ts b/src/lib/onboard/runtime-provider/docker-llama-cpp-managed-lifecycle.test.ts index 4a106fb946d..208f32f72e8 100644 --- a/src/lib/onboard/runtime-provider/docker-llama-cpp-managed-lifecycle.test.ts +++ b/src/lib/onboard/runtime-provider/docker-llama-cpp-managed-lifecycle.test.ts @@ -387,7 +387,8 @@ interface DockerFixture { readonly seed: (journal: HostLocalCreateJournalRecord, running: boolean) => void; } -function dockerFixture(configuredHostPort = ""): DockerFixture { +function dockerFixture(configuredHostPort = "", publishedHostPort?: string): DockerFixture { + const effectivePublishedHostPort = publishedHostPort ?? (configuredHostPort || "49152"); let networkId = NETWORK_ID; let networkPresent = false; let networkTransactionId = TRANSACTION_ID; @@ -466,7 +467,7 @@ function dockerFixture(configuredHostPort = ""): DockerFixture { Networks: { "nemoclaw-llama-cpp-internal": { NetworkID: networkId } }, Ports: { "8081/tcp": startedOnce - ? [{ HostIp: "127.0.0.1", HostPort: configuredHostPort || "49152" }] + ? [{ HostIp: "127.0.0.1", HostPort: effectivePublishedHostPort }] : null, }, }, @@ -784,7 +785,6 @@ describe("dormant Docker llama.cpp managed lifecycle", () => { const writer = receiptWriter(); const receipt = lifecycle.start(writer); const serialized = serializeHostLocalInferenceReceipt(receipt); - expect(receipt.endpoint.port).toBe(8081); expect(receipt.runtime).toMatchObject({ kind: "container", @@ -801,22 +801,23 @@ describe("dormant Docker llama.cpp managed lifecycle", () => { expect(serialized).not.toContain(apiKeyPath); expect(serialized).not.toContain("filesystemIdentity"); expect(serialized).not.toContain("test-only-secret"); - - expect(serializeHostLocalInferenceReceipt(parseHostLocalInferenceReceipt(serialized))).toBe( - serialized, + const roundTrip = serializeHostLocalInferenceReceipt( + parseHostLocalInferenceReceipt(serialized), ); + expect(roundTrip).toBe(serialized); expect(lifecycle.runtime.inspectManaged(receipt).running).toBe(true); expect(lifecycle.runtime.stopManaged(receipt).running).toBe(false); expect(lifecycle.runtime.prepareDestroy(receipt)).toEqual(receipt); expect(lifecycle.runtime.destroy(receipt).status).toBe("removed"); expect(lifecycle.runtime.destroy(receipt).status).toBe("already-absent"); }); - - it("rejects a Docker binding that differs from the declared product port (#8544)", () => { - const lifecycle = createDockerLlamaCppManagedLifecycle( - options(dockerFixture("8082"), journalStore(), { ...bindings(), hostPort: 8081 }), - ); - expect(() => lifecycle.start(receiptWriter())).toThrow("does not match its loopback binding"); + it("rejects configured or published Docker port drift from the recipe (#8544)", () => { + for (const args of [["8082"], ["8081", "8082"]] as const) { + const lifecycle = createDockerLlamaCppManagedLifecycle( + options(dockerFixture(...args), journalStore(), { ...bindings(), hostPort: 8081 }), + ); + expect(() => lifecycle.start(receiptWriter())).toThrow(/binding/u); + } }); it("uses the declarative readiness timeout as both curl retry budget and capture budget", () => { @@ -825,7 +826,6 @@ describe("dormant Docker llama.cpp managed lifecycle", () => { ...options(fixture), readinessTimeoutSeconds: 37, }); - lifecycle.start(receiptWriter()); const probe = fixture.capture.mock.calls.find(([args]) => args[0] === "run"); diff --git a/src/lib/onboard/runtime-provider/docker-llama-cpp-managed-lifecycle.ts b/src/lib/onboard/runtime-provider/docker-llama-cpp-managed-lifecycle.ts index 630697ea905..9abe0744534 100644 --- a/src/lib/onboard/runtime-provider/docker-llama-cpp-managed-lifecycle.ts +++ b/src/lib/onboard/runtime-provider/docker-llama-cpp-managed-lifecycle.ts @@ -321,6 +321,10 @@ function parseInspection( if (published !== null && published.HostIp !== "127.0.0.1") { throw new Error("Docker llama.cpp host port is not loopback-only."); } + const publishedHostPort = published === null ? null : exactPort(published.HostPort); + if (hostPort !== undefined && publishedHostPort !== null && publishedHostPort !== hostPort) { + throw new Error("Docker llama.cpp published host port differs from its declared binding."); + } if (!Array.isArray(source.Mounts)) { throw new Error("Docker llama.cpp inspection returned malformed mounts."); } @@ -388,7 +392,7 @@ function parseInspection( status: stateStatus, networkId: exactId(attached.NetworkID, "Docker attached network identity"), networkName, - hostPort: published === null ? null : exactPort(published.HostPort), + hostPort: publishedHostPort, mounts: Object.freeze(mounts), hardening: Object.freeze({ user: String(config.User ?? ""), From 8d59eb3f075c2545ba6b2b43292864727ab7ebcd Mon Sep 17 00:00:00 2001 From: Aaron Erickson Date: Fri, 7 Aug 2026 08:24:16 -0700 Subject: [PATCH 3/6] fix(inference): recover exact port drift Signed-off-by: Aaron Erickson --- ...docker-llama-cpp-managed-lifecycle.test.ts | 10 ++++----- .../docker-llama-cpp-managed-lifecycle.ts | 21 ++++++++++++++++--- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/lib/onboard/runtime-provider/docker-llama-cpp-managed-lifecycle.test.ts b/src/lib/onboard/runtime-provider/docker-llama-cpp-managed-lifecycle.test.ts index 208f32f72e8..7ef40df294b 100644 --- a/src/lib/onboard/runtime-provider/docker-llama-cpp-managed-lifecycle.test.ts +++ b/src/lib/onboard/runtime-provider/docker-llama-cpp-managed-lifecycle.test.ts @@ -813,13 +813,16 @@ describe("dormant Docker llama.cpp managed lifecycle", () => { }); it("rejects configured or published Docker port drift from the recipe (#8544)", () => { for (const args of [["8082"], ["8081", "8082"]] as const) { + const [fixture, store] = [dockerFixture(...args), journalStore()]; const lifecycle = createDockerLlamaCppManagedLifecycle( - options(dockerFixture(...args), journalStore(), { ...bindings(), hostPort: 8081 }), + options(fixture, store, { ...bindings(), hostPort: 8081 }), ); expect(() => lifecycle.start(receiptWriter())).toThrow(/binding/u); + const calls = fixture.capture.mock.calls.map((call) => call[0]); + expect(store.list()).toEqual([]); + expect(calls).toContainEqual(["rm", "--force", RUNTIME_ID]); } }); - it("uses the declarative readiness timeout as both curl retry budget and capture budget", () => { const fixture = dockerFixture(); const lifecycle = createDockerLlamaCppManagedLifecycle({ @@ -827,7 +830,6 @@ describe("dormant Docker llama.cpp managed lifecycle", () => { readinessTimeoutSeconds: 37, }); lifecycle.start(receiptWriter()); - const probe = fixture.capture.mock.calls.find(([args]) => args[0] === "run"); expect(probe).toBeDefined(); const [args, timeoutMs] = probe!; @@ -844,10 +846,8 @@ describe("dormant Docker llama.cpp managed lifecycle", () => { ).toEqual(["--retry-max-time", "37"]); expect(timeoutMs).toBe(52_000); }); - it("rejects an invalid declarative readiness timeout before inspection or mutation", () => { const fixture = dockerFixture(); - expect(() => createDockerLlamaCppManagedLifecycle({ ...options(fixture), diff --git a/src/lib/onboard/runtime-provider/docker-llama-cpp-managed-lifecycle.ts b/src/lib/onboard/runtime-provider/docker-llama-cpp-managed-lifecycle.ts index 9abe0744534..875fae050d5 100644 --- a/src/lib/onboard/runtime-provider/docker-llama-cpp-managed-lifecycle.ts +++ b/src/lib/onboard/runtime-provider/docker-llama-cpp-managed-lifecycle.ts @@ -276,6 +276,7 @@ function parseInspection( contract: LlamaCppHostLocalLaunchContract, networkName: string, hostPort: number | undefined, + portValidation: "exact" | "cleanup", ): DockerContainerInspection { let parsed: unknown; try { @@ -309,8 +310,12 @@ function parseInspection( throw new Error("Docker llama.cpp container has unexpected configured ports."); } const configuredPort = record(configuredBindings[0], "Docker llama.cpp configured port"); - const expectedHostPort = hostPort === undefined ? "" : String(hostPort); - if (configuredPort.HostIp !== "127.0.0.1" || configuredPort.HostPort !== expectedHostPort) { + if (configuredPort.HostIp !== "127.0.0.1") { + throw new Error("Docker llama.cpp configured host port is not loopback-only."); + } + const configuredHostPort = + configuredPort.HostPort === "" ? null : exactPort(configuredPort.HostPort); + if (portValidation === "exact" && configuredHostPort !== (hostPort ?? null)) { throw new Error("Docker llama.cpp configured host port does not match its loopback binding."); } const bindings = ports[portKey]; @@ -322,7 +327,12 @@ function parseInspection( throw new Error("Docker llama.cpp host port is not loopback-only."); } const publishedHostPort = published === null ? null : exactPort(published.HostPort); - if (hostPort !== undefined && publishedHostPort !== null && publishedHostPort !== hostPort) { + if ( + portValidation === "exact" && + hostPort !== undefined && + publishedHostPort !== null && + publishedHostPort !== hostPort + ) { throw new Error("Docker llama.cpp published host port differs from its declared binding."); } if (!Array.isArray(source.Mounts)) { @@ -431,6 +441,7 @@ function inspectContainer( contract: LlamaCppHostLocalLaunchContract, networkName: string, hostPort: number | undefined, + portValidation: "exact" | "cleanup" = "exact", ): DockerContainerInspection | null { const result = engine.capture(["container", "inspect", target], INSPECT_TIMEOUT_MS); const escapedTarget = target.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&"); @@ -446,6 +457,7 @@ function inspectContainer( contract, networkName, hostPort, + portValidation, ); } @@ -933,6 +945,7 @@ function rollbackExact( options.contract, options.bindings.network.name, options.bindings.hostPort, + "cleanup", ); if (container === null && record.phase === "creating" && uncertainRecoveryUnixMs !== undefined) { if ( @@ -949,6 +962,7 @@ function rollbackExact( options.contract, options.bindings.network.name, options.bindings.hostPort, + "cleanup", ); } if (container !== null) { @@ -964,6 +978,7 @@ function rollbackExact( options.contract, options.bindings.network.name, options.bindings.hostPort, + "cleanup", ) !== null ) { throw new Error("Docker llama.cpp exact rollback left the owned runtime present."); From 9913f322a954c7d93ae95e140232b7ebd14c2363 Mon Sep 17 00:00:00 2001 From: Aaron Erickson Date: Fri, 7 Aug 2026 08:35:34 -0700 Subject: [PATCH 4/6] fix(inference): reject malformed cleanup bindings Signed-off-by: Aaron Erickson --- ...docker-llama-cpp-managed-lifecycle.test.ts | 95 ++++++++----------- .../docker-llama-cpp-managed-lifecycle.ts | 14 ++- 2 files changed, 50 insertions(+), 59 deletions(-) diff --git a/src/lib/onboard/runtime-provider/docker-llama-cpp-managed-lifecycle.test.ts b/src/lib/onboard/runtime-provider/docker-llama-cpp-managed-lifecycle.test.ts index 7ef40df294b..0870bb72c4b 100644 --- a/src/lib/onboard/runtime-provider/docker-llama-cpp-managed-lifecycle.test.ts +++ b/src/lib/onboard/runtime-provider/docker-llama-cpp-managed-lifecycle.test.ts @@ -387,7 +387,12 @@ interface DockerFixture { readonly seed: (journal: HostLocalCreateJournalRecord, running: boolean) => void; } -function dockerFixture(configuredHostPort = "", publishedHostPort?: string): DockerFixture { +function dockerFixture( + configuredHostPort = "", + publishedHostPort?: string, + publishedHostIp = "127.0.0.1", + publishedBindingCount = 1, +): DockerFixture { const effectivePublishedHostPort = publishedHostPort ?? (configuredHostPort || "49152"); let networkId = NETWORK_ID; let networkPresent = false; @@ -467,7 +472,10 @@ function dockerFixture(configuredHostPort = "", publishedHostPort?: string): Doc Networks: { "nemoclaw-llama-cpp-internal": { NetworkID: networkId } }, Ports: { "8081/tcp": startedOnce - ? [{ HostIp: "127.0.0.1", HostPort: effectivePublishedHostPort }] + ? Array.from({ length: publishedBindingCount }, () => ({ + HostIp: publishedHostIp, + HostPort: effectivePublishedHostPort, + })) : null, }, }, @@ -692,6 +700,10 @@ function dockerFixture(configuredHostPort = "", publishedHostPort?: string): Doc }; } +function dockerCommandPrefixes(fixture: DockerFixture): unknown[] { + return fixture.capture.mock.calls.map((call) => call[0]?.slice(0, 2)); +} + function options( fixture: DockerFixture, store = journalStore(), @@ -822,6 +834,20 @@ describe("dormant Docker llama.cpp managed lifecycle", () => { expect(store.list()).toEqual([]); expect(calls).toContainEqual(["rm", "--force", RUNTIME_ID]); } + for (const args of [ + ["8081", "8082", "0.0.0.0", 1], + ["8081", "invalid", "127.0.0.1", 1], + ["8081", "8082", "127.0.0.1", 2], + ] as const) { + const [fixture, store] = [dockerFixture(args[0], args[1], args[2], args[3]), journalStore()]; + const lifecycle = createDockerLlamaCppManagedLifecycle( + options(fixture, store, { ...bindings(), hostPort: 8081 }), + ); + expect(() => lifecycle.start(receiptWriter())).toThrow("Exact rollback also failed"); + const calls = fixture.capture.mock.calls.map((call) => call[0]); + expect(store.list()).not.toEqual([]); + expect(calls).not.toContainEqual(["rm", "--force", RUNTIME_ID]); + } }); it("uses the declarative readiness timeout as both curl retry budget and capture budget", () => { const fixture = dockerFixture(); @@ -961,10 +987,7 @@ describe("dormant Docker llama.cpp managed lifecycle", () => { }); expect(() => controller(fixture, store).start(receiptWriter())).toThrow("filesystem identity"); expect(store.list()).toEqual([]); - expect(fixture.capture.mock.calls.map((call) => call[0]?.slice(0, 2))).toContainEqual([ - "rm", - "--force", - ]); + expect(dockerCommandPrefixes(fixture)).toContainEqual(["rm", "--force"]); }); it("rolls back pathname replacement from inside Docker create capture before persistence (#8395)", () => { @@ -978,10 +1001,7 @@ describe("dormant Docker llama.cpp managed lifecycle", () => { expect(() => controller(fixture, store).start(writer)).toThrow("filesystem identity"); expect(writer.writeExact).not.toHaveBeenCalled(); expect(store.list()).toEqual([]); - expect(fixture.capture.mock.calls.map((call) => call[0]?.slice(0, 2))).toContainEqual([ - "rm", - "--force", - ]); + expect(dockerCommandPrefixes(fixture)).toContainEqual(["rm", "--force"]); }); it("rolls back an API-key root swap-and-restore inside Docker create capture (#8395)", () => { @@ -1003,10 +1023,7 @@ describe("dormant Docker llama.cpp managed lifecycle", () => { expect(() => controller(fixture, store).start(writer)).toThrow("API-key file changed"); expect(writer.writeExact).not.toHaveBeenCalled(); expect(store.list()).toEqual([]); - expect(fixture.capture.mock.calls.map((call) => call[0]?.slice(0, 2))).toContainEqual([ - "rm", - "--force", - ]); + expect(dockerCommandPrefixes(fixture)).toContainEqual(["rm", "--force"]); }); it("rolls back malformed create output and readiness failure before receipt prepare (#8395)", () => { @@ -1020,10 +1037,7 @@ describe("dormant Docker llama.cpp managed lifecycle", () => { arrangeFailure[failure](fixture); expect(() => controller(fixture, store).start(receiptWriter())).toThrow(); expect(store.list()).toEqual([]); - expect(fixture.capture.mock.calls.map((call) => call[0]?.slice(0, 2))).toContainEqual([ - "rm", - "--force", - ]); + expect(dockerCommandPrefixes(fixture)).toContainEqual(["rm", "--force"]); } }); @@ -1036,10 +1050,7 @@ describe("dormant Docker llama.cpp managed lifecycle", () => { "prepare receipt failed", ); expect(store.list()).toEqual([]); - expect(fixture.capture.mock.calls.map((call) => call[0]?.slice(0, 2))).toContainEqual([ - "rm", - "--force", - ]); + expect(dockerCommandPrefixes(fixture)).toContainEqual(["rm", "--force"]); }); it("preserves and replays when receipt preparation commits then throws (#8414)", () => { @@ -1052,10 +1063,7 @@ describe("dormant Docker llama.cpp managed lifecycle", () => { expect(() => lifecycle.start(writer)).toThrow("prepare receipt outcome unknown"); expect(store.load(TRANSACTION_ID)?.phase).toBe("receipt-prepared"); expect(writer.writeExact).not.toHaveBeenCalled(); - expect(fixture.capture.mock.calls.map((call) => call[0]?.slice(0, 2))).not.toContainEqual([ - "rm", - "--force", - ]); + expect(dockerCommandPrefixes(fixture)).not.toContainEqual(["rm", "--force"]); expect(lifecycle.recoverUnfinished(writer)).toEqual({ recovered: [TRANSACTION_ID], failures: [], @@ -1085,10 +1093,7 @@ describe("dormant Docker llama.cpp managed lifecycle", () => { phase: "receipt-prepared", serializedReceipt: committed, }); - expect(fixture.capture.mock.calls.map((call) => call[0]?.slice(0, 2))).not.toContainEqual([ - "rm", - "--force", - ]); + expect(dockerCommandPrefixes(fixture)).not.toContainEqual(["rm", "--force"]); expect(lifecycle.recoverUnfinished(writer)).toEqual({ recovered: [TRANSACTION_ID], failures: [], @@ -1249,14 +1254,8 @@ describe("dormant Docker llama.cpp managed lifecycle", () => { failures: [], }); expect(store.list()).toEqual([]); - expect(fixture.capture.mock.calls.map((call) => call[0]?.slice(0, 2))).toContainEqual([ - "network", - "rm", - ]); - expect(fixture.capture.mock.calls.map((call) => call[0]?.slice(0, 2))).not.toContainEqual([ - "rm", - "--force", - ]); + expect(dockerCommandPrefixes(fixture)).toContainEqual(["network", "rm"]); + expect(dockerCommandPrefixes(fixture)).not.toContainEqual(["rm", "--force"]); }); it("holds an absent uncertain network intent through grace and refuses another transaction", () => { @@ -1277,10 +1276,7 @@ describe("dormant Docker llama.cpp managed lifecycle", () => { const foreign = lifecycle.recoverUnfinished(receiptWriter()); expect(foreign.recovered).toEqual([]); expect(foreign.failures[0]?.message).toContain("exact internal Docker network"); - expect(fixture.capture.mock.calls.map((call) => call[0]?.slice(0, 2))).not.toContainEqual([ - "network", - "rm", - ]); + expect(dockerCommandPrefixes(fixture)).not.toContainEqual(["network", "rm"]); expect(store.load(TRANSACTION_ID)?.phase).toBe("network-creating"); }); @@ -1309,10 +1305,7 @@ describe("dormant Docker llama.cpp managed lifecycle", () => { expect(recovery.recovered).toEqual([]); expect(recovery.failures[0]?.message).toContain("network inspection failed"); expect(store.load(TRANSACTION_ID)).toEqual(journal); - expect(fixture.capture.mock.calls.map((call) => call[0]?.slice(0, 2))).not.toContainEqual([ - "network", - "rm", - ]); + expect(dockerCommandPrefixes(fixture)).not.toContainEqual(["network", "rm"]); }); it("accepts an exact alternate Docker network-absence response during rollback", () => { @@ -1336,10 +1329,7 @@ describe("dormant Docker llama.cpp managed lifecycle", () => { ).recoverUnfinished(receiptWriter()), ).toEqual({ recovered: [TRANSACTION_ID], failures: [] }); expect(store.list()).toEqual([]); - expect(fixture.capture.mock.calls.map((call) => call[0]?.slice(0, 2))).not.toContainEqual([ - "network", - "rm", - ]); + expect(dockerCommandPrefixes(fixture)).not.toContainEqual(["network", "rm"]); }); it("recovers prepared and exact creating/created/started journals without touching finalized ownership (#8395)", () => { @@ -1399,10 +1389,7 @@ describe("dormant Docker llama.cpp managed lifecycle", () => { expect(recovery.recovered).toEqual([]); expect(recovery.failures).toHaveLength(1); expect(store.load(TRANSACTION_ID)).not.toBeNull(); - expect(fixture.capture.mock.calls.map((call) => call[0]?.slice(0, 2))).not.toContainEqual([ - "rm", - "--force", - ]); + expect(dockerCommandPrefixes(fixture)).not.toContainEqual(["rm", "--force"]); } }); diff --git a/src/lib/onboard/runtime-provider/docker-llama-cpp-managed-lifecycle.ts b/src/lib/onboard/runtime-provider/docker-llama-cpp-managed-lifecycle.ts index 875fae050d5..9d111354e3c 100644 --- a/src/lib/onboard/runtime-provider/docker-llama-cpp-managed-lifecycle.ts +++ b/src/lib/onboard/runtime-provider/docker-llama-cpp-managed-lifecycle.ts @@ -271,6 +271,14 @@ function parseLabels(value: unknown): Readonly> { return Object.freeze(labels); } +function parsePublishedPortBinding(value: unknown): Record | null { + if (value === null) return null; + if (!Array.isArray(value) || value.length !== 1) { + throw new Error("Docker llama.cpp container has unexpected published ports."); + } + return record(value[0], "Docker llama.cpp published port"); +} + function parseInspection( output: string, contract: LlamaCppHostLocalLaunchContract, @@ -318,11 +326,7 @@ function parseInspection( if (portValidation === "exact" && configuredHostPort !== (hostPort ?? null)) { throw new Error("Docker llama.cpp configured host port does not match its loopback binding."); } - const bindings = ports[portKey]; - const published = - Array.isArray(bindings) && bindings.length === 1 - ? record(bindings[0], "Docker llama.cpp published port") - : null; + const published = parsePublishedPortBinding(ports[portKey]); if (published !== null && published.HostIp !== "127.0.0.1") { throw new Error("Docker llama.cpp host port is not loopback-only."); } From 423852542b124808078bd05f6da96283370e2aee Mon Sep 17 00:00:00 2001 From: Aaron Erickson Date: Fri, 7 Aug 2026 08:49:31 -0700 Subject: [PATCH 5/6] test(inference): make port drift cleanup explicit Signed-off-by: Aaron Erickson --- ...docker-llama-cpp-managed-lifecycle.test.ts | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/lib/onboard/runtime-provider/docker-llama-cpp-managed-lifecycle.test.ts b/src/lib/onboard/runtime-provider/docker-llama-cpp-managed-lifecycle.test.ts index 0870bb72c4b..4b35c28af3d 100644 --- a/src/lib/onboard/runtime-provider/docker-llama-cpp-managed-lifecycle.test.ts +++ b/src/lib/onboard/runtime-provider/docker-llama-cpp-managed-lifecycle.test.ts @@ -823,17 +823,21 @@ describe("dormant Docker llama.cpp managed lifecycle", () => { expect(lifecycle.runtime.destroy(receipt).status).toBe("removed"); expect(lifecycle.runtime.destroy(receipt).status).toBe("already-absent"); }); - it("rejects configured or published Docker port drift from the recipe (#8544)", () => { - for (const args of [["8082"], ["8081", "8082"]] as const) { - const [fixture, store] = [dockerFixture(...args), journalStore()]; - const lifecycle = createDockerLlamaCppManagedLifecycle( - options(fixture, store, { ...bindings(), hostPort: 8081 }), - ); - expect(() => lifecycle.start(receiptWriter())).toThrow(/binding/u); - const calls = fixture.capture.mock.calls.map((call) => call[0]); - expect(store.list()).toEqual([]); - expect(calls).toContainEqual(["rm", "--force", RUNTIME_ID]); - } + it.each([ + ["configured", "8082", undefined], + ["published", "8081", "8082"], + ] as const)("rolls back exact ownership for %s loopback port drift (#8544)", (_kind, configured, published) => { + const [fixture, store] = [dockerFixture(configured, published), journalStore()]; + const lifecycle = createDockerLlamaCppManagedLifecycle( + options(fixture, store, { ...bindings(), hostPort: 8081 }), + ); + expect(() => lifecycle.start(receiptWriter())).toThrow(/binding/u); + const calls = fixture.capture.mock.calls.map((call) => call[0]); + expect(calls).toContainEqual(["rm", "--force", RUNTIME_ID]); + expect(calls).toContainEqual(["network", "rm", NETWORK_ID]); + expect(store.list()).toEqual([]); + }); + it("fails closed on unsafe published bindings during port-drift rollback (#8544)", () => { for (const args of [ ["8081", "8082", "0.0.0.0", 1], ["8081", "invalid", "127.0.0.1", 1], From d07506fe5c71b5dd0da17f95fd0e8b5711dc01c6 Mon Sep 17 00:00:00 2001 From: Aaron Erickson Date: Fri, 7 Aug 2026 08:52:42 -0700 Subject: [PATCH 6/6] test(inference): name rejected binding shapes Signed-off-by: Aaron Erickson --- .../runtime-provider/docker-llama-cpp-managed-lifecycle.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/onboard/runtime-provider/docker-llama-cpp-managed-lifecycle.test.ts b/src/lib/onboard/runtime-provider/docker-llama-cpp-managed-lifecycle.test.ts index 4b35c28af3d..d803ff1bef5 100644 --- a/src/lib/onboard/runtime-provider/docker-llama-cpp-managed-lifecycle.test.ts +++ b/src/lib/onboard/runtime-provider/docker-llama-cpp-managed-lifecycle.test.ts @@ -837,7 +837,7 @@ describe("dormant Docker llama.cpp managed lifecycle", () => { expect(calls).toContainEqual(["network", "rm", NETWORK_ID]); expect(store.list()).toEqual([]); }); - it("fails closed on unsafe published bindings during port-drift rollback (#8544)", () => { + it("fails closed on non-loopback, malformed, or multiple published bindings during port-drift rollback (#8544)", () => { for (const args of [ ["8081", "8082", "0.0.0.0", 1], ["8081", "invalid", "127.0.0.1", 1],