From bd9b9cedcecad26e63a5ef53b9a79e586a34a748 Mon Sep 17 00:00:00 2001 From: Aaron Trowbridge Date: Sun, 30 Aug 2026 07:41:03 -0400 Subject: [PATCH 1/3] test(coordination): assert contract-suite claim writes stay in an isolated partition (#642) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RED tracer: the guard test constructs the coordination service the way the suite always has — bare — and asserts (a) the claim line lands in the suite's per-run tmp partition and (b) the production claims ledger (~/.amico/ledger/claims.jsonl) stays byte-identical to its module-load snapshot. It fails today: with $AMICO_CLAIMS_FILE unset the service's default-path resolution appends every fixture claim to the production ledger (reproduced under a fake HOME: 1 pristine line grew to 6 in one run; on the server this accumulated 176 rows before ops archived the ledger 2026-08-30). --- .../test/coordination-ledger.contract.test.ts | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/packages/amico-run/test/coordination-ledger.contract.test.ts b/packages/amico-run/test/coordination-ledger.contract.test.ts index f127e3d2..21b0cd74 100644 --- a/packages/amico-run/test/coordination-ledger.contract.test.ts +++ b/packages/amico-run/test/coordination-ledger.contract.test.ts @@ -1,10 +1,31 @@ // Contract suite for coordination ledger (spec #318) — runs against both cloud and sqlite ref import { describe, it, expect } from "vitest"; +import { existsSync, mkdtempSync, readFileSync } 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 ISOLATED_CLAIMS = join(mkdtempSync(join(tmpdir(), "amico-claims-iso-")), "claims.jsonl"); + +const productionNow = (): string | null => + existsSync(PRODUCTION_CLAIMS) ? readFileSync(PRODUCTION_CLAIMS).toString("base64") : null; + describe("coordination ledger — claim serialization (spec §3)", () => { it("simultaneous claims serialize by receipt order; loser gets holder", async () => { const svc = new SqliteCoordinationService(); @@ -52,3 +73,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); + }); +}); From df50e0432ccd2b78716742186adabbd7bc31560d Mon Sep 17 00:00:00 2001 From: Aaron Trowbridge Date: Sun, 30 Aug 2026 07:42:18 -0400 Subject: [PATCH 2/3] fix(coordination): route contract-suite claims through the AMICO_CLAIMS_FILE bridge (#642) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GREEN: file-level beforeAll points $AMICO_CLAIMS_FILE at a per-run tmp partition (mkdtemp) — the seam claimsFile() reads per append, so every service construction in the suite, present and future, writes there while production callers keep their default. afterAll adds the order-robust suite-wide guard (production ledger byte-identical to its module-load snapshot, or still absent), restores the env per the repo idiom so nothing leaks across suites, and removes the tmp partition. Verified in all three shapes: pristine ledger byte-identical (sha256 equal), CI shape (no ledger) creates nothing, and the archived real path stays absent. Suite 7/7 green. --- .../test/coordination-ledger.contract.test.ts | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/packages/amico-run/test/coordination-ledger.contract.test.ts b/packages/amico-run/test/coordination-ledger.contract.test.ts index 21b0cd74..98629cb5 100644 --- a/packages/amico-run/test/coordination-ledger.contract.test.ts +++ b/packages/amico-run/test/coordination-ledger.contract.test.ts @@ -1,6 +1,6 @@ // Contract suite for coordination ledger (spec #318) — runs against both cloud and sqlite ref -import { describe, it, expect } from "vitest"; -import { existsSync, mkdtempSync, readFileSync } from "node:fs"; +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"; @@ -21,11 +21,30 @@ const PRODUCTION_CLAIMS = join(homedir(), ".amico", "ledger", "claims.jsonl"); const productionSnapshot = existsSync(PRODUCTION_CLAIMS) ? readFileSync(PRODUCTION_CLAIMS).toString("base64") : null; -const ISOLATED_CLAIMS = join(mkdtempSync(join(tmpdir(), "amico-claims-iso-")), "claims.jsonl"); +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(); From 2f4966ee8720d163c1109861982a584b85afd53c Mon Sep 17 00:00:00 2001 From: Aaron Trowbridge Date: Sun, 30 Aug 2026 07:46:18 -0400 Subject: [PATCH 3/3] =?UTF-8?q?test(setup):=20fail-closed=20AMICO=5FCLAIMS?= =?UTF-8?q?=5FFILE=20backstop=20=E2=80=94=20no=20suite=20may=20write=20the?= =?UTF-8?q?=20real=20claims=20ledger=20(#642)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The runs-ledger backstop above exists because "the risk is in the test someone writes next" — the claims ledger had no such guard, which is exactly how the contract suite polluted it: 176 fixture rows before ops archived the ledger 2026-08-30 as claims.jsonl.archive-20260830-test-pollution (no in-repo action beyond this guard). Mirrors the AMICO_LEDGER pattern: set-if-unset to a /nonexistent path, so an unset claims env fails closed (mkdir at the filesystem root throws; preflight's durable append is deliberately best-effort) instead of resolving to ~/.amico/ledger/claims.jsonl. Suites that want real claim-file I/O point the env at their own tmp partition — the contract suite now does; this is the backstop for the ones that forget. Verified behaviorally with a scratch forgetful suite (bare construction, env unset): polluted the default path before this commit, fails closed after — no file created, claim semantics unaffected (preflight still ok). --- packages/amico-run/test/setup.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/amico-run/test/setup.ts b/packages/amico-run/test/setup.ts index 4a2596a8..cc6efffe 100644 --- a/packages/amico-run/test/setup.ts +++ b/packages/amico-run/test/setup.ts @@ -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"; +}