import { mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { buildChildAgentDoctrine } from "../src/core/prompts/rlm.js";
import {
beginRlmChildResultTurn,
bindRlmChildContractSession,
completeRlmChildResultTurn,
failRlmChildResultTurn,
formatRlmResultContractInstructions,
loadRlmChildContract,
markRlmChildResultTurnNotified,
normalizeRlmChildContract,
prepareRlmChildContract,
RLM_RESULT_FILE_NAME,
readRlmChildResult,
settleRlmChildResultTurn,
submitRlmChildResult,
validateRlmChildResult,
} from "../src/core/rlm-contract.js";
const roots: string[] = [];
function temporaryDirectory(): string {
const path = mkdtempSync(join(tmpdir(), "rlm-contract-test-"));
roots.push(path);
return path;
}
const schema = {
type: "object",
properties: {
status: { type: "string", maxLength: 32 },
finding_ids: { type: "array", items: { type: "string" } },
private_notes: { type: "string" },
},
required: ["status", "finding_ids"],
additionalProperties: false,
};
const rawContract = {
result: { schema, sink: { kind: "file" }, required: true },
context: {
terminal: { payload: false, notify: true },
direct_messages: { payload: false, notify: false },
observe: false,
result: { fields: ["status", "finding_ids"] },
},
};
afterEach(() => {
for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true });
});
describe("RLM child contracts", () => {
it("preserves native behavior when no contract fields are supplied", () => {
expect(normalizeRlmChildContract(undefined, undefined)).toBeUndefined();
});
it("normalizes independent result and context policies", () => {
const contract = normalizeRlmChildContract(rawContract.result, rawContract.context);
expect(contract).toEqual(rawContract);
expect(contract?.result?.schema).not.toBe(schema);
});
it("rejects ambiguous or unsupported policies", () => {
expect(() => normalizeRlmChildContract(undefined, { result: { fields: [] } })).toThrow(
"requires a result contract",
);
expect(() => normalizeRlmChildContract(rawContract.result, { result: { fields: ["missing"] } })).toThrow(
"admits unknown schema field",
);
expect(() =>
normalizeRlmChildContract(rawContract.result, { terminal: { payload: true, notify: false } }),
).toThrow("cannot be false");
expect(() => normalizeRlmChildContract({ ...rawContract.result, mode: "artifact" }, undefined)).toThrow(
"unsupported fields",
);
expect(() =>
normalizeRlmChildContract(
{ schema: { type: "object", properties: { status: { type: "not-real" } } } },
undefined,
),
).toThrow("type is unsupported");
expect(() =>
normalizeRlmChildContract(
{ schema: { type: "object", properties: { status: { $ref: "https://example.com/schema" } } } },
undefined,
),
).toThrow("unsupported fields");
expect(() =>
normalizeRlmChildContract(
{ schema: { type: "object", properties: { count: { type: "number", multipleOf: 0 } } } },
undefined,
),
).toThrow("greater than zero");
expect(() =>
normalizeRlmChildContract(
{ schema: { type: "object", properties: { count: { const: Number.MAX_SAFE_INTEGER + 1 } } } },
undefined,
),
).toThrow("safe range");
});
it("persists and binds a host-owned contract", () => {
const root = temporaryDirectory();
const contract = normalizeRlmChildContract(rawContract.result, rawContract.context)!;
const prepared = prepareRlmChildContract(root, "sub-1234", "worker", contract)!;
expect(prepared.resultPath).toBe(join(root, RLM_RESULT_FILE_NAME));
expect(loadRlmChildContract(root)).toMatchObject({ childId: "sub-1234", sessionName: "worker" });
const bound = bindRlmChildContractSession(prepared, "session-1234");
expect(loadRlmChildContract(root)).toMatchObject({ sessionId: "session-1234" });
const persisted = JSON.parse(readFileSync(bound.contractPath, "utf8"));
expect(persisted.format_version).toBe(1);
writeFileSync(bound.contractPath, JSON.stringify({ ...persisted, unexpected: true }));
expect(() => loadRlmChildContract(root)).toThrow("unsupported fields");
});
it("validates turn-scoped results and projects only admitted fields", () => {
const root = temporaryDirectory();
const contract = normalizeRlmChildContract(rawContract.result, rawContract.context)!;
let prepared = prepareRlmChildContract(root, "sub-1234", "worker", contract)!;
const initial = beginRlmChildResultTurn(prepared, "action-0");
prepared = initial.prepared;
const value = { status: "approved", finding_ids: ["F1"], private_notes: "do not admit" };
expect(() =>
submitRlmChildResult(prepared, {
...value,
finding_ids: [Number.MAX_SAFE_INTEGER + 1],
}),
).toThrow("safe range");
const submission = submitRlmChildResult(prepared, value);
expect(submission).toMatchObject({ generation: 0, path: join(root, RLM_RESULT_FILE_NAME) });
expect(submission.sha256).toMatch(/^[0-9a-f]{64}$/);
expect(submitRlmChildResult(prepared, value)).toEqual(submission);
expect(() => submitRlmChildResult(prepared, { ...value, finding_ids: [] })).toThrow(
"already submitted with different content",
);
prepared = settleRlmChildResultTurn(prepared, "action-0").prepared;
const full = readRlmChildResult(prepared);
expect(full).toMatchObject({ generation: 0, result: { status: "approved", finding_ids: ["F1"] } });
expect(full.sha256).toMatch(/^[0-9a-f]{64}$/);
expect(readRlmChildResult(prepared, ["status"]).result).toEqual({ status: "approved" });
expect(() => readRlmChildResult(prepared, ["private_notes"])).toThrow("exceed the child context contract");
});
it("omits absent optional fields from direct and wire projections", () => {
const root = temporaryDirectory();
const contract = normalizeRlmChildContract(rawContract.result, {
...rawContract.context,
result: { fields: ["private_notes"] },
})!;
let prepared = prepareRlmChildContract(root, "sub-optional-field", "worker", contract)!;
prepared = beginRlmChildResultTurn(prepared, "action-0").prepared;
submitRlmChildResult(prepared, { status: "approved", finding_ids: [] });
prepared = settleRlmChildResultTurn(prepared, "action-0").prepared;
expect(readRlmChildResult(prepared).result).toEqual({});
expect(JSON.parse(JSON.stringify(readRlmChildResult(prepared).result))).toEqual({});
});
it("retains old settled generations while a follow-up result is active", () => {
const root = temporaryDirectory();
const contract = normalizeRlmChildContract(rawContract.result, rawContract.context)!;
let prepared = prepareRlmChildContract(root, "sub-1234", "worker", contract)!;
prepared = beginRlmChildResultTurn(prepared, "action-0").prepared;
submitRlmChildResult(prepared, { status: "initial", finding_ids: [] });
prepared = settleRlmChildResultTurn(prepared, "action-0").prepared;
const followup = beginRlmChildResultTurn(prepared, "action-1");
prepared = followup.prepared;
expect(followup.turn).toMatchObject({ generation: 1, status: "active" });
expect(readRlmChildResult(prepared).result).toEqual({ status: "initial", finding_ids: [] });
submitRlmChildResult(prepared, { status: "follow-up", finding_ids: ["F2"] });
prepared = settleRlmChildResultTurn(prepared, "action-1").prepared;
expect(readRlmChildResult(prepared)).toMatchObject({
generation: 1,
result: { status: "follow-up", finding_ids: ["F2"] },
});
expect(readRlmChildResult(prepared, undefined, 0).result).toEqual({ status: "initial", finding_ids: [] });
expect(beginRlmChildResultTurn(loadRlmChildContract(root)!, "action-1").turn).toMatchObject({
generation: 1,
status: "settled",
});
});
it("persists lifecycle-only turns without inventing notice admission", () => {
const root = temporaryDirectory();
let prepared = prepareRlmChildContract(root, "sub-plain", "plain-worker", {})!;
prepared = bindRlmChildContractSession(prepared, "session-plain");
prepared = beginRlmChildResultTurn(prepared, "parent-turn").prepared;
prepared = completeRlmChildResultTurn(prepared, "parent-turn", false).prepared;
prepared = settleRlmChildResultTurn(prepared, "parent-turn").prepared;
expect(loadRlmChildContract(root)?.resultState?.turn).toMatchObject({
status: "settled",
provider_completed: true,
replied_to_parent: false,
notice_admitted: false,
});
const persisted = JSON.parse(readFileSync(prepared.contractPath, "utf8"));
delete persisted.result_state.turn.notice_admitted;
writeFileSync(prepared.contractPath, `${JSON.stringify(persisted)}\n`);
prepared = loadRlmChildContract(root)!;
expect(prepared.resultState?.turn?.notice_admitted).toBe(false);
prepared = markRlmChildResultTurnNotified(prepared, "parent-turn");
expect(loadRlmChildContract(root)?.resultState?.turn?.notice_admitted).toBe(true);
});
it("persists provider completion and notice admission for replay", () => {
const root = temporaryDirectory();
const contract = normalizeRlmChildContract(rawContract.result, rawContract.context)!;
let prepared = prepareRlmChildContract(root, "sub-1234", "worker", contract)!;
prepared = beginRlmChildResultTurn(prepared, "action-0").prepared;
submitRlmChildResult(prepared, { status: "approved", finding_ids: [] });
prepared = completeRlmChildResultTurn(prepared, "action-0", true).prepared;
expect(loadRlmChildContract(root)?.resultState?.turn).toMatchObject({
provider_completed: true,
replied_to_parent: true,
notice_admitted: false,
});
prepared = settleRlmChildResultTurn(prepared, "action-0").prepared;
expect(loadRlmChildContract(root)?.resultState?.turn).toMatchObject({
status: "settled",
notice_admitted: false,
});
prepared = markRlmChildResultTurnNotified(prepared, "action-0");
expect(loadRlmChildContract(root)?.resultState?.turn).toMatchObject({
status: "settled",
notice_admitted: true,
});
const failedRoot = temporaryDirectory();
let failed = beginRlmChildResultTurn(
prepareRlmChildContract(failedRoot, "sub-failed", "worker", contract)!,
"action-failed",
).prepared;
failed = failRlmChildResultTurn(failed, "action-failed", "provider failed");
expect(loadRlmChildContract(failedRoot)?.resultState?.turn).toMatchObject({
status: "failed",
notice_admitted: false,
error: "provider failed",
});
});
it("fails closed for invalid and symlinked active-turn results", () => {
const root = temporaryDirectory();
const contract = normalizeRlmChildContract(rawContract.result, rawContract.context)!;
let prepared = beginRlmChildResultTurn(
prepareRlmChildContract(root, "sub-1234", "worker", contract)!,
"action-invalid",
).prepared;
expect(() => submitRlmChildResult(prepared, { status: 3, finding_ids: [] })).toThrow("does not match its schema");
writeFileSync(prepared.resultPath!, JSON.stringify({ status: 3, finding_ids: [] }));
const invalid = settleRlmChildResultTurn(prepared, "action-invalid");
expect(invalid.error).toContain("does not match its schema");
expect(() => validateRlmChildResult(invalid.prepared)).toThrow("no settled result");
const symlinkRoot = temporaryDirectory();
prepared = beginRlmChildResultTurn(
prepareRlmChildContract(symlinkRoot, "sub-5678", "worker-2", contract)!,
"action-symlink",
).prepared;
const target = join(symlinkRoot, "outside.json");
writeFileSync(target, JSON.stringify({ status: "approved", finding_ids: [] }));
symlinkSync(target, prepared.resultPath!);
expect(settleRlmChildResultTurn(prepared, "action-symlink").error).toContain("not a regular file");
});
it("allows optional result turns to settle without claiming a ready generation", () => {
const root = temporaryDirectory();
const contract = normalizeRlmChildContract({ ...rawContract.result, required: false }, rawContract.context)!;
let prepared = prepareRlmChildContract(root, "sub-optional", "optional-worker", contract)!;
prepared = beginRlmChildResultTurn(prepared, "action-optional").prepared;
prepared = completeRlmChildResultTurn(prepared, "action-optional", false).prepared;
prepared = settleRlmChildResultTurn(prepared, "action-optional").prepared;
expect(prepared.resultState).toMatchObject({ turn: { status: "settled" } });
expect(prepared.resultState?.latest_settled).toBeUndefined();
expect(formatRlmResultContractInstructions(prepared)).toContain("submission is optional");
expect(
buildChildAgentDoctrine({
depth: 1,
activeTools: ["ipython"],
resultContract: true,
resultRequired: false,
}),
).toContain("omission is allowed");
});
it("rejects unsafe integers written directly to result artifacts", () => {
const unsafeRoot = temporaryDirectory();
const integerContract = normalizeRlmChildContract(
{
schema: {
type: "object",
properties: { count: { type: "integer" } },
required: ["count"],
additionalProperties: false,
},
},
{ result: { fields: ["count"] } },
)!;
const unsafe = beginRlmChildResultTurn(
prepareRlmChildContract(unsafeRoot, "sub-unsafe", "worker", integerContract)!,
"action-unsafe",
).prepared;
writeFileSync(unsafe.resultPath!, '{"count":9007199254740993}\n');
const rejected = settleRlmChildResultTurn(unsafe, "action-unsafe");
expect(rejected.error).toContain("safe range");
const safeRoot = temporaryDirectory();
let safe = beginRlmChildResultTurn(
prepareRlmChildContract(safeRoot, "sub-safe", "worker", integerContract)!,
"action-safe",
).prepared;
writeFileSync(safe.resultPath!, `{"count":${Number.MAX_SAFE_INTEGER}}\n`);
safe = settleRlmChildResultTurn(safe, "action-safe").prepared;
expect(readRlmChildResult(safe).result).toEqual({ count: Number.MAX_SAFE_INTEGER });
});
it("makes structured submission authoritative in child doctrine", () => {
const doctrine = buildChildAgentDoctrine({
depth: 1,
activeTools: ["ipython"],
resultContract: true,
directMessagePayload: false,
});
expect(doctrine).toContain("await rlm.submit_result(value)");
expect(doctrine).not.toContain("await rlm.send_parent");
expect(doctrine).not.toContain("reply explicitly");
});
it("renders the file contract without inventing a named mode", () => {
const root = temporaryDirectory();
const contract = normalizeRlmChildContract(rawContract.result, rawContract.context)!;
const prepared = prepareRlmChildContract(root, "sub-1234", "worker", contract)!;
const instructions = formatRlmResultContractInstructions(prepared);
expect(instructions).toContain("await rlm.submit_result(value)");
expect(instructions).toContain('"finding_ids"');
expect(instructions).not.toContain("artifact-only");
});
describe("guarded-by-default context", () => {
it("withholds terminal, direct messages, and observation when a result is declared without context", () => {
const contract = normalizeRlmChildContract(
{ schema: { type: "object", properties: {} }, required: false },
undefined,
{ guardedByDefault: true },
)!;
expect(contract.context).toEqual({
terminal: { payload: false, notify: true },
direct_messages: { payload: false, notify: true },
observe: false,
});
});
it("keeps explicit context entries and fills the rest with guarded defaults", () => {
const contract = normalizeRlmChildContract(
{ schema: { type: "object", properties: {} }, required: false },
{ direct_messages: { payload: true } },
{ guardedByDefault: true },
)!;
expect(contract.context).toEqual({
terminal: { payload: false, notify: true },
direct_messages: { payload: true, notify: true },
observe: false,
});
});
it("does not add guarded defaults when the caller opted out", () => {
expect(normalizeRlmChildContract(undefined, undefined, { guardedByDefault: true })).toBeUndefined();
expect(
normalizeRlmChildContract({ schema: { type: "object", properties: {} }, required: false }, undefined, {
guardedByDefault: false,
})?.context,
).toBeUndefined();
});
it("renders guarded doctrine for a child whose payloads are withheld", () => {
const doctrine = buildChildAgentDoctrine({
depth: 1,
activeTools: ["ipython"],
resultContract: true,
resultRequired: true,
contextGuarded: true,
});
expect(doctrine).toContain("your only communication channel");
expect(doctrine).not.toContain("only for coordination or clarification");
});
});
});