fix(changes): pass verified opencode delivery context
This commit is contained in:
parent
ee590d0a62
commit
53012ed623
4 changed files with 43 additions and 17 deletions
|
|
@ -9,7 +9,7 @@ import {
|
|||
} from '@shared/utils/taskChangeState';
|
||||
import { createHash } from 'crypto';
|
||||
import { existsSync } from 'fs';
|
||||
import { mkdtemp, readdir, readFile, rm, stat, writeFile } from 'fs/promises';
|
||||
import { chmod, mkdtemp, readdir, readFile, rm, stat, writeFile } from 'fs/promises';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
|
||||
|
|
@ -77,6 +77,7 @@ interface OpenCodeBackfillAttempt {
|
|||
|
||||
interface OpenCodeDeliveryContextTempFile {
|
||||
filePath: string | null;
|
||||
hash: string | null;
|
||||
cleanup: () => Promise<void>;
|
||||
}
|
||||
|
||||
|
|
@ -513,7 +514,12 @@ export class ChangeExtractorService {
|
|||
workspaceRoot,
|
||||
attributionMode: OPEN_CODE_AUTO_BACKFILL_ATTRIBUTION_MODE,
|
||||
...(backfillMemberName ? { memberName: backfillMemberName } : {}),
|
||||
...(deliveryContext.filePath ? { deliveryContextPath: deliveryContext.filePath } : {}),
|
||||
...(deliveryContext.filePath
|
||||
? {
|
||||
deliveryContextPath: deliveryContext.filePath,
|
||||
deliveryContextHash: deliveryContext.hash ?? undefined,
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
void appendOpenCodeTaskChangeDiag({
|
||||
event: 'backfill_result',
|
||||
|
|
@ -661,27 +667,26 @@ export class ChangeExtractorService {
|
|||
records: Awaited<ReturnType<ChangeExtractorService['readOpenCodeDeliveryContextRecords']>>
|
||||
): Promise<OpenCodeDeliveryContextTempFile> {
|
||||
if (records.length === 0) {
|
||||
return { filePath: null, cleanup: async () => undefined };
|
||||
return { filePath: null, hash: null, cleanup: async () => undefined };
|
||||
}
|
||||
|
||||
const dir = await mkdtemp(path.join(os.tmpdir(), 'claude-team-opencode-ledger-context-'));
|
||||
await chmod(dir, 0o700).catch(() => undefined);
|
||||
const filePath = path.join(dir, 'delivery-context.json');
|
||||
await writeFile(
|
||||
filePath,
|
||||
`${JSON.stringify(
|
||||
{
|
||||
schemaVersion: 1,
|
||||
teamName,
|
||||
taskId,
|
||||
records,
|
||||
},
|
||||
null,
|
||||
2
|
||||
)}\n`,
|
||||
{ encoding: 'utf8', mode: 0o600 }
|
||||
);
|
||||
const rawContext = `${JSON.stringify(
|
||||
{
|
||||
schemaVersion: 1,
|
||||
teamName,
|
||||
taskId,
|
||||
records,
|
||||
},
|
||||
null,
|
||||
2
|
||||
)}\n`;
|
||||
await writeFile(filePath, rawContext, { encoding: 'utf8', mode: 0o600 });
|
||||
return {
|
||||
filePath,
|
||||
hash: createHash('sha256').update(rawContext).digest('hex'),
|
||||
cleanup: async () => {
|
||||
await rm(dir, { recursive: true, force: true }).catch(() => undefined);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -239,6 +239,7 @@ export interface OpenCodeBackfillTaskLedgerCommandBody {
|
|||
projectDir?: string;
|
||||
workspaceRoot?: string;
|
||||
deliveryContextPath?: string;
|
||||
deliveryContextHash?: string;
|
||||
attributionMode?: OpenCodeBackfillTaskLedgerAttributionMode;
|
||||
dryRun?: boolean;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { createHash } from 'crypto';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import * as fs from 'fs/promises';
|
||||
|
|
@ -999,7 +1000,12 @@ describe('ChangeExtractorService', () => {
|
|||
await fs.mkdir(projectPath, { recursive: true });
|
||||
await writeOpenCodeDeliveryLedger(tmpDir);
|
||||
|
||||
let deliveryContextHashVerified = false;
|
||||
const backfillOpenCodeTaskLedger = vi.fn(async (input: any) => {
|
||||
deliveryContextHashVerified =
|
||||
createHash('sha256')
|
||||
.update(await fs.readFile(input.deliveryContextPath, 'utf8'))
|
||||
.digest('hex') === input.deliveryContextHash;
|
||||
await writeOpenCodeLedgerBundle(input.projectDir, projectPath);
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
|
|
@ -1069,6 +1075,12 @@ describe('ChangeExtractorService', () => {
|
|||
attributionMode: 'strict-delivery',
|
||||
})
|
||||
);
|
||||
const backfillInput = backfillOpenCodeTaskLedger.mock.calls[0]?.[0];
|
||||
expect(backfillInput.deliveryContextPath).toEqual(
|
||||
expect.stringContaining('delivery-context.json')
|
||||
);
|
||||
expect(backfillInput.deliveryContextHash).toMatch(/^[a-f0-9]{64}$/);
|
||||
expect(deliveryContextHashVerified).toBe(true);
|
||||
expect(backfillOpenCodeTaskLedger.mock.calls[0]?.[0]).not.toHaveProperty('evidenceMode');
|
||||
expect(workerClient.computeTaskChanges).not.toHaveBeenCalled();
|
||||
});
|
||||
|
|
@ -1513,6 +1525,7 @@ describe('ChangeExtractorService', () => {
|
|||
projectDir,
|
||||
workspaceRoot: projectPath,
|
||||
deliveryContextPath: expect.stringContaining('delivery-context.json'),
|
||||
deliveryContextHash: expect.stringMatching(/^[a-f0-9]{64}$/),
|
||||
attributionMode: 'strict-delivery',
|
||||
})
|
||||
);
|
||||
|
|
@ -1621,6 +1634,7 @@ describe('ChangeExtractorService', () => {
|
|||
expect(backfillOpenCodeTaskLedger.mock.calls[0]?.[0]?.deliveryContextPath).toEqual(
|
||||
expect.stringContaining('delivery-context.json')
|
||||
);
|
||||
expect(backfillOpenCodeTaskLedger.mock.calls[0]?.[0]?.deliveryContextHash).toMatch(/^[a-f0-9]{64}$/);
|
||||
});
|
||||
|
||||
it('does not cache negative OpenCode backfill while delivery context already exists', async () => {
|
||||
|
|
@ -1732,8 +1746,10 @@ describe('ChangeExtractorService', () => {
|
|||
expect(backfillOpenCodeTaskLedger.mock.calls[0]?.[0]?.deliveryContextPath).toEqual(
|
||||
expect.stringContaining('delivery-context.json')
|
||||
);
|
||||
expect(backfillOpenCodeTaskLedger.mock.calls[0]?.[0]?.deliveryContextHash).toMatch(/^[a-f0-9]{64}$/);
|
||||
expect(backfillOpenCodeTaskLedger.mock.calls[1]?.[0]?.deliveryContextPath).toEqual(
|
||||
expect.stringContaining('delivery-context.json')
|
||||
);
|
||||
expect(backfillOpenCodeTaskLedger.mock.calls[1]?.[0]?.deliveryContextHash).toMatch(/^[a-f0-9]{64}$/);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -170,6 +170,8 @@ describe('OpenCodeReadinessBridge', () => {
|
|||
taskDisplayId: 'abc12345',
|
||||
projectDir: '/claude/project',
|
||||
workspaceRoot: '/repo',
|
||||
deliveryContextPath: '/tmp/claude-team-opencode-ledger-context-test/delivery-context.json',
|
||||
deliveryContextHash: 'a'.repeat(64),
|
||||
})
|
||||
).resolves.toMatchObject({
|
||||
outcome: 'imported',
|
||||
|
|
@ -184,6 +186,8 @@ describe('OpenCodeReadinessBridge', () => {
|
|||
taskDisplayId: 'abc12345',
|
||||
projectDir: '/claude/project',
|
||||
workspaceRoot: '/repo',
|
||||
deliveryContextPath: '/tmp/claude-team-opencode-ledger-context-test/delivery-context.json',
|
||||
deliveryContextHash: 'a'.repeat(64),
|
||||
},
|
||||
{
|
||||
cwd: '/repo',
|
||||
|
|
|
|||
Loading…
Reference in a new issue