agent-ecosystem/test/main/services/team/OpenCodeRuntimeManifestEvidenceReader.test.ts

353 lines
11 KiB
TypeScript

import { promises as fs } from 'fs';
import * as os from 'os';
import * as path from 'path';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import {
OpenCodeRuntimeManifestEvidenceReader,
getOpenCodeLaneScopedRuntimeFilePath,
getOpenCodeRuntimeLaneIndexPath,
getOpenCodeTeamRuntimeDirectory,
inspectOpenCodeRuntimeLaneStorage,
migrateLegacyOpenCodeRuntimeState,
readOpenCodeRuntimeLaneIndex,
recoverStaleOpenCodeRuntimeLaneIndexEntry,
upsertOpenCodeRuntimeLaneIndexEntry,
} from '../../../../src/main/services/team/opencode/store/OpenCodeRuntimeManifestEvidenceReader';
describe('OpenCodeRuntimeManifestEvidenceReader migration', () => {
let tempDir: string;
let now: Date;
beforeEach(async () => {
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'opencode-runtime-migration-'));
now = new Date('2026-04-22T10:00:00.000Z');
});
afterEach(async () => {
await fs.rm(tempDir, { recursive: true, force: true });
});
it('migrates legacy team-scoped OpenCode runtime files into the addressed lane', async () => {
const teamName = 'team-alpha';
const laneId = 'secondary:opencode:alice';
const runtimeDir = getOpenCodeTeamRuntimeDirectory(tempDir, teamName);
await fs.mkdir(runtimeDir, { recursive: true });
await fs.writeFile(path.join(runtimeDir, 'manifest.json'), '{"highWatermark":7}\n', 'utf8');
await fs.writeFile(
path.join(runtimeDir, 'opencode-launch-transaction.json'),
'{"transactionId":"tx-1"}\n',
'utf8'
);
const result = await migrateLegacyOpenCodeRuntimeState({
teamsBasePath: tempDir,
teamName,
laneId,
clock: () => now,
});
expect(result).toEqual({
migrated: true,
degraded: false,
diagnostics: ['migrated 2 legacy OpenCode runtime files'],
});
await expect(fs.readFile(path.join(runtimeDir, 'manifest.json'), 'utf8')).rejects.toThrow();
await expect(
fs.readFile(path.join(runtimeDir, 'opencode-launch-transaction.json'), 'utf8')
).rejects.toThrow();
await expect(
fs.readFile(
getOpenCodeLaneScopedRuntimeFilePath({
teamsBasePath: tempDir,
teamName,
laneId,
fileName: 'manifest.json',
}),
'utf8'
)
).resolves.toBe('{"highWatermark":7}\n');
await expect(
fs.readFile(
getOpenCodeLaneScopedRuntimeFilePath({
teamsBasePath: tempDir,
teamName,
laneId,
fileName: 'opencode-launch-transaction.json',
}),
'utf8'
)
).resolves.toBe('{"transactionId":"tx-1"}\n');
await expect(fs.readFile(getOpenCodeRuntimeLaneIndexPath(tempDir, teamName), 'utf8')).resolves.toContain(
`"${laneId}"`
);
await expect(readOpenCodeRuntimeLaneIndex(tempDir, teamName)).resolves.toMatchObject({
lanes: {
[laneId]: {
laneId,
state: 'active',
diagnostics: [
`migrated legacy team-scoped OpenCode runtime state at ${now.toISOString()}`,
],
},
},
});
});
it('marks ambiguous legacy runtime state as degraded instead of guessing a lane', async () => {
const teamName = 'team-beta';
const laneId = 'secondary:opencode:alice';
const otherLaneId = 'secondary:opencode:bob';
const runtimeDir = getOpenCodeTeamRuntimeDirectory(tempDir, teamName);
await fs.mkdir(runtimeDir, { recursive: true });
await fs.writeFile(path.join(runtimeDir, 'manifest.json'), '{"highWatermark":11}\n', 'utf8');
await upsertOpenCodeRuntimeLaneIndexEntry({
teamsBasePath: tempDir,
teamName,
laneId: otherLaneId,
state: 'active',
});
const result = await migrateLegacyOpenCodeRuntimeState({
teamsBasePath: tempDir,
teamName,
laneId,
clock: () => now,
});
expect(result.migrated).toBe(false);
expect(result.degraded).toBe(true);
expect(result.diagnostics).toEqual([
`Legacy OpenCode runtime state is ambiguous for ${teamName}; existing lanes: ${otherLaneId}`,
]);
await expect(fs.readFile(path.join(runtimeDir, 'manifest.json'), 'utf8')).resolves.toBe(
'{"highWatermark":11}\n'
);
await expect(
fs.readFile(
getOpenCodeLaneScopedRuntimeFilePath({
teamsBasePath: tempDir,
teamName,
laneId,
fileName: 'manifest.json',
}),
'utf8'
)
).rejects.toThrow();
await expect(readOpenCodeRuntimeLaneIndex(tempDir, teamName)).resolves.toMatchObject({
lanes: {
[otherLaneId]: {
laneId: otherLaneId,
state: 'active',
},
[laneId]: {
laneId,
state: 'degraded',
diagnostics: [
`Legacy OpenCode runtime state is ambiguous for ${teamName}; existing lanes: ${otherLaneId}`,
],
},
},
});
});
it('does not fall back to team-scoped legacy manifest when sibling lane metadata already exists', async () => {
const teamName = 'team-gamma';
const laneId = 'secondary:opencode:alice';
const otherLaneId = 'secondary:opencode:bob';
const runtimeDir = getOpenCodeTeamRuntimeDirectory(tempDir, teamName);
const reader = new OpenCodeRuntimeManifestEvidenceReader({ teamsBasePath: tempDir });
await fs.mkdir(runtimeDir, { recursive: true });
await fs.writeFile(
path.join(
runtimeDir,
'manifest.json'
),
JSON.stringify({
schemaVersion: 1,
updatedAt: '2026-04-22T10:00:00.000Z',
data: {
schemaVersion: 1,
teamName,
activeRunId: 'legacy-run',
activeCapabilitySnapshotId: 'cap-1',
activeBehaviorFingerprint: null,
highWatermark: 11,
lastCommittedBatchId: null,
lastPreparingBatchId: null,
entries: [],
lastRecoveryPlanId: null,
updatedAt: '2026-04-22T10:00:00.000Z',
},
}),
'utf8'
);
await upsertOpenCodeRuntimeLaneIndexEntry({
teamsBasePath: tempDir,
teamName,
laneId: otherLaneId,
state: 'active',
});
await expect(reader.read(teamName, laneId)).resolves.toEqual({
highWatermark: 0,
activeRunId: null,
capabilitySnapshotId: null,
});
});
it('still falls back to team-scoped legacy manifest for safe single-lane backward compatibility', async () => {
const teamName = 'team-delta';
const laneId = 'secondary:opencode:alice';
const runtimeDir = getOpenCodeTeamRuntimeDirectory(tempDir, teamName);
const reader = new OpenCodeRuntimeManifestEvidenceReader({ teamsBasePath: tempDir });
await fs.mkdir(runtimeDir, { recursive: true });
await fs.writeFile(
path.join(runtimeDir, 'manifest.json'),
JSON.stringify({
schemaVersion: 1,
updatedAt: '2026-04-22T10:00:00.000Z',
data: {
schemaVersion: 1,
teamName,
activeRunId: 'legacy-run',
activeCapabilitySnapshotId: 'cap-1',
activeBehaviorFingerprint: null,
highWatermark: 11,
lastCommittedBatchId: null,
lastPreparingBatchId: null,
entries: [],
lastRecoveryPlanId: null,
updatedAt: '2026-04-22T10:00:00.000Z',
},
}),
'utf8'
);
await expect(reader.read(teamName, laneId)).resolves.toEqual({
highWatermark: 11,
activeRunId: 'legacy-run',
capabilitySnapshotId: 'cap-1',
});
});
it('reports missing lane storage when an active lane index entry has no lane dir or state', async () => {
const teamName = 'team-epsilon';
const laneId = 'secondary:opencode:alice';
await upsertOpenCodeRuntimeLaneIndexEntry({
teamsBasePath: tempDir,
teamName,
laneId,
state: 'active',
});
await expect(
inspectOpenCodeRuntimeLaneStorage({
teamsBasePath: tempDir,
teamName,
laneId,
})
).resolves.toEqual({
laneDirectoryExists: false,
hasStateOnDisk: false,
fileNames: [],
});
const result = await recoverStaleOpenCodeRuntimeLaneIndexEntry({
teamsBasePath: tempDir,
teamName,
laneId,
});
expect(result).toEqual({
stale: true,
degraded: true,
diagnostics: [
`OpenCode lane ${laneId} is marked active in lanes.json, but no lane state exists on disk.`,
],
});
await expect(readOpenCodeRuntimeLaneIndex(tempDir, teamName)).resolves.toMatchObject({
lanes: {
[laneId]: {
laneId,
state: 'degraded',
diagnostics: [
`OpenCode lane ${laneId} is marked active in lanes.json, but no lane state exists on disk.`,
],
},
},
});
});
it('quarantines malformed lanes.json and falls back to an empty index', async () => {
const teamName = 'team-zeta';
const runtimeDir = getOpenCodeTeamRuntimeDirectory(tempDir, teamName);
const filePath = getOpenCodeRuntimeLaneIndexPath(tempDir, teamName);
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
try {
await fs.mkdir(runtimeDir, { recursive: true });
await fs.writeFile(
filePath,
['{', ' "version": 1,', ' "updatedAt": "2026-04-22T10:00:00.000Z",', ' "lanes": {}', '}', '}'].join('\n'),
'utf8'
);
await expect(readOpenCodeRuntimeLaneIndex(tempDir, teamName)).resolves.toEqual({
version: 1,
updatedAt: expect.any(String),
lanes: {},
});
await expect(fs.readFile(filePath, 'utf8')).rejects.toThrow();
const runtimeEntries = await fs.readdir(runtimeDir);
expect(runtimeEntries.some((entry) => /^lanes\.invalid\.\d+\.json$/.test(entry))).toBe(true);
} finally {
warnSpy.mockRestore();
}
});
it('serializes concurrent lane index upserts without losing sibling lanes', async () => {
const teamName = 'team-eta';
await Promise.all([
upsertOpenCodeRuntimeLaneIndexEntry({
teamsBasePath: tempDir,
teamName,
laneId: 'secondary:opencode:bob',
state: 'active',
}),
upsertOpenCodeRuntimeLaneIndexEntry({
teamsBasePath: tempDir,
teamName,
laneId: 'secondary:opencode:jack',
state: 'active',
}),
upsertOpenCodeRuntimeLaneIndexEntry({
teamsBasePath: tempDir,
teamName,
laneId: 'secondary:opencode:tom',
state: 'active',
}),
]);
await expect(readOpenCodeRuntimeLaneIndex(tempDir, teamName)).resolves.toMatchObject({
lanes: {
'secondary:opencode:bob': { state: 'active' },
'secondary:opencode:jack': { state: 'active' },
'secondary:opencode:tom': { state: 'active' },
},
});
});
});