diff --git a/ci/source-shape-test-budget.json b/ci/source-shape-test-budget.json index c0e79303693..953b66e9903 100644 --- a/ci/source-shape-test-budget.json +++ b/ci/source-shape-test-budget.json @@ -246,6 +246,11 @@ "test": "accepts the prospective dev MUSL sandbox template with its trusted digest", "category": "security" }, + { + "file": "test/install/installer-supervisor-manifest-trust.test.ts", + "test": "accepts the prospective shared gateway state resolver template (#10544)", + "category": "security" + }, { "file": "test/mcp/mcp-tool-discovery-image-contract.test.ts", "test": "pins the reviewed image runtime artifacts exactly", diff --git a/scripts/checks/extract-installer-pins.mts b/scripts/checks/extract-installer-pins.mts index 6760e5e0d50..b1e02196af0 100644 --- a/scripts/checks/extract-installer-pins.mts +++ b/scripts/checks/extract-installer-pins.mts @@ -85,6 +85,10 @@ const MAX_INSTALLER_INPUT_BYTES = 1024 * 1024; // operational template. Its exact prospective digests are repeated only for // release records that can select that same template; dependent installer // hash tests execute the corresponding release and qualification fixtures. +// The shared gateway state resolver likewise changes the version-independent +// supervisor runtime template. Its prospective digest is repeated only for +// release records with a supervisor identity, and its trust test constructs +// the exact follow-up template before the runtime change can land. const TRUSTED_OPENSHELL_RELEASES: readonly OpenShellReleaseTrust[] = [ { brevTemplateSha256: ["c0a4ddf25a02a9fe02b2df53a60942ea887610f04d4ce16a121b6e79a5aeff1a"], @@ -126,7 +130,10 @@ const TRUSTED_OPENSHELL_RELEASES: readonly OpenShellReleaseTrust[] = [ image: "ghcr.io/nvidia/openshell/supervisor", manifestDigest: "sha256:80ed9cda5bf672fefdb9dcd4604b40a8b09c0891b6eb9d03e10227c7e3dfb49d", required: true, - runtimeTemplateSha256: ["c1922eaa4f73c1a05aa8bccf50fc40208d7f71db0e6c110dcd09d0372d1aa068"], + runtimeTemplateSha256: [ + "c1922eaa4f73c1a05aa8bccf50fc40208d7f71db0e6c110dcd09d0372d1aa068", + "abfc1337284d437e71e47945936af7ef0bc6f28ac2495e12fac41894eb24ce3c", + ], }, version: "0.0.72", }, @@ -239,7 +246,10 @@ const TRUSTED_OPENSHELL_RELEASES: readonly OpenShellReleaseTrust[] = [ image: "ghcr.io/nvidia/openshell/supervisor", manifestDigest: "sha256:ea3632b6e9528e2309103af5b6949606fcdc83ca1f69e8db81482a25bea84bb6", required: true, - runtimeTemplateSha256: ["c1922eaa4f73c1a05aa8bccf50fc40208d7f71db0e6c110dcd09d0372d1aa068"], + runtimeTemplateSha256: [ + "c1922eaa4f73c1a05aa8bccf50fc40208d7f71db0e6c110dcd09d0372d1aa068", + "abfc1337284d437e71e47945936af7ef0bc6f28ac2495e12fac41894eb24ce3c", + ], }, version: "0.0.99", }, @@ -286,7 +296,10 @@ const TRUSTED_OPENSHELL_RELEASES: readonly OpenShellReleaseTrust[] = [ image: "ghcr.io/nvidia/openshell/supervisor", manifestDigest: "sha256:b58be5e40c788977ffa0e8305a8cad9c656efdf1a3fe182582a00ca870bb0edb", required: true, - runtimeTemplateSha256: ["c1922eaa4f73c1a05aa8bccf50fc40208d7f71db0e6c110dcd09d0372d1aa068"], + runtimeTemplateSha256: [ + "c1922eaa4f73c1a05aa8bccf50fc40208d7f71db0e6c110dcd09d0372d1aa068", + "abfc1337284d437e71e47945936af7ef0bc6f28ac2495e12fac41894eb24ce3c", + ], }, version: "0.0.101", }, @@ -330,7 +343,10 @@ const TRUSTED_OPENSHELL_RELEASES: readonly OpenShellReleaseTrust[] = [ image: "ghcr.io/nvidia/openshell/supervisor", manifestDigest: "sha256:96228f110362ffd415bb12d3b7f584063c3c52c0c93f3ccf59faada1dc2dd5d3", required: false, - runtimeTemplateSha256: ["c1922eaa4f73c1a05aa8bccf50fc40208d7f71db0e6c110dcd09d0372d1aa068"], + runtimeTemplateSha256: [ + "c1922eaa4f73c1a05aa8bccf50fc40208d7f71db0e6c110dcd09d0372d1aa068", + "abfc1337284d437e71e47945936af7ef0bc6f28ac2495e12fac41894eb24ce3c", + ], }, version: "0.0.103", }, @@ -391,7 +407,10 @@ const TRUSTED_OPENSHELL_RELEASES: readonly OpenShellReleaseTrust[] = [ image: "ghcr.io/nvidia/openshell/supervisor", manifestDigest: "sha256:722f44669722961b7f432b0b81de25b91a58f34a61d6403bef967acaf2b3af01", required: false, - runtimeTemplateSha256: ["c1922eaa4f73c1a05aa8bccf50fc40208d7f71db0e6c110dcd09d0372d1aa068"], + runtimeTemplateSha256: [ + "c1922eaa4f73c1a05aa8bccf50fc40208d7f71db0e6c110dcd09d0372d1aa068", + "abfc1337284d437e71e47945936af7ef0bc6f28ac2495e12fac41894eb24ce3c", + ], }, version: "0.0.106", }, diff --git a/test/install/installer-supervisor-manifest-trust.test.ts b/test/install/installer-supervisor-manifest-trust.test.ts index c1aff3135a9..a46d661a4bc 100644 --- a/test/install/installer-supervisor-manifest-trust.test.ts +++ b/test/install/installer-supervisor-manifest-trust.test.ts @@ -122,6 +122,27 @@ function selectOpenShellV00103(): { return { blueprint, brevInstaller, installer, supervisorRuntime }; } +function selectSharedGatewayStateResolver(source: string): string { + const localResolver = ` function getDockerDriverGatewayStateDir(): string { + const configured = process.env.NEMOCLAW_OPENSHELL_GATEWAY_STATE_DIR; + if (configured && configured.trim()) return path.resolve(configured.trim()); + const dir = gatewayBinding.resolveGatewayStateDirName(currentGatewayPort()); + return path.join(os.homedir(), ".local", "state", "nemoclaw", dir); + }`; + const sharedResolver = ` function getDockerDriverGatewayStateDir(): string { + return gatewayBinding.resolveGatewayStateDirForPort({ + configured: process.env.NEMOCLAW_OPENSHELL_GATEWAY_STATE_DIR, + home: os.homedir(), + port: currentGatewayPort(), + }); + }`; + const prospective = source.includes(sharedResolver) + ? source + : source.replace(localResolver, sharedResolver); + expect(prospective, "shared gateway state resolver").toContain(sharedResolver); + return prospective; +} + type RunOptions = { candidateParserBypass?: boolean; selectV00103?: boolean; @@ -206,6 +227,15 @@ describe("OpenShell supervisor manifest trust", () => { expect(result.status, result.stderr).toBe(0); }); + // source-shape-contract: security -- Exact prospective supervisor runtime bytes must be base-authorized before trusted CI can admit the dependent state-resolver change + it("accepts the prospective shared gateway state resolver template (#10544)", () => { + const prospective = selectSharedGatewayStateResolver(SUPERVISOR_RUNTIME_TEMPLATE); + expect(prospective).toContain("gatewayBinding.resolveGatewayStateDirForPort({"); + const result = runParser({ transformSupervisor: () => prospective }); + + expect(result.status, result.stderr).toBe(0); + }); + it.each([["0.0.103", V00103_SUPERVISOR_MANIFEST_DIGEST]] as const)( "accepts the base-trusted OpenShell %s supervisor identity before version selection (#8893)", (version, digest) => {