diff --git a/src/lib/onboard/experimental/hermes-portable-build-context.test.ts b/src/lib/onboard/experimental/hermes-portable-build-context.test.ts index 02328071aaf..d54d2411fdf 100644 --- a/src/lib/onboard/experimental/hermes-portable-build-context.test.ts +++ b/src/lib/onboard/experimental/hermes-portable-build-context.test.ts @@ -112,9 +112,18 @@ describe("Hermes portable staged build context", testTimeoutOptions(30_000), () const plan = createHermesPortableBuildContextPlan(ROOT, BUILD_SETTINGS); const first = plan.materialize(contextInput()); - expect(first.dockerfilePath).toBe( - path.join(first.buildContextPath, "agents/hermes/Dockerfile"), - ); + expect(first.dockerfilePath).toBe(path.join(first.buildContextPath, "Dockerfile")); + const inferredContext = path.dirname(first.dockerfilePath); + expect(inferredContext).toBe(first.buildContextPath); + expect( + fs.existsSync( + path.join( + inferredContext, + "tools/mcp-tool-discovery-runtime/reviewed-runtime-bundle/mcp-tool-discovery/BUNDLED_PACKAGES.json", + ), + ), + ).toBe(true); + expect(fs.existsSync(path.join(inferredContext, "agents/hermes/Dockerfile"))).toBe(false); expect(plan.authority.sourceRevision).toMatch(/^[a-f0-9]{40,64}$/u); expect(plan.authority.contextManifestSha256).toMatch(/^[a-f0-9]{64}$/u); const stagedDockerfile = fs.readFileSync(first.dockerfilePath, "utf8"); diff --git a/src/lib/onboard/experimental/hermes-portable-build-context.ts b/src/lib/onboard/experimental/hermes-portable-build-context.ts index 3796568d694..1931b2209bb 100644 --- a/src/lib/onboard/experimental/hermes-portable-build-context.ts +++ b/src/lib/onboard/experimental/hermes-portable-build-context.ts @@ -26,6 +26,8 @@ const OPEN_READ_FLAGS = fs.constants.O_RDONLY | (typeof fs.constants.O_NOFOLLOW === "number" ? fs.constants.O_NOFOLLOW : 0) | (typeof fs.constants.O_NONBLOCK === "number" ? fs.constants.O_NONBLOCK : 0); +const SOURCE_DOCKERFILE_RELATIVE_PATH = "agents/hermes/Dockerfile" as const; +const CONTEXT_DOCKERFILE_RELATIVE_PATH = "Dockerfile" as const; const LOCAL_COPY_SOURCES = [ "agents/hermes/build-mcp-digest.py", @@ -128,7 +130,7 @@ type DirectoryEvidence = { export interface HermesPortableBuildContextAuthority { readonly schemaVersion: typeof CONTEXT_SCHEMA_VERSION; readonly sourceRevision: string; - readonly dockerfileRelativePath: "agents/hermes/Dockerfile"; + readonly dockerfileRelativePath: typeof CONTEXT_DOCKERFILE_RELATIVE_PATH; readonly sourceManifestSha256: string; readonly contextManifestSha256: string; } @@ -640,7 +642,7 @@ function captureSourceEntries( entries.push(entry); }; - visit("agents/hermes/Dockerfile"); + visit(SOURCE_DOCKERFILE_RELATIVE_PATH); for (const token of LOCAL_COPY_SOURCES) { if (token.endsWith("/")) { visit(token.slice(0, -1)); @@ -676,7 +678,7 @@ function sourceAuthority( }); const contextManifestSha256 = canonicalDigest({ schemaVersion: CONTEXT_SCHEMA_VERSION, - dockerfileRelativePath: "agents/hermes/Dockerfile", + dockerfileRelativePath: CONTEXT_DOCKERFILE_RELATIVE_PATH, entries: contextEntries.map((entry) => ({ kind: entry.kind, relativePath: entry.relativePath, @@ -687,7 +689,7 @@ function sourceAuthority( return { schemaVersion: CONTEXT_SCHEMA_VERSION, sourceRevision: revision.revision, - dockerfileRelativePath: "agents/hermes/Dockerfile", + dockerfileRelativePath: CONTEXT_DOCKERFILE_RELATIVE_PATH, sourceManifestSha256, contextManifestSha256, }; @@ -698,12 +700,20 @@ function renderContextEntries( settings: HermesPortableBuildContextSettings, ): readonly SourceEntry[] { return sourceEntries.map((entry) => { - if (entry.kind !== "file" || entry.relativePath !== "agents/hermes/Dockerfile") return entry; + if (entry.kind !== "file" || entry.relativePath !== SOURCE_DOCKERFILE_RELATIVE_PATH) { + return entry; + } const bytes = Buffer.from( renderHermesPortableDockerfileBuildSettings(UTF8.decode(entry.bytes!), settings), "utf8", ); - return { ...entry, bytes, size: bytes.byteLength, sha256: digest(bytes) }; + return { + ...entry, + relativePath: CONTEXT_DOCKERFILE_RELATIVE_PATH, + bytes, + size: bytes.byteLength, + sha256: digest(bytes), + }; }); } @@ -724,7 +734,7 @@ function capture( ); const sourceEntries = captureSourceEntries(rootPath, tracked); const dockerfile = sourceEntries.find( - (entry) => entry.relativePath === "agents/hermes/Dockerfile", + (entry) => entry.relativePath === SOURCE_DOCKERFILE_RELATIVE_PATH, ); if (dockerfile?.kind !== "file" || !dockerfile.bytes) fail("Dockerfile source is unavailable"); parseDockerfileSources(dockerfile.bytes); @@ -1375,7 +1385,7 @@ export function createHermesPortableBuildContextPlan( }; return { authority: captured.authority, - sourceDockerfilePath: path.join(rootPath, captured.authority.dockerfileRelativePath), + sourceDockerfilePath: path.join(rootPath, SOURCE_DOCKERFILE_RELATIVE_PATH), assertCurrentSource, materialize: (input) => { assertCurrentSource(); diff --git a/test/helpers/hermes-portable-onboarding-fixture.ts b/test/helpers/hermes-portable-onboarding-fixture.ts index ab822f0b071..21b97c27f97 100644 --- a/test/helpers/hermes-portable-onboarding-fixture.ts +++ b/test/helpers/hermes-portable-onboarding-fixture.ts @@ -221,7 +221,7 @@ export function createHermesPortableTestInput(stateDir: string, policyPath: stri authority: { schemaVersion: 1, sourceRevision: "1".repeat(40), - dockerfileRelativePath: "agents/hermes/Dockerfile", + dockerfileRelativePath: "Dockerfile", sourceManifestSha256: "2".repeat(64), contextManifestSha256: "3".repeat(64), }, @@ -229,7 +229,7 @@ export function createHermesPortableTestInput(stateDir: string, policyPath: stri assertCurrentSource: vi.fn(), materialize: vi.fn(() => ({ buildContextPath: "/private/staged-hermes", - dockerfilePath: "/private/staged-hermes/agents/hermes/Dockerfile", + dockerfilePath: "/private/staged-hermes/Dockerfile", assertCurrent: vi.fn(), })), retire: vi.fn(() => true), @@ -390,7 +390,7 @@ export function createHermesPortableTransactionFixture( const policyIndex = argv.indexOf("--policy"); expect(argv[policyIndex + 1]).toContain("policy."); expect(argv[argv.indexOf("--from") + 1]).toBe( - options.expectedDockerfilePath ?? "/private/staged-hermes/agents/hermes/Dockerfile", + options.expectedDockerfilePath ?? "/private/staged-hermes/Dockerfile", ); expect(buildContextPath).toBe(options.expectedBuildContextPath ?? "/private/staged-hermes"); present = true;