Enforce resolved execution context for deterministic actor policy
This commit is contained in:
@@ -76,6 +76,21 @@ test("parses a valid AgentManifest", () => {
|
||||
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: {
|
||||
@@ -136,3 +151,18 @@ test("rejects legacy edge trigger aliases", () => {
|
||||
/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/,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -193,7 +193,10 @@ test("runs DAG pipeline with state-dependent routing and retry behavior", async
|
||||
},
|
||||
coder: async (input): Promise<ActorExecutionResult> => {
|
||||
assert.match(input.prompt, /AIOPS-123/);
|
||||
assert.deepEqual(input.toolClearance.allowlist, ["read_file", "write_file"]);
|
||||
assert.deepEqual(input.executionContext.allowedTools, ["read_file", "write_file"]);
|
||||
assert.equal(input.executionContext.phase, "coder-1");
|
||||
assert.equal(typeof input.executionContext.modelConstraint, "string");
|
||||
assert.ok(input.executionContext.modelConstraint.length > 0);
|
||||
assert.ok(input.security);
|
||||
coderAttempts += 1;
|
||||
if (coderAttempts === 1) {
|
||||
@@ -254,7 +257,7 @@ test("runs DAG pipeline with state-dependent routing and retry behavior", async
|
||||
assert.deepEqual(engine.planChildPersonas({ parentPersonaId: "task", depth: 1 }), ["coder"]);
|
||||
});
|
||||
|
||||
test("injects mcp registry/config helpers and enforces Claude tool gate in actor executor", async () => {
|
||||
test("injects resolved mcp/helpers and enforces Claude tool gate in actor executor", async () => {
|
||||
const workspaceRoot = await mkdtemp(resolve(tmpdir(), "ai-ops-workspace-"));
|
||||
const stateRoot = await mkdtemp(resolve(tmpdir(), "ai-ops-session-state-"));
|
||||
const projectContextPath = resolve(stateRoot, "project-context.json");
|
||||
@@ -302,6 +305,7 @@ test("injects mcp registry/config helpers and enforces Claude tool gate in actor
|
||||
id: "task",
|
||||
displayName: "Task",
|
||||
systemPromptTemplate: "Task executor",
|
||||
modelConstraint: "claude-3-haiku",
|
||||
toolClearance: {
|
||||
allowlist: ["read_file", "write_file"],
|
||||
banlist: ["rm"],
|
||||
@@ -340,7 +344,11 @@ test("injects mcp registry/config helpers and enforces Claude tool gate in actor
|
||||
},
|
||||
actorExecutors: {
|
||||
task_actor: async (input) => {
|
||||
assert.equal(input.mcp.registry, customRegistry);
|
||||
assert.deepEqual(input.executionContext.allowedTools, ["read_file", "write_file"]);
|
||||
assert.equal(input.executionContext.phase, "task-node");
|
||||
assert.equal(input.executionContext.modelConstraint, "claude-3-haiku");
|
||||
assert.equal(input.executionContext.security.worktreePath, workspaceRoot);
|
||||
assert.equal(input.executionContext.security.violationMode, "hard_abort");
|
||||
|
||||
const codexConfig = input.mcp.resolveConfig({
|
||||
providerHint: "codex",
|
||||
@@ -350,7 +358,11 @@ test("injects mcp registry/config helpers and enforces Claude tool gate in actor
|
||||
];
|
||||
assert.ok(codexServer);
|
||||
assert.deepEqual(codexServer.enabled_tools, ["read_file", "write_file"]);
|
||||
assert.deepEqual(codexServer.disabled_tools, ["rm"]);
|
||||
assert.deepEqual(input.mcp.allowedTools, ["read_file", "write_file"]);
|
||||
assert.deepEqual(
|
||||
input.mcp.filterToolsForProvider(["read_file", "search", "write_file"]),
|
||||
["read_file", "write_file"],
|
||||
);
|
||||
|
||||
const claudeConfig = input.mcp.resolveConfig({
|
||||
providerHint: "claude",
|
||||
@@ -371,25 +383,31 @@ test("injects mcp registry/config helpers and enforces Claude tool gate in actor
|
||||
toolUseID: "allow-1",
|
||||
});
|
||||
|
||||
const denyBlocked = await canUseTool(
|
||||
"mcp__claude-task-master__rm",
|
||||
{},
|
||||
{
|
||||
signal: new AbortController().signal,
|
||||
toolUseID: "deny-1",
|
||||
},
|
||||
await assert.rejects(
|
||||
() =>
|
||||
canUseTool(
|
||||
"mcp__claude-task-master__rm",
|
||||
{},
|
||||
{
|
||||
signal: new AbortController().signal,
|
||||
toolUseID: "deny-1",
|
||||
},
|
||||
),
|
||||
/Tool .* is not present in allowlist/,
|
||||
);
|
||||
assert.equal(denyBlocked.behavior, "deny");
|
||||
|
||||
const denyMissingAllowlist = await canUseTool(
|
||||
"mcp__claude-task-master__search",
|
||||
{},
|
||||
{
|
||||
signal: new AbortController().signal,
|
||||
toolUseID: "deny-2",
|
||||
},
|
||||
await assert.rejects(
|
||||
() =>
|
||||
canUseTool(
|
||||
"mcp__claude-task-master__search",
|
||||
{},
|
||||
{
|
||||
signal: new AbortController().signal,
|
||||
toolUseID: "deny-2",
|
||||
},
|
||||
),
|
||||
/Tool .* is not present in allowlist/,
|
||||
);
|
||||
assert.equal(denyMissingAllowlist.behavior, "deny");
|
||||
|
||||
return {
|
||||
status: "success",
|
||||
|
||||
Reference in New Issue
Block a user