agent-ecosystem/test/main/services/team/OpenCodeLaunchTransactionStore.test.ts
2026-04-21 20:28:22 +03:00

258 lines
7.1 KiB
TypeScript

import { promises as fs } from 'fs';
import * as os from 'os';
import * as path from 'path';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import {
canMarkOpenCodeRunReady,
createOpenCodeLaunchEvidenceHash,
createOpenCodeLaunchTransactionStore,
redactOpenCodeLaunchEvidence,
type OpenCodeLaunchCheckpoint,
type OpenCodeLaunchTransaction,
} from '../../../../src/main/services/team/opencode/store/OpenCodeLaunchTransactionStore';
let tempDir: string;
let now: Date;
describe('OpenCodeLaunchTransactionStore', () => {
beforeEach(async () => {
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'opencode-launch-tx-'));
now = new Date('2026-04-21T12:00:00.000Z');
});
afterEach(async () => {
await fs.rm(tempDir, { recursive: true, force: true });
});
it('begins a run and blocks duplicate launch while active', async () => {
const store = createStore();
await expect(
store.beginRun({
teamName: 'team-a',
runId: 'run-1',
})
).resolves.toMatchObject({
state: 'created',
transaction: {
teamName: 'team-a',
runId: 'run-1',
status: 'active',
},
});
await expect(
store.beginRun({
teamName: 'team-a',
runId: 'run-2',
})
).resolves.toMatchObject({
state: 'already_active',
transaction: {
runId: 'run-1',
},
});
});
it('adds checkpoints idempotently and ignores late checkpoints from old runs', async () => {
const store = createStore();
await store.beginRun({ teamName: 'team-a', runId: 'run-1' });
const checkpoint = buildCheckpoint({
name: 'member_session_recorded',
memberName: 'Builder',
evidenceHash: createOpenCodeLaunchEvidenceHash({ sessionId: 'session-1' }),
});
await expect(store.addCheckpoint(checkpoint)).resolves.toBe('created');
await expect(store.addCheckpoint(checkpoint)).resolves.toBe('unchanged');
await expect(
store.hasCheckpoint({
teamName: 'team-a',
runId: 'run-1',
memberName: 'Builder',
name: 'member_session_recorded',
})
).resolves.toBe(true);
await expect(
store.addCheckpoint({
...checkpoint,
runId: 'old-run',
})
).rejects.toThrow('OpenCode launch transaction not found: old-run');
await expect(store.read('team-a', 'run-1')).resolves.toMatchObject({
checkpoints: [expect.objectContaining({ name: 'member_session_recorded' })],
});
});
it('finishes active transaction and rejects stale finish for another run', async () => {
const store = createStore();
await store.beginRun({ teamName: 'team-a', runId: 'run-1' });
await expect(
store.finish({
teamName: 'team-a',
runId: 'old-run',
status: 'failed',
})
).rejects.toThrow('OpenCode launch transaction old-run is stale; active run is run-1');
await expect(
store.finish({
teamName: 'team-a',
runId: 'run-1',
status: 'ready',
})
).resolves.toBe('finished');
await expect(store.readActive('team-a')).resolves.toBeNull();
await expect(store.read('team-a', 'run-1')).resolves.toMatchObject({
status: 'ready',
});
});
it('quarantines future or invalid transaction data through VersionedJsonStore', async () => {
const filePath = path.join(tempDir, 'launch-transactions.json');
await fs.writeFile(
filePath,
JSON.stringify({
schemaVersion: 1,
updatedAt: '2026-04-21T12:00:00.000Z',
data: [{ teamName: 'team-a', runId: '' }],
}),
'utf8'
);
const store = createStore(filePath);
await expect(store.list()).rejects.toMatchObject({
reason: 'invalid_data',
});
const files = await fs.readdir(tempDir);
expect(files.some((file) => file.includes('invalid_data'))).toBe(true);
});
});
describe('canMarkOpenCodeRunReady', () => {
it('lists exact missing readiness checkpoints before run_ready', () => {
const transaction = transactionWithCheckpoints([
buildCheckpoint({
name: 'member_session_recorded',
memberName: 'Builder',
}),
]);
expect(
canMarkOpenCodeRunReady({
members: [
{ name: 'Builder', launchState: 'confirmed_alive' },
{ name: 'Reviewer', launchState: 'pending' },
],
transaction,
toolProof: { ok: false },
deliveryReady: false,
})
).toEqual({
ok: false,
missing: [
'Builder:required_tools_proven',
'Reviewer:member_session_recorded',
'Reviewer:required_tools_proven',
'Reviewer:bootstrap_confirmed',
'required_runtime_tools',
'runtime_delivery_service',
],
});
});
it('allows ready only when every member and runtime delivery proof exists', () => {
const transaction = transactionWithCheckpoints([
buildCheckpoint({ name: 'member_session_recorded', memberName: 'Builder' }),
buildCheckpoint({ name: 'required_tools_proven', memberName: 'Builder' }),
buildCheckpoint({ name: 'member_session_recorded', memberName: 'Reviewer' }),
buildCheckpoint({ name: 'required_tools_proven', memberName: 'Reviewer' }),
]);
expect(
canMarkOpenCodeRunReady({
members: [
{ name: 'Builder', launchState: 'confirmed_alive' },
{ name: 'Reviewer', launchState: 'confirmed_alive' },
],
transaction,
toolProof: { ok: true },
deliveryReady: true,
})
).toEqual({
ok: true,
missing: [],
});
});
});
describe('OpenCode launch evidence redaction', () => {
it('redacts secret fields before hashing evidence', () => {
const evidence = {
sessionId: 'session-1',
token: 'live-token',
nested: {
apiKey: 'live-key',
},
};
expect(redactOpenCodeLaunchEvidence(evidence)).toEqual({
sessionId: 'session-1',
token: '[redacted]',
nested: {
apiKey: '[redacted]',
},
});
expect(createOpenCodeLaunchEvidenceHash(evidence)).toBe(
createOpenCodeLaunchEvidenceHash({
sessionId: 'session-1',
token: 'other-token',
nested: {
apiKey: 'other-key',
},
})
);
});
});
function createStore(filePath = path.join(tempDir, 'launch-transactions.json')) {
return createOpenCodeLaunchTransactionStore({
filePath,
clock: () => now,
});
}
function buildCheckpoint(
overrides: Partial<OpenCodeLaunchCheckpoint>
): OpenCodeLaunchCheckpoint {
return {
name: 'run_created',
teamName: 'team-a',
runId: 'run-1',
memberName: null,
runtimeSessionId: null,
hostKey: null,
evidenceHash: createOpenCodeLaunchEvidenceHash({ ok: true }),
createdAt: '2026-04-21T12:00:00.000Z',
diagnostics: [],
...overrides,
};
}
function transactionWithCheckpoints(
checkpoints: OpenCodeLaunchCheckpoint[]
): OpenCodeLaunchTransaction {
return {
teamName: 'team-a',
runId: 'run-1',
providerId: 'opencode',
startedAt: '2026-04-21T12:00:00.000Z',
updatedAt: '2026-04-21T12:00:00.000Z',
status: 'active',
checkpoints,
};
}