Files
upstream-atomic-crm/.claude/hooks/test/block-nested-orchestrator.test.mjs
upstream-mirror 93ecc1a6f8
Some checks failed
✅ Check / 🔬 ESLint (push) Failing after 21s
✅ Check / 🏷️ Typecheck (push) Failing after 11s
✅ Check / 🔎 Test (push) Failing after 11s
✅ Check / e2e-test (push) Failing after 11s
✅ Check / 🔨 Build (push) Failing after 11s
🚀 Deploy / 🚀 Deploy (doc) (push) Failing after 12s
🚀 Deploy / 🚀 Deploy (demo) (push) Failing after 12s
🚀 Deploy / 🚀 Deploy (supabase) (push) Failing after 18s
snapshot of https://github.com/marmelab/atomic-crm.git (main, 2026-09-18; blobs > 131072B omitted)
2026-09-18 01:48:50 +05:00

91 lines
3.3 KiB
JavaScript

// Tests for block-nested-orchestrator.mjs — the PreToolUse(Agent) gate that
// enforces the harness dispatch topology. Two rules:
// 1. the orchestrator may dispatch ONLY the typed harness agents
// (planner, developer, quality-reviewer, merger, documentator).
// 2. a subagent (has agent_id) must NOT dispatch an orchestrator.
// A block is emitted via decisionBlock: exit 0 with {"decision":"block"} on
// stdout. An allowed dispatch exits 0 with no decision JSON.
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { spawnSync } from "node:child_process";
import { afterAll, describe, test, expect } from "vitest";
const HERE = dirname(fileURLToPath(import.meta.url));
const HOOK = join(HERE, "..", "block-nested-orchestrator.mjs");
const TMP = mkdtempSync(join(tmpdir(), "block-nested-orch-test-"));
const env = { ...process.env, APP_DIR: TMP, CRM_TMP_ROOT: TMP };
delete env.CLAUDE_AGENT_NAME;
afterAll(() => rmSync(TMP, { recursive: true, force: true }));
// callerType = the dispatching agent's agent_type; agentId distinguishes the
// main thread ("") from a subagent (non-empty).
const run = (callerType, target, agentId = "") => {
const r = spawnSync("node", [HOOK], {
input: JSON.stringify({
session_id: "td-nested-1",
agent_type: callerType,
agent_id: agentId,
tool_input: { subagent_type: target },
}),
env,
encoding: "utf8",
});
return {
blocked: /"decision":"block"/.test(r.stdout || ""),
status: r.status,
};
};
describe("block-nested-orchestrator — Rule 1 (orchestrator allowlist)", () => {
for (const t of [
"planner",
"developer",
"quality-reviewer",
"merger",
"documentator",
]) {
test(`orchestrator dispatching ${t} → allowed`, () => {
expect(run("orchestrator", t, "orch-1").blocked).toBe(false);
});
}
test("orchestrator dispatching general-purpose → blocked", () => {
expect(run("orchestrator", "general-purpose", "orch-1").blocked).toBe(true);
});
test("orchestrator dispatching another orchestrator → blocked", () => {
expect(run("orchestrator", "orchestrator", "orch-1").blocked).toBe(true);
});
test("a suffixed orchestrator-name caller is still gated by the allowlist", () => {
expect(run("orchestrator-abc12", "Explore", "orch-1").blocked).toBe(true);
expect(run("orchestrator-abc12", "planner", "orch-1").blocked).toBe(false);
});
test("chat-orchestrator dispatching planner → allowed", () => {
expect(run("chat-orchestrator", "planner", "chat-1").blocked).toBe(false);
});
});
describe("block-nested-orchestrator — Rule 2 (no nested orchestrator)", () => {
test("a subagent dispatching orchestrator → blocked", () => {
expect(run("general-purpose", "orchestrator", "gp-1").blocked).toBe(true);
});
test("a subagent dispatching chat-orchestrator → blocked", () => {
expect(run("developer", "chat-orchestrator", "dev-1").blocked).toBe(true);
});
test("the main thread (no agent_id) dispatching orchestrator → allowed", () => {
expect(run("", "orchestrator", "").blocked).toBe(false);
});
test("a subagent dispatching a non-orchestrator agent → allowed", () => {
expect(run("developer", "general-purpose", "dev-1").blocked).toBe(false);
});
});