blob: 335deb8195ecb89d1e8478b85d22ba3ecf0f243a [file]
import assert from 'node:assert/strict';
import { describe, test } from 'node:test';
import {
type FixedPromptTaskWalEvent,
type PromptCandidateRationale,
} from '../fixed-prompt-controller.js';
import {
buildRsiControllerAttribution,
projectRsiPromptAttribution,
} from '../rsi-controller-attribution.js';
import type { RsiRoundAnalysis } from '../rsi-round-analysis.js';
import type { PromptAcceptanceResult } from '../prompt-acceptance-policy.js';
import { tokenSummary } from './helpers/cell-output-fixtures.js';
describe('RSI controller attribution', () => {
test('treats a direct signal reference as authoritative without a coarse fallback', () => {
const analysis: RsiRoundAnalysis = {
heldInTaskSetHash: 'sha256:held-in',
transitionVsLastKept: [],
transitionVsPreviousCandidate: [],
coverageRegressionTaskIds: ['task-a'],
errorClassDistribution: [],
toolFailureClusters: [],
signals: [{ id: 'rsi-sig:coverage', kind: 'coverage_regression', taskIds: ['task-a'] }],
};
const attribution = buildRsiControllerAttribution({
runId: 'run-1',
roundId: 'round-1',
candidateCommitSha: 'candidate-sha',
candidateRationaleHash: 'sha256:rationale',
candidateRationale: {
editedSurface: 'system_prompt',
evidenceRefs: ['rsi-sig:coverage'],
hypothesis: 'restore coverage for the unstable held-in task',
targetedFix: 'make the required artifact explicit',
predictedFixes: ['task-a'],
riskTasks: [],
},
analysis,
heldInTaskIds: ['task-a'],
lastKeptEvents: [completed({ taskId: 'task-a', passed: false })],
candidateEvents: [completed({ taskId: 'task-a', passed: true })],
decision: acceptanceResult({ decision: 'keep', reason: 'held_in_improved' }),
});
assert.equal(attribution.rootCauseSignalMatch, 'matched');
});
test('compares candidate rationale predictions to held-in outcomes', () => {
const rationale: PromptCandidateRationale = {
editedSurface: 'system_prompt',
failurePattern: 'coverage_regression',
evidenceRefs: ['rsi-sig:coverage'],
hypothesis: 'restore coverage for the unstable held-in task',
targetedFix: 'keep artifact creation requirements explicit',
predictedFixes: ['task-a', 'task-b'],
riskTasks: ['task-c'],
};
const analysis: RsiRoundAnalysis = {
heldInTaskSetHash: 'sha256:held-in',
transitionVsLastKept: [
{ taskId: 'task-a', from: 'fail', to: 'pass' },
{ taskId: 'task-b', from: 'pass', to: 'unscored' },
{ taskId: 'task-c', from: 'pass', to: 'fail' },
{ taskId: 'task-d', from: 'fail', to: 'pass' },
],
transitionVsPreviousCandidate: [],
coverageRegressionTaskIds: ['task-b'],
errorClassDistribution: [{ errorClass: 'max_tokens', count: 1 }],
toolFailureClusters: [],
signals: [{ id: 'rsi-sig:coverage', kind: 'coverage_regression', taskIds: ['task-b'] }],
};
const attribution = buildRsiControllerAttribution({
runId: 'run-1',
roundId: 'round-0',
candidateCommitSha: 'commit-1',
candidateRationaleHash: 'sha256:rationale',
candidateRationale: rationale,
analysis,
heldInTaskIds: ['task-a', 'task-b', 'task-c', 'task-d'],
lastKeptEvents: [
completed({ taskId: 'task-a', passed: false }),
completed({ taskId: 'task-b', passed: true }),
completed({ taskId: 'task-c', passed: true }),
completed({ taskId: 'task-d', passed: false }),
],
candidateEvents: [
completed({ taskId: 'task-a', passed: true }),
completed({ taskId: 'task-b', passed: false, scored: false, errorClass: 'max_tokens' }),
completed({ taskId: 'task-c', passed: false }),
completed({ taskId: 'task-d', passed: true }),
completed({ taskId: 'held-out-secret', passed: false }),
],
decision: acceptanceResult({ reason: 'coverage_regressed', decision: 'discard' }),
});
assert.deepEqual(attribution.predictedFixes, [
{ taskId: 'task-a', outcome: 'improved' },
{ taskId: 'task-b', outcome: 'unscored' },
]);
assert.deepEqual(attribution.riskTasks, [{ taskId: 'task-c', outcome: 'regressed' }]);
assert.deepEqual(attribution.unexpectedHeldInFlips, [
{ taskId: 'task-d', from: 'fail', to: 'pass' },
]);
assert.deepEqual(attribution.evidenceRefs, ['rsi-sig:coverage']);
assert.equal(attribution.rootCauseSignalMatch, 'matched');
assert.equal(JSON.stringify(attribution).includes('held-out-secret'), false);
});
test('projects prompt attribution without controller decision reasons', () => {
const attribution = buildRsiControllerAttribution({
runId: 'run-1',
roundId: 'round-0',
candidateCommitSha: 'commit-1',
candidateRationaleHash: 'sha256:rationale',
candidateRationale: {
editedSurface: 'system_prompt',
failurePattern: 'other',
evidenceRefs: [],
hypothesis: 'unknown held-in behavior changed',
targetedFix: 'make prompt wording simpler',
predictedFixes: [],
riskTasks: [],
},
analysis: {
heldInTaskSetHash: 'sha256:held-in',
transitionVsLastKept: [],
transitionVsPreviousCandidate: [],
coverageRegressionTaskIds: [],
errorClassDistribution: [],
toolFailureClusters: [],
signals: [],
},
heldInTaskIds: ['task-a'],
lastKeptEvents: [completed({ taskId: 'task-a', passed: true })],
candidateEvents: [completed({ taskId: 'task-a', passed: true })],
decision: acceptanceResult({ decision: 'discard', reason: 'held_out_regressed' }),
});
const promptAttribution = projectRsiPromptAttribution(attribution);
assert.equal('candidateCommitSha' in promptAttribution, false);
assert.equal('decisionReason' in promptAttribution, false);
assert.deepEqual(promptAttribution.predictedFixes, []);
assert.deepEqual(promptAttribution.riskTasks, []);
assert.deepEqual(promptAttribution.unexpectedHeldInFlips, []);
assert.equal(promptAttribution.rootCauseSignalMatch, 'unknown');
assert.equal(JSON.stringify(promptAttribution).includes('held_out_regressed'), false);
assert.equal(JSON.stringify(promptAttribution).includes('coverage_regressed'), false);
});
test('keeps root cause match unknown when the rationale cites no evidence refs', () => {
const attribution = buildRsiControllerAttribution({
runId: 'run-1',
roundId: 'round-0',
candidateCommitSha: 'commit-1',
candidateRationaleHash: 'sha256:rationale',
candidateRationale: {
editedSurface: 'system_prompt',
failurePattern: 'coverage_regression',
evidenceRefs: [],
hypothesis: 'coverage fell after the previous prompt change',
targetedFix: 'make prompt wording simpler',
predictedFixes: [],
riskTasks: [],
},
analysis: {
heldInTaskSetHash: 'sha256:held-in',
transitionVsLastKept: [],
transitionVsPreviousCandidate: [],
coverageRegressionTaskIds: ['task-a'],
errorClassDistribution: [],
toolFailureClusters: [],
signals: [{ id: 'rsi-sig:coverage', kind: 'coverage_regression', taskIds: ['task-a'] }],
},
heldInTaskIds: ['task-a'],
lastKeptEvents: [completed({ taskId: 'task-a', passed: true })],
candidateEvents: [completed({ taskId: 'task-a', passed: false, scored: false })],
decision: acceptanceResult({ decision: 'discard', reason: 'coverage_regressed' }),
});
assert.equal(attribution.rootCauseSignalMatch, 'unknown');
});
test('matches root cause only against cited analysis signals', () => {
const analysis: RsiRoundAnalysis = {
heldInTaskSetHash: 'sha256:held-in',
transitionVsLastKept: [],
transitionVsPreviousCandidate: [],
coverageRegressionTaskIds: ['task-a'],
errorClassDistribution: [],
toolFailureClusters: [],
signals: [
{ id: 'rsi-sig:coverage', kind: 'coverage_regression', taskIds: ['task-a'] },
{
id: 'rsi-sig:tool',
kind: 'tool_failure_cluster',
taskIds: ['task-b'],
cluster: { name: 'shell', count: 1, taskIds: ['task-b'] },
},
],
};
const matched = buildRsiControllerAttribution({
runId: 'run-1',
roundId: 'round-0',
candidateCommitSha: 'commit-1',
candidateRationaleHash: 'sha256:rationale',
candidateRationale: {
editedSurface: 'system_prompt',
failurePattern: 'coverage_regression',
evidenceRefs: ['rsi-sig:coverage'],
hypothesis: 'coverage fell after the previous prompt change',
targetedFix: 'restore the missing artifact instruction',
predictedFixes: [],
riskTasks: [],
},
analysis,
heldInTaskIds: ['task-a'],
lastKeptEvents: [completed({ taskId: 'task-a', passed: true })],
candidateEvents: [completed({ taskId: 'task-a', passed: false, scored: false })],
decision: acceptanceResult({ decision: 'discard', reason: 'coverage_regressed' }),
});
const contradicted = buildRsiControllerAttribution({
runId: 'run-1',
roundId: 'round-0',
candidateCommitSha: 'commit-1',
candidateRationaleHash: 'sha256:rationale',
candidateRationale: {
editedSurface: 'system_prompt',
failurePattern: 'coverage_regression',
evidenceRefs: ['rsi-sig:tool'],
hypothesis: 'coverage fell after the previous prompt change',
targetedFix: 'restore the missing artifact instruction',
predictedFixes: [],
riskTasks: [],
},
analysis,
heldInTaskIds: ['task-a'],
lastKeptEvents: [completed({ taskId: 'task-a', passed: true })],
candidateEvents: [completed({ taskId: 'task-a', passed: false, scored: false })],
decision: acceptanceResult({ decision: 'discard', reason: 'coverage_regressed' }),
});
assert.equal(matched.rootCauseSignalMatch, 'matched');
assert.equal(contradicted.rootCauseSignalMatch, 'contradicted');
});
test('matches runtime error root cause against cited error-class signals', () => {
const analysis: RsiRoundAnalysis = {
heldInTaskSetHash: 'sha256:held-in',
transitionVsLastKept: [],
transitionVsPreviousCandidate: [],
coverageRegressionTaskIds: [],
errorClassDistribution: [{ errorClass: 'runtime_error', count: 1 }],
toolFailureClusters: [],
signals: [
{
id: 'rsi-sig:runtime',
kind: 'error_class',
taskIds: ['task-a'],
errorClass: 'runtime_error',
count: 1,
},
{
id: 'rsi-sig:max-tokens',
kind: 'error_class',
taskIds: ['task-b'],
errorClass: 'max_tokens',
count: 1,
},
],
};
const matched = buildRsiControllerAttribution({
runId: 'run-1',
roundId: 'round-0',
candidateCommitSha: 'commit-1',
candidateRationaleHash: 'sha256:rationale',
candidateRationale: {
editedSurface: 'system_prompt',
failurePattern: 'runtime_error',
evidenceRefs: ['rsi-sig:runtime'],
hypothesis: 'runtime errors block held-in progress',
targetedFix: 'make execution constraints explicit',
predictedFixes: [],
riskTasks: [],
},
analysis,
heldInTaskIds: ['task-a'],
lastKeptEvents: [completed({ taskId: 'task-a', passed: true })],
candidateEvents: [
completed({ taskId: 'task-a', passed: false, errorClass: 'runtime_error' }),
],
decision: acceptanceResult({ decision: 'discard', reason: 'coverage_regressed' }),
});
const contradicted = buildRsiControllerAttribution({
runId: 'run-1',
roundId: 'round-0',
candidateCommitSha: 'commit-1',
candidateRationaleHash: 'sha256:rationale',
candidateRationale: {
editedSurface: 'system_prompt',
failurePattern: 'runtime_error',
evidenceRefs: ['rsi-sig:max-tokens'],
hypothesis: 'runtime errors block held-in progress',
targetedFix: 'make execution constraints explicit',
predictedFixes: [],
riskTasks: [],
},
analysis,
heldInTaskIds: ['task-a'],
lastKeptEvents: [completed({ taskId: 'task-a', passed: true })],
candidateEvents: [
completed({ taskId: 'task-a', passed: false, errorClass: 'runtime_error' }),
],
decision: acceptanceResult({ decision: 'discard', reason: 'coverage_regressed' }),
});
assert.equal(matched.rootCauseSignalMatch, 'matched');
assert.equal(contradicted.rootCauseSignalMatch, 'contradicted');
});
test('matches root cause against prompt-time analysis when post-eval signals disappear', () => {
const attribution = buildRsiControllerAttribution({
runId: 'run-1',
roundId: 'round-0',
candidateCommitSha: 'commit-1',
candidateRationaleHash: 'sha256:rationale',
candidateRationale: {
editedSurface: 'system_prompt',
failurePattern: 'coverage_regression',
evidenceRefs: ['rsi-sig:coverage'],
hypothesis: 'coverage fell before this candidate',
targetedFix: 'restore the missing completion requirement',
predictedFixes: ['task-a'],
riskTasks: [],
},
promptTimeAnalysis: {
heldInTaskSetHash: 'sha256:held-in-before',
transitionVsLastKept: [],
transitionVsPreviousCandidate: [],
coverageRegressionTaskIds: ['task-a'],
errorClassDistribution: [],
toolFailureClusters: [],
signals: [{ id: 'rsi-sig:coverage', kind: 'coverage_regression', taskIds: ['task-a'] }],
},
analysis: {
heldInTaskSetHash: 'sha256:held-in-after',
transitionVsLastKept: [{ taskId: 'task-a', from: 'fail', to: 'pass' }],
transitionVsPreviousCandidate: [],
coverageRegressionTaskIds: [],
errorClassDistribution: [],
toolFailureClusters: [],
signals: [],
},
heldInTaskIds: ['task-a'],
lastKeptEvents: [completed({ taskId: 'task-a', passed: false })],
candidateEvents: [completed({ taskId: 'task-a', passed: true })],
decision: acceptanceResult({ decision: 'keep', reason: 'held_in_improved' }),
});
assert.equal(attribution.heldInTaskSetHash, 'sha256:held-in-after');
assert.deepEqual(attribution.predictedFixes, [{ taskId: 'task-a', outcome: 'improved' }]);
assert.equal(attribution.rootCauseSignalMatch, 'matched');
});
});
function completed(input: {
taskId: string;
passed: boolean;
scored?: boolean;
errorClass?: string;
}): FixedPromptTaskWalEvent {
return {
schemaVersion: 1,
type: 'task_completed',
id: `${input.taskId}-event`,
ts: 1,
runId: 'run-1',
roundId: 'round-1',
taskId: input.taskId,
status: input.passed ? 'completed' : 'failed',
passed: input.passed,
scored: input.scored ?? true,
eligible: true,
...(input.errorClass ? { errorClass: input.errorClass } : {}),
promptHash: 'sha256:prompt',
tokenSummary: tokenSummary({ input: 1, output: 1, reasoning: 0, total: 2, costUsd: 0.01 }),
steps: 1,
durationMs: 10,
runtimeEventsPath: '/tmp/runtime-events.jsonl',
harbor: { reward: input.passed ? 1 : 0 },
};
}
function acceptanceResult(input: {
decision: 'keep' | 'discard';
reason: PromptAcceptanceResult['reason'];
}): PromptAcceptanceResult {
return {
runId: 'run-1',
roundId: 'round-0',
decision: input.decision,
reason: input.reason,
candidateCommitSha: 'commit-1',
previousLastKeptCommitSha: 'commit-0',
lastKeptCommitSha: input.decision === 'keep' ? 'commit-1' : 'commit-0',
previousHeldInReferencePassEligibleRate: 0.5,
heldInReferencePassEligibleRate: 0.5,
originalCommitSha: 'commit-0',
originalHeldOutPassEligibleRate: 0.5,
heldInPassRateNoiseBand: 0.1,
heldOutPassRateNoiseBand: 0.1,
rewardHackScan: { decision: 'clean' },
metrics: {
original: { heldOut: partitionSummary() },
lastKept: { heldIn: partitionSummary() },
candidate: { heldIn: partitionSummary(), heldOut: partitionSummary() },
},
};
}
function partitionSummary(): PromptAcceptanceResult['metrics']['candidate']['heldIn'] {
return {
taskCount: 0,
observed: 0,
eligible: 0,
scored: 0,
passed: 0,
passEligibleRate: null,
coverageRate: null,
unscoredTaskIds: [],
infraFailedTaskIds: [],
plumbingFailedTaskIds: [],
missingTaskIds: [],
};
}