evOo-local/packages/core/src/approval-effect-key.test.ts
cynarAI 6c7eed7580 Initial clean evOo-local from f00577b
Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline
(Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/*
packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
2026-09-20 17:15:16 +02:00

95 lines
3.6 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
approvalEffectKey,
isToolEffectIdempotencyKey,
legacyScopedToolEffectIdempotencyKey,
stableJsonValue,
toolEffectIdempotencyKey,
} from "./approval-effect-key.js";
describe("stableJsonValue", () => {
it("sorts object keys", () => {
expect(stableJsonValue({ b: 1, a: 2 })).toBe('{"a":2,"b":1}');
});
it("rejects values that cannot be represented uniquely as JSON", () => {
const sparse = Array(1);
for (const value of [undefined, [undefined], sparse, { value: undefined }, Number.NaN]) {
expect(() => stableJsonValue(value)).toThrow("only JSON values");
}
expect(stableJsonValue([])).toBe("[]");
expect(stableJsonValue([null])).toBe("[null]");
});
it("rejects cyclic and non-plain objects", () => {
const cyclic: { self?: unknown } = {};
cyclic.self = cyclic;
expect(() => stableJsonValue(cyclic)).toThrow("only JSON values");
expect(() => stableJsonValue(new Date(0))).toThrow("only JSON values");
});
});
describe("approvalEffectKey", () => {
it("includes run and tool with an opaque digest of canonical args", () => {
const key = approvalEffectKey("run-1", "destination.write", { body: "private draft" });
expect(key).toMatch(/^run-1:destination\.write:[a-f0-9]{64}$/);
expect(key).not.toContain("private draft");
});
});
describe("toolEffectIdempotencyKey", () => {
it("scopes effects to run, tool, and args without the model tool-call id", () => {
const write = { path: "a.txt", content: "one" };
expect(toolEffectIdempotencyKey("run-1", "write_file", write)).toMatch(
/^run-1:write_file:[a-f0-9]{64}$/,
);
expect(toolEffectIdempotencyKey("run-1", "write_file", write)).toBe(
approvalEffectKey("run-1", "write_file", write),
);
expect(toolEffectIdempotencyKey("run-1", "write_file", write)).not.toBe(
toolEffectIdempotencyKey("run-2", "write_file", write),
);
expect(toolEffectIdempotencyKey("run-1", "write_file", write)).not.toBe(
toolEffectIdempotencyKey("run-1", "shell", write),
);
expect(toolEffectIdempotencyKey("run-1", "write_file", write)).not.toBe(
toolEffectIdempotencyKey("run-1", "write_file", {
path: "a.txt",
content: "two",
}),
);
expect(toolEffectIdempotencyKey("run-1", "write_file", write)).not.toContain("call_0");
});
it("is stable across replay when the model assigns a new tool-call id", () => {
const args = { path: "MEMORY.md", content: "fact" };
expect(toolEffectIdempotencyKey("run-1", "remember", args)).toBe(
toolEffectIdempotencyKey("run-1", "remember", args),
);
expect(legacyScopedToolEffectIdempotencyKey("run-1", "remember", "call_0", args)).not.toBe(
toolEffectIdempotencyKey("run-1", "remember", args),
);
});
it("distinguishes identical-args calls in one run via occurrence", () => {
const args = { actions: [{ kind: "click", x: 12, y: 40 }] };
const first = toolEffectIdempotencyKey("run-1", "computer_act", args);
const second = toolEffectIdempotencyKey("run-1", "computer_act", args, 1);
expect(first).toBe(approvalEffectKey("run-1", "computer_act", args));
expect(second).not.toBe(first);
expect(second).toBe(`${first}:1`);
expect(isToolEffectIdempotencyKey(first, "run-1", "computer_act")).toBe(true);
expect(isToolEffectIdempotencyKey(second, "run-1", "computer_act")).toBe(true);
expect(
isToolEffectIdempotencyKey(
legacyScopedToolEffectIdempotencyKey("run-1", "computer_act", "call_0", args),
"run-1",
"computer_act",
),
).toBe(false);
});
});