Files
ai_ops/tests/manifest-schema.test.ts

169 lines
4.0 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
import { parseAgentManifest } from "../src/agents/manifest.js";
function validManifest(): unknown {
return {
schemaVersion: "1",
topologies: ["hierarchical", "retry-unrolled", "sequential"],
personas: [
{
id: "product",
displayName: "Product",
systemPromptTemplate: "Product for {{repo}}",
toolClearance: {
allowlist: ["read_file"],
banlist: ["delete_file"],
},
},
{
id: "coder",
displayName: "Coder",
systemPromptTemplate: "Coder for {{repo}}",
toolClearance: {
allowlist: ["read_file", "write_file"],
banlist: [],
},
},
],
relationships: [
{
parentPersonaId: "product",
childPersonaId: "coder",
constraints: {
maxDepth: 2,
maxChildren: 3,
},
},
],
topologyConstraints: {
maxDepth: 5,
maxRetries: 2,
},
pipeline: {
entryNodeId: "product-node",
nodes: [
{
id: "product-node",
actorId: "product_actor",
personaId: "product",
},
{
id: "coder-node",
actorId: "coder_actor",
personaId: "coder",
constraints: {
maxRetries: 1,
},
},
],
edges: [
{
from: "product-node",
to: "coder-node",
on: "success",
when: [{ type: "always" }],
},
],
},
};
}
test("parses a valid AgentManifest", () => {
const manifest = parseAgentManifest(validManifest());
assert.equal(manifest.schemaVersion, "1");
assert.equal(manifest.pipeline.nodes.length, 2);
assert.equal(manifest.relationships.length, 1);
});
test("parses optional persona modelConstraint", () => {
const manifest = validManifest() as {
personas: Array<Record<string, unknown>>;
};
manifest.personas[1] = {
...manifest.personas[1],
modelConstraint: "claude-3-haiku",
};
const parsed = parseAgentManifest(manifest);
const coder = parsed.personas.find((persona) => persona.id === "coder");
assert.ok(coder);
assert.equal(coder.modelConstraint, "claude-3-haiku");
});
test("rejects pipeline cycles", () => {
const manifest = validManifest() as {
pipeline: {
edges: Array<{ from: string; to: string; on: string }>;
};
};
manifest.pipeline.edges.push({
from: "coder-node",
to: "product-node",
on: "success",
});
assert.throws(() => parseAgentManifest(manifest), /strict DAG/);
});
test("rejects relationship with unknown persona", () => {
const manifest = validManifest() as {
relationships: Array<{ parentPersonaId: string; childPersonaId: string }>;
};
manifest.relationships.push({
parentPersonaId: "product",
childPersonaId: "unknown",
});
assert.throws(() => parseAgentManifest(manifest), /unknown child persona/);
});
test("rejects relationship cycles", () => {
const manifest = validManifest() as {
relationships: Array<{ parentPersonaId: string; childPersonaId: string }>;
};
manifest.relationships.push({
parentPersonaId: "coder",
childPersonaId: "product",
});
assert.throws(() => parseAgentManifest(manifest), /Relationship graph must be acyclic/);
});
test("rejects legacy edge trigger aliases", () => {
const manifest = validManifest() as {
pipeline: {
edges: Array<{ from: string; to: string; on: string }>;
};
};
manifest.pipeline.edges[0] = {
from: "product-node",
to: "coder-node",
on: "onValidationFail",
};
assert.throws(
() => parseAgentManifest(manifest),
/unsupported event "onValidationFail"/,
);
});
test("rejects empty persona modelConstraint", () => {
const manifest = validManifest() as {
personas: Array<Record<string, unknown>>;
};
manifest.personas[0] = {
...manifest.personas[0],
modelConstraint: " ",
};
assert.throws(
() => parseAgentManifest(manifest),
/modelConstraint/,
);
});