agent-ecosystem/src/shared/utils/__tests__/taskChangeReviewability.test.ts

257 lines
8.1 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { resolveTaskChangePresenceFromResult } from '../taskChangePresence';
import {
classifyTaskChangeReviewability,
EMPTY_INTERVAL_NO_EDITS_WARNING,
} from '../taskChangeReviewability';
import type { TaskChangeSetV2 } from '../../types';
function changeSet(overrides: Partial<TaskChangeSetV2> = {}): TaskChangeSetV2 {
return {
teamName: 'team-a',
taskId: 'task-a',
files: [],
totalFiles: 0,
totalLinesAdded: 0,
totalLinesRemoved: 0,
confidence: 'high',
computedAt: '2026-05-09T12:00:00.000Z',
scope: {
taskId: 'task-a',
memberName: 'alice',
startLine: 0,
endLine: 0,
startTimestamp: '2026-05-09T11:00:00.000Z',
endTimestamp: '2026-05-09T11:10:00.000Z',
toolUseIds: [],
filePaths: [],
confidence: { tier: 1, label: 'high', reason: 'test' },
},
warnings: [],
...overrides,
};
}
describe('taskChangeReviewability', () => {
it('treats changed files with non-blocking multi-scope diagnostics as reviewable', () => {
const result = changeSet({
files: [
{
filePath: '/repo/src/file.ts',
relativePath: 'src/file.ts',
snippets: [],
linesAdded: 1,
linesRemoved: 0,
isNewFile: true,
},
],
totalFiles: 1,
totalLinesAdded: 1,
warnings: [
'Task change ledger skipped attribution because multiple task scopes were active.',
],
});
expect(classifyTaskChangeReviewability(result).reviewability).toBe('reviewable');
expect(resolveTaskChangePresenceFromResult(result)).toBe('has_changes');
});
it('classifies warning-only multi-scope notices as diagnostic-only', () => {
const result = changeSet({
warnings: [
'Task change ledger skipped attribution because multiple task scopes were active.',
],
});
expect(classifyTaskChangeReviewability(result)).toMatchObject({
reviewability: 'diagnostic_only',
reasonCode: 'diagnostic_only',
userAction: 'inspect_diagnostics',
});
expect(resolveTaskChangePresenceFromResult(result)).toBeNull();
});
it('fails closed for unclassified warning-only summaries', () => {
const result = changeSet({ warnings: ['Unexpected ledger warning.'] });
expect(classifyTaskChangeReviewability(result).reviewability).toBe('attention_required');
expect(resolveTaskChangePresenceFromResult(result)).toBe('needs_attention');
});
it('keeps active no-edit intervals unknown instead of needs attention', () => {
const result = changeSet({
warnings: [EMPTY_INTERVAL_NO_EDITS_WARNING],
scope: {
...changeSet().scope,
startTimestamp: '2026-05-09T11:00:00.000Z',
endTimestamp: '',
toolUseIds: [],
},
});
expect(classifyTaskChangeReviewability(result).reviewability).toBe('unknown');
expect(resolveTaskChangePresenceFromResult(result)).toBeNull();
});
it('keeps active no-edit intervals fail-closed when blocking diagnostics are present', () => {
const result = changeSet({
warnings: [EMPTY_INTERVAL_NO_EDITS_WARNING, 'Task changes scan timed out.'],
scope: {
...changeSet().scope,
startTimestamp: '2026-05-09T11:00:00.000Z',
endTimestamp: '',
toolUseIds: [],
},
});
expect(classifyTaskChangeReviewability(result).reviewability).toBe('attention_required');
expect(resolveTaskChangePresenceFromResult(result)).toBe('needs_attention');
});
it('marks partial ledger evidence as attention required', () => {
const result = changeSet({
provenance: {
sourceKind: 'ledger',
sourceFingerprint: 'fingerprint',
integrity: 'partial',
},
});
expect(classifyTaskChangeReviewability(result).reviewability).toBe('attention_required');
expect(resolveTaskChangePresenceFromResult(result)).toBe('needs_attention');
});
it('deduplicates recovered ledger diagnostics from typed diagnostics and provenance', () => {
const result = changeSet({
reviewDiagnostics: [
{
code: 'ledger_integrity_recovered',
severity: 'warning',
reviewBlocking: true,
message: 'The task-change ledger was recovered from malformed journal lines.',
source: 'ledger',
},
],
provenance: {
sourceKind: 'ledger',
sourceFingerprint: 'fingerprint',
integrity: 'recovered',
},
});
const status = classifyTaskChangeReviewability(result);
expect(status.reviewability).toBe('attention_required');
expect(status.diagnostics).toHaveLength(1);
expect(status.diagnostics[0]?.code).toBe('ledger_integrity_recovered');
});
it('does not downgrade typed blocking diagnostics when legacy warnings duplicate them', () => {
const result = changeSet({
reviewDiagnostics: [
{
code: 'multi_scope_no_safe_diff',
severity: 'warning',
reviewBlocking: true,
message:
'Activity was observed while multiple task scopes were active, so file edits were not safely assigned to this task.',
source: 'ledger',
},
],
warnings: [
'Task change ledger skipped attribution because multiple task scopes were active.',
],
});
const status = classifyTaskChangeReviewability(result);
expect(status.reviewability).toBe('attention_required');
expect(status.diagnostics).toHaveLength(1);
expect(status.diagnostics[0]?.reviewBlocking).toBe(true);
});
it('upgrades duplicate diagnostics when legacy warnings are more strict', () => {
const result = changeSet({
reviewDiagnostics: [
{
code: 'legacy_warning',
severity: 'info',
reviewBlocking: false,
message: 'Unexpected ledger warning.',
source: 'summary',
},
],
warnings: ['Unexpected ledger warning.'],
});
const status = classifyTaskChangeReviewability(result);
expect(status.reviewability).toBe('attention_required');
expect(status.diagnostics).toHaveLength(1);
expect(status.diagnostics[0]).toMatchObject({
code: 'legacy_warning',
severity: 'warning',
reviewBlocking: true,
source: 'legacy',
});
});
it('fails closed when reported files are missing safe review details', () => {
const result = changeSet({
totalFiles: 2,
files: [
{
filePath: '/repo/src/file.ts',
relativePath: 'src/file.ts',
snippets: [],
linesAdded: 1,
linesRemoved: 0,
isNewFile: true,
},
],
totalLinesAdded: 1,
});
const status = classifyTaskChangeReviewability(result);
expect(status).toMatchObject({
reviewability: 'attention_required',
reasonCode: 'blocking_diagnostics',
userAction: 'review_diff',
});
expect(status.diagnostics).toContainEqual(
expect.objectContaining({
code: 'unsafe_or_untrusted_evidence',
reviewBlocking: true,
})
);
expect(resolveTaskChangePresenceFromResult(result)).toBe('needs_attention');
});
it('tolerates malformed cached scope and diagnostic shapes', () => {
const result = changeSet({
totalFiles: 'not-a-number' as unknown as number,
reviewDiagnostics: {} as unknown as TaskChangeSetV2['reviewDiagnostics'],
warnings: [EMPTY_INTERVAL_NO_EDITS_WARNING],
scope: {
taskId: 'task-a',
memberName: 'alice',
startTimestamp: '2026-05-09T11:00:00.000Z',
endTimestamp: '',
confidence: { tier: 2, label: 'medium', reason: 'legacy cache fixture' },
} as unknown as TaskChangeSetV2['scope'],
});
expect(classifyTaskChangeReviewability(result).reviewability).toBe('unknown');
expect(resolveTaskChangePresenceFromResult(result)).toBeNull();
});
it('confirms empty high-confidence summaries as no changes', () => {
const result = changeSet();
expect(classifyTaskChangeReviewability(result).reviewability).toBe('none');
expect(resolveTaskChangePresenceFromResult(result)).toBe('no_changes');
});
});