fix(changes): keep metadata-only ledger events manual
This commit is contained in:
parent
8f7712ed74
commit
e48ecf664a
4 changed files with 107 additions and 3 deletions
|
|
@ -471,6 +471,15 @@ export class ReviewApplierService {
|
|||
);
|
||||
const relation = this.resolveLedgerRelation(ledgerSnippets);
|
||||
|
||||
if (hasUnavailableState) {
|
||||
return {
|
||||
handled: true,
|
||||
status: 'error',
|
||||
code: 'manual-review-required',
|
||||
error: 'Ledger content metadata is unavailable; manual review is required.',
|
||||
};
|
||||
}
|
||||
|
||||
if (!fullReject) {
|
||||
if (relation?.kind === 'rename' || relation?.kind === 'copy') {
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -105,7 +105,8 @@ interface LedgerEvent {
|
|||
| 'powershell_snapshot'
|
||||
| 'post_tool_hook_snapshot'
|
||||
| 'opencode_toolpart_write'
|
||||
| 'opencode_toolpart_edit';
|
||||
| 'opencode_toolpart_edit'
|
||||
| 'opencode_toolpart_apply_patch';
|
||||
operation: 'create' | 'modify' | 'delete';
|
||||
confidence: LedgerConfidence;
|
||||
workspaceRoot: string;
|
||||
|
|
@ -1135,6 +1136,7 @@ export class TaskChangeLedgerReader {
|
|||
case 'notebook_edit':
|
||||
return 'NotebookEdit';
|
||||
case 'opencode_toolpart_edit':
|
||||
case 'opencode_toolpart_apply_patch':
|
||||
return 'Edit';
|
||||
case 'bash_simulated_sed':
|
||||
case 'shell_snapshot':
|
||||
|
|
|
|||
|
|
@ -189,6 +189,72 @@ describe('ReviewApplierService', () => {
|
|||
expect(unlink).toHaveBeenCalledWith(filePath);
|
||||
});
|
||||
|
||||
it('ledger create reject blocks metadata-only create even when final hash is known', async () => {
|
||||
const fsPromises = await import('fs/promises');
|
||||
const readFile = fsPromises.readFile as unknown as ReturnType<typeof vi.fn>;
|
||||
const unlink = fsPromises.unlink as unknown as ReturnType<typeof vi.fn>;
|
||||
|
||||
const { ReviewApplierService } = await import('@main/services/team/ReviewApplierService');
|
||||
const svc = new ReviewApplierService();
|
||||
const filePath = '/tmp/metadata-only-created.txt';
|
||||
const content = 'created\n';
|
||||
|
||||
const res = await svc.applyReviewDecisions(
|
||||
{
|
||||
teamName: 'team',
|
||||
decisions: [{ filePath, fileDecision: 'rejected', hunkDecisions: { 0: 'rejected' } }],
|
||||
},
|
||||
new Map([
|
||||
[
|
||||
filePath,
|
||||
{
|
||||
filePath,
|
||||
relativePath: 'metadata-only-created.txt',
|
||||
snippets: [
|
||||
{
|
||||
toolUseId: 'ledger-1',
|
||||
filePath,
|
||||
toolName: 'Edit',
|
||||
type: 'edit',
|
||||
oldString: '',
|
||||
newString: '',
|
||||
replaceAll: false,
|
||||
timestamp: '2026-03-01T10:00:00.000Z',
|
||||
isError: false,
|
||||
ledger: {
|
||||
eventId: 'event-1',
|
||||
source: 'ledger-snapshot',
|
||||
confidence: 'medium',
|
||||
originalFullContent: null,
|
||||
modifiedFullContent: null,
|
||||
beforeHash: null,
|
||||
afterHash: sha(content),
|
||||
operation: 'create',
|
||||
beforeState: {
|
||||
exists: false,
|
||||
unavailableReason: 'gitless-before-content-unavailable',
|
||||
},
|
||||
afterState: { exists: true, sha256: sha(content), sizeBytes: content.length },
|
||||
},
|
||||
},
|
||||
],
|
||||
linesAdded: 0,
|
||||
linesRemoved: 0,
|
||||
isNewFile: true,
|
||||
originalFullContent: null,
|
||||
modifiedFullContent: null,
|
||||
contentSource: 'ledger-snapshot',
|
||||
},
|
||||
],
|
||||
])
|
||||
);
|
||||
|
||||
expect(res.applied).toBe(0);
|
||||
expect(res.errors[0]?.code).toBe('manual-review-required');
|
||||
expect(readFile).not.toHaveBeenCalled();
|
||||
expect(unlink).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('ledger create reject blocks when current hash changed', async () => {
|
||||
const fsPromises = await import('fs/promises');
|
||||
const readFile = fsPromises.readFile as unknown as ReturnType<typeof vi.fn>;
|
||||
|
|
|
|||
|
|
@ -241,6 +241,32 @@ describe('TaskChangeLedgerReader', () => {
|
|||
linesAdded: 1,
|
||||
linesRemoved: 1,
|
||||
},
|
||||
{
|
||||
schemaVersion: 1,
|
||||
eventId: 'event-apply-patch',
|
||||
taskId: TASK_ID,
|
||||
taskRef: TASK_ID,
|
||||
taskRefKind: 'canonical',
|
||||
phase: 'work',
|
||||
executionSeq: 3,
|
||||
sessionId: 'opencode-session-1',
|
||||
memberName: 'bob',
|
||||
toolUseId: 'part-apply-patch',
|
||||
source: 'opencode_toolpart_apply_patch',
|
||||
operation: 'modify',
|
||||
confidence: 'medium',
|
||||
workspaceRoot: '/repo',
|
||||
filePath: '/repo/src/new.ts',
|
||||
relativePath: 'src/new.ts',
|
||||
timestamp: '2026-03-01T10:02:00.000Z',
|
||||
toolStatus: 'succeeded',
|
||||
before: null,
|
||||
after: null,
|
||||
beforeState: { exists: true, unavailableReason: 'opencode-apply-patch-before-content-unavailable' },
|
||||
afterState: { exists: true, unavailableReason: 'opencode-apply-patch-final-content-unavailable' },
|
||||
linesAdded: 0,
|
||||
linesRemoved: 0,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
|
|
@ -254,8 +280,9 @@ describe('TaskChangeLedgerReader', () => {
|
|||
});
|
||||
|
||||
const snippets = result?.files[0]?.snippets ?? [];
|
||||
expect(snippets.map((snippet) => snippet.toolName)).toEqual(['Write', 'Edit']);
|
||||
expect(snippets.map((snippet) => snippet.type)).toEqual(['write-new', 'edit']);
|
||||
expect(snippets.map((snippet) => snippet.toolName)).toEqual(['Write', 'Edit', 'Edit']);
|
||||
expect(snippets.map((snippet) => snippet.type)).toEqual(['write-new', 'edit', 'edit']);
|
||||
expect(snippets[2]?.ledger?.source).toBe('ledger-snapshot');
|
||||
});
|
||||
|
||||
it('groups rename relations in summary-only bundles without losing absolute paths', async () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue