Add evolver promotion controller

This commit is contained in:
Trae AI 2026-05-26 01:24:26 +08:00
parent a96b289ba7
commit 8cf33982ab
3 changed files with 205 additions and 0 deletions

View file

@ -4,3 +4,4 @@ export * from './resource-manager.js';
export * from './variant-generator.js';
export * from './sandbox-runner.js';
export * from './safety-gate.js';
export * from './promotion-controller.js';

View file

@ -0,0 +1,59 @@
import type { GateDecision, PromotionRecord, VariantSpec } from './types.js';
const appliedAt = '2026-05-25T00:00:00.000Z';
export function applyPromotion(
target: Record<string, unknown>,
variant: VariantSpec,
gateDecision: GateDecision,
): PromotionRecord | null {
if (gateDecision.decision !== 'allow') {
return null;
}
const previousState = structuredClone(target);
for (const operation of variant.patch.operations) {
if (operation.op === 'setParameter') {
const segments = operation.path.split('.');
setNestedValue(target, segments, operation.nextValue);
}
}
return {
id: `promo-${variant.id}-${gateDecision.id}`,
variantId: variant.id,
gateDecisionId: gateDecision.id,
appliedAt,
rollbackPlan: variant.rollbackPlan,
previousState,
nextState: structuredClone(target),
};
}
export function rollbackPromotion(
target: Record<string, unknown>,
record: PromotionRecord,
): void {
const keys = Object.keys(target);
for (const key of keys) {
delete target[key];
}
Object.assign(target, record.previousState);
}
function setNestedValue(
obj: Record<string, unknown>,
path: string[],
value: unknown,
): void {
if (path.length === 1) {
obj[path[0]] = value;
return;
}
const [head, ...rest] = path;
if (obj[head] === undefined || typeof obj[head] !== 'object' || obj[head] === null) {
obj[head] = {};
}
setNestedValue(obj[head] as Record<string, unknown>, rest, value);
}

View file

@ -0,0 +1,145 @@
import { describe, expect, it } from 'vitest';
import { applyPromotion, rollbackPromotion } from '../../../src/core/evolver/index.js';
import type { GateDecision, VariantSpec } from '../../../src/core/evolver/index.js';
const capturedAt = '2026-05-25T00:00:00.000Z';
function parameterVariant(): VariantSpec {
return {
id: 'variant-parameter-1',
planId: 'plan-1',
mutationType: 'parameter',
description: 'Adjust topK',
patch: {
operations: [
{
op: 'setParameter',
path: 'retrieval.topK',
previousValue: 5,
nextValue: 8,
},
],
},
expectedGain: [
{
metricId: 'task_success_rate',
expectedDelta: 0.05,
direction: 'increase',
},
],
riskLevel: 'low',
rollbackPlan: {
strategy: 'restore-previous-parameters',
operations: [
{
op: 'setParameter',
path: 'retrieval.topK',
previousValue: 8,
nextValue: 5,
},
],
},
provenance: [
{
source: 'synthetic-benchmark',
reference: 'run-1',
capturedAt,
},
],
};
}
function allowDecision(): GateDecision {
return {
id: 'gate-variant-parameter-1-report-1',
variantId: 'variant-parameter-1',
reportId: 'report-1',
decision: 'allow',
reasons: ['parameter variant passed promotion gate'],
decidedAt: capturedAt,
};
}
function needsReviewDecision(): GateDecision {
return {
id: 'gate-variant-algorithm-1-report-2',
variantId: 'variant-algorithm-1',
reportId: 'report-2',
decision: 'needs-review',
reasons: ['algorithm variants require review before promotion'],
decidedAt: capturedAt,
};
}
function rejectDecision(): GateDecision {
return {
id: 'gate-variant-parameter-1-report-3',
variantId: 'variant-parameter-1',
reportId: 'report-3',
decision: 'reject',
reasons: ['evaluation report contains safety findings'],
decidedAt: capturedAt,
};
}
describe('applyPromotion', () => {
it('applies parameter patch to target when gate decision is allow', () => {
const target = { retrieval: { topK: 5 }, threshold: 0.7 };
const variant = parameterVariant();
const decision = allowDecision();
const record = applyPromotion(target, variant, decision);
expect(target).toEqual({ retrieval: { topK: 8 }, threshold: 0.7 });
expect(record).toEqual({
id: 'promo-variant-parameter-1-gate-variant-parameter-1-report-1',
variantId: 'variant-parameter-1',
gateDecisionId: 'gate-variant-parameter-1-report-1',
appliedAt: capturedAt,
rollbackPlan: variant.rollbackPlan,
previousState: { retrieval: { topK: 5 }, threshold: 0.7 },
nextState: { retrieval: { topK: 8 }, threshold: 0.7 },
});
});
it('does not mutate target when gate decision is needs-review', () => {
const target = { topK: 5 };
const variant = parameterVariant();
const decision = needsReviewDecision();
const record = applyPromotion(target, variant, decision);
expect(target).toEqual({ topK: 5 });
expect(record).toBeNull();
});
it('does not mutate target when gate decision is reject', () => {
const target = { topK: 5 };
const variant = parameterVariant();
const decision = rejectDecision();
const record = applyPromotion(target, variant, decision);
expect(target).toEqual({ topK: 5 });
expect(record).toBeNull();
});
});
describe('rollbackPromotion', () => {
it('restores target to previous state', () => {
const target = { retrieval: { topK: 8 }, threshold: 0.7 };
const record = {
id: 'promo-variant-parameter-1-gate-variant-parameter-1-report-1',
variantId: 'variant-parameter-1',
gateDecisionId: 'gate-variant-parameter-1-report-1',
appliedAt: capturedAt,
rollbackPlan: parameterVariant().rollbackPlan,
previousState: { retrieval: { topK: 5 }, threshold: 0.7 },
nextState: { retrieval: { topK: 8 }, threshold: 0.7 },
};
rollbackPromotion(target, record);
expect(target).toEqual({ retrieval: { topK: 5 }, threshold: 0.7 });
});
});