import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync, chmodSync } from "node:fs";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import test from "node:test";
import { gzipSync } from "node:zlib";
import {
PI_RUNTIME_IDENTITY,
PROVIDER_CONTRACT_BUNDLE_INVALID,
PROVIDER_CONTRACT_ROLE_IDS,
PROVIDER_TRANSPORT_CAPABILITY,
generateProviderContractBaselines,
piRuntimeRegistration,
verifyProviderContractBundleArchive,
verifyProviderContractBundleEntries,
verifyProviderContractBundleTree,
} from "../lib/provider-contract-bundle.ts";
// Fixture provenance: generated from gentle-ai main (contract 1.1.0) with
// `go run ./internal/providercontractbundlecmd generate --out
`.
const FIXTURE_ROOT = join(import.meta.dirname, "fixtures", "provider-contract-bundle", "v1.1.0");
const FIXTURE_PATHS = [
"README.md",
"manifest.json",
"schemas/lens.schema.json",
"schemas/refuter.schema.json",
"schemas/targeted-validator.schema.json",
"vectors/lens.json",
"vectors/refuter.json",
"vectors/targeted-validator.json",
] as const;
function fixtureEntries(): Map {
return new Map(FIXTURE_PATHS.map((path) => [path, readFileSync(join(FIXTURE_ROOT, path))]));
}
function sha256Hex(bytes: Buffer): string {
return createHash("sha256").update(bytes).digest("hex");
}
function withManifest(entries: Map, mutate: (manifest: Record) => void): Map {
const manifest = JSON.parse((entries.get("manifest.json") as Buffer).toString("utf8")) as Record;
mutate(manifest);
entries.set("manifest.json", Buffer.from(`${JSON.stringify(manifest, null, 2)}\n`, "utf8"));
return entries;
}
// A valid 1.0.0-shaped bundle: the manifest predates the runtime registry.
function contractOneZeroEntries(): Map {
return withManifest(fixtureEntries(), (manifest) => {
manifest.contract_semver = "1.0.0";
delete manifest.runtimes;
});
}
function assertRejects(entries: Map, messagePart: string): void {
assert.throws(
() => verifyProviderContractBundleEntries(entries),
(error: unknown) =>
error instanceof Error &&
error.message.startsWith(`${PROVIDER_CONTRACT_BUNDLE_INVALID}:`) &&
error.message.includes(messagePart),
`expected rejection mentioning ${JSON.stringify(messagePart)}`,
);
}
// --- minimal deterministic tar.gz writer for archive-leg tests ---------------
interface TarEntry {
readonly name: string;
readonly bytes: Buffer;
readonly mode?: number;
readonly typeflag?: string;
}
function tarHeader(entry: TarEntry): Buffer {
const header = Buffer.alloc(512);
header.write(entry.name, 0, "ascii");
header.write(`${(entry.mode ?? 0o644).toString(8).padStart(7, "0")}\0`, 100, "ascii");
header.write("0000000\0", 108, "ascii");
header.write("0000000\0", 116, "ascii");
header.write(`${entry.bytes.length.toString(8).padStart(11, "0")}\0`, 124, "ascii");
header.write("00000000000\0", 136, "ascii");
header.write(" ", 148, "ascii");
header.write(entry.typeflag ?? "0", 156, "ascii");
header.write("ustar\0", 257, "ascii");
header.write("00", 263, "ascii");
let checksum = 0;
for (const byte of header) checksum += byte;
header.write(`${checksum.toString(8).padStart(6, "0")}\0 `, 148, "ascii");
return header;
}
function buildTarGz(entries: readonly TarEntry[], trailingBytes = Buffer.alloc(0)): Buffer {
const blocks: Buffer[] = [];
for (const entry of entries) {
blocks.push(tarHeader(entry));
const padding = (512 - (entry.bytes.length % 512)) % 512;
blocks.push(entry.bytes, Buffer.alloc(padding));
}
blocks.push(Buffer.alloc(1024), trailingBytes);
return gzipSync(Buffer.concat(blocks));
}
function fixtureArchive(): Buffer {
return buildTarGz([...fixtureEntries()].map(([name, bytes]) => ({ name, bytes })));
}
function withTemporaryDirectory(run: (directory: string) => T): T {
const directory = mkdtempSync(join(tmpdir(), "gentle-pi-provider-bundle-"));
try {
return run(directory);
} finally {
rmSync(directory, { recursive: true, force: true });
}
}
function materialize(entries: Map, directory: string): void {
for (const [name, bytes] of entries) {
const destination = join(directory, ...name.split("/"));
mkdirSync(dirname(destination), { recursive: true });
writeFileSync(destination, bytes);
chmodSync(destination, 0o644);
}
}
// --- positive legs ------------------------------------------------------------
test("verifies the generated 1.1.0 fixture tree and reports the exact contract surface", () => {
const bundle = verifyProviderContractBundleTree(FIXTURE_ROOT);
assert.equal(bundle.contractSemver, "1.1.0");
assert.deepEqual([bundle.major, bundle.minor, bundle.patch], [1, 1, 0]);
assert.equal(bundle.transportCapability, PROVIDER_TRANSPORT_CAPABILITY);
assert.deepEqual(bundle.runtimes, ["claude-code", "codex", "opencode", PI_RUNTIME_IDENTITY]);
assert.deepEqual(bundle.roles.map((role) => role.id), [...PROVIDER_CONTRACT_ROLE_IDS]);
assert.equal(bundle.entries.size, 8);
for (const role of bundle.roles) {
assert.equal(bundle.entrySha256.get(role.schemaPath), role.schemaSha256);
assert.equal(bundle.entrySha256.get(role.vectorPath), role.vectorSha256);
assert.deepEqual(role.requiredCapabilities, [PROVIDER_TRANSPORT_CAPABILITY]);
}
assert.match(bundle.treeSha256, /^[0-9a-f]{64}$/);
assert.deepEqual(piRuntimeRegistration(bundle), { registered: true });
});
test("verifies the same bundle as an in-memory-read archive and reports the archive digest", () => {
withTemporaryDirectory((directory) => {
const archiveBytes = fixtureArchive();
const archivePath = join(directory, "gentle-ai-review-provider-contract-1.1.0.tar.gz");
writeFileSync(archivePath, archiveBytes);
const { bundle, archiveSha256 } = verifyProviderContractBundleArchive(archivePath);
assert.equal(archiveSha256, sha256Hex(archiveBytes));
const tree = verifyProviderContractBundleTree(FIXTURE_ROOT);
assert.equal(bundle.treeSha256, tree.treeSha256);
assert.deepEqual([...bundle.entrySha256.entries()], [...tree.entrySha256.entries()]);
});
});
test("a 1.0.0 manifest without runtimes is a VALID bundle, but pi is reported unregistered for relay trust", () => {
const bundle = verifyProviderContractBundleEntries(contractOneZeroEntries());
assert.equal(bundle.contractSemver, "1.0.0");
assert.equal(bundle.runtimes, undefined);
const registration = piRuntimeRegistration(bundle);
assert.equal(registration.registered, false);
assert.match(registration.reason ?? "", /predates the runtime identity registry/);
assert.match(registration.reason ?? "", /must not trust/);
});
test("generated baselines project roles, capabilities, and runtime registration deterministically", () => {
const bundle = verifyProviderContractBundleTree(FIXTURE_ROOT);
const baselines = generateProviderContractBaselines(bundle);
assert.deepEqual([...baselines.keys()].sort(), ["provider-capabilities.baseline.json", "provider-roles.baseline.json"]);
const roles = JSON.parse(baselines.get("provider-roles.baseline.json") as string) as { roles: { id: string; result_schema_id: string; schema_sha256: string }[] };
assert.deepEqual(roles.roles.map((role) => role.id), [...PROVIDER_CONTRACT_ROLE_IDS]);
const capabilities = JSON.parse(baselines.get("provider-capabilities.baseline.json") as string) as Record;
assert.equal(capabilities.transport_capability, PROVIDER_TRANSPORT_CAPABILITY);
assert.deepEqual(capabilities.mandatory_capabilities, [PROVIDER_TRANSPORT_CAPABILITY]);
assert.equal(capabilities.pi_registered, true);
// Byte determinism: regenerating from the same bundle is identical.
const again = generateProviderContractBaselines(verifyProviderContractBundleTree(FIXTURE_ROOT));
assert.deepEqual([...again.entries()], [...baselines.entries()]);
});
// --- inventory and hash rejections ---------------------------------------------
test("rejects a ninth file in the inventory", () => {
const entries = fixtureEntries();
entries.set("extra.txt", Buffer.from("surprise\n"));
assertRejects(entries, "9 files");
});
test("rejects a missing bundle entry", () => {
const entries = fixtureEntries();
entries.delete("vectors/lens.json");
assertRejects(entries, "7 files");
});
test("rejects a bundle whose schema bytes do not match the manifest SHA-256", () => {
const entries = fixtureEntries();
const tampered = Buffer.from(entries.get("schemas/lens.schema.json") as Buffer);
tampered[tampered.length - 2] = 0x20;
entries.set("schemas/lens.schema.json", tampered);
assertRejects(entries, "does not match its manifest SHA-256");
});
test("rejects a vector that no longer equals the schema's canonical example even when its hash is updated", () => {
const entries = fixtureEntries();
const vector = JSON.parse((entries.get("vectors/refuter.json") as Buffer).toString("utf8")) as Record;
vector.subject_hash = "0".repeat(64);
const tampered = Buffer.from(`${JSON.stringify(vector)}\n`, "utf8");
entries.set("vectors/refuter.json", tampered);
withManifest(entries, (manifest) => {
const roles = manifest.roles as { id: string; vector: { sha256: string } }[];
(roles.find((role) => role.id === "refuter") as { vector: { sha256: string } }).vector.sha256 = sha256Hex(tampered);
});
assertRejects(entries, "does not equal the schema's canonical example");
});
// --- strict manifest decode ------------------------------------------------------
test("rejects unknown manifest fields", () => {
assertRejects(
withManifest(fixtureEntries(), (manifest) => {
manifest.surprise = true;
}),
'unknown field "surprise"',
);
});
test("rejects duplicate manifest JSON keys", () => {
const entries = fixtureEntries();
const text = (entries.get("manifest.json") as Buffer).toString("utf8");
const duplicated = text.replace('"contract_semver"', '"schema": "gentle-ai.review-provider-contract-bundle/v1",\n "contract_semver"');
entries.set("manifest.json", Buffer.from(duplicated, "utf8"));
assertRejects(entries, "duplicate key");
});
test("rejects an unpinned transport capability", () => {
assertRejects(
withManifest(fixtureEntries(), (manifest) => {
manifest.transport_capability = "gentle-ai.provider-transport/v2";
}),
"transport_capability must be pinned",
);
});
test("rejects an unsupported contract major", () => {
assertRejects(
withManifest(fixtureEntries(), (manifest) => {
manifest.contract_semver = "2.0.0";
}),
"unsupported provider contract major 2",
);
});
// --- runtimes registry rules -----------------------------------------------------
test("rejects a 1.1.0 manifest without the required runtimes registry", () => {
assertRejects(
withManifest(fixtureEntries(), (manifest) => {
delete manifest.runtimes;
}),
"manifest.runtimes is required from contract 1.1.0",
);
});
test("rejects a 1.1.0 manifest whose runtimes registry does not register pi", () => {
assertRejects(
withManifest(fixtureEntries(), (manifest) => {
manifest.runtimes = ["claude-code", "codex", "opencode"];
}),
'does not register the "pi" runtime identity',
);
});
test("rejects a 1.0.0 manifest that carries a runtimes registry it cannot have", () => {
assertRejects(
withManifest(fixtureEntries(), (manifest) => {
manifest.contract_semver = "1.0.0";
}),
"manifest.runtimes is not part of contract 1.0.0",
);
});
// --- roles and capabilities --------------------------------------------------------
test("rejects an unknown role id", () => {
assertRejects(
withManifest(fixtureEntries(), (manifest) => {
(manifest.roles as { id: string }[])[1].id = "judge";
}),
'manifest role 1 is "judge"',
);
});
test("rejects an unknown mandatory capability", () => {
assertRejects(
withManifest(fixtureEntries(), (manifest) => {
(manifest.roles as { required_capabilities: string[] }[])[0].required_capabilities = [
PROVIDER_TRANSPORT_CAPABILITY,
"gentle-ai.telepathy/v1",
];
}),
'unknown mandatory capability "gentle-ai.telepathy/v1"',
);
});
test("rejects a schema whose $id does not match the manifest result schema id", () => {
assertRejects(
withManifest(fixtureEntries(), (manifest) => {
(manifest.roles as { result_schema_id: string }[])[0].result_schema_id = "https://gentle-ai.dev/schema/review/other/v1";
}),
"$id does not match",
);
});
// --- archive safety ------------------------------------------------------------------
test("rejects a tar entry that escapes the bundle root", () => {
withTemporaryDirectory((directory) => {
const entries = [...fixtureEntries()].map(([name, bytes]) => ({ name, bytes }));
entries.push({ name: "../evil.txt", bytes: Buffer.from("escape\n") });
const archivePath = join(directory, "escape.tar.gz");
writeFileSync(archivePath, buildTarGz(entries));
assert.throws(() => verifyProviderContractBundleArchive(archivePath), /is unsafe/);
});
});
test("rejects a non-regular tar entry (symlink typeflag)", () => {
withTemporaryDirectory((directory) => {
const entries = [...fixtureEntries()].map(([name, bytes]) => ({ name, bytes } as { name: string; bytes: Buffer; typeflag?: string }));
(entries[0] as { typeflag?: string }).typeflag = "2";
const archivePath = join(directory, "symlink.tar.gz");
writeFileSync(archivePath, buildTarGz(entries));
assert.throws(() => verifyProviderContractBundleArchive(archivePath), /type "2" is forbidden/);
});
});
test("rejects an executable or setuid tar entry mode", () => {
withTemporaryDirectory((directory) => {
const entries = [...fixtureEntries()].map(([name, bytes]) => ({ name, bytes } as { name: string; bytes: Buffer; mode?: number }));
(entries[2] as { mode?: number }).mode = 0o4755;
const archivePath = join(directory, "setuid.tar.gz");
writeFileSync(archivePath, buildTarGz(entries));
assert.throws(() => verifyProviderContractBundleArchive(archivePath), /unsafe file mode/);
});
});
test("rejects trailing data after the tar terminator", () => {
withTemporaryDirectory((directory) => {
const archivePath = join(directory, "trailing.tar.gz");
writeFileSync(archivePath, buildTarGz([...fixtureEntries()].map(([name, bytes]) => ({ name, bytes })), Buffer.from("smuggled")));
assert.throws(() => verifyProviderContractBundleArchive(archivePath), /data after its terminator/);
});
});
test("rejects a duplicated tar entry path", () => {
withTemporaryDirectory((directory) => {
const entries = [...fixtureEntries()].map(([name, bytes]) => ({ name, bytes }));
entries.push({ name: "README.md", bytes: Buffer.from("second\n") });
const archivePath = join(directory, "duplicate.tar.gz");
writeFileSync(archivePath, buildTarGz(entries));
assert.throws(() => verifyProviderContractBundleArchive(archivePath), /duplicated|more than 8/);
});
});
// --- extracted tree safety --------------------------------------------------------------
test("rejects a bundle tree containing a symlink", { skip: process.platform === "win32" }, () => {
withTemporaryDirectory((directory) => {
materialize(fixtureEntries(), directory);
rmSync(join(directory, "README.md"));
symlinkSync(join(directory, "manifest.json"), join(directory, "README.md"));
assert.throws(() => verifyProviderContractBundleTree(directory), /is a symlink/);
});
});
test("rejects a bundle tree entry with an executable mode", { skip: process.platform === "win32" }, () => {
withTemporaryDirectory((directory) => {
materialize(fixtureEntries(), directory);
chmodSync(join(directory, "schemas/lens.schema.json"), 0o755);
assert.throws(() => verifyProviderContractBundleTree(directory), /unsafe file mode/);
});
});