Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/lib/onboard/capability-provisioning/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!--
SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0
-->

# Capability provisioning contract

This internal, dormant contract replaces Dockerfile-shaped customization intent with a portable capability manifest. A trusted catalog resolves requested tools, runtimes, and skills into an exact, secret-free bill of materials (BOM) for one agent and platform.

The first slice intentionally has no onboarding caller and no runtime-provider implementation. It accepts only digest-pinned OCI artifacts, fixed managed install prefixes, relative `PATH` entries, named policy presets, and catalog-owned dependencies. It does not accept package-manager commands, shell scripts, mutable image tags, credentials, arbitrary destinations, or provider identities.

A later runtime-provider facet will consume the resolved BOM through explicit support declarations tracked in [#7744](https://github.com/NVIDIA/NemoClaw/issues/7744). Docker and Podman must install the same BOM for every supported agent and platform in that issue's qualification matrix. A socket-free MXC-style fixture must accept the same BOM shape, declare unsupported installation with an actionable reason, and require no central provider switch. The feature remains dormant until these contracts and the protected E2E matrix pass.
121 changes: 121 additions & 0 deletions src/lib/onboard/capability-provisioning/contract.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { describe, expect, it } from "vitest";
import {
CAPABILITY_CATALOG_SCHEMA_VERSION,
CAPABILITY_MANIFEST_SCHEMA_VERSION,
CapabilityProvisioningContractError,
parseCapabilityCatalogV1,
parseCapabilityManifestV1,
} from "./contract";

const RUNTIME_DIGEST = "a".repeat(64);
const TOOL_DIGEST = "b".repeat(64);

function manifest(capabilities: Array<{ id: string; version: string | null }> = []) {
return {
schemaVersion: CAPABILITY_MANIFEST_SCHEMA_VERSION,
agent: "hermes",
capabilities,
};
}

function catalog() {
return {
schemaVersion: CAPABILITY_CATALOG_SCHEMA_VERSION,
capabilities: [
{
id: "rust-runtime",
displayName: "Rust runtime",
kind: "runtime",
version: "1.90.0",
agents: ["openclaw", "hermes", "langchain-deepagents-code"],
requires: [],
policyPresets: [],
artifacts: [
{
platform: "linux/amd64",
reference: `oci://ghcr.io/nvidia/nemoclaw-capabilities/rust-runtime@sha256:${RUNTIME_DIGEST}`,
installPrefix: "/opt/nemoclaw/capabilities/rust-runtime",
pathEntries: ["bin"],
},
{
platform: "linux/arm64",
reference: `oci://ghcr.io/nvidia/nemoclaw-capabilities/rust-runtime@sha256:${TOOL_DIGEST}`,
installPrefix: "/opt/nemoclaw/capabilities/rust-runtime",
pathEntries: ["bin"],
},
],
},
{
id: "switchyard",
displayName: "Switchyard",
kind: "tool",
version: "2.4.1",
agents: ["hermes"],
requires: ["rust-runtime"],
policyPresets: ["switchyard"],
artifacts: [
{
platform: "linux/amd64",
reference: `oci://ghcr.io/nvidia/nemoclaw-capabilities/switchyard@sha256:${TOOL_DIGEST}`,
installPrefix: "/opt/nemoclaw/capabilities/switchyard",
pathEntries: ["bin"],
},
],
},
],
};
}

describe("capability provisioning contracts", () => {
it("rejects duplicate capability requests", () => {
expect(() =>
parseCapabilityManifestV1(
manifest([
{ id: "switchyard", version: null },
{ id: "switchyard", version: null },
]),
),
).toThrow(/must not contain duplicates/u);
});

it("rejects mutable artifacts, arbitrary destinations, and executable instructions", () => {
const mutable = catalog();
mutable.capabilities[0]!.artifacts[0]!.reference =
"oci://ghcr.io/nvidia/nemoclaw-capabilities/rust-runtime:latest";
expect(() => parseCapabilityCatalogV1(mutable)).toThrow(/reference has an unsupported format/u);

const destination = catalog();
destination.capabilities[0]!.artifacts[0]!.installPrefix = "/usr/local";
expect(() => parseCapabilityCatalogV1(destination)).toThrow(
/installPrefix has an unsupported format/u,
);

const executable = catalog() as unknown as { capabilities: Array<Record<string, unknown>> };
executable.capabilities[0]!.command = "apt-get install rustc";
expect(() => parseCapabilityCatalogV1(executable)).toThrow(/must contain exactly/u);
});

it("rejects catalog dependency gaps and cycles", () => {
const missing = catalog();
missing.capabilities[1]!.requires = ["missing-runtime"];
expect(() => parseCapabilityCatalogV1(missing)).toThrow(/requires unknown capability/u);

const cyclic = catalog();
cyclic.capabilities[0]!.requires = ["switchyard"];
expect(() => parseCapabilityCatalogV1(cyclic)).toThrow(/dependency cycle/u);
});

it("rejects inherited objects and malformed relative path entries", () => {
const inherited = Object.create({ schemaVersion: CAPABILITY_MANIFEST_SCHEMA_VERSION });
inherited.agent = "hermes";
inherited.capabilities = [];
expect(() => parseCapabilityManifestV1(inherited)).toThrow(CapabilityProvisioningContractError);

const traversal = catalog();
traversal.capabilities[0]!.artifacts[0]!.pathEntries = ["../bin"];
expect(() => parseCapabilityCatalogV1(traversal)).toThrow(/pathEntries\[0\]/u);
});
});
Loading
Loading