diff --git a/.github/workflows/native-runtime-qualification-collector.yaml b/.github/workflows/native-runtime-qualification-collector.yaml new file mode 100644 index 00000000000..127d1a32812 --- /dev/null +++ b/.github/workflows/native-runtime-qualification-collector.yaml @@ -0,0 +1,102 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +name: E2E / Native Runtime Qualification Evidence Collector + +on: + workflow_dispatch: + inputs: + provider_id: + description: Provider identity expected by the canonical native qualification contract + required: true + type: string + pr_number: + description: Candidate pull request number + required: true + type: string + head_sha: + description: Candidate PR commit SHA + required: true + type: string + base_sha: + description: Target-branch base SHA and trusted collector revision + required: true + type: string + evidence_workflow: + description: Path of the separately trusted producer workflow + required: true + type: string + evidence_run_id: + description: Successful producer workflow run ID + required: true + type: string + evidence_job_name: + description: Exact successful producer job name + required: true + type: string + evidence_artifact_name: + description: Exact immutable producer artifact name + required: true + type: string + +permissions: + actions: read + contents: read + pull-requests: read + +concurrency: + group: native-runtime-qualification-collector-${{ inputs.pr_number }}-${{ inputs.head_sha }} + cancel-in-progress: false + +jobs: + collect-protected-evidence: + name: Authenticate native runtime qualification evidence + if: github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main' + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - name: Check out trusted collector revision + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ github.workflow_sha }} + path: trusted + persist-credentials: false + sparse-checkout: | + scripts/scorecard/read-artifact-zip.mts + src/lib/onboard/runtime-provider/native-qualification-authority.ts + test/e2e/registry/native-runtime-qualification.ts + tools/e2e/native-runtime-qualification-collector.mts + sparse-checkout-cone-mode: false + + - name: Set up pinned Node.js + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: 22.23.1 + + - name: Authenticate and consume protected qualification evidence + id: collect + working-directory: trusted + env: + GH_TOKEN: ${{ github.token }} + GITHUB_WORKFLOW_SHA: ${{ github.workflow_sha }} + EXPECTED_PROVIDER_ID: ${{ inputs.provider_id }} + EXPECTED_PR_NUMBER: ${{ inputs.pr_number }} + EXPECTED_HEAD_SHA: ${{ inputs.head_sha }} + EXPECTED_BASE_SHA: ${{ inputs.base_sha }} + EVIDENCE_WORKFLOW: ${{ inputs.evidence_workflow }} + EVIDENCE_RUN_ID: ${{ inputs.evidence_run_id }} + EVIDENCE_JOB_NAME: ${{ inputs.evidence_job_name }} + EVIDENCE_ARTIFACT_NAME: ${{ inputs.evidence_artifact_name }} + QUALIFICATION_AUTHORITY_PATH: ${{ runner.temp }}/native-runtime-qualification-authority.json + run: >- + node --experimental-strip-types --no-warnings + tools/e2e/native-runtime-qualification-collector.mts + + - name: Preserve authenticated authority receipt + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: native-runtime-qualification-authority-${{ github.run_id }}-${{ github.run_attempt }} + path: ${{ runner.temp }}/native-runtime-qualification-authority.json + if-no-files-found: error + retention-days: 30 + compression-level: 0 diff --git a/scripts/scorecard/read-artifact-zip.mts b/scripts/scorecard/read-artifact-zip.mts index 8e2266b4310..167d5c56d7d 100644 --- a/scripts/scorecard/read-artifact-zip.mts +++ b/scripts/scorecard/read-artifact-zip.mts @@ -43,25 +43,31 @@ function crc32(data: Buffer): number { return (crc ^ 0xffffffff) >>> 0; } +function isSafeExpectedFile(expectedFile: string): boolean { + const segments = expectedFile.split("/"); + return ( + expectedFile.length > 0 && + !expectedFile.startsWith("/") && + !expectedFile.endsWith("/") && + !expectedFile.includes("\\") && + !expectedFile.includes("\0") && + segments.every((segment) => segment !== "" && segment !== "." && segment !== "..") + ); +} + /** - * Reads one exact root-level file from a GitHub artifact ZIP without - * extracting paths to disk. Duplicate targets, links, encryption, split - * archives, ZIP64, excess entries, and oversized payloads are rejected. + * Reads the exact bytes of one safe relative file from a GitHub artifact ZIP + * without extracting paths to disk. Duplicate targets, links, encryption, + * split archives, ZIP64, excess entries, and oversized payloads are rejected. */ -export function readValidatedArtifactZipEntry( +export function readValidatedArtifactZipEntryBytes( archive: Buffer, expectedFile: string, options: { maxBytes: number; maxEntries?: number }, -): string | null { +): Buffer | null { const maxEntries = options.maxEntries ?? 1000; const expectedFileName = Buffer.from(expectedFile, "utf8"); - if ( - expectedFile.length === 0 || - expectedFile.includes("/") || - expectedFile.includes("\\") || - options.maxBytes < 1 || - maxEntries < 1 - ) { + if (!isSafeExpectedFile(expectedFile) || options.maxBytes < 1 || maxEntries < 1) { return null; } @@ -175,5 +181,14 @@ export function readValidatedArtifactZipEntry( return null; } if (contents.length !== uncompressedSize || crc32(contents) !== expectedCrc) return null; - return contents.toString("utf8"); + return contents; +} + +/** Read one validated artifact entry as UTF-8 text. */ +export function readValidatedArtifactZipEntry( + archive: Buffer, + expectedFile: string, + options: { maxBytes: number; maxEntries?: number }, +): string | null { + return readValidatedArtifactZipEntryBytes(archive, expectedFile, options)?.toString("utf8") ?? null; } diff --git a/src/lib/onboard/runtime-provider/access.ts b/src/lib/onboard/runtime-provider/access.ts index d624d4eab58..d4b5a00d425 100644 --- a/src/lib/onboard/runtime-provider/access.ts +++ b/src/lib/onboard/runtime-provider/access.ts @@ -1,6 +1,21 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +export type { + RuntimeProviderActivationCatalog, + RuntimeProviderActivationDeclaration, + RuntimeProviderActivationRegistration, +} from "./activation"; +export { + composeActivatedRuntimeProviderBundles, + createRuntimeProviderActivationCatalog, + RuntimeProviderActivationError, +} from "./activation"; +export type { + NativeRuntimeQualificationAuthority, + NativeRuntimeQualificationExpectedSource, + NativeRuntimeQualificationProtectedRun, +} from "./native-qualification-authority"; export type { RuntimeProviderBundle, RuntimeProviderBundleRegistry, @@ -22,6 +37,7 @@ export type { } from "./contract"; export { CURRENT_RUNTIME_PROVIDER_BUNDLES, + createCurrentRuntimeProviderBundles, resolveCurrentRuntimeProviderBundle, } from "./current"; export { diff --git a/src/lib/onboard/runtime-provider/activation.test.ts b/src/lib/onboard/runtime-provider/activation.test.ts new file mode 100644 index 00000000000..efc83e7d00f --- /dev/null +++ b/src/lib/onboard/runtime-provider/activation.test.ts @@ -0,0 +1,427 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { describe, expect, it } from "vitest"; +import { + compileNativeRuntimeQualification, + consumeNativeRuntimeQualificationEvidence, + nativeRuntimeQualificationDefinition, + type NativeRuntimeQualificationAuthority, +} from "../../../../test/e2e/registry/native-runtime-qualification"; +import { + nativeQualificationEvidenceForDefinition, + nativeQualificationExpectedSource, + nativeQualificationReceiptReader, +} from "../../../../test/helpers/native-runtime-qualification-evidence"; +import { createInMemoryRuntimeProviderBundle } from "../../../../test/helpers/runtime-provider-bundle"; +import { + MANAGED_IMAGE_CAPABILITY_CONTRACT_VERSION, + MANAGED_IMAGE_STARTUP_PROFILE_CONTRACT_VERSION, +} from "../managed-image/contract"; +import { + createRuntimeProviderActivationCatalog, + RUNTIME_PROVIDER_ACTIVATION_ACCELERATION_MODES, + RUNTIME_PROVIDER_ACTIVATION_AGENTS, + RUNTIME_PROVIDER_ACTIVATION_CONTRACT_VERSION, + RUNTIME_PROVIDER_ACTIVATION_ENGINE_SCOPES, + RUNTIME_PROVIDER_ACTIVATION_INFERENCE_SERVICES, + RUNTIME_PROVIDER_ACTIVATION_JOURNEYS, + RUNTIME_PROVIDER_ACTIVATION_PLATFORMS, + RUNTIME_PROVIDER_ACTIVATION_ROOT_MODES, + type RuntimeProviderActivationHostAuthority, + type RuntimeProviderActivationRegistration, + type RuntimeProviderActivationTransport, +} from "./activation"; +import { + RUNTIME_PROVIDER_SNAPSHOT_CONTRACT_VERSION, + RUNTIME_PROVIDER_STATE_MUTATION_CONTRACT_VERSION, + type RuntimeProviderBundle, +} from "./contract"; +import { CURRENT_RUNTIME_PROVIDER_BUNDLES, createCurrentRuntimeProviderBundles } from "./current"; + +type CandidateTopology = { + readonly providerId: string; + readonly hostAuthority: RuntimeProviderActivationHostAuthority; + readonly transport: RuntimeProviderActivationTransport; +}; + +const CANDIDATE_TOPOLOGIES = [ + { + providerId: "podman-rootful-contract", + hostAuthority: "rootful", + transport: "operation-scoped", + }, + { + providerId: "podman-rootless-contract", + hostAuthority: "rootless", + transport: "operation-scoped", + }, + { + providerId: "mxc-style-contract", + hostAuthority: "external", + transport: "socket-free", + }, +] as const satisfies readonly CandidateTopology[]; + +const QUALIFICATION_AUTHORITIES = new Map(); + +function createQualificationAuthority(providerId: string): NativeRuntimeQualificationAuthority { + const qualification = compileNativeRuntimeQualification( + nativeRuntimeQualificationDefinition(providerId), + ); + const authority = consumeNativeRuntimeQualificationEvidence( + qualification, + nativeQualificationEvidenceForDefinition(qualification), + nativeQualificationExpectedSource(), + nativeQualificationReceiptReader, + ); + QUALIFICATION_AUTHORITIES.set(providerId, authority); + return authority; +} + +function qualificationAuthority(providerId: string): NativeRuntimeQualificationAuthority { + return QUALIFICATION_AUTHORITIES.get(providerId) ?? createQualificationAuthority(providerId); +} + +function unreachable(): never { + throw new Error("Activation contract fixture operations are never executed."); +} + +function completeBundle(providerId: string): RuntimeProviderBundle { + const base = createInMemoryRuntimeProviderBundle({ + providerId, + workloadProfile: { + support: { + exactDigestReferences: true, + platforms: [...RUNTIME_PROVIDER_ACTIVATION_PLATFORMS], + startupProfileContractVersions: [MANAGED_IMAGE_STARTUP_PROFILE_CONTRACT_VERSION], + capabilityContractVersions: [MANAGED_IMAGE_CAPABILITY_CONTRACT_VERSION], + }, + hostArchitectures: ["amd64", "arm64"], + managedImageSelectionPolicy: "require-managed", + legacyDockerfileBuilds: false, + }, + hostLocalInference: { + services: [...RUNTIME_PROVIDER_ACTIVATION_INFERENCE_SERVICES], + createOperation: unreachable, + }, + }); + return { + ...base, + stateMutation: { + providerId, + supported: true, + contractVersion: RUNTIME_PROVIDER_STATE_MUTATION_CONTRACT_VERSION, + acquire: unreachable, + assertFenced: unreachable, + publish: unreachable, + rollback: unreachable, + activate: unreachable, + release: unreachable, + recover: unreachable, + }, + bootstrap: { + providerId, + supported: true, + createAuthorityStore: unreachable, + createLifecycle: unreachable, + createOnboardRouting: unreachable, + }, + snapshot: { + providerId, + supported: true, + contractVersion: RUNTIME_PROVIDER_SNAPSHOT_CONTRACT_VERSION, + capabilities: { backup: true, restore: true, managedProfileRestore: true }, + preflight: unreachable, + capture: unreachable, + validateRestore: unreachable, + restore: unreachable, + }, + recovery: { + providerId, + supported: true, + recover: () => ({ exitCode: 0 }), + }, + containerEngine: { + providerId, + supported: true, + identities: RUNTIME_PROVIDER_ACTIVATION_ENGINE_SCOPES.map((operation) => ({ + operation, + engineId: "contract-fixture", + displayName: "Contract fixture", + })), + }, + }; +} + +function registration( + topology: CandidateTopology = CANDIDATE_TOPOLOGIES[1], + bundle: RuntimeProviderBundle = completeBundle(topology.providerId), +): RuntimeProviderActivationRegistration { + const requiredSource = nativeQualificationExpectedSource(); + const authority = qualificationAuthority(topology.providerId); + return { + declaration: { + contractVersion: RUNTIME_PROVIDER_ACTIVATION_CONTRACT_VERSION, + providerId: topology.providerId, + topology: { + hostAuthority: topology.hostAuthority, + transport: topology.transport, + }, + agents: [...RUNTIME_PROVIDER_ACTIVATION_AGENTS], + platforms: [...RUNTIME_PROVIDER_ACTIVATION_PLATFORMS], + qualificationRootModes: [...RUNTIME_PROVIDER_ACTIVATION_ROOT_MODES], + accelerationModes: [...RUNTIME_PROVIDER_ACTIVATION_ACCELERATION_MODES], + hostLocalInferenceServices: [...RUNTIME_PROVIDER_ACTIVATION_INFERENCE_SERVICES], + journeys: [...RUNTIME_PROVIDER_ACTIVATION_JOURNEYS], + installer: { releaseInstaller: true, dockerUnavailable: true }, + qualification: { + qualificationId: `${topology.providerId}-protected-host-local-inference`, + source: { + ...requiredSource, + artifact: { ...requiredSource.artifact }, + }, + }, + }, + qualificationAuthority: authority, + bundle, + }; +} + +const INCOMPLETE_SURFACES = [ + [ + "stateMutation", + (bundle: RuntimeProviderBundle) => ({ + ...bundle, + stateMutation: { + providerId: bundle.identity.id, + supported: false as const, + reason: "incomplete fixture", + }, + }), + ], + [ + "bootstrap", + (bundle: RuntimeProviderBundle) => ({ + ...bundle, + bootstrap: { + providerId: bundle.identity.id, + supported: false as const, + reason: "incomplete fixture", + }, + }), + ], + [ + "snapshot", + (bundle: RuntimeProviderBundle) => ({ + ...bundle, + snapshot: { + providerId: bundle.identity.id, + supported: false as const, + reason: "incomplete fixture", + }, + }), + ], + [ + "recovery", + (bundle: RuntimeProviderBundle) => ({ + ...bundle, + recovery: { + providerId: bundle.identity.id, + supported: false as const, + reason: "incomplete fixture", + }, + }), + ], + [ + "cleanup", + (bundle: RuntimeProviderBundle) => ({ + ...bundle, + capabilities: { ...bundle.capabilities, workloadImageCleanup: false }, + cleanup: { + providerId: bundle.identity.id, + supported: false as const, + reason: "incomplete fixture", + }, + }), + ], +] as const; + +describe("runtime provider activation catalog", () => { + it("composes rootful, rootless, and external socket-free topologies through one seam", () => { + const registrations = CANDIDATE_TOPOLOGIES.map((topology) => registration(topology)); + const catalog = createRuntimeProviderActivationCatalog(registrations); + const providers = createCurrentRuntimeProviderBundles(registrations); + + expect(Object.keys(catalog)).toEqual(CANDIDATE_TOPOLOGIES.map(({ providerId }) => providerId)); + expect(Object.keys(providers)).toEqual([ + "docker", + "kubernetes", + ...CANDIDATE_TOPOLOGIES.map(({ providerId }) => providerId), + ]); + expect( + CANDIDATE_TOPOLOGIES.map(({ providerId }) => providers[providerId]?.identity.id), + ).toEqual(CANDIDATE_TOPOLOGIES.map(({ providerId }) => providerId)); + expect( + CANDIDATE_TOPOLOGIES.map(({ providerId }) => + Object.isFrozen(catalog[providerId]?.declaration.topology), + ), + ).toEqual([true, true, true]); + expect( + CANDIDATE_TOPOLOGIES.map(({ providerId }) => + Object.isFrozen(catalog[providerId]?.qualificationAuthority.source.artifact), + ), + ).toEqual([true, true, true]); + }); + + it("leaves Docker and Kubernetes unchanged with no production candidate registration", () => { + expect(Object.keys(CURRENT_RUNTIME_PROVIDER_BUNDLES)).toEqual(["docker", "kubernetes"]); + expect(Object.keys(createCurrentRuntimeProviderBundles())).toEqual(["docker", "kubernetes"]); + expect(CURRENT_RUNTIME_PROVIDER_BUNDLES).not.toHaveProperty("podman"); + expect(CURRENT_RUNTIME_PROVIDER_BUNDLES).not.toHaveProperty("mxc"); + }); + + it.each(INCOMPLETE_SURFACES)( + "rejects incomplete %s authority before composition", + (surface, makeIncomplete) => { + const candidate = CANDIDATE_TOPOLOGIES[1]; + const incomplete = makeIncomplete(completeBundle(candidate.providerId)); + + expect(() => + createRuntimeProviderActivationCatalog([registration(candidate, incomplete)]), + ).toThrow(`incomplete ${surface} authority`); + }, + ); + + it.each(RUNTIME_PROVIDER_ACTIVATION_ENGINE_SCOPES)( + "rejects a bundle missing the %s operation scope", + (operation) => { + const candidate = CANDIDATE_TOPOLOGIES[1]; + const bundle = completeBundle(candidate.providerId); + const containerEngine = bundle.containerEngine as Extract< + RuntimeProviderBundle["containerEngine"], + { readonly supported: true } + >; + const incomplete = { + ...bundle, + containerEngine: { + ...containerEngine, + identities: containerEngine.identities.filter( + (identity) => identity.operation !== operation, + ), + }, + } as RuntimeProviderBundle; + + expect(() => + createRuntimeProviderActivationCatalog([registration(candidate, incomplete)]), + ).toThrow(`missing: ${operation}`); + }, + ); + + it("rejects incomplete host-local inference authority", () => { + const candidate = CANDIDATE_TOPOLOGIES[1]; + const bundle = completeBundle(candidate.providerId); + const incomplete = { + ...bundle, + capabilities: { ...bundle.capabilities, hostLocalInference: false }, + hostLocalInference: { + providerId: candidate.providerId, + supported: false, + reason: "incomplete fixture", + }, + } as RuntimeProviderBundle; + + expect(() => + createRuntimeProviderActivationCatalog([registration(candidate, incomplete)]), + ).toThrow("incomplete hostLocalInference authority"); + }); + + it("rejects declaration and bundle identity mismatch", () => { + const candidate = CANDIDATE_TOPOLOGIES[1]; + const mismatched = completeBundle("different-provider"); + + expect(() => + createRuntimeProviderActivationCatalog([registration(candidate, mismatched)]), + ).toThrow("does not match"); + }); + + it("rejects a missing validated qualification authority", () => { + const candidate = registration(); + const { qualificationAuthority: _authority, ...incomplete } = candidate; + + expect(() => + createRuntimeProviderActivationCatalog([ + incomplete as unknown as RuntimeProviderActivationRegistration, + ]), + ).toThrow("validated qualification authority is required"); + }); + + it("rejects qualification authority for a different provider", () => { + const candidate = registration(); + const mismatched = { + ...candidate, + qualificationAuthority: { + ...candidate.qualificationAuthority, + providerId: "different-provider", + }, + } as RuntimeProviderActivationRegistration; + + expect(() => createRuntimeProviderActivationCatalog([mismatched])).toThrow( + "does not match provider", + ); + }); + + it("rejects qualification authority for a different candidate commit", () => { + const candidate = registration(); + const mismatched = { + ...candidate, + qualificationAuthority: { + ...candidate.qualificationAuthority, + source: { + ...candidate.qualificationAuthority.source, + headSha: "c".repeat(40), + }, + }, + } as RuntimeProviderActivationRegistration; + + expect(() => createRuntimeProviderActivationCatalog([mismatched])).toThrow( + "does not match the required source identity", + ); + }); + + it("rejects qualification authority from a different producer workflow", () => { + const candidate = registration(); + const mismatched = { + ...candidate, + qualificationAuthority: { + ...candidate.qualificationAuthority, + source: { + ...candidate.qualificationAuthority.source, + workflow: ".github/workflows/untrusted-native-qualification.yaml", + }, + }, + } as RuntimeProviderActivationRegistration; + + expect(() => createRuntimeProviderActivationCatalog([mismatched])).toThrow( + "must bind the protected qualification repository and producer workflow", + ); + }); + + it("rejects qualification authority from a different candidate repository", () => { + const candidate = registration(); + const mismatched = { + ...candidate, + qualificationAuthority: { + ...candidate.qualificationAuthority, + source: { + ...candidate.qualificationAuthority.source, + candidateRepository: "different/NemoClaw", + }, + }, + } as RuntimeProviderActivationRegistration; + + expect(() => createRuntimeProviderActivationCatalog([mismatched])).toThrow( + "must bind the protected qualification repository and producer workflow", + ); + }); +}); diff --git a/src/lib/onboard/runtime-provider/activation.ts b/src/lib/onboard/runtime-provider/activation.ts new file mode 100644 index 00000000000..e5ef2bfd440 --- /dev/null +++ b/src/lib/onboard/runtime-provider/activation.ts @@ -0,0 +1,603 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { + MANAGED_IMAGE_CAPABILITY_CONTRACT_VERSION, + MANAGED_IMAGE_STARTUP_PROFILE_CONTRACT_VERSION, +} from "../managed-image/contract"; +import { + NATIVE_RUNTIME_QUALIFICATION_PRODUCER_WORKFLOW, + NATIVE_RUNTIME_QUALIFICATION_PROTECTED_REPOSITORY, + type NativeRuntimeQualificationAuthority, + type NativeRuntimeQualificationExpectedSource, +} from "./native-qualification-authority"; +import { + RUNTIME_PROVIDER_STATE_MUTATION_CONTRACT_VERSION, + type RuntimeProviderBundle, + type RuntimeProviderBundleRegistry, + type RuntimeProviderContainerEngineOperation, + type RuntimeProviderMutationOperation, +} from "./contract"; +import { createRuntimeProviderBundleRegistry } from "./registry"; + +export const RUNTIME_PROVIDER_ACTIVATION_CONTRACT_VERSION = 1 as const; +export const RUNTIME_PROVIDER_ACTIVATION_AGENTS = [ + "openclaw", + "hermes", + "langchain-deepagents-code", +] as const; +export const RUNTIME_PROVIDER_ACTIVATION_PLATFORMS = ["linux/amd64", "linux/arm64"] as const; +export const RUNTIME_PROVIDER_ACTIVATION_ROOT_MODES = ["rootless"] as const; +export const RUNTIME_PROVIDER_ACTIVATION_ACCELERATION_MODES = ["cpu", "nvidia-cdi"] as const; +export const RUNTIME_PROVIDER_ACTIVATION_INFERENCE_SERVICES = ["ollama", "nim", "vllm"] as const; +export const RUNTIME_PROVIDER_ACTIVATION_JOURNEYS = [ + "onboard", + "agent-turn", + "stop-start", + "snapshot-restore", + "rebuild", + "restart-reconcile", + "exact-cleanup", +] as const; +export const RUNTIME_PROVIDER_ACTIVATION_HOST_AUTHORITIES = [ + "rootful", + "rootless", + "external", +] as const; +export const RUNTIME_PROVIDER_ACTIVATION_TRANSPORTS = ["operation-scoped", "socket-free"] as const; + +const QUALIFICATION_ID = /^[a-z][a-z0-9-]{0,62}-protected-host-local-inference$/u; +const SOURCE_REVISION = /^[a-f0-9]{40}$/u; +const SOURCE_DIGEST = /^sha256:[a-f0-9]{64}$/u; +const ARTIFACT_NAME = /^[A-Za-z0-9._-]{1,128}$/u; + +const REQUIRED_MUTATIONS = [ + "registration", + "start", + "stop", + "inference-set", + "rebuild", + "clone", + "provider-cleanup", + "destroy", + "workload-cleanup", +] as const satisfies readonly RuntimeProviderMutationOperation[]; + +export const RUNTIME_PROVIDER_ACTIVATION_ENGINE_SCOPES = [ + "host-doctor", + "gateway-inspection", + "host-local-inference", + "sandbox-lifecycle", + "state-mutation", + "workload-cleanup", +] as const satisfies readonly RuntimeProviderContainerEngineOperation[]; + +export type RuntimeProviderActivationAgent = (typeof RUNTIME_PROVIDER_ACTIVATION_AGENTS)[number]; +export type RuntimeProviderActivationPlatform = + (typeof RUNTIME_PROVIDER_ACTIVATION_PLATFORMS)[number]; +export type RuntimeProviderActivationRootMode = + (typeof RUNTIME_PROVIDER_ACTIVATION_ROOT_MODES)[number]; +export type RuntimeProviderActivationAccelerationMode = + (typeof RUNTIME_PROVIDER_ACTIVATION_ACCELERATION_MODES)[number]; +export type RuntimeProviderActivationInferenceService = + (typeof RUNTIME_PROVIDER_ACTIVATION_INFERENCE_SERVICES)[number]; +export type RuntimeProviderActivationJourney = + (typeof RUNTIME_PROVIDER_ACTIVATION_JOURNEYS)[number]; +export type RuntimeProviderActivationHostAuthority = + (typeof RUNTIME_PROVIDER_ACTIVATION_HOST_AUTHORITIES)[number]; +export type RuntimeProviderActivationTransport = + (typeof RUNTIME_PROVIDER_ACTIVATION_TRANSPORTS)[number]; + +export interface RuntimeProviderActivationDeclaration { + readonly contractVersion: typeof RUNTIME_PROVIDER_ACTIVATION_CONTRACT_VERSION; + readonly providerId: string; + readonly topology: { + readonly hostAuthority: RuntimeProviderActivationHostAuthority; + readonly transport: RuntimeProviderActivationTransport; + }; + readonly agents: readonly RuntimeProviderActivationAgent[]; + readonly platforms: readonly RuntimeProviderActivationPlatform[]; + readonly qualificationRootModes: readonly RuntimeProviderActivationRootMode[]; + readonly accelerationModes: readonly RuntimeProviderActivationAccelerationMode[]; + readonly hostLocalInferenceServices: readonly RuntimeProviderActivationInferenceService[]; + readonly journeys: readonly RuntimeProviderActivationJourney[]; + readonly installer: { + readonly releaseInstaller: true; + readonly dockerUnavailable: true; + }; + readonly qualification: { + readonly qualificationId: string; + readonly source: NativeRuntimeQualificationExpectedSource; + }; +} + +export interface RuntimeProviderActivationRegistration { + readonly declaration: RuntimeProviderActivationDeclaration; + readonly qualificationAuthority: NativeRuntimeQualificationAuthority; + readonly bundle: RuntimeProviderBundle; +} + +export type RuntimeProviderActivationCatalog = Readonly< + Record> +>; + +export class RuntimeProviderActivationError extends Error { + constructor(message: string) { + super(`Runtime provider activation is invalid: ${message}`); + this.name = "RuntimeProviderActivationError"; + } +} + +function exactSequence( + value: readonly unknown[], + expected: readonly string[], + label: string, +): void { + if ( + !Array.isArray(value) || + value.length !== expected.length || + value.some((entry, index) => entry !== expected[index]) + ) { + throw new RuntimeProviderActivationError( + `${label} must be exactly '${expected.join(",")}' in canonical order`, + ); + } +} + +function exactSet(value: readonly unknown[], expected: readonly string[], label: string): void { + const actual = new Set(value); + const missing = expected.filter((entry) => !actual.has(entry)); + const unknown = value.filter((entry) => typeof entry !== "string" || !expected.includes(entry)); + if ( + !Array.isArray(value) || + actual.size !== value.length || + missing.length > 0 || + unknown.length > 0 + ) { + throw new RuntimeProviderActivationError( + `${label} is incomplete (missing: ${missing.join(", ") || "none"})`, + ); + } +} + +type UnknownRecord = Record; + +function record(value: unknown, label: string): UnknownRecord { + if (typeof value !== "object" || value === null || Array.isArray(value)) { + throw new RuntimeProviderActivationError(`${label} must be an object`); + } + return value as UnknownRecord; +} + +function exactKeys(value: UnknownRecord, expected: readonly string[], label: string): void { + const actual = Object.keys(value).sort(); + const canonical = [...expected].sort(); + if (actual.length !== canonical.length || actual.some((key, index) => key !== canonical[index])) { + throw new RuntimeProviderActivationError(`${label} has unexpected or missing fields`); + } +} + +function positiveInteger(value: unknown, label: string): number { + if (!Number.isSafeInteger(value) || Number(value) < 1) { + throw new RuntimeProviderActivationError(`${label} must be a positive integer`); + } + return Number(value); +} + +function singleLine(value: unknown, label: string): string { + if ( + typeof value !== "string" || + value.trim() !== value || + value.length === 0 || + /[\r\n]/u.test(value) + ) { + throw new RuntimeProviderActivationError(`${label} must be a non-empty single-line string`); + } + return value; +} + +function validatedQualificationSource( + value: unknown, + label: string, +): NativeRuntimeQualificationExpectedSource { + const source = record(value, label); + exactKeys( + source, + [ + "repository", + "workflow", + "pullRequestNumber", + "candidateRepository", + "headSha", + "baseRef", + "baseSha", + "runId", + "attempt", + "jobId", + "artifact", + ], + label, + ); + const repository = singleLine(source.repository, `${label} repository`); + const workflow = singleLine(source.workflow, `${label} workflow`); + const candidateRepository = singleLine( + source.candidateRepository, + `${label} candidate repository`, + ); + if ( + repository !== NATIVE_RUNTIME_QUALIFICATION_PROTECTED_REPOSITORY || + workflow !== NATIVE_RUNTIME_QUALIFICATION_PRODUCER_WORKFLOW || + candidateRepository !== NATIVE_RUNTIME_QUALIFICATION_PROTECTED_REPOSITORY + ) { + throw new RuntimeProviderActivationError( + `${label} must bind the protected qualification repository and producer workflow`, + ); + } + if ( + source.baseRef !== "main" || + typeof source.headSha !== "string" || + !SOURCE_REVISION.test(source.headSha) || + typeof source.baseSha !== "string" || + !SOURCE_REVISION.test(source.baseSha) || + source.headSha === source.baseSha + ) { + throw new RuntimeProviderActivationError( + `${label} must bind the candidate commit and target-branch base SHA`, + ); + } + const artifact = record(source.artifact, `${label} artifact`); + exactKeys(artifact, ["id", "name", "digest"], `${label} artifact`); + const artifactName = singleLine(artifact.name, `${label} artifact name`); + if ( + !ARTIFACT_NAME.test(artifactName) || + typeof artifact.digest !== "string" || + !SOURCE_DIGEST.test(artifact.digest) + ) { + throw new RuntimeProviderActivationError(`${label} artifact identity is invalid`); + } + return { + repository, + workflow, + pullRequestNumber: positiveInteger(source.pullRequestNumber, `${label} pull request number`), + candidateRepository, + headSha: source.headSha, + baseRef: "main", + baseSha: source.baseSha, + runId: positiveInteger(source.runId, `${label} run id`), + attempt: positiveInteger(source.attempt, `${label} run attempt`), + jobId: positiveInteger(source.jobId, `${label} job id`), + artifact: { + id: positiveInteger(artifact.id, `${label} artifact id`), + name: artifactName, + digest: artifact.digest, + }, + }; +} + +function sameQualificationSource( + left: NativeRuntimeQualificationExpectedSource, + right: NativeRuntimeQualificationExpectedSource, +): boolean { + return JSON.stringify(left) === JSON.stringify(right); +} + +function validatedQualificationAuthority( + declaration: RuntimeProviderActivationDeclaration, + value: unknown, +): NativeRuntimeQualificationAuthority { + const requirement = record(declaration.qualification, "qualification requirement"); + exactKeys(requirement, ["qualificationId", "source"], "qualification requirement"); + const qualificationId = singleLine( + requirement.qualificationId, + "qualification requirement identity", + ); + if ( + !QUALIFICATION_ID.test(qualificationId) || + qualificationId !== `${declaration.providerId}-protected-host-local-inference` + ) { + throw new RuntimeProviderActivationError( + "qualification requirement does not match the provider identity", + ); + } + const requiredSource = validatedQualificationSource( + requirement.source, + "required qualification source", + ); + if (typeof value !== "object" || value === null || Array.isArray(value)) { + throw new RuntimeProviderActivationError("validated qualification authority is required"); + } + const authority = record(value, "qualification authority"); + exactKeys( + authority, + ["schemaVersion", "qualificationId", "providerId", "source"], + "qualification authority", + ); + const authoritySource = validatedQualificationSource( + authority.source, + "qualification authority source", + ); + if ( + authority.schemaVersion !== 1 || + authority.qualificationId !== qualificationId || + authority.providerId !== declaration.providerId + ) { + throw new RuntimeProviderActivationError( + `qualification authority does not match provider '${declaration.providerId}'`, + ); + } + if (!sameQualificationSource(authoritySource, requiredSource)) { + throw new RuntimeProviderActivationError( + "qualification authority does not match the required source identity", + ); + } + return { + schemaVersion: 1, + qualificationId, + providerId: declaration.providerId, + source: authoritySource, + }; +} + +function validateDeclaration(declaration: RuntimeProviderActivationDeclaration): void { + if ( + typeof declaration !== "object" || + declaration === null || + Array.isArray(declaration) || + declaration.contractVersion !== RUNTIME_PROVIDER_ACTIVATION_CONTRACT_VERSION || + typeof declaration.providerId !== "string" || + !/^[a-z][a-z0-9-]{0,62}$/u.test(declaration.providerId) + ) { + throw new RuntimeProviderActivationError("declaration identity is malformed"); + } + exactSequence(declaration.agents, RUNTIME_PROVIDER_ACTIVATION_AGENTS, "agents"); + exactSequence(declaration.platforms, RUNTIME_PROVIDER_ACTIVATION_PLATFORMS, "platforms"); + exactSequence( + declaration.qualificationRootModes, + RUNTIME_PROVIDER_ACTIVATION_ROOT_MODES, + "qualification root modes", + ); + exactSequence( + declaration.accelerationModes, + RUNTIME_PROVIDER_ACTIVATION_ACCELERATION_MODES, + "acceleration modes", + ); + exactSequence( + declaration.hostLocalInferenceServices, + RUNTIME_PROVIDER_ACTIVATION_INFERENCE_SERVICES, + "host-local inference services", + ); + exactSequence(declaration.journeys, RUNTIME_PROVIDER_ACTIVATION_JOURNEYS, "journeys"); + if ( + !RUNTIME_PROVIDER_ACTIVATION_HOST_AUTHORITIES.includes(declaration.topology?.hostAuthority) || + !RUNTIME_PROVIDER_ACTIVATION_TRANSPORTS.includes(declaration.topology?.transport) + ) { + throw new RuntimeProviderActivationError("execution topology is invalid"); + } + if ( + declaration.installer?.releaseInstaller !== true || + declaration.installer.dockerUnavailable !== true + ) { + throw new RuntimeProviderActivationError( + "release-installer qualification with Docker unavailable is required", + ); + } +} + +function requireSupported( + bundle: RuntimeProviderBundle, + surfaceName: keyof RuntimeProviderBundle, +): { readonly supported: true } { + const surface = bundle[surfaceName] as { readonly supported?: boolean }; + if (surface.supported !== true) { + throw new RuntimeProviderActivationError( + `provider '${bundle.identity.id}' has incomplete ${String(surfaceName)} authority`, + ); + } + return surface as { readonly supported: true }; +} + +function validateCompleteBundle(bundle: RuntimeProviderBundle): void { + const providerId = bundle.identity.id; + for (const surface of [ + "plan", + "capabilities", + "preflightDoctor", + "gateway", + "workload", + "hostLocalInference", + "lifecycle", + "mutationAuthority", + "stateMutation", + "bootstrap", + "snapshot", + "recovery", + "cleanup", + "containerEngine", + ] as const) { + requireSupported(bundle, surface); + } + if ( + bundle.capabilities.hostLocalInference !== true || + bundle.capabilities.directLifecycle !== true || + bundle.capabilities.workloadImageCleanup !== true + ) { + throw new RuntimeProviderActivationError( + `provider '${providerId}' does not declare the complete lifecycle capability set`, + ); + } + const workload = bundle.workload.profile; + const managedImages = workload.support; + if ( + managedImages === null || + managedImages.exactDigestReferences !== true || + workload.managedImageSelectionPolicy !== "require-managed" || + workload.legacyDockerfileBuilds !== false + ) { + throw new RuntimeProviderActivationError( + `provider '${providerId}' must require exact-digest managed images`, + ); + } + exactSequence( + workload.hostArchitectures, + RUNTIME_PROVIDER_ACTIVATION_PLATFORMS.map((platform) => platform.split("/")[1] as string), + `provider '${providerId}' host architectures`, + ); + exactSequence( + managedImages.platforms, + RUNTIME_PROVIDER_ACTIVATION_PLATFORMS, + `provider '${providerId}' managed-image platforms`, + ); + if ( + !managedImages.startupProfileContractVersions.includes( + MANAGED_IMAGE_STARTUP_PROFILE_CONTRACT_VERSION, + ) || + !managedImages.capabilityContractVersions.includes(MANAGED_IMAGE_CAPABILITY_CONTRACT_VERSION) + ) { + throw new RuntimeProviderActivationError( + `provider '${providerId}' does not accept the current managed-image contracts`, + ); + } + if (bundle.hostLocalInference.supported !== true) { + throw new RuntimeProviderActivationError( + `provider '${providerId}' has incomplete host-local inference authority`, + ); + } + exactSequence( + bundle.hostLocalInference.services, + RUNTIME_PROVIDER_ACTIVATION_INFERENCE_SERVICES, + `provider '${providerId}' host-local inference services`, + ); + if (bundle.mutationAuthority.supported !== true) { + throw new RuntimeProviderActivationError( + `provider '${providerId}' has incomplete mutation authority`, + ); + } + exactSequence( + bundle.mutationAuthority.operations, + REQUIRED_MUTATIONS, + `provider '${providerId}' mutation authority`, + ); + if ( + bundle.stateMutation.supported !== true || + bundle.stateMutation.contractVersion !== RUNTIME_PROVIDER_STATE_MUTATION_CONTRACT_VERSION + ) { + throw new RuntimeProviderActivationError( + `provider '${providerId}' has incomplete state-mutation authority`, + ); + } + if ( + bundle.snapshot.supported !== true || + bundle.snapshot.capabilities.backup !== true || + bundle.snapshot.capabilities.restore !== true || + bundle.snapshot.capabilities.managedProfileRestore !== true + ) { + throw new RuntimeProviderActivationError( + `provider '${providerId}' has incomplete snapshot and restore authority`, + ); + } + if (bundle.containerEngine.supported !== true) { + throw new RuntimeProviderActivationError( + `provider '${providerId}' has incomplete operation-scoped engine authority`, + ); + } + exactSet( + bundle.containerEngine.identities.map(({ operation }) => operation), + RUNTIME_PROVIDER_ACTIVATION_ENGINE_SCOPES, + `provider '${providerId}' engine scopes`, + ); +} + +function validatedRegistration( + registration: RuntimeProviderActivationRegistration, +): Readonly { + validateDeclaration(registration.declaration); + const qualificationAuthority = validatedQualificationAuthority( + registration.declaration, + registration.qualificationAuthority, + ); + const providerId = registration.declaration.providerId; + if (registration.bundle.identity.id !== providerId) { + throw new RuntimeProviderActivationError( + `declaration '${providerId}' does not match its provider bundle`, + ); + } + const validated = createRuntimeProviderBundleRegistry([[providerId, registration.bundle]])[ + providerId + ]; + if (!validated || validated.identity.id !== providerId) { + throw new RuntimeProviderActivationError( + `declaration '${providerId}' does not match its provider bundle`, + ); + } + validateCompleteBundle(validated); + return Object.freeze({ + declaration: Object.freeze({ + ...registration.declaration, + topology: Object.freeze({ ...registration.declaration.topology }), + agents: Object.freeze([...registration.declaration.agents]), + platforms: Object.freeze([...registration.declaration.platforms]), + qualificationRootModes: Object.freeze([...registration.declaration.qualificationRootModes]), + accelerationModes: Object.freeze([...registration.declaration.accelerationModes]), + hostLocalInferenceServices: Object.freeze([ + ...registration.declaration.hostLocalInferenceServices, + ]), + journeys: Object.freeze([...registration.declaration.journeys]), + installer: Object.freeze({ ...registration.declaration.installer }), + qualification: Object.freeze({ + qualificationId: registration.declaration.qualification.qualificationId, + source: Object.freeze({ + ...registration.declaration.qualification.source, + artifact: Object.freeze({ + ...registration.declaration.qualification.source.artifact, + }), + }), + }), + }), + qualificationAuthority: Object.freeze({ + ...qualificationAuthority, + source: Object.freeze({ + ...qualificationAuthority.source, + artifact: Object.freeze({ ...qualificationAuthority.source.artifact }), + }), + }), + bundle: validated, + }); +} + +export function createRuntimeProviderActivationCatalog( + registrations: readonly RuntimeProviderActivationRegistration[], +): RuntimeProviderActivationCatalog { + const catalog: Record> = Object.create( + null, + ); + for (const registration of registrations) { + const providerId = registration.declaration?.providerId; + if (typeof providerId === "string" && Object.hasOwn(catalog, providerId)) { + throw new RuntimeProviderActivationError(`duplicate provider identity '${providerId}'`); + } + const validated = validatedRegistration(registration); + catalog[validated.declaration.providerId] = validated; + } + return Object.freeze(catalog); +} + +export function composeActivatedRuntimeProviderBundles( + base: RuntimeProviderBundleRegistry, + activations: readonly RuntimeProviderActivationRegistration[] = [], +): RuntimeProviderBundleRegistry { + const baseRegistry = createRuntimeProviderBundleRegistry(Object.entries(base)); + const catalog = createRuntimeProviderActivationCatalog(activations); + for (const providerId of Object.keys(catalog)) { + if (Object.hasOwn(baseRegistry, providerId)) { + throw new RuntimeProviderActivationError( + `provider identity '${providerId}' is already production-selectable`, + ); + } + } + return createRuntimeProviderBundleRegistry([ + ...Object.entries(baseRegistry), + ...Object.entries(catalog).map( + ([providerId, registration]) => [providerId, registration.bundle] as const, + ), + ]); +} diff --git a/src/lib/onboard/runtime-provider/current.ts b/src/lib/onboard/runtime-provider/current.ts index 3f812630d69..43c587512fa 100644 --- a/src/lib/onboard/runtime-provider/current.ts +++ b/src/lib/onboard/runtime-provider/current.ts @@ -1,6 +1,10 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { + composeActivatedRuntimeProviderBundles, + type RuntimeProviderActivationRegistration, +} from "./activation"; import type { RuntimeProviderBundle, RuntimeProviderBundleRegistry } from "./contract"; import { createDockerRuntimeProviderBundle, createKubernetesRuntimeProviderBundle } from "./docker"; import { createRuntimeProviderBundleRegistry, requireRuntimeProviderBundle } from "./registry"; @@ -10,12 +14,21 @@ import { createRuntimeProviderBundleRegistry, requireRuntimeProviderBundle } fro * providers NemoClaw already ships. Future providers must land as one complete * bundle and separately pass their activation gate. */ -export const CURRENT_RUNTIME_PROVIDER_BUNDLES: RuntimeProviderBundleRegistry = +const ESTABLISHED_RUNTIME_PROVIDER_BUNDLES: RuntimeProviderBundleRegistry = createRuntimeProviderBundleRegistry([ ["docker", createDockerRuntimeProviderBundle()], ["kubernetes", createKubernetesRuntimeProviderBundle()], ]); +export function createCurrentRuntimeProviderBundles( + activations: readonly RuntimeProviderActivationRegistration[] = [], +): RuntimeProviderBundleRegistry { + return composeActivatedRuntimeProviderBundles(ESTABLISHED_RUNTIME_PROVIDER_BUNDLES, activations); +} + +export const CURRENT_RUNTIME_PROVIDER_BUNDLES: RuntimeProviderBundleRegistry = + createCurrentRuntimeProviderBundles(); + export function resolveCurrentRuntimeProviderBundle( platform: NodeJS.Platform = process.platform, arch: NodeJS.Architecture = process.arch, diff --git a/src/lib/onboard/runtime-provider/native-qualification-authority.ts b/src/lib/onboard/runtime-provider/native-qualification-authority.ts new file mode 100644 index 00000000000..4c811882afc --- /dev/null +++ b/src/lib/onboard/runtime-provider/native-qualification-authority.ts @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +/** + * Immutable GitHub identities resolved by the trusted native qualification + * collector. The evidence consumer rechecks these identities before issuing + * an authority receipt; activation must match the receipt to its independently + * required source identity. + */ +export const NATIVE_RUNTIME_QUALIFICATION_PROTECTED_REPOSITORY = "NVIDIA/NemoClaw"; +/** The trusted collector is separate and rejects evidence emitted by its own workflow. */ +export const NATIVE_RUNTIME_QUALIFICATION_PRODUCER_WORKFLOW = + ".github/workflows/native-runtime-qualification.yaml"; + +export interface NativeRuntimeQualificationProtectedRun { + readonly repository: string; + readonly workflow: string; + readonly pullRequestNumber: number; + readonly candidateRepository: string; + readonly headSha: string; + readonly baseRef: "main"; + readonly baseSha: string; + readonly runId: number; + readonly attempt: number; + readonly jobId: number; +} + +export interface NativeRuntimeQualificationExpectedSource extends NativeRuntimeQualificationProtectedRun { + readonly artifact: { + readonly id: number; + readonly name: string; + readonly digest: string; + }; +} + +export interface NativeRuntimeQualificationAuthority { + readonly schemaVersion: 1; + readonly qualificationId: string; + readonly providerId: string; + readonly source: NativeRuntimeQualificationExpectedSource; +} diff --git a/test/e2e/registry/native-runtime-qualification.ts b/test/e2e/registry/native-runtime-qualification.ts index aa248baed0e..a5d2c3b6e8c 100644 --- a/test/e2e/registry/native-runtime-qualification.ts +++ b/test/e2e/registry/native-runtime-qualification.ts @@ -1,6 +1,24 @@ // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { createHash } from "node:crypto"; + +export { + NATIVE_RUNTIME_QUALIFICATION_PRODUCER_WORKFLOW, + NATIVE_RUNTIME_QUALIFICATION_PROTECTED_REPOSITORY, +} from "../../../src/lib/onboard/runtime-provider/native-qualification-authority"; +import type { + NativeRuntimeQualificationAuthority, + NativeRuntimeQualificationExpectedSource, + NativeRuntimeQualificationProtectedRun, +} from "../../../src/lib/onboard/runtime-provider/native-qualification-authority"; + +export type { + NativeRuntimeQualificationAuthority, + NativeRuntimeQualificationExpectedSource, + NativeRuntimeQualificationProtectedRun, +} from "../../../src/lib/onboard/runtime-provider/native-qualification-authority"; + export const NATIVE_RUNTIME_QUALIFICATION_AGENTS = [ "openclaw", "hermes", @@ -76,6 +94,13 @@ const REQUIRED_CAPABILITIES = [ ] as const; const PROVIDER_ID = /^[a-z][a-z0-9-]{0,62}$/u; const SOURCE_REVISION = /^[a-f0-9]{40}$/u; +const SOURCE_DIGEST = /^sha256:[a-f0-9]{64}$/u; +const ARTIFACT_SHA256 = /^[a-f0-9]{64}$/u; +const REPOSITORY = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u; +const WORKFLOW = /^\.github\/workflows\/[A-Za-z0-9_.-]+\.ya?ml$/u; +const ARTIFACT_NAME = /^[A-Za-z0-9._-]{1,128}$/u; +const ARTIFACT_PATH = /^[A-Za-z0-9._/-]{1,256}$/u; +const compiledNativeRuntimeQualifications = new WeakSet(); export interface NativeRuntimeQualificationCase { readonly id: string; @@ -129,10 +154,97 @@ export interface NativeRuntimeCandidateAuthority { readonly executionPath: "runtime-provider-bundle"; } +export interface NativeRuntimeQualificationArtifactReceipt { + readonly path: string; + readonly sha256: string; +} + +export interface NativeRuntimeQualificationCaseEvidence { + readonly schemaVersion: 1; + readonly caseId: string; + readonly protectedRun: NativeRuntimeQualificationProtectedRun; + readonly installer: { + readonly providerId: string; + readonly architecture: NativeRuntimeQualificationArchitecture; + readonly dockerAvailability: "unavailable"; + readonly exitCode: 0; + readonly invocation: NativeRuntimeQualificationArtifactReceipt; + readonly script: NativeRuntimeQualificationArtifactReceipt; + }; + readonly runtime: { + readonly providerId: string; + readonly agent: NativeRuntimeQualificationAgent; + readonly inference: NativeRuntimeQualificationInference; + readonly architecture: NativeRuntimeQualificationArchitecture; + readonly acceleration: NativeRuntimeQualificationAcceleration; + readonly rootMode: "rootless"; + readonly engineName: string; + readonly engineVersion: string; + readonly managedImages: readonly { + readonly role: string; + readonly digest: string; + }[]; + readonly result: NativeRuntimeQualificationArtifactReceipt; + }; + readonly operations: readonly { + readonly id: NativeRuntimeQualificationObligation; + readonly artifact: NativeRuntimeQualificationArtifactReceipt; + }[]; + readonly nvidiaCdi?: { + readonly device: "nvidia.com/gpu=all"; + readonly artifact: NativeRuntimeQualificationArtifactReceipt; + }; +} + +export interface NativeRuntimeQualificationEvidenceEnvelope { + readonly schemaVersion: 1; + readonly qualificationId: string; + readonly providerId: string; + readonly cases: readonly NativeRuntimeQualificationCaseEvidence[]; +} + +export type NativeRuntimeQualificationReceiptReader = (path: string) => Buffer | null; + function compareCodeUnits(left: string, right: string): number { return left < right ? -1 : left > right ? 1 : 0; } +type UnknownRecord = Record; + +function record(value: unknown, label: string): UnknownRecord { + if (typeof value !== "object" || value === null || Array.isArray(value)) { + throw new Error(`${label} must be an object`); + } + return value as UnknownRecord; +} + +function exactKeys(value: UnknownRecord, expected: readonly string[], label: string): void { + const actual = Object.keys(value).sort(compareCodeUnits); + const canonical = [...expected].sort(compareCodeUnits); + if (actual.length !== canonical.length || actual.some((key, index) => key !== canonical[index])) { + throw new Error(`${label} has unexpected or missing fields`); + } +} + +function positiveInteger(value: unknown, label: string): number { + if (!Number.isSafeInteger(value) || Number(value) < 1) { + throw new Error(`${label} must be a positive integer`); + } + return Number(value); +} + +function singleLine(value: unknown, label: string): string { + if ( + typeof value !== "string" || + value.trim() !== value || + value.length === 0 || + /[\r\n]/u.test(value) + ) { + throw new Error(`${label} must be a non-empty single-line string`); + } + return value; +} + function exactSet(actual: readonly T[], expected: readonly T[], label: string) { const actualSet = new Set(actual); const missing = expected.filter((value) => !actualSet.has(value)); @@ -251,10 +363,12 @@ export function compileNativeRuntimeQualification( `Native runtime qualification coverage is incomplete (missing: ${missing.join(", ") || "none"})`, ); } - return Object.freeze({ + const compiled = Object.freeze({ ...definition, cases: Object.freeze([...cases].sort((left, right) => compareCodeUnits(left.id, right.id))), }); + compiledNativeRuntimeQualifications.add(compiled); + return compiled; } export function nativeRuntimeQualificationDefinition( @@ -300,6 +414,353 @@ export function nativeRuntimeQualificationDefinition( export const PODMAN_PROTECTED_HOST_LOCAL_INFERENCE_QUALIFICATION = compileNativeRuntimeQualification(nativeRuntimeQualificationDefinition("podman")); +function validatedArtifactReceipt( + value: unknown, + label: string, + readReceipt: NativeRuntimeQualificationReceiptReader, +): NativeRuntimeQualificationArtifactReceipt { + const artifact = record(value, label); + exactKeys(artifact, ["path", "sha256"], label); + const artifactPath = singleLine(artifact.path, `${label} path`); + if ( + !ARTIFACT_PATH.test(artifactPath) || + artifactPath.startsWith("/") || + artifactPath.startsWith("-") || + artifactPath.includes("//") || + artifactPath.split("/").some((segment) => segment === "." || segment === "..") + ) { + throw new Error(`${label} path must be a safe repository-relative path`); + } + if (typeof artifact.sha256 !== "string" || !ARTIFACT_SHA256.test(artifact.sha256)) { + throw new Error(`${label} sha256 must be an exact lowercase SHA-256 digest`); + } + const contents = readReceipt(artifactPath); + if (contents === null) { + throw new Error(`${label} receipt '${artifactPath}' is missing from the authenticated artifact`); + } + const actualSha256 = createHash("sha256").update(contents).digest("hex"); + if (actualSha256 !== artifact.sha256) { + throw new Error(`${label} receipt '${artifactPath}' does not match its SHA-256 digest`); + } + return Object.freeze({ path: artifactPath, sha256: artifact.sha256 }); +} + +function validatedExpectedSource( + value: unknown, + definition: NativeRuntimeQualificationDefinition, +): NativeRuntimeQualificationExpectedSource { + const source = record(value, "Native runtime qualification expected source"); + exactKeys( + source, + [ + "repository", + "workflow", + "pullRequestNumber", + "candidateRepository", + "headSha", + "baseRef", + "baseSha", + "runId", + "attempt", + "jobId", + "artifact", + ], + "Native runtime qualification expected source", + ); + const repository = singleLine(source.repository, "Expected repository"); + const workflow = singleLine(source.workflow, "Expected protected workflow"); + const candidateRepository = singleLine( + source.candidateRepository, + "Expected candidate repository", + ); + if ( + repository !== definition.repository || + !REPOSITORY.test(repository) || + !REPOSITORY.test(candidateRepository) || + !WORKFLOW.test(workflow) || + source.baseRef !== "main" || + typeof source.headSha !== "string" || + !SOURCE_REVISION.test(source.headSha) || + typeof source.baseSha !== "string" || + !SOURCE_REVISION.test(source.baseSha) || + source.headSha === source.baseSha + ) { + throw new Error("Native runtime qualification expected source identity is invalid"); + } + const artifact = record(source.artifact, "Expected GitHub artifact"); + exactKeys(artifact, ["id", "name", "digest"], "Expected GitHub artifact"); + const artifactName = singleLine(artifact.name, "Expected GitHub artifact name"); + if ( + !ARTIFACT_NAME.test(artifactName) || + typeof artifact.digest !== "string" || + !SOURCE_DIGEST.test(artifact.digest) + ) { + throw new Error("Expected GitHub artifact identity is invalid"); + } + return Object.freeze({ + repository, + workflow, + pullRequestNumber: positiveInteger(source.pullRequestNumber, "Expected pull request number"), + candidateRepository, + headSha: source.headSha, + baseRef: "main", + baseSha: source.baseSha, + runId: positiveInteger(source.runId, "Expected protected run id"), + attempt: positiveInteger(source.attempt, "Expected protected run attempt"), + jobId: positiveInteger(source.jobId, "Expected protected job id"), + artifact: Object.freeze({ + id: positiveInteger(artifact.id, "Expected GitHub artifact id"), + name: artifactName, + digest: artifact.digest, + }), + }); +} + +function assertProtectedRun( + value: unknown, + expected: NativeRuntimeQualificationExpectedSource, + label: string, +): void { + const source = record(value, `${label} protected run`); + exactKeys( + source, + [ + "repository", + "workflow", + "pullRequestNumber", + "candidateRepository", + "headSha", + "baseRef", + "baseSha", + "runId", + "attempt", + "jobId", + ], + `${label} protected run`, + ); + if ( + source.repository !== expected.repository || + source.workflow !== expected.workflow || + source.pullRequestNumber !== expected.pullRequestNumber || + source.candidateRepository !== expected.candidateRepository || + source.headSha !== expected.headSha || + source.baseRef !== expected.baseRef || + source.baseSha !== expected.baseSha || + source.runId !== expected.runId || + source.attempt !== expected.attempt || + source.jobId !== expected.jobId + ) { + throw new Error(`${label} does not match the externally expected protected source`); + } +} + +function assertInstallerEvidence( + value: unknown, + definition: NativeRuntimeQualificationDefinition, + qualificationCase: NativeRuntimeQualificationCase, + label: string, + readReceipt: NativeRuntimeQualificationReceiptReader, +): void { + const installer = record(value, `${label} installer`); + exactKeys( + installer, + ["providerId", "architecture", "dockerAvailability", "exitCode", "invocation", "script"], + `${label} installer`, + ); + if ( + installer.providerId !== definition.providerId || + installer.architecture !== qualificationCase.architecture || + installer.dockerAvailability !== "unavailable" || + installer.exitCode !== 0 + ) { + throw new Error(`${label} has an invalid installer receipt`); + } + validatedArtifactReceipt(installer.invocation, `${label} installer invocation`, readReceipt); + validatedArtifactReceipt(installer.script, `${label} installer script`, readReceipt); +} + +function assertRuntimeEvidence( + value: unknown, + definition: NativeRuntimeQualificationDefinition, + qualificationCase: NativeRuntimeQualificationCase, + label: string, + readReceipt: NativeRuntimeQualificationReceiptReader, +): void { + const runtime = record(value, `${label} runtime`); + exactKeys( + runtime, + [ + "providerId", + "agent", + "inference", + "architecture", + "acceleration", + "rootMode", + "engineName", + "engineVersion", + "managedImages", + "result", + ], + `${label} runtime`, + ); + if ( + runtime.providerId !== definition.providerId || + runtime.agent !== qualificationCase.agent || + runtime.inference !== qualificationCase.inference || + runtime.architecture !== qualificationCase.architecture || + runtime.acceleration !== qualificationCase.acceleration || + runtime.rootMode !== qualificationCase.rootMode + ) { + throw new Error(`${label} has an invalid runtime identity`); + } + singleLine(runtime.engineName, `${label} runtime engine name`); + singleLine(runtime.engineVersion, `${label} runtime engine version`); + if (!Array.isArray(runtime.managedImages) || runtime.managedImages.length === 0) { + throw new Error(`${label} must name exact managed images`); + } + const roles = new Set(); + for (const value of runtime.managedImages) { + const image = record(value, `${label} managed image`); + exactKeys(image, ["role", "digest"], `${label} managed image`); + const role = singleLine(image.role, `${label} managed image role`); + if (roles.has(role) || typeof image.digest !== "string" || !SOURCE_DIGEST.test(image.digest)) { + throw new Error(`${label} must use unique roles and exact managed-image digests`); + } + roles.add(role); + } + validatedArtifactReceipt(runtime.result, `${label} runtime result`, readReceipt); +} + +function assertOperationEvidence( + value: unknown, + qualificationCase: NativeRuntimeQualificationCase, + label: string, + readReceipt: NativeRuntimeQualificationReceiptReader, +): void { + if (!Array.isArray(value)) { + throw new Error(`${label} operations must be an array`); + } + const operations = value.map((entry) => record(entry, `${label} operation`)); + const operationIds = operations + .map((operation) => operation.id) + .filter( + (operation): operation is NativeRuntimeQualificationObligation => + typeof operation === "string", + ); + exactSet(operationIds, qualificationCase.obligations, `${label} operations`); + if (operationIds.length !== operations.length) { + throw new Error(`${label} operations contain an invalid obligation`); + } + operations.forEach((operation, index) => { + exactKeys(operation, ["id", "artifact"], `${label} operation`); + validatedArtifactReceipt( + operation.artifact, + `${label} operation '${String(operationIds[index])}'`, + readReceipt, + ); + }); +} + +function assertCaseEvidence( + value: unknown, + definition: NativeRuntimeQualificationDefinition, + qualificationCase: NativeRuntimeQualificationCase, + expected: NativeRuntimeQualificationExpectedSource, + readReceipt: NativeRuntimeQualificationReceiptReader, +): void { + const label = `Native runtime qualification case '${qualificationCase.id}'`; + const evidence = record(value, label); + const gpu = qualificationCase.acceleration === "nvidia-gpu"; + exactKeys( + evidence, + [ + "schemaVersion", + "caseId", + "protectedRun", + "installer", + "runtime", + "operations", + ...(gpu ? ["nvidiaCdi"] : []), + ], + label, + ); + if (evidence.schemaVersion !== 1 || evidence.caseId !== qualificationCase.id) { + throw new Error(`${label} identity is invalid`); + } + assertProtectedRun(evidence.protectedRun, expected, label); + assertInstallerEvidence(evidence.installer, definition, qualificationCase, label, readReceipt); + assertRuntimeEvidence(evidence.runtime, definition, qualificationCase, label, readReceipt); + assertOperationEvidence(evidence.operations, qualificationCase, label, readReceipt); + if (gpu) { + const cdi = record(evidence.nvidiaCdi, `${label} NVIDIA CDI`); + exactKeys(cdi, ["device", "artifact"], `${label} NVIDIA CDI`); + if (cdi.device !== "nvidia.com/gpu=all") { + throw new Error(`${label} must prove NVIDIA CDI access`); + } + validatedArtifactReceipt(cdi.artifact, `${label} NVIDIA CDI`, readReceipt); + } +} + +/** + * Consume one aggregate evidence artifact only after a trusted controller has + * resolved its expected GitHub identities independently from the artifact. + */ +export function consumeNativeRuntimeQualificationEvidence( + definition: NativeRuntimeQualificationDefinition, + value: unknown, + expectedSource: NativeRuntimeQualificationExpectedSource, + readReceipt: NativeRuntimeQualificationReceiptReader, +): NativeRuntimeQualificationAuthority { + if (!compiledNativeRuntimeQualifications.has(definition)) { + throw new Error("Native runtime qualification evidence requires a compiled definition"); + } + const expected = validatedExpectedSource(expectedSource, definition); + const envelope = record(value, "Native runtime qualification evidence"); + exactKeys( + envelope, + ["schemaVersion", "qualificationId", "providerId", "cases"], + "Native runtime qualification evidence", + ); + if ( + envelope.schemaVersion !== 1 || + envelope.qualificationId !== definition.id || + envelope.providerId !== definition.providerId || + !Array.isArray(envelope.cases) + ) { + throw new Error("Native runtime qualification evidence identity is invalid"); + } + const casesById = new Map(definition.cases.map((entry) => [entry.id, entry])); + const evidenceById = new Map(); + for (const entry of envelope.cases) { + const evidence = record(entry, "Native runtime qualification case evidence"); + if (typeof evidence.caseId !== "string" || evidenceById.has(evidence.caseId)) { + throw new Error("Native runtime qualification evidence repeats or omits a case identity"); + } + const qualificationCase = casesById.get(evidence.caseId); + if (!qualificationCase) { + throw new Error( + `Native runtime qualification evidence names unknown case '${evidence.caseId}'`, + ); + } + assertCaseEvidence(evidence, definition, qualificationCase, expected, readReceipt); + evidenceById.set(evidence.caseId, evidence); + } + const missing = definition.cases.filter((entry) => !evidenceById.has(entry.id)); + if (missing.length > 0 || evidenceById.size !== definition.cases.length) { + throw new Error( + `Native runtime qualification evidence is incomplete: ${missing + .map((entry) => entry.id) + .join(", ")}`, + ); + } + return Object.freeze({ + schemaVersion: 1, + qualificationId: definition.id, + providerId: definition.providerId, + source: expected, + }); +} + /** * Consume only the current credential-free candidate prerequisites. This does * not issue protected qualification evidence or activate a runtime provider. diff --git a/test/e2e/support/artifact-zip.test.ts b/test/e2e/support/artifact-zip.test.ts index 4c18ba8ec1a..ddddf6fa778 100644 --- a/test/e2e/support/artifact-zip.test.ts +++ b/test/e2e/support/artifact-zip.test.ts @@ -3,7 +3,10 @@ import { describe, expect, it } from "vitest"; -import { readValidatedArtifactZipEntry } from "../../../scripts/scorecard/read-artifact-zip.mts"; +import { + readValidatedArtifactZipEntry, + readValidatedArtifactZipEntryBytes, +} from "../../../scripts/scorecard/read-artifact-zip.mts"; import { artifactZip } from "../../helpers/artifact-zip"; const structuralMutations: Array<[string, (archive: Buffer, centralOffset: number) => void]> = [ @@ -19,7 +22,7 @@ const structuralMutations: Array<[string, (archive: Buffer, centralOffset: numbe ]; describe("validated GitHub artifact ZIP reader", () => { - it("reads only the exact root-level entry from a multi-entry archive", () => { + it("reads only the exact safe relative entry from a multi-entry archive", () => { const archive = artifactZip([ { name: "diagnostics/log.txt", contents: "ignored" }, { name: "summary.json", contents: '{"safe":true}' }, @@ -32,9 +35,33 @@ describe("validated GitHub artifact ZIP reader", () => { expect(readValidatedArtifactZipEntry(archive, "résumé.json", { maxBytes: 1_024 })).toBe( '{"utf8":true}', ); + expect( + readValidatedArtifactZipEntryBytes(archive, "diagnostics/log.txt", { maxBytes: 1_024 }), + ).toEqual(Buffer.from("ignored")); expect(readValidatedArtifactZipEntry(archive, "log.txt", { maxBytes: 1_024 })).toBeNull(); }); + it.each([ + ["empty", ""], + ["absolute", "/summary.json"], + ["parent", "../summary.json"], + ["nested parent", "diagnostics/../summary.json"], + ["backslash", "a\\b"], + ["empty segment", "diagnostics//log.txt"], + ["dot segment", "diagnostics/./log.txt"], + ["trailing slash", "diagnostics/"], + ["NUL byte", "diagnostics/\0log.txt"], + ])("rejects unsafe requested path with %s", (_case, requestedPath) => { + const archive = artifactZip([ + { name: requestedPath, contents: "unsafe" }, + { name: "summary.json", contents: '{"safe":true}' }, + ]); + + expect( + readValidatedArtifactZipEntryBytes(archive, requestedPath, { maxBytes: 1_024 }), + ).toBeNull(); + }); + it("rejects duplicate target entries and payloads over the caller's bound", () => { expect( readValidatedArtifactZipEntry( diff --git a/test/e2e/support/native-runtime-qualification-collector-workflow.test.ts b/test/e2e/support/native-runtime-qualification-collector-workflow.test.ts new file mode 100644 index 00000000000..49d74188119 --- /dev/null +++ b/test/e2e/support/native-runtime-qualification-collector-workflow.test.ts @@ -0,0 +1,147 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { existsSync } from "node:fs"; +import { dirname, extname, join, normalize } from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, expect, it } from "vitest"; +import { + readRepoText, + readYaml, + type Workflow, + type WorkflowJob, + type WorkflowStep, +} from "../../helpers/e2e-workflow-contract"; + +type CollectorWorkflow = Workflow & { + readonly on: { readonly workflow_dispatch: { readonly inputs: Record } }; + readonly permissions: Record; +}; + +const REPO_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..", ".."); +const COLLECTOR_ENTRYPOINT = "tools/e2e/native-runtime-qualification-collector.mts"; + +function resolveLocalModule(importer: string, specifier: string): string { + const unresolved = normalize(join(dirname(importer), specifier)); + const candidates = + extname(unresolved).length > 0 + ? [unresolved] + : [`${unresolved}.ts`, `${unresolved}.mts`, join(unresolved, "index.ts")]; + const resolved = candidates.find((candidate) => existsSync(join(REPO_ROOT, candidate))); + expect(resolved, `cannot resolve '${specifier}' imported by '${importer}'`).toBeDefined(); + return resolved!; +} + +function localImportClosure(entrypoint: string): string[] { + const pending = [entrypoint]; + const visited = new Set(); + for (let index = 0; index < pending.length; index += 1) { + const modulePath = pending[index]!; + const imports = visited.has(modulePath) + ? [] + : [ + ...readRepoText(modulePath).matchAll(/(?:from\s+|import\s*)["'](\.{1,2}\/[^"']+)["']/gu), + ].map((match) => resolveLocalModule(modulePath, match[1]!)); + visited.add(modulePath); + pending.push(...imports.filter((candidate) => !visited.has(candidate))); + } + return [...visited].sort(); +} + +function workflow(): CollectorWorkflow { + return readYaml( + ".github/workflows/native-runtime-qualification-collector.yaml", + ) as CollectorWorkflow; +} + +function collectorJob(): WorkflowJob { + const job = workflow().jobs["collect-protected-evidence"]; + expect(job).toBeDefined(); + return job!; +} + +function namedStep(name: string): WorkflowStep { + const step = collectorJob().steps?.find((candidate) => candidate.name === name); + expect(step, `missing native qualification collector step '${name}'`).toBeDefined(); + return step!; +} + +describe("native runtime qualification collector workflow", () => { + it("is a dispatch-only trusted-main collector with least-privilege permissions", () => { + const parsed = workflow(); + const job = collectorJob(); + + expect(Object.keys(parsed.on)).toEqual(["workflow_dispatch"]); + expect(Object.keys(parsed.on.workflow_dispatch.inputs).sort()).toEqual([ + "base_sha", + "evidence_artifact_name", + "evidence_job_name", + "evidence_run_id", + "evidence_workflow", + "head_sha", + "pr_number", + "provider_id", + ]); + expect(parsed.permissions).toEqual({ + actions: "read", + contents: "read", + "pull-requests": "read", + }); + expect(job.if).toBe( + "github.repository == 'NVIDIA/NemoClaw' && github.ref == 'refs/heads/main'", + ); + expect(job["runs-on"]).toBe("ubuntu-24.04"); + expect(job["timeout-minutes"]).toBe(10); + expect( + readRepoText(".github/workflows/native-runtime-qualification-collector.yaml"), + ).not.toMatch(/\$\{\{\s*secrets\./u); + }); + + it("checks out the exact trusted import closure and never executes candidate code", () => { + const checkout = namedStep("Check out trusted collector revision"); + const collect = namedStep("Authenticate and consume protected qualification evidence"); + const steps = collectorJob().steps ?? []; + const tokenSteps = steps.filter((step) => + JSON.stringify(step.env ?? {}).includes("${{ github.token }}"), + ); + + expect(checkout.with).toMatchObject({ + ref: "${{ github.workflow_sha }}", + path: "trusted", + "persist-credentials": false, + "sparse-checkout-cone-mode": false, + }); + expect(String(checkout.with?.["sparse-checkout"]).trim().split(/\s+/u).sort()).toEqual( + localImportClosure(COLLECTOR_ENTRYPOINT), + ); + expect( + (collect as WorkflowStep & { readonly "working-directory"?: string })["working-directory"], + ).toBe("trusted"); + expect(collect.run).toContain( + "node --experimental-strip-types --no-warnings tools/e2e/native-runtime-qualification-collector.mts", + ); + expect(tokenSteps.map((step) => step.name)).toEqual([ + "Authenticate and consume protected qualification evidence", + ]); + expect(JSON.stringify(steps)).not.toContain("github.event.pull_request.head"); + expect(JSON.stringify(steps)).not.toContain("actions/checkout/merge"); + }); + + it("binds controller inputs into the executable canonical evidence consumer", () => { + const collect = namedStep("Authenticate and consume protected qualification evidence"); + const source = readRepoText("tools/e2e/native-runtime-qualification-collector.mts"); + + expect(collect.env).toMatchObject({ + GITHUB_WORKFLOW_SHA: "${{ github.workflow_sha }}", + EXPECTED_PROVIDER_ID: "${{ inputs.provider_id }}", + EXPECTED_PR_NUMBER: "${{ inputs.pr_number }}", + EXPECTED_HEAD_SHA: "${{ inputs.head_sha }}", + EXPECTED_BASE_SHA: "${{ inputs.base_sha }}", + EVIDENCE_WORKFLOW: "${{ inputs.evidence_workflow }}", + EVIDENCE_RUN_ID: "${{ inputs.evidence_run_id }}", + EVIDENCE_JOB_NAME: "${{ inputs.evidence_job_name }}", + EVIDENCE_ARTIFACT_NAME: "${{ inputs.evidence_artifact_name }}", + }); + expect(source).not.toMatch(/node:child_process|execFile|spawn\(/u); + }); +}); diff --git a/test/e2e/support/native-runtime-qualification-collector.test.ts b/test/e2e/support/native-runtime-qualification-collector.test.ts new file mode 100644 index 00000000000..7ac90810263 --- /dev/null +++ b/test/e2e/support/native-runtime-qualification-collector.test.ts @@ -0,0 +1,352 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { createHash } from "node:crypto"; +import { describe, expect, it, vi } from "vitest"; +import { + collectNativeRuntimeQualificationEvidence, + NATIVE_RUNTIME_QUALIFICATION_COLLECTOR_WORKFLOW, + NATIVE_RUNTIME_QUALIFICATION_EVIDENCE_FILE, + type GitHubQualificationReader, + type NativeRuntimeQualificationCollectorInput, +} from "../../../tools/e2e/native-runtime-qualification-collector.mts"; +import { artifactZip } from "../../helpers/artifact-zip"; +import { + nativeQualificationEvidence, + nativeQualificationExpectedSource, + NATIVE_QUALIFICATION_BASE_SHA, + NATIVE_QUALIFICATION_HEAD_SHA, + NATIVE_QUALIFICATION_RECEIPT_CONTENT, +} from "../../helpers/native-runtime-qualification-evidence"; +import type { NativeRuntimeQualificationEvidenceEnvelope } from "../registry/native-runtime-qualification"; + +const REPOSITORY = "NVIDIA/NemoClaw"; +const ACTOR = "maintainer"; +const WORKFLOW = ".github/workflows/native-runtime-qualification.yaml"; +const JOB_NAME = "Aggregate native runtime qualification evidence"; +const ARTIFACT_NAME = "native-runtime-qualification-9143"; + +function collectorInput( + overrides: Partial = {}, +): NativeRuntimeQualificationCollectorInput { + return { + repository: REPOSITORY, + actor: ACTOR, + eventName: "workflow_dispatch", + ref: "refs/heads/main", + collectorWorkflowRef: `${REPOSITORY}/${NATIVE_RUNTIME_QUALIFICATION_COLLECTOR_WORKFLOW}@refs/heads/main`, + collectorWorkflowSha: NATIVE_QUALIFICATION_BASE_SHA, + collectorRunId: 6001, + providerId: "podman", + pullRequestNumber: 9143, + expectedHeadSha: NATIVE_QUALIFICATION_HEAD_SHA, + expectedBaseSha: NATIVE_QUALIFICATION_BASE_SHA, + evidenceWorkflow: WORKFLOW, + evidenceRunId: 7001, + evidenceJobName: JOB_NAME, + evidenceArtifactName: ARTIFACT_NAME, + ...overrides, + }; +} + +type ReceiptArchiveOptions = { + readonly omitReceipt?: string; + readonly tamperReceipt?: string; +}; + +function receiptPaths(envelope: NativeRuntimeQualificationEvidenceEnvelope): string[] { + return [ + ...new Set( + envelope.cases.flatMap((entry) => [ + entry.installer.invocation.path, + entry.installer.script.path, + entry.runtime.result.path, + ...entry.operations.map(({ artifact }) => artifact.path), + ...(entry.nvidiaCdi ? [entry.nvidiaCdi.artifact.path] : []), + ]), + ), + ]; +} + +function archiveFor( + value: NativeRuntimeQualificationEvidenceEnvelope, + options: ReceiptArchiveOptions = {}, +): Buffer { + const receipts = receiptPaths(value) + .filter((receiptPath) => receiptPath !== options.omitReceipt) + .map((receiptPath) => ({ + name: receiptPath, + contents: + receiptPath === options.tamperReceipt + ? '{"qualified":false}\n' + : NATIVE_QUALIFICATION_RECEIPT_CONTENT, + })); + return artifactZip([ + { name: NATIVE_RUNTIME_QUALIFICATION_EVIDENCE_FILE, contents: JSON.stringify(value) }, + ...receipts, + ]); +} + +function githubFixture( + value: NativeRuntimeQualificationEvidenceEnvelope = nativeQualificationEvidence(), + archiveOptions: ReceiptArchiveOptions = {}, +): { + readonly api: GitHubQualificationReader; + readonly archive: Buffer; + readonly json: Map; + readonly jsonSequences: Map; +} { + const archive = archiveFor(value, archiveOptions); + const digest = `sha256:${createHash("sha256").update(archive).digest("hex")}`; + const pull = { + number: 9143, + state: "open", + head: { sha: NATIVE_QUALIFICATION_HEAD_SHA, repo: { full_name: REPOSITORY } }, + base: { + sha: NATIVE_QUALIFICATION_BASE_SHA, + ref: "main", + repo: { full_name: REPOSITORY }, + }, + }; + const run = { + id: 7001, + workflow_id: 101, + run_attempt: 2, + event: "workflow_dispatch", + status: "completed", + conclusion: "success", + head_sha: NATIVE_QUALIFICATION_BASE_SHA, + head_branch: "main", + path: WORKFLOW, + repository: { full_name: REPOSITORY }, + }; + const artifact = { + id: 9001, + name: ARTIFACT_NAME, + size_in_bytes: archive.length, + expired: false, + digest, + archive_download_url: `https://api.github.com/repos/${REPOSITORY}/actions/artifacts/9001/zip`, + workflow_run: { id: 7001, head_sha: NATIVE_QUALIFICATION_BASE_SHA }, + }; + const json = new Map([ + [ + `repos/${REPOSITORY}/collaborators/${ACTOR}/permission`, + { user: { login: ACTOR }, permission: "maintain" }, + ], + [`repos/${REPOSITORY}/pulls/9143`, pull], + [`repos/${REPOSITORY}/commits/main`, { sha: NATIVE_QUALIFICATION_BASE_SHA }], + [ + `repos/${REPOSITORY}/actions/workflows/native-runtime-qualification.yaml`, + { id: 101, path: WORKFLOW, state: "active" }, + ], + [`repos/${REPOSITORY}/actions/runs/7001`, run], + [ + `repos/${REPOSITORY}/actions/runs/7001/attempts/2/jobs?per_page=100&page=1`, + { + total_count: 1, + jobs: [ + { + id: 8001, + name: JOB_NAME, + run_id: 7001, + run_attempt: 2, + head_sha: NATIVE_QUALIFICATION_BASE_SHA, + status: "completed", + conclusion: "success", + }, + ], + }, + ], + [ + `repos/${REPOSITORY}/actions/runs/7001/artifacts?per_page=100&page=1`, + { total_count: 1, artifacts: [artifact] }, + ], + [`repos/${REPOSITORY}/actions/artifacts/9001`, artifact], + ]); + const jsonSequences = new Map(); + const getJson = vi.fn(async (apiPath: string) => { + expect(json.has(apiPath) || jsonSequences.has(apiPath)).toBe(true); + const value = jsonSequences.get(apiPath)?.shift() ?? json.get(apiPath); + return structuredClone(value); + }); + const getBytes = vi.fn(async (apiPath: string) => { + expect(apiPath).toBe(`repos/${REPOSITORY}/actions/artifacts/9001/zip`); + return Buffer.from(archive); + }); + return { api: { getJson, getBytes }, archive, json, jsonSequences }; +} + +type QualificationFixture = ReturnType; + +function sequenceConfirmationJson( + fixture: QualificationFixture, + apiPath: string, + mutate: (confirmed: Record) => void, +): void { + const initial = structuredClone(fixture.json.get(apiPath)) as Record; + const confirmed = structuredClone(initial); + mutate(confirmed); + fixture.jsonSequences.set(apiPath, [initial, confirmed]); +} + +function changePullHeadOnConfirmation(fixture: QualificationFixture): void { + const apiPath = `repos/${REPOSITORY}/pulls/9143`; + sequenceConfirmationJson(fixture, apiPath, (confirmed) => { + (confirmed.head as { sha: string }).sha = "c".repeat(40); + }); +} + +function changeMainRevisionOnConfirmation(fixture: QualificationFixture): void { + const apiPath = `repos/${REPOSITORY}/commits/main`; + sequenceConfirmationJson(fixture, apiPath, (confirmed) => { + confirmed.sha = "c".repeat(40); + }); +} + +function changeRunAttemptOnConfirmation(fixture: QualificationFixture): void { + const apiPath = `repos/${REPOSITORY}/actions/runs/7001`; + sequenceConfirmationJson(fixture, apiPath, (confirmed) => { + confirmed.run_attempt = 3; + }); +} + +function changeArtifactDigestOnConfirmation(fixture: QualificationFixture): void { + const apiPath = `repos/${REPOSITORY}/actions/artifacts/9001`; + const confirmed = structuredClone(fixture.json.get(apiPath)) as Record; + confirmed.digest = `sha256:${"e".repeat(64)}`; + fixture.json.set(apiPath, confirmed); +} + +const CONFIRMATION_DRIFT_CASES = [ + [ + "candidate commit", + changePullHeadOnConfirmation, + "candidate commit, candidate repository, target-branch base SHA", + ], + ["main revision", changeMainRevisionOnConfirmation, "current main SHA"], + ["workflow run attempt", changeRunAttemptOnConfirmation, "protected source changed"], + [ + "artifact digest", + changeArtifactDigestOnConfirmation, + "protected artifact changed during collection", + ], +] as const; + +describe("native runtime qualification protected evidence collector", () => { + it("authenticates live GitHub identities and invokes the canonical evidence consumer", async () => { + const fixture = githubFixture(); + const authority = await collectNativeRuntimeQualificationEvidence( + fixture.api, + collectorInput(), + ); + const digest = `sha256:${createHash("sha256").update(fixture.archive).digest("hex")}`; + + expect(authority).toMatchObject({ + schemaVersion: 1, + qualificationId: "podman-protected-host-local-inference", + providerId: "podman", + source: { + repository: REPOSITORY, + workflow: WORKFLOW, + pullRequestNumber: 9143, + headSha: NATIVE_QUALIFICATION_HEAD_SHA, + baseSha: NATIVE_QUALIFICATION_BASE_SHA, + runId: 7001, + attempt: 2, + jobId: 8001, + artifact: { id: 9001, name: ARTIFACT_NAME, digest }, + }, + }); + expect(fixture.api.getBytes).toHaveBeenCalledOnce(); + expect(fixture.api.getJson).toHaveBeenCalledWith(`repos/${REPOSITORY}/pulls/9143`); + }); + + it.each(CONFIRMATION_DRIFT_CASES)( + "rejects %s drift on the confirmation read", + async (_identity, arrangeDrift, expectedError) => { + const fixture = githubFixture(); + arrangeDrift(fixture); + + await expect( + collectNativeRuntimeQualificationEvidence(fixture.api, collectorInput()), + ).rejects.toThrow(expectedError); + }, + ); + + it.each([ + ["candidate commit", { headSha: "e".repeat(40), baseSha: "f".repeat(40) }], + ["target-branch base SHA", { headSha: "f".repeat(40), baseSha: "e".repeat(40) }], + ])("rejects replayed evidence with an internally consistent wrong %s", async (_name, pair) => { + const fixture = githubFixture(nativeQualificationEvidence(pair)); + + await expect( + collectNativeRuntimeQualificationEvidence(fixture.api, collectorInput()), + ).rejects.toThrow("externally expected protected source"); + }); + + it("rejects a successful run at candidate code instead of the target-branch base SHA", async () => { + const fixture = githubFixture(); + const runPath = `repos/${REPOSITORY}/actions/runs/7001`; + fixture.json.set(runPath, { + ...(fixture.json.get(runPath) as Record), + head_sha: NATIVE_QUALIFICATION_HEAD_SHA, + }); + + await expect( + collectNativeRuntimeQualificationEvidence(fixture.api, collectorInput()), + ).rejects.toThrow("workflow run identity"); + }); + + it("rejects its own workflow as qualification evidence before GitHub access", async () => { + const fixture = githubFixture(); + + await expect( + collectNativeRuntimeQualificationEvidence( + fixture.api, + collectorInput({ + evidenceWorkflow: NATIVE_RUNTIME_QUALIFICATION_COLLECTOR_WORKFLOW, + }), + ), + ).rejects.toThrow("trusted workflow boundary"); + expect(fixture.api.getJson).not.toHaveBeenCalled(); + expect(fixture.api.getBytes).not.toHaveBeenCalled(); + }); + + it("rejects an artifact whose downloaded bytes do not match GitHub's immutable digest", async () => { + const fixture = githubFixture(); + vi.mocked(fixture.api.getBytes).mockResolvedValueOnce( + artifactZip([ + { + name: NATIVE_RUNTIME_QUALIFICATION_EVIDENCE_FILE, + contents: JSON.stringify(nativeQualificationExpectedSource()), + }, + ]), + ); + + await expect( + collectNativeRuntimeQualificationEvidence(fixture.api, collectorInput()), + ).rejects.toThrow("downloaded artifact digest"); + }); + + it("rejects a declared installer receipt that is absent from the authenticated artifact", async () => { + const evidence = nativeQualificationEvidence(); + const missing = evidence.cases[0]!.installer.invocation.path; + const fixture = githubFixture(evidence, { omitReceipt: missing }); + + await expect( + collectNativeRuntimeQualificationEvidence(fixture.api, collectorInput()), + ).rejects.toThrow(`receipt '${missing}' is missing from the authenticated artifact`); + }); + + it("rejects a declared NVIDIA CDI receipt whose bytes do not match its digest", async () => { + const evidence = nativeQualificationEvidence(); + const gpuCase = evidence.cases.find((entry) => entry.nvidiaCdi !== undefined)!; + const tampered = gpuCase.nvidiaCdi!.artifact.path; + const fixture = githubFixture(evidence, { tamperReceipt: tampered }); + + await expect( + collectNativeRuntimeQualificationEvidence(fixture.api, collectorInput()), + ).rejects.toThrow(`receipt '${tampered}' does not match its SHA-256 digest`); + }); +}); diff --git a/test/e2e/support/native-runtime-qualification.test.ts b/test/e2e/support/native-runtime-qualification.test.ts index e391400d8da..480c262adfc 100644 --- a/test/e2e/support/native-runtime-qualification.test.ts +++ b/test/e2e/support/native-runtime-qualification.test.ts @@ -3,17 +3,25 @@ import { describe, expect, it, vi } from "vitest"; import { CURRENT_RUNTIME_PROVIDER_BUNDLES } from "../../../src/lib/onboard/runtime-provider/current"; +import { + nativeQualificationEvidence as qualificationEvidence, + nativeQualificationExpectedSource as expectedProtectedSource, + nativeQualificationReceiptReader, + NATIVE_QUALIFICATION_HEAD_SHA, +} from "../../helpers/native-runtime-qualification-evidence"; import { compileNativeRuntimeQualification, consumeNativeRuntimeCandidateEvidence, + consumeNativeRuntimeQualificationEvidence, nativeRuntimeQualificationDefinition, NATIVE_RUNTIME_QUALIFICATION_AGENTS, NATIVE_RUNTIME_QUALIFICATION_OBLIGATIONS, PODMAN_PROTECTED_HOST_LOCAL_INFERENCE_QUALIFICATION, type NativeRuntimeCandidateEvidence, + type NativeRuntimeQualificationExpectedSource, } from "../registry/native-runtime-qualification"; -const SOURCE_REVISION = "a".repeat(40); +const SOURCE_REVISION = NATIVE_QUALIFICATION_HEAD_SHA; function candidateEvidence(): NativeRuntimeCandidateEvidence { return { @@ -146,7 +154,7 @@ describe("native runtime qualification contract", () => { ); }); - it("accepts only exact-source candidate prerequisites without activating Podman", () => { + it("accepts candidate prerequisites only for the expected candidate commit and target-branch base SHA without activating Podman", () => { expect(consumeNativeRuntimeCandidateEvidence(candidateEvidence(), SOURCE_REVISION)).toEqual({ schemaVersion: 1, candidateId: "podman-cpu-lifecycle", @@ -159,4 +167,66 @@ describe("native runtime qualification contract", () => { ).toThrow("does not match source"); expect(CURRENT_RUNTIME_PROVIDER_BUNDLES).not.toHaveProperty("podman"); }); + + it("consumes complete evidence only against externally resolved protected identities", () => { + const authority = consumeNativeRuntimeQualificationEvidence( + PODMAN_PROTECTED_HOST_LOCAL_INFERENCE_QUALIFICATION, + qualificationEvidence(), + expectedProtectedSource(), + nativeQualificationReceiptReader, + ); + + expect(authority).toEqual({ + schemaVersion: 1, + qualificationId: "podman-protected-host-local-inference", + providerId: "podman", + source: expectedProtectedSource(), + }); + expect(Object.isFrozen(authority.source.artifact)).toBe(true); + expect(CURRENT_RUNTIME_PROVIDER_BUNDLES).not.toHaveProperty("podman"); + }); + + it.each([ + [ + "candidate commit", + { headSha: "e".repeat(40), baseSha: "f".repeat(40) }, + "externally expected protected source", + ], + [ + "target-branch base SHA", + { headSha: "f".repeat(40), baseSha: "e".repeat(40) }, + "externally expected protected source", + ], + ])( + "rejects an internally consistent but wrong %s evidence pair", + (_label, source, error) => { + expect(() => + consumeNativeRuntimeQualificationEvidence( + PODMAN_PROTECTED_HOST_LOCAL_INFERENCE_QUALIFICATION, + qualificationEvidence(source), + expectedProtectedSource(), + nativeQualificationReceiptReader, + ), + ).toThrow(error); + }, + ); + + it("rejects missing immutable GitHub artifact identity", () => { + const source = { + ...expectedProtectedSource(), + artifact: { + ...expectedProtectedSource().artifact, + digest: "", + }, + } as NativeRuntimeQualificationExpectedSource; + + expect(() => + consumeNativeRuntimeQualificationEvidence( + PODMAN_PROTECTED_HOST_LOCAL_INFERENCE_QUALIFICATION, + qualificationEvidence(), + source, + nativeQualificationReceiptReader, + ), + ).toThrow("GitHub artifact identity is invalid"); + }); }); diff --git a/test/helpers/native-runtime-qualification-evidence.ts b/test/helpers/native-runtime-qualification-evidence.ts new file mode 100644 index 00000000000..f85ec2648d7 --- /dev/null +++ b/test/helpers/native-runtime-qualification-evidence.ts @@ -0,0 +1,126 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { createHash } from "node:crypto"; + +import { + NATIVE_RUNTIME_QUALIFICATION_PRODUCER_WORKFLOW, + NATIVE_RUNTIME_QUALIFICATION_PROTECTED_REPOSITORY, + PODMAN_PROTECTED_HOST_LOCAL_INFERENCE_QUALIFICATION, + type NativeRuntimeQualificationDefinition, + type NativeRuntimeQualificationEvidenceEnvelope, + type NativeRuntimeQualificationExpectedSource, + type NativeRuntimeQualificationProtectedRun, +} from "../e2e/registry/native-runtime-qualification"; + +export const NATIVE_QUALIFICATION_HEAD_SHA = "a".repeat(40); +export const NATIVE_QUALIFICATION_BASE_SHA = "b".repeat(40); +export const NATIVE_QUALIFICATION_ARTIFACT_SHA256 = "c".repeat(64); +export const NATIVE_QUALIFICATION_RECEIPT_CONTENT = '{"qualified":true}\n'; +export const NATIVE_QUALIFICATION_RECEIPT_SHA256 = createHash("sha256") + .update(NATIVE_QUALIFICATION_RECEIPT_CONTENT) + .digest("hex"); +const IMAGE_DIGEST = `sha256:${"d".repeat(64)}`; + +export function nativeQualificationReceiptReader(): Buffer { + return Buffer.from(NATIVE_QUALIFICATION_RECEIPT_CONTENT, "utf8"); +} + +export function nativeQualificationExpectedSource(): NativeRuntimeQualificationExpectedSource { + return { + repository: NATIVE_RUNTIME_QUALIFICATION_PROTECTED_REPOSITORY, + workflow: NATIVE_RUNTIME_QUALIFICATION_PRODUCER_WORKFLOW, + pullRequestNumber: 9143, + candidateRepository: NATIVE_RUNTIME_QUALIFICATION_PROTECTED_REPOSITORY, + headSha: NATIVE_QUALIFICATION_HEAD_SHA, + baseRef: "main", + baseSha: NATIVE_QUALIFICATION_BASE_SHA, + runId: 7001, + attempt: 2, + jobId: 8001, + artifact: { + id: 9001, + name: "native-runtime-qualification-9143", + digest: `sha256:${NATIVE_QUALIFICATION_ARTIFACT_SHA256}`, + }, + }; +} + +function protectedRun( + overrides: Partial, +): NativeRuntimeQualificationProtectedRun { + const { artifact: _artifact, ...source } = nativeQualificationExpectedSource(); + return { ...source, ...overrides }; +} + +export function nativeQualificationEvidence( + sourceOverrides: Partial = {}, +): NativeRuntimeQualificationEvidenceEnvelope { + return nativeQualificationEvidenceForDefinition( + PODMAN_PROTECTED_HOST_LOCAL_INFERENCE_QUALIFICATION, + sourceOverrides, + ); +} + +export function nativeQualificationEvidenceForDefinition( + qualification: NativeRuntimeQualificationDefinition, + sourceOverrides: Partial = {}, +): NativeRuntimeQualificationEvidenceEnvelope { + return { + schemaVersion: 1, + qualificationId: qualification.id, + providerId: qualification.providerId, + cases: qualification.cases.map((entry) => ({ + schemaVersion: 1, + caseId: entry.id, + protectedRun: protectedRun(sourceOverrides), + installer: { + providerId: qualification.providerId, + architecture: entry.architecture, + dockerAvailability: "unavailable", + exitCode: 0, + invocation: { + path: `installer/${entry.id}.json`, + sha256: NATIVE_QUALIFICATION_RECEIPT_SHA256, + }, + script: { + path: "installer/install.sh", + sha256: NATIVE_QUALIFICATION_RECEIPT_SHA256, + }, + }, + runtime: { + providerId: qualification.providerId, + agent: entry.agent, + inference: entry.inference, + architecture: entry.architecture, + acceleration: entry.acceleration, + rootMode: "rootless", + engineName: "candidate-runtime", + engineVersion: "1.0.0", + managedImages: [{ role: "agent", digest: IMAGE_DIGEST }], + result: { + path: `runtime/${entry.id}.json`, + sha256: NATIVE_QUALIFICATION_RECEIPT_SHA256, + }, + }, + operations: entry.obligations.map((id) => ({ + id, + artifact: { + path: `operations/${entry.id}-${id}.json`, + sha256: NATIVE_QUALIFICATION_RECEIPT_SHA256, + }, + })), + ...(entry.acceleration === "nvidia-gpu" + ? { + nvidiaCdi: { + device: "nvidia.com/gpu=all" as const, + artifact: { + path: `cdi/${entry.id}.json`, + sha256: NATIVE_QUALIFICATION_RECEIPT_SHA256, + }, + }, + } + : {}), + })), + }; +} diff --git a/test/runtime-provider-source-shape.test.ts b/test/runtime-provider-source-shape.test.ts index c568d585339..9a5c1286cbb 100644 --- a/test/runtime-provider-source-shape.test.ts +++ b/test/runtime-provider-source-shape.test.ts @@ -101,6 +101,7 @@ describe("runtime provider central source boundary", () => { "onboard/workload/runtime.ts": read("src/lib/onboard/workload/runtime.ts"), }; const providerContract = { + activation: read("src/lib/onboard/runtime-provider/activation.ts"), contract: read("src/lib/onboard/runtime-provider/contract.ts"), current: read("src/lib/onboard/runtime-provider/current.ts"), docker: read("src/lib/onboard/runtime-provider/docker.ts"), @@ -137,6 +138,10 @@ describe("runtime provider central source boundary", () => { "../managed-bootstrap/docker-runtime", ]); expect(providerContract.current).not.toMatch(/\b(?:podman|mxc)\b/iu); + expect(providerContract.activation).not.toMatch(/\b(?:podman|mxc)\b/iu); + expect(providerContract.activation).not.toMatch( + /(?:providerId|driverName)\s*(?:===|!==)\s*["'](?:docker|podman|mxc)["']/iu, + ); }); it("inventories every managed-bootstrap protocol source", () => { @@ -165,6 +170,7 @@ describe("runtime provider central source boundary", () => { it("inventories every runtime-provider implementation", () => { expect(providerPaths).toEqual([ "src/lib/onboard/runtime-provider/access.ts", + "src/lib/onboard/runtime-provider/activation.ts", "src/lib/onboard/runtime-provider/container-state-mutation.ts", "src/lib/onboard/runtime-provider/contract.ts", "src/lib/onboard/runtime-provider/current.ts", @@ -180,6 +186,7 @@ describe("runtime provider central source boundary", () => { "src/lib/onboard/runtime-provider/host-local-inference-routing.ts", "src/lib/onboard/runtime-provider/host-local-inference.ts", "src/lib/onboard/runtime-provider/mxc.ts", + "src/lib/onboard/runtime-provider/native-qualification-authority.ts", "src/lib/onboard/runtime-provider/persisted-engine-authority.ts", "src/lib/onboard/runtime-provider/persisted-engine-lifecycle.ts", "src/lib/onboard/runtime-provider/podman-gpu.ts", diff --git a/tools/e2e/native-runtime-qualification-collector.mts b/tools/e2e/native-runtime-qualification-collector.mts new file mode 100644 index 00000000000..01aa2bd7fa8 --- /dev/null +++ b/tools/e2e/native-runtime-qualification-collector.mts @@ -0,0 +1,587 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { createHash } from "node:crypto"; +import { appendFileSync, writeFileSync } from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { setTimeout as delay } from "node:timers/promises"; +import { + readValidatedArtifactZipEntry, + readValidatedArtifactZipEntryBytes, +} from "../../scripts/scorecard/read-artifact-zip.mts"; +import { + compileNativeRuntimeQualification, + consumeNativeRuntimeQualificationEvidence, + nativeRuntimeQualificationDefinition, + type NativeRuntimeQualificationAuthority, + type NativeRuntimeQualificationExpectedSource, + type NativeRuntimeQualificationReceiptReader, +} from "../../test/e2e/registry/native-runtime-qualification.ts"; + +export const NATIVE_RUNTIME_QUALIFICATION_EVIDENCE_FILE = + "native-runtime-qualification-evidence.json"; +export const NATIVE_RUNTIME_QUALIFICATION_COLLECTOR_WORKFLOW = + ".github/workflows/native-runtime-qualification-collector.yaml"; + +const API_ROOT = "https://api.github.com"; +const PAGE_SIZE = 100; +const MAX_ITEMS = 100; +const MAX_API_BYTES = 2 * 1024 * 1024; +const MAX_ARCHIVE_BYTES = 4 * 1024 * 1024; +const MAX_EVIDENCE_BYTES = 1024 * 1024; +const MAX_RECEIPT_BYTES = 256 * 1024; +const MAX_ARCHIVE_ENTRIES = 512; +const REQUEST_ATTEMPTS = 3; +const SHA = /^[a-f0-9]{40}$/u; +const SAFE_PROVIDER_ID = /^[a-z][a-z0-9-]{0,62}$/u; +const SAFE_NAME = /^[A-Za-z0-9][A-Za-z0-9 ._:/()\[\]-]{0,199}$/u; +const SAFE_ARTIFACT_NAME = /^[A-Za-z0-9._-]{1,128}$/u; +const SAFE_WORKFLOW = /^\.github\/workflows\/[A-Za-z0-9._-]+\.ya?ml$/u; +const ARTIFACT_DIGEST = /^sha256:[a-f0-9]{64}$/u; +const WRITE_PERMISSIONS = new Set(["admin", "maintain", "write"]); + +type JsonRecord = Record; + +export interface GitHubQualificationReader { + getJson(apiPath: string): Promise; + getBytes(apiPath: string): Promise; +} + +export interface NativeRuntimeQualificationCollectorInput { + readonly repository: string; + readonly actor: string; + readonly eventName: string; + readonly ref: string; + readonly collectorWorkflowRef: string; + readonly collectorWorkflowSha: string; + readonly collectorRunId: number; + readonly providerId: string; + readonly pullRequestNumber: number; + readonly expectedHeadSha: string; + readonly expectedBaseSha: string; + readonly evidenceWorkflow: string; + readonly evidenceRunId: number; + readonly evidenceJobName: string; + readonly evidenceArtifactName: string; +} + +type PullRequestIdentity = { + readonly candidateRepository: string; + readonly headSha: string; + readonly baseSha: string; +}; + +type WorkflowIdentity = { readonly id: number }; + +type WorkflowRun = { + readonly id: number; + readonly workflowId: number; + readonly attempt: number; + readonly headSha: string; +}; + +type WorkflowJob = { readonly id: number }; + +type WorkflowArtifact = { + readonly id: number; + readonly name: string; + readonly digest: string; + readonly archivePath: string; +}; + +function fail(message: string): never { + throw new Error(`Native runtime qualification collector rejected evidence: ${message}`); +} + +function record(value: unknown, label: string): JsonRecord { + if (typeof value !== "object" || value === null || Array.isArray(value)) { + fail(`${label} is not an object`); + } + return value as JsonRecord; +} + +function positiveInteger(value: unknown, label: string): number { + if (!Number.isSafeInteger(value) || Number(value) < 1) fail(`${label} is invalid`); + return Number(value); +} + +function exactSha(value: unknown, label: string): string { + if (typeof value !== "string" || !SHA.test(value)) fail(`${label} is invalid`); + return value; +} + +function expectedString(value: unknown, expected: string, label: string): void { + if (value !== expected) fail(`${label} does not match '${expected}'`); +} + +function validateCollectorBoundary(input: NativeRuntimeQualificationCollectorInput): void { + const expectedWorkflowRef = `${input.repository}/${NATIVE_RUNTIME_QUALIFICATION_COLLECTOR_WORKFLOW}@refs/heads/main`; + if ( + input.repository !== "NVIDIA/NemoClaw" || + input.eventName !== "workflow_dispatch" || + input.ref !== "refs/heads/main" || + input.collectorWorkflowRef !== expectedWorkflowRef || + input.collectorWorkflowSha !== input.expectedBaseSha || + input.evidenceWorkflow === NATIVE_RUNTIME_QUALIFICATION_COLLECTOR_WORKFLOW || + input.collectorRunId === input.evidenceRunId || + !SAFE_PROVIDER_ID.test(input.providerId) || + !SHA.test(input.expectedHeadSha) || + !SHA.test(input.expectedBaseSha) || + input.expectedHeadSha === input.expectedBaseSha || + !SAFE_WORKFLOW.test(input.evidenceWorkflow) || + !SAFE_NAME.test(input.evidenceJobName) || + !SAFE_ARTIFACT_NAME.test(input.evidenceArtifactName) + ) { + fail("the trusted workflow boundary or controller inputs are invalid"); + } + positiveInteger(input.collectorRunId, "collector run id"); + positiveInteger(input.pullRequestNumber, "pull request number"); + positiveInteger(input.evidenceRunId, "evidence run id"); +} + +async function assertActorPermission( + api: GitHubQualificationReader, + input: NativeRuntimeQualificationCollectorInput, +): Promise { + const permission = record( + await api.getJson( + `repos/${input.repository}/collaborators/${encodeURIComponent(input.actor)}/permission`, + ), + "actor permission", + ); + const user = record(permission.user, "actor permission user"); + if (user.login !== input.actor || !WRITE_PERMISSIONS.has(String(permission.permission))) { + fail(`actor '${input.actor}' lacks write, maintain, or admin permission`); + } +} + +function validatePullRequest( + value: unknown, + input: NativeRuntimeQualificationCollectorInput, +): PullRequestIdentity { + const pull = record(value, "pull request"); + const head = record(pull.head, "candidate commit"); + const base = record(pull.base, "target-branch base"); + const headRepository = record(head.repo, "candidate repository"); + const baseRepository = record(base.repo, "target repository"); + if ( + pull.number !== input.pullRequestNumber || + pull.state !== "open" || + head.sha !== input.expectedHeadSha || + base.sha !== input.expectedBaseSha || + base.ref !== "main" || + baseRepository.full_name !== input.repository || + typeof headRepository.full_name !== "string" + ) { + fail( + "candidate commit, candidate repository, target-branch base SHA, or pull request state does not match controller inputs", + ); + } + return { + candidateRepository: headRepository.full_name, + headSha: exactSha(head.sha, "candidate commit SHA"), + baseSha: exactSha(base.sha, "target-branch base SHA"), + }; +} + +async function loadPullRequest( + api: GitHubQualificationReader, + input: NativeRuntimeQualificationCollectorInput, +): Promise { + return validatePullRequest( + await api.getJson(`repos/${input.repository}/pulls/${input.pullRequestNumber}`), + input, + ); +} + +async function assertMainRevision( + api: GitHubQualificationReader, + input: NativeRuntimeQualificationCollectorInput, +): Promise { + const commit = record(await api.getJson(`repos/${input.repository}/commits/main`), "main commit"); + expectedString(commit.sha, input.expectedBaseSha, "current main SHA"); +} + +function workflowFile(workflowPath: string): string { + return workflowPath.slice(workflowPath.lastIndexOf("/") + 1); +} + +async function loadWorkflow( + api: GitHubQualificationReader, + input: NativeRuntimeQualificationCollectorInput, +): Promise { + const workflow = record( + await api.getJson( + `repos/${input.repository}/actions/workflows/${encodeURIComponent( + workflowFile(input.evidenceWorkflow), + )}`, + ), + "protected workflow", + ); + if (workflow.path !== input.evidenceWorkflow || workflow.state !== "active") { + fail("protected workflow path is mismatched or inactive"); + } + return { id: positiveInteger(workflow.id, "protected workflow id") }; +} + +function validateRun( + value: unknown, + input: NativeRuntimeQualificationCollectorInput, + workflow: WorkflowIdentity, +): WorkflowRun { + const run = record(value, "protected workflow run"); + const repository = record(run.repository, "protected workflow run repository"); + if ( + run.id !== input.evidenceRunId || + run.workflow_id !== workflow.id || + run.event !== "workflow_dispatch" || + run.status !== "completed" || + run.conclusion !== "success" || + run.head_sha !== input.expectedBaseSha || + run.head_branch !== "main" || + run.path !== input.evidenceWorkflow || + repository.full_name !== input.repository + ) { + fail("protected workflow run identity or successful conclusion is invalid"); + } + return { + id: positiveInteger(run.id, "protected run id"), + workflowId: positiveInteger(run.workflow_id, "protected workflow id"), + attempt: positiveInteger(run.run_attempt, "protected run attempt"), + headSha: exactSha(run.head_sha, "protected workflow SHA"), + }; +} + +async function loadRun( + api: GitHubQualificationReader, + input: NativeRuntimeQualificationCollectorInput, + workflow: WorkflowIdentity, +): Promise { + return validateRun( + await api.getJson(`repos/${input.repository}/actions/runs/${input.evidenceRunId}`), + input, + workflow, + ); +} + +async function loadCountedPage( + api: GitHubQualificationReader, + apiPath: string, + collection: string, + label: string, +): Promise { + const page = record(await api.getJson(`${apiPath}?per_page=${PAGE_SIZE}&page=1`), label); + const total = positiveInteger(page.total_count, `${label} total_count`); + const items = page[collection]; + if (!Array.isArray(items) || total !== items.length || total > MAX_ITEMS) { + fail(`${label} is incomplete, inconsistent, or exceeds ${MAX_ITEMS} items`); + } + return items; +} + +async function loadExpectedJob( + api: GitHubQualificationReader, + input: NativeRuntimeQualificationCollectorInput, + run: WorkflowRun, +): Promise { + const jobs = await loadCountedPage( + api, + `repos/${input.repository}/actions/runs/${run.id}/attempts/${run.attempt}/jobs`, + "jobs", + "protected run jobs", + ); + const matches = jobs + .map((value) => record(value, "protected run job")) + .filter((job) => job.name === input.evidenceJobName); + if (matches.length !== 1) fail("expected protected job identity is missing or duplicated"); + const job = matches[0]!; + if ( + job.run_id !== run.id || + job.run_attempt !== run.attempt || + job.head_sha !== run.headSha || + job.status !== "completed" || + job.conclusion !== "success" + ) { + fail("expected protected job did not complete successfully in the bound run attempt"); + } + return { id: positiveInteger(job.id, "protected job id") }; +} + +function validateArtifact( + value: unknown, + input: NativeRuntimeQualificationCollectorInput, + run: WorkflowRun, +): WorkflowArtifact { + const artifact = record(value, "protected evidence artifact"); + const artifactRun = record(artifact.workflow_run, "protected evidence artifact run"); + const id = positiveInteger(artifact.id, "protected evidence artifact id"); + const expectedArchivePath = `repos/${input.repository}/actions/artifacts/${id}/zip`; + const archiveUrl = + typeof artifact.archive_download_url === "string" + ? new URL(artifact.archive_download_url) + : null; + if ( + artifact.name !== input.evidenceArtifactName || + artifact.expired !== false || + typeof artifact.digest !== "string" || + !ARTIFACT_DIGEST.test(artifact.digest) || + !Number.isSafeInteger(artifact.size_in_bytes) || + Number(artifact.size_in_bytes) < 1 || + Number(artifact.size_in_bytes) > MAX_ARCHIVE_BYTES || + artifactRun.id !== run.id || + artifactRun.head_sha !== run.headSha || + archiveUrl?.origin !== API_ROOT || + archiveUrl.pathname !== `/${expectedArchivePath}` + ) { + fail("protected evidence artifact identity is invalid"); + } + return { + id, + name: input.evidenceArtifactName, + digest: artifact.digest, + archivePath: expectedArchivePath, + }; +} + +async function loadExpectedArtifact( + api: GitHubQualificationReader, + input: NativeRuntimeQualificationCollectorInput, + run: WorkflowRun, +): Promise { + const artifacts = await loadCountedPage( + api, + `repos/${input.repository}/actions/runs/${run.id}/artifacts`, + "artifacts", + "protected run artifacts", + ); + const matches = artifacts + .map((value) => record(value, "protected evidence artifact")) + .filter((artifact) => artifact.name === input.evidenceArtifactName); + if (matches.length !== 1) fail("expected protected artifact identity is missing or duplicated"); + return validateArtifact(matches[0], input, run); +} + +async function loadEvidenceEnvelope( + api: GitHubQualificationReader, + artifact: WorkflowArtifact, +): Promise<{ + readonly envelope: unknown; + readonly readReceipt: NativeRuntimeQualificationReceiptReader; +}> { + const archive = await api.getBytes(artifact.archivePath); + if (archive.length > MAX_ARCHIVE_BYTES) fail("protected evidence artifact is oversized"); + const actualDigest = `sha256:${createHash("sha256").update(archive).digest("hex")}`; + if (actualDigest !== artifact.digest) fail("downloaded artifact digest does not match GitHub"); + const source = readValidatedArtifactZipEntry( + archive, + NATIVE_RUNTIME_QUALIFICATION_EVIDENCE_FILE, + { maxBytes: MAX_EVIDENCE_BYTES, maxEntries: MAX_ARCHIVE_ENTRIES }, + ); + if (source === null) fail("artifact does not contain one bounded evidence JSON file"); + let envelope: unknown; + try { + envelope = JSON.parse(source) as unknown; + } catch { + fail("protected evidence artifact is not valid JSON"); + } + const cache = new Map(); + const readReceipt: NativeRuntimeQualificationReceiptReader = (receiptPath) => { + if (!cache.has(receiptPath)) { + cache.set( + receiptPath, + readValidatedArtifactZipEntryBytes(archive, receiptPath, { + maxBytes: MAX_RECEIPT_BYTES, + maxEntries: MAX_ARCHIVE_ENTRIES, + }), + ); + } + return cache.get(receiptPath) ?? null; + }; + return { envelope, readReceipt }; +} + +export async function collectNativeRuntimeQualificationEvidence( + api: GitHubQualificationReader, + input: NativeRuntimeQualificationCollectorInput, +): Promise { + validateCollectorBoundary(input); + await assertActorPermission(api, input); + const pull = await loadPullRequest(api, input); + await assertMainRevision(api, input); + const workflow = await loadWorkflow(api, input); + const run = await loadRun(api, input, workflow); + const job = await loadExpectedJob(api, input, run); + const artifact = await loadExpectedArtifact(api, input, run); + const evidence = await loadEvidenceEnvelope(api, artifact); + const expected: NativeRuntimeQualificationExpectedSource = { + repository: input.repository, + workflow: input.evidenceWorkflow, + pullRequestNumber: input.pullRequestNumber, + candidateRepository: pull.candidateRepository, + headSha: pull.headSha, + baseRef: "main", + baseSha: pull.baseSha, + runId: run.id, + attempt: run.attempt, + jobId: job.id, + artifact: { id: artifact.id, name: artifact.name, digest: artifact.digest }, + }; + const qualification = compileNativeRuntimeQualification( + nativeRuntimeQualificationDefinition(input.providerId), + ); + const authority = consumeNativeRuntimeQualificationEvidence( + qualification, + evidence.envelope, + expected, + evidence.readReceipt, + ); + + const [confirmedPull, confirmedRun, confirmedArtifact] = await Promise.all([ + loadPullRequest(api, input), + loadRun(api, input, workflow), + api.getJson(`repos/${input.repository}/actions/artifacts/${artifact.id}`), + ]); + await assertMainRevision(api, input); + if ( + confirmedPull.candidateRepository !== pull.candidateRepository || + confirmedPull.headSha !== pull.headSha || + confirmedPull.baseSha !== pull.baseSha || + confirmedRun.attempt !== run.attempt + ) { + fail("protected source changed while evidence was being collected"); + } + const confirmed = validateArtifact(confirmedArtifact, input, run); + if (confirmed.digest !== artifact.digest) fail("protected artifact changed during collection"); + return authority; +} + +async function readBoundedResponse(response: Response, maxBytes: number): Promise { + const length = Number(response.headers.get("content-length") ?? "0"); + if (Number.isFinite(length) && length > maxBytes) fail("GitHub response is oversized"); + if (response.body === null) fail("GitHub response body is missing"); + const reader = response.body.getReader(); + const chunks: Uint8Array[] = []; + let total = 0; + while (true) { + const chunk = await reader.read(); + if (chunk.done) break; + total += chunk.value.length; + if (total > maxBytes) { + await reader.cancel(); + fail("GitHub response exceeded its byte bound"); + } + chunks.push(chunk.value); + } + return Buffer.concat(chunks, total); +} + +export function createGitHubQualificationReader( + token: string, + fetchImpl: typeof fetch = fetch, +): GitHubQualificationReader { + if (token.trim() === "") fail("GH_TOKEN is missing"); + const request = async (apiPath: string, maxBytes: number): Promise => { + const url = `${API_ROOT}/${apiPath}`; + for (let attempt = 1; attempt <= REQUEST_ATTEMPTS; attempt += 1) { + const response = await fetchImpl(url, { + headers: { + Accept: "application/vnd.github+json", + Authorization: `Bearer ${token}`, + "User-Agent": "NemoClaw-native-runtime-qualification-collector", + "X-GitHub-Api-Version": "2022-11-28", + }, + signal: AbortSignal.timeout(20_000), + }); + if (response.ok) return readBoundedResponse(response, maxBytes); + const retryable = response.status === 429 || response.status >= 500; + if (!retryable || attempt === REQUEST_ATTEMPTS) { + fail(`GitHub API ${apiPath} returned HTTP ${response.status}`); + } + await delay(250 * 2 ** (attempt - 1)); + } + fail(`GitHub API ${apiPath} exhausted retries`); + }; + return { + async getJson(apiPath) { + const source = await request(apiPath, MAX_API_BYTES); + try { + return JSON.parse(source.toString("utf8")) as unknown; + } catch { + fail(`GitHub API ${apiPath} did not return valid JSON`); + } + }, + getBytes: (apiPath) => request(apiPath, MAX_ARCHIVE_BYTES), + }; +} + +function requiredEnvironment(name: string): string { + const value = process.env[name]; + if (value === undefined || value.trim() === "") fail(`environment '${name}' is missing`); + return value; +} + +function environmentInput(): NativeRuntimeQualificationCollectorInput { + return { + repository: requiredEnvironment("GITHUB_REPOSITORY"), + actor: requiredEnvironment("GITHUB_ACTOR"), + eventName: requiredEnvironment("GITHUB_EVENT_NAME"), + ref: requiredEnvironment("GITHUB_REF"), + collectorWorkflowRef: requiredEnvironment("GITHUB_WORKFLOW_REF"), + collectorWorkflowSha: requiredEnvironment("GITHUB_WORKFLOW_SHA"), + collectorRunId: Number(requiredEnvironment("GITHUB_RUN_ID")), + providerId: requiredEnvironment("EXPECTED_PROVIDER_ID"), + pullRequestNumber: Number(requiredEnvironment("EXPECTED_PR_NUMBER")), + expectedHeadSha: requiredEnvironment("EXPECTED_HEAD_SHA"), + expectedBaseSha: requiredEnvironment("EXPECTED_BASE_SHA"), + evidenceWorkflow: requiredEnvironment("EVIDENCE_WORKFLOW"), + evidenceRunId: Number(requiredEnvironment("EVIDENCE_RUN_ID")), + evidenceJobName: requiredEnvironment("EVIDENCE_JOB_NAME"), + evidenceArtifactName: requiredEnvironment("EVIDENCE_ARTIFACT_NAME"), + }; +} + +function writeAuthority(authority: NativeRuntimeQualificationAuthority): void { + const outputPath = requiredEnvironment("QUALIFICATION_AUTHORITY_PATH"); + if (!path.isAbsolute(outputPath) || /[\r\n]/u.test(outputPath)) { + fail("qualification authority output path is invalid"); + } + writeFileSync(outputPath, `${JSON.stringify(authority, null, 2)}\n`, { + encoding: "utf8", + mode: 0o600, + }); + const githubOutput = requiredEnvironment("GITHUB_OUTPUT"); + appendFileSync( + githubOutput, + [ + `qualification_id=${authority.qualificationId}`, + `provider_id=${authority.providerId}`, + `source_run_id=${authority.source.runId}`, + `source_run_attempt=${authority.source.attempt}`, + `source_job_id=${authority.source.jobId}`, + `source_artifact_id=${authority.source.artifact.id}`, + `source_artifact_digest=${authority.source.artifact.digest}`, + "", + ].join("\n"), + "utf8", + ); +} + +async function main(): Promise { + const input = environmentInput(); + const authority = await collectNativeRuntimeQualificationEvidence( + createGitHubQualificationReader(requiredEnvironment("GH_TOKEN")), + input, + ); + writeAuthority(authority); + console.log( + `Authenticated ${authority.qualificationId} from protected run ${authority.source.runId} attempt ${authority.source.attempt}, job ${authority.source.jobId}, artifact ${authority.source.artifact.id}.`, + ); +} + +if (fileURLToPath(import.meta.url) === path.resolve(process.argv[1] ?? "")) { + main().catch((error) => { + console.error(error instanceof Error ? error.message : String(error)); + process.exitCode = 1; + }); +}