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.

g1t.sh
59 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') throw new Error('Build API/Work Workers (node scripts/deploy.mjs build --only work,api) and the g1t CLI (cargo build -p g1t) first');
14
15test('real Workers: CLI and MCP share durable updates, receipts and ownership rules', { skip: !built && 'Build API/Work Workers and the g1t CLI first', timeout: 60000 }, async () => {
16 const dir=mkdtempSync(join(tmpdir(),'g1t-local-coordination-'));
17 let local;
18 try {
19 local=await startLocal({port:0,persist:dir});
20 const rest=async(path,body,token='local-rehearsal')=>{
21 const response=await fetch(`${local.url}/repos/tester/rehearsal/${path}`,{method:body?'POST':'GET',headers:{authorization:`Bearer ${token}`,'content-type':'application/json'},body:body?JSON.stringify(body):undefined});
22 const result=await response.json(); assert.equal(response.status,200,JSON.stringify(result)); return result;
23 };
24 const mcp=async(args,token='local-rehearsal')=>{
25 const response=await fetch(`${local.url}/mcp`,{method:'POST',headers:{authorization:`Bearer ${token}`,'content-type':'application/json'},body:JSON.stringify({jsonrpc:'2.0',id:1,method:'tools/call',params:{name:'session',arguments:{repo:'tester/rehearsal',...args}}})});
26 const text=await response.text(); assert.equal(response.status,200,text); return JSON.parse(text).result;
27 };
28 const unwrap=result=>{assert.equal(result.isError,false,JSON.stringify(result));return JSON.parse(result.content[0].text);};
29 const a=await rest('sessions',{harness:'codex',harness_session_id:'codex-test'});
30 const b=unwrap(await mcp({action:'start',harness:'claude_code',harness_session_id:'claude-test'}));
31 await rest(`sessions/${a.id}/entries`,{entries:[{kind:'note',text:'Use fetchUser; implementation belongs to Codex',key:'plan'}],touched:[{kind:'file',value:'src/contract.ts'}]});
32 unwrap(await mcp({action:'record',id:b.id,entries:[{kind:'note',text:'Claude owns caller tests',key:'plan'}],touched:[{kind:'file',value:'src/contract.ts'}]}));
33 let detail=await rest(`sessions/${a.id}`);
34 const overlap=detail.updates.find(u=>u.kind==='overlap.detected'); assert.ok(overlap);
35 const bOverlap=unwrap(await mcp({action:'get',id:b.id})).updates.find(u=>u.kind==='overlap.detected');
36 assert.ok(bOverlap); assert.notEqual(bOverlap.id,overlap.id);
37 assert.ok((await rest(`sessions/${a.id}`)).updates.some(u=>u.id===overlap.id),'reading never acknowledges');
38 const foreign=await mcp({action:'record',id:a.id,acknowledge:[overlap.id]},'local-other');
39 assert.equal(foreign.isError,true,'another actor cannot acknowledge this session');
40 // The same person's other session cannot clear it either, and is told so.
41 const sibling=unwrap(await mcp({action:'record',id:b.id,acknowledge:[overlap.id]}));
42 assert.equal(sibling.acknowledged,0,'an ID from another session is not counted');
43 assert.ok((await rest(`sessions/${a.id}`)).updates.some(u=>u.id===overlap.id),'a sibling session cannot clear this inbox');
44 const args=['-R','tester/rehearsal','session','acknowledge',overlap.id,'--session',a.id,'--note','Separate scopes; agree the interface before editing','--json'];
45 for(let n=0;n<2;n++) await exec(binary,args,{env:{...process.env,G1T_TOKEN:'local-rehearsal',G1T_API_URL:local.url,G1T_SESSION:b.id},windowsHide:true});
46 detail=await rest(`sessions/${a.id}`);
47 assert.ok(!detail.updates.some(u=>u.id===overlap.id));
48 assert.equal(detail.entries.filter(e=>e.text.includes('Separate scopes;')).length,1,'a retry records one decision');
49 assert.ok(unwrap(await mcp({action:'get',id:b.id})).updates.some(u=>u.id===bOverlap.id),'one harness cannot clear the other inbox');
50 await local.mf.dispose();
51 local=await startLocal({port:0,persist:dir});
52 assert.ok(!(await rest(`sessions/${a.id}`)).updates.some(u=>u.id===overlap.id),'receipt survives restart');
53 assert.ok(unwrap(await mcp({action:'get',id:b.id})).updates.some(u=>u.id===bOverlap.id),'pending update survives restart');
54 const resumed=await rest('sessions',{harness:'codex',harness_session_id:'codex-test'});
55 assert.equal(resumed.id,a.id,'a restarted harness recovers its session');
56 assert.equal(unwrap(await mcp({action:'record',id:b.id,entries:[{kind:'note',text:'Handoff considered',key:'ack'}],acknowledge:[bOverlap.id]})).acknowledged,1);
57 assert.ok(!unwrap(await mcp({action:'get',id:b.id})).updates.some(u=>u.id===bOverlap.id));
58 } finally {if(local)await local.mf.dispose();rmSync(dir,{recursive:true,force:true,maxRetries:10,retryDelay:100});}
59});