@claude-flow/embeddings
Version:
V3 Embedding Service - OpenAI, Transformers.js, Agentic-Flow (ONNX), Mock providers with hyperbolic embeddings, normalization, and chunking
154 lines • 6.58 kB
JavaScript
/**
* ADR-121 Phase 22 — Ledger analyzer + regression detection.
*
* The witness ledger from Phase 18 stores N historical benchmark
* runs as a tamper-evident hash chain. Until now it served only as
* provenance — "I observed these numbers at this commit." This phase
* adds the missing layer: **using the chain to detect performance
* regressions** on every new run.
*
* Pattern:
* 1. Read the ledger.
* 2. For each benchmark name, pull every historical entry that
* ran the same benchmark.
* 3. Extract a named metric path (e.g. "results.summary.adaptive.meanRecallAt5").
* 4. Compare the current value to the baseline (most recent prior
* entry, or the best, or a rolling window) and report the delta.
* 5. Fail CI if the delta exceeds the configured threshold.
*
* Because the ledger is hash-chained (Phase 18), an attacker can't
* silently rewrite a historical baseline to make a regression look
* like an improvement — the chain verification would catch the
* tamper before the regression check ran.
*/
/**
* Extract a value at a dotted path from a nested object. Returns
* `undefined` if any segment is missing or the final value is not
* numeric.
*/
export function getMetricAtPath(obj, path) {
if (path.length === 0)
return undefined;
const segments = path.split('.');
let cur = obj;
for (const seg of segments) {
if (cur === null || cur === undefined)
return undefined;
if (typeof cur !== 'object')
return undefined;
cur = cur[seg];
}
return typeof cur === 'number' && Number.isFinite(cur) ? cur : undefined;
}
/**
* Collect all ledger entries with a given benchmark name, in chain
* order (oldest first). Useful for building plots, computing
* rolling-window stats, etc.
*/
export function entriesForBenchmark(ledger, benchmarkName) {
return ledger.entries.filter(e => e.benchmark === benchmarkName);
}
/**
* Pick the baseline value from a ledger for a (benchmark, metric)
* pair under the supplied strategy. Returns null when the chain has
* no entries with both a matching benchmark name AND a numeric value
* at the metric path.
*/
export function pickBaseline(ledger, benchmarkName, metricPath, selection) {
const candidates = entriesForBenchmark(ledger, benchmarkName)
.map(e => ({ entry: e, value: getMetricAtPath(e, metricPath) }))
.filter((x) => x.value !== undefined);
if (candidates.length === 0)
return null;
const direction = selection.direction ?? 'higher';
switch (selection.strategy) {
case 'first':
return { value: candidates[0].value, entry: candidates[0].entry };
case 'latest':
return { value: candidates[candidates.length - 1].value, entry: candidates[candidates.length - 1].entry };
case 'best': {
let best = candidates[0];
for (const c of candidates.slice(1)) {
const isBetter = direction === 'higher' ? c.value > best.value : c.value < best.value;
if (isBetter)
best = c;
}
return { value: best.value, entry: best.entry };
}
default:
throw new Error(`unknown baseline strategy: ${selection.strategy}`);
}
}
/**
* Check whether the current value regressed against the ledger
* baseline. Returns a structured result with the delta and reason.
*/
export function checkRegression(ledger, benchmarkName, metricPath, currentValue, options = {}) {
const threshold = options.threshold ?? 0.10;
const baselineSel = options.baseline ?? { strategy: 'latest', direction: 'higher' };
const baseline = pickBaseline(ledger, benchmarkName, metricPath, baselineSel);
if (baseline === null) {
return {
benchmark: benchmarkName,
metricPath,
currentValue,
baselineValue: null,
baselineFrom: null,
delta: 0,
percentChange: 0,
passed: true,
reason: `no prior entries for benchmark='${benchmarkName}' with numeric '${metricPath}' — first run, nothing to regress against`,
};
}
const direction = baselineSel.direction ?? 'higher';
const delta = currentValue - baseline.value;
// Guard divide-by-zero (rare in practice — recall=0 baselines).
const percentChange = baseline.value === 0
? (currentValue === 0 ? 0 : Infinity)
: delta / Math.abs(baseline.value);
// For higher-is-better: regression = percentChange < -threshold
// For lower-is-better: regression = percentChange > +threshold
let passed;
let reason;
if (direction === 'higher') {
if (percentChange >= -threshold) {
passed = true;
const sign = percentChange >= 0 ? '+' : '';
reason = `current ${currentValue.toFixed(4)} ${sign}${(percentChange * 100).toFixed(1)}% vs baseline ${baseline.value.toFixed(4)} (within ${(threshold * 100).toFixed(0)}% tolerance for higher-is-better)`;
}
else {
passed = false;
reason = `REGRESSION: current ${currentValue.toFixed(4)} dropped ${Math.abs(percentChange * 100).toFixed(1)}% vs baseline ${baseline.value.toFixed(4)} (exceeds ${(threshold * 100).toFixed(0)}% threshold)`;
}
}
else {
if (percentChange <= threshold) {
passed = true;
const sign = percentChange >= 0 ? '+' : '';
reason = `current ${currentValue.toFixed(4)} ${sign}${(percentChange * 100).toFixed(1)}% vs baseline ${baseline.value.toFixed(4)} (within ${(threshold * 100).toFixed(0)}% tolerance for lower-is-better)`;
}
else {
passed = false;
reason = `REGRESSION: current ${currentValue.toFixed(4)} grew ${(percentChange * 100).toFixed(1)}% vs baseline ${baseline.value.toFixed(4)} (exceeds ${(threshold * 100).toFixed(0)}% threshold)`;
}
}
return {
benchmark: benchmarkName,
metricPath,
currentValue,
baselineValue: baseline.value,
baselineFrom: { sequence: baseline.entry.sequence, timestamp: baseline.entry.timestamp },
delta,
percentChange,
passed,
reason,
};
}
export function checkRegressionBatch(ledger, inputs) {
const checks = inputs.map(i => checkRegression(ledger, i.benchmark, i.metricPath, i.currentValue, i.options));
return {
allPassed: checks.every(c => c.passed),
checks,
};
}
//# sourceMappingURL=ledger-analyzer.js.map