Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 54 additions & 1 deletion packages/amico-run/test/coordination-ledger.contract.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,50 @@
// Contract suite for coordination ledger (spec #318) — runs against both cloud and sqlite ref
import { describe, it, expect } from "vitest";
import { describe, it, expect, beforeAll, afterAll } from "vitest";
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
import { homedir, tmpdir } from "node:os";
import { join } from "node:path";
import { coordinationService, SqliteCoordinationService, workId } from "../src/coordination_ledger.js";
import { degradedStamp } from "../src/coordination_ledger.js";

const wid = (s: string) => workId({ structure_hash: s, goal: "CZ", N: 100, T: 30 });

// ── isolation bridge (#642) ─────────────────────────────────────────────────
// Claim appends made by this suite must land in a per-run tmp partition and
// NEVER in the production claims ledger (~/.amico/ledger/claims.jsonl) — the
// Prova simulated-ledger-partition pattern from docs/ledger.md, via the
// $AMICO_CLAIMS_FILE seam the service reads per append. Before the bridge, the
// suite constructed the service bare with the env unset, so every contract run
// appended fixture rows to the real ledger: 176 rows before ops archived it as
// claims.jsonl.archive-20260830-test-pollution (#642).
const PRODUCTION_CLAIMS = join(homedir(), ".amico", "ledger", "claims.jsonl");
// Snapshot at module load — before any test in this file has run.
const productionSnapshot = existsSync(PRODUCTION_CLAIMS)
? readFileSync(PRODUCTION_CLAIMS).toString("base64")
: null;
const ISO_ROOT = mkdtempSync(join(tmpdir(), "amico-claims-iso-"));
const ISOLATED_CLAIMS = join(ISO_ROOT, "claims.jsonl");

const productionNow = (): string | null =>
existsSync(PRODUCTION_CLAIMS) ? readFileSync(PRODUCTION_CLAIMS).toString("base64") : null;

// The bridge itself: route EVERY service construction in this file at the
// isolated partition. The env is the seam claimsFile() reads per append, so
// bare constructions and future ones are covered equally; production callers
// never set it and keep their default path.
const prevClaimsEnv = process.env.AMICO_CLAIMS_FILE;
beforeAll(() => {
process.env.AMICO_CLAIMS_FILE = ISOLATED_CLAIMS;
});
afterAll(() => {
// Suite-wide guard, order-robust: after every test has run, the production
// ledger is still pristine (byte-identical, or still absent — CI and local).
expect(productionNow()).toBe(productionSnapshot);
// Never leak process state across suites (vitest reuses forked workers).
if (prevClaimsEnv === undefined) delete process.env.AMICO_CLAIMS_FILE;
else process.env.AMICO_CLAIMS_FILE = prevClaimsEnv;
rmSync(ISO_ROOT, { recursive: true, force: true });
});

describe("coordination ledger — claim serialization (spec §3)", () => {
it("simultaneous claims serialize by receipt order; loser gets holder", async () => {
const svc = new SqliteCoordinationService();
Expand Down Expand Up @@ -52,3 +92,16 @@ describe("coordination ledger — claim serialization (spec §3)", () => {
expect(list.some(s => s.user === "alice" && s.host === "h1")).toBe(true);
});
});

describe("coordination ledger — test isolation (#642)", () => {
it("claim appends land in the per-run tmp partition, never the production claims ledger", async () => {
const svc = new SqliteCoordinationService();
const r = await svc.preflight({ work_id: wid("iso-guard"), agent_id: "iso", user: "iso", org: "iso", host: "iso" });
expect(r.ok).toBe(true);
// the durable append still works — the claim line landed in the isolated partition
const written = existsSync(ISOLATED_CLAIMS) ? readFileSync(ISOLATED_CLAIMS, "utf8") : "";
expect(written).toContain(wid("iso-guard"));
// and the production ledger is byte-identical to its pre-suite snapshot (or still absent)
expect(productionNow()).toBe(productionSnapshot);
});
});
10 changes: 10 additions & 0 deletions packages/amico-run/test/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,13 @@ process.env.AMICO_CRITIC_BIN = "/nonexistent/amico-test-guard/no-real-model-call
if (!process.env.AMICO_LEDGER) {
process.env.AMICO_LEDGER = "/nonexistent/amico-test-guard/ledger.jsonl";
}

// And for the coordination claims ledger ($AMICO_CLAIMS_FILE → ~/.amico/ledger/claims.jsonl):
// the coordination-ledger contract suite constructed its service bare, and every run appended
// fixture rows to the real claims ledger — 176 rows before ops archived it 2026-08-30 as
// claims.jsonl.archive-20260830-test-pollution (#642; no in-repo action needed beyond this
// guard). The suite now routes its writes through its own per-run tmp partition; this backstop
// fails closed for the next suite that forgets.
if (!process.env.AMICO_CLAIMS_FILE) {
process.env.AMICO_CLAIMS_FILE = "/nonexistent/amico-test-guard/claims.jsonl";
}
Loading