Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

122 linesCodeBlameRaw
1import assert from "node:assert/strict";
2import { test } from "node:test";
3import { mkdtempSync, rmSync, existsSync } from "node:fs";
4import { tmpdir } from "node:os";
5import { join, resolve } from "node:path";
6import { execFile } from "node:child_process";
7import { promisify } from "node:util";
8import { startLocal } from "./coordination-local.mjs";
9const exec = promisify(execFile);
10const binary = resolve(`target/debug/g1t${process.platform === "win32" ? ".exe" : ""}`);
11const built = ["apps/api/build/index.js", "services/work/build/index.js", binary].every(existsSync);
12// A release check must not pass by skipping: G1T_REQUIRE_LOCAL_WORKERS=1 turns a missing build into a failure.
13if (!built && process.env.G1T_REQUIRE_LOCAL_WORKERS === "1")
14 throw new Error("Build API/Work Workers (node scripts/deploy.mjs build --only work,api) and the g1t CLI (cargo build -p g1t) first");
15
16test(
17 "real Workers: CLI and MCP share durable updates, receipts and ownership rules",
18 { skip: !built && "Build API/Work Workers and the g1t CLI first", timeout: 60000 },
19 async () => {
20 const dir = mkdtempSync(join(tmpdir(), "g1t-local-coordination-"));
21 let local;
22 try {
23 local = await startLocal({ port: 0, persist: dir });
24 const rest = async (path, body, token = "local-rehearsal") => {
25 const response = await fetch(`${local.url}/repos/tester/rehearsal/${path}`, {
26 method: body ? "POST" : "GET",
27 headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
28 body: body ? JSON.stringify(body) : undefined,
29 });
30 const result = await response.json();
31 assert.equal(response.status, 200, JSON.stringify(result));
32 return result;
33 };
34 const mcp = async (args, token = "local-rehearsal") => {
35 const response = await fetch(`${local.url}/mcp`, {
36 method: "POST",
37 headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
38 body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/call", params: { name: "session", arguments: { repo: "tester/rehearsal", ...args } } }),
39 });
40 const text = await response.text();
41 assert.equal(response.status, 200, text);
42 return JSON.parse(text).result;
43 };
44 const unwrap = (result) => {
45 assert.equal(result.isError, false, JSON.stringify(result));
46 return JSON.parse(result.content[0].text);
47 };
48 const a = await rest("sessions", { harness: "codex", harness_session_id: "codex-test" });
49 const b = unwrap(await mcp({ action: "start", harness: "claude_code", harness_session_id: "claude-test" }));
50 await rest(`sessions/${a.id}/entries`, {
51 entries: [{ kind: "note", text: "Use fetchUser; implementation belongs to Codex", key: "plan" }],
52 touched: [{ kind: "file", value: "src/contract.ts" }],
53 });
54 unwrap(
55 await mcp({
56 action: "record",
57 id: b.id,
58 entries: [{ kind: "note", text: "Claude owns caller tests", key: "plan" }],
59 touched: [{ kind: "file", value: "src/contract.ts" }],
60 }),
61 );
62 let detail = await rest(`sessions/${a.id}`);
63 const overlap = detail.updates.find((u) => u.kind === "overlap.detected");
64 assert.ok(overlap);
65 const bOverlap = unwrap(await mcp({ action: "get", id: b.id })).updates.find((u) => u.kind === "overlap.detected");
66 assert.ok(bOverlap);
67 assert.notEqual(bOverlap.id, overlap.id);
68 assert.ok(
69 (await rest(`sessions/${a.id}`)).updates.some((u) => u.id === overlap.id),
70 "reading never acknowledges",
71 );
72 const foreign = await mcp({ action: "record", id: a.id, acknowledge: [overlap.id] }, "local-other");
73 assert.equal(foreign.isError, true, "another actor cannot acknowledge this session");
74 // The same person's other session cannot clear it either, and is told so.
75 const sibling = unwrap(await mcp({ action: "record", id: b.id, acknowledge: [overlap.id] }));
76 assert.equal(sibling.acknowledged, 0, "an ID from another session is not counted");
77 assert.ok(
78 (await rest(`sessions/${a.id}`)).updates.some((u) => u.id === overlap.id),
79 "a sibling session cannot clear this inbox",
80 );
81 const args = [
82 "-R",
83 "tester/rehearsal",
84 "session",
85 "acknowledge",
86 overlap.id,
87 "--session",
88 a.id,
89 "--note",
90 "Separate scopes; agree the interface before editing",
91 "--json",
92 ];
93 for (let n = 0; n < 2; n++)
94 await exec(binary, args, { env: { ...process.env, G1T_TOKEN: "local-rehearsal", G1T_API_URL: local.url, G1T_SESSION: b.id }, windowsHide: true });
95 detail = await rest(`sessions/${a.id}`);
96 assert.ok(!detail.updates.some((u) => u.id === overlap.id));
97 assert.equal(detail.entries.filter((e) => e.text.includes("Separate scopes;")).length, 1, "a retry records one decision");
98 assert.ok(
99 unwrap(await mcp({ action: "get", id: b.id })).updates.some((u) => u.id === bOverlap.id),
100 "one harness cannot clear the other inbox",
101 );
102 await local.mf.dispose();
103 local = await startLocal({ port: 0, persist: dir });
104 assert.ok(!(await rest(`sessions/${a.id}`)).updates.some((u) => u.id === overlap.id), "receipt survives restart");
105 assert.ok(
106 unwrap(await mcp({ action: "get", id: b.id })).updates.some((u) => u.id === bOverlap.id),
107 "pending update survives restart",
108 );
109 const resumed = await rest("sessions", { harness: "codex", harness_session_id: "codex-test" });
110 assert.equal(resumed.id, a.id, "a restarted harness recovers its session");
111 assert.equal(
112 unwrap(await mcp({ action: "record", id: b.id, entries: [{ kind: "note", text: "Handoff considered", key: "ack" }], acknowledge: [bOverlap.id] }))
113 .acknowledged,
114 1,
115 );
116 assert.ok(!unwrap(await mcp({ action: "get", id: b.id })).updates.some((u) => u.id === bOverlap.id));
117 } finally {
118 if (local) await local.mf.dispose();
119 rmSync(dir, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
120 }
121 },
122);