tests/decision-budget.test.ts
Rama main · réplica de lectura del repositorio original.
import assert from 'node:assert/strict';
import test from 'node:test';
import { CandidateDecisionEvaluator, decisionBudgetStatus } from '../src/application/decisions.js';
import { JevClient } from '../src/adapters/decision/jev-client.js';
import { decisionIdentity, summarizeDecision } from '../src/domain/decision-policy.js';
import { configSchema } from '../src/domain/config.js';
import { configDigest, pipelineId } from '../src/domain/identity.js';
import { DecisionModelError } from '../src/ports/decision.js';
import { decisionFixture, decisionResult, decisionSubject } from './decision-helpers.js';
const signal = () => new AbortController().signal;
test('concurrent identical decisions reuse one durable result; threshold changes reuse evidence without replenishing budgets', async () => {
const { evaluator, calls, store, config } = await decisionFixture();
const results = await Promise.all([evaluator.evaluate(decisionSubject, signal()), evaluator.evaluate(decisionSubject, signal())]);
assert.equal(calls(), 1); assert.equal(results[0]!.summary.route, 'continue'); assert.equal(results[1]!.attempted, false);
const record = (await store.list('owner/repo'))[0]!;
assert.equal(summarizeDecision(record, { ...config, minConfidence: 0.99 }).route, 'review');
assert.equal((await store.ledger('owner/repo'))!.days[new Date().toISOString().slice(0, 10)]!.tokens, 120);
assert.deepEqual(await store.list('other/repo'), []); assert.throws(() => store.read('../secret'), /Invalid/);
});
test('unknown usage survives restart and failed decisions respect their original retry allowance and Retry-After', async () => {
let time = Date.parse('2026-10-19T00:00:00Z'), requests = 0;
const model = { evaluate: async () => { requests++; throw new DecisionModelError('rate_limited', true, 120000); } };
const { evaluator, config, store } = await decisionFixture({ maxAttempts: 2 }, model, () => time);
await evaluator.evaluate(decisionSubject, signal()); time += 61000;
const resumed = new CandidateDecisionEvaluator('owner/repo', config, store, model, () => time);
assert.equal((await resumed.evaluate(decisionSubject, signal())).attempted, false); assert.equal(requests, 1);
time += 61000; await resumed.evaluate(decisionSubject, signal()); time += 86400000;
await resumed.evaluate(decisionSubject, signal()); assert.equal(requests, 2);
const ledger = (await store.ledger('owner/repo'))!;
assert.deepEqual(ledger.days['2026-10-19'], { calls: 2, tokens: config.limits.tokensPerCall * 2 });
assert.equal(Object.keys(ledger.reservations).length, 2);
});
test('interrupted decision receipts recover conservatively instead of replaying immediately', async () => {
let time = Date.parse('2026-10-19T12:00:00Z');
const { store, config, evaluator, calls } = await decisionFixture({}, undefined, () => time);
const identity = decisionIdentity(decisionSubject, config.model);
await store.save({ schemaVersion: 1, ...identity, repository: 'owner/repo', candidateId: decisionSubject.candidateId,
evidenceDigest: decisionSubject.evidenceDigest, intent: 'bugfix', questionVersion: 1, requestedModel: config.model,
status: 'running', attempts: 1, createdAt: new Date(time).toISOString(), updatedAt: new Date(time).toISOString() });
const deferred = await evaluator.evaluate(decisionSubject, signal());
assert.equal(deferred.summary.route, 'review'); assert.equal(calls(), 0);
time += 61000; await evaluator.evaluate(decisionSubject, signal()); assert.equal(calls(), 1);
assert.equal((await store.read(identity.id))!.attempts, 2);
});
test('daily decision admission serializes distinct requests and resumes on a new UTC day', async () => {
let time = Date.parse('2026-10-19T23:59:59Z');
const { evaluator, calls, config, store } = await decisionFixture({ limits: { dailyCalls: 1 } }, undefined, () => time);
const second = { ...decisionSubject, issue: 8, candidateId: 'd'.repeat(24) };
const results = await Promise.all([evaluator.evaluate(decisionSubject, signal()), evaluator.evaluate(second, signal())]);
assert.equal(calls(), 1); assert.equal(results[1]!.summary.status, 'deferred');
assert.equal(decisionBudgetStatus(await store.ledger('owner/repo'), config, time).available, false);
time += 2000; await evaluator.evaluate(second, signal()); assert.equal(calls(), 2);
});
test('late settlement stays on its reserved day and records actual usage above the token reservation', async () => {
let time = Date.parse('2026-10-19T23:59:59Z');
const result = decisionResult(); result.usage.input_tokens = 25000;
const { evaluator, store } = await decisionFixture({}, { evaluate: async () => { time += 2000; return result; } }, () => time);
await evaluator.evaluate(decisionSubject, signal());
const ledger = (await store.ledger('owner/repo'))!;
assert.equal(ledger.days['2026-10-19']!.tokens, 25020); assert.equal(Object.keys(ledger.reservations).length, 0);
});
test('failed durable settlement never becomes a successful shadow-mode fallback', async () => {
const { config, store } = await decisionFixture(); let writes = 0;
const original = store.saveLedger.bind(store);
store.saveLedger = async ledger => { if (++writes === 2) throw new Error('synthetic disk failure'); await original(ledger); };
const evaluator = new CandidateDecisionEvaluator('owner/repo', config, store, { evaluate: async () => decisionResult() });
await assert.rejects(evaluator.evaluate(decisionSubject, signal()), /disk failure/);
assert.equal((await store.list('owner/repo'))[0]!.status, 'running');
assert.equal(Object.keys((await store.ledger('owner/repo'))!.reservations).length, 1);
});
test('optional admission-only Jev settings preserve existing task and goal configuration identity', () => {
const config = configSchema.parse({ repository: 'owner/repo' });
const enabled = configSchema.parse({ ...config, decision: { enabled: true, mode: 'gate' } });
const input = { baseSha: 'a'.repeat(40), headSha: 'b'.repeat(40) };
assert.equal(configDigest(config), configDigest(enabled)); assert.equal(pipelineId(input, config), pipelineId(input, enabled));
assert.throws(() => configSchema.parse({ ...config, decision: { model: 'jev-latest' } }));
assert.throws(() => configSchema.parse({ ...config, decision: { baseUrl: 'https://attacker.invalid' } }));
assert.throws(() => configSchema.parse({ ...config, decision: { limits: { dailyTokens: 1000, tokensPerCall: 2000 } } }));
});
test('local credential preflight failures retain an inspectable record without spending a remote-call allowance', async () => {
const { config, store } = await decisionFixture(); let requests = 0;
const client = new JevClient(config, undefined, async () => { requests++; return Response.json(decisionResult()); });
const evaluator = new CandidateDecisionEvaluator('owner/repo', config, store, client);
const result = await evaluator.evaluate(decisionSubject, signal());
assert.equal(result.summary.route, 'review'); assert.equal(result.attempted, false); assert.equal(requests, 0);
assert.equal((await store.list('owner/repo'))[0]!.failure, 'missing_key');
assert.equal((await store.list('owner/repo'))[0]!.attempts, 0); assert.equal(await store.ledger('owner/repo'), undefined);
});
test('changing an operational decision allowance unblocks admission without discarding the prior ledger', async () => {
const { evaluator, config, store, calls } = await decisionFixture({ limits: { dailyCalls: 1 } });
const second = { ...decisionSubject, issue: 8 };
await evaluator.evaluate(decisionSubject, signal()); await evaluator.evaluate(second, signal());
config.limits.dailyCalls = 2; await evaluator.evaluate(second, signal());
assert.equal(calls(), 2); assert.equal(decisionBudgetStatus(await store.ledger('owner/repo'), config).used.calls, 2);
});
test('a decision for changed input cannot reuse the earlier evidence record', async () => {
const { evaluator, calls, store } = await decisionFixture();
const first = await evaluator.evaluate(decisionSubject, signal());
const next = await evaluator.evaluate({ ...decisionSubject, request: 'New acceptance behavior.', evidenceDigest: 'e'.repeat(24) }, signal());
assert.notEqual(first.summary.id, next.summary.id); assert.equal(calls(), 2);
assert.equal((await store.list('owner/repo')).length, 2);
});