claude-flow 3.22.0 → 3.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/helpers/.helpers-version +1 -0
- package/.claude/helpers/auto-memory-hook.mjs +59 -274
- package/.claude/helpers/helpers.manifest.json +12 -0
- package/.claude/helpers/hook-handler.cjs +132 -112
- package/.claude/helpers/intelligence.cjs +992 -196
- package/package.json +1 -1
- package/v3/@claude-flow/cli/dist/src/commands/memory-backup.d.ts +11 -0
- package/v3/@claude-flow/cli/dist/src/commands/memory-backup.js +46 -0
- package/v3/@claude-flow/cli/dist/src/commands/memory.js +2 -1
- package/v3/@claude-flow/cli/dist/src/config/harness-feedback-applier.d.ts +50 -0
- package/v3/@claude-flow/cli/dist/src/config/harness-feedback-applier.js +122 -0
- package/v3/@claude-flow/cli/dist/src/config/proven-config-refresh.d.ts +39 -0
- package/v3/@claude-flow/cli/dist/src/config/proven-config-refresh.js +154 -0
- package/v3/@claude-flow/cli/dist/src/config/proven-config-rvfa.d.ts +23 -0
- package/v3/@claude-flow/cli/dist/src/config/proven-config-rvfa.js +73 -0
- package/v3/@claude-flow/cli/dist/src/config/proven-config.d.ts +86 -0
- package/v3/@claude-flow/cli/dist/src/config/proven-config.js +176 -0
- package/v3/@claude-flow/cli/dist/src/index.js +35 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/neural-tools.d.ts +10 -0
- package/v3/@claude-flow/cli/dist/src/mcp-tools/neural-tools.js +107 -18
- package/v3/@claude-flow/cli/dist/src/services/daemon-autostart.d.ts +17 -0
- package/v3/@claude-flow/cli/dist/src/services/daemon-autostart.js +79 -0
- package/v3/@claude-flow/cli/dist/src/services/evolve-proof.d.ts +247 -0
- package/v3/@claude-flow/cli/dist/src/services/evolve-proof.js +341 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-benchmark.d.ts +68 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-benchmark.js +94 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-canary.d.ts +60 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-canary.js +69 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-corpus-harvester.d.ts +51 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-corpus-harvester.js +74 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-flywheel-generations.d.ts +111 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-flywheel-generations.js +354 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-flywheel-runtime.d.ts +25 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-flywheel-runtime.js +101 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-flywheel.d.ts +48 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-flywheel.js +207 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-hosts.d.ts +40 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-hosts.js +88 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-improvement-ledger.d.ts +63 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-improvement-ledger.js +101 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-loop.d.ts +53 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-loop.js +85 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-qualification.d.ts +66 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-qualification.js +143 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-replay.d.ts +37 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-replay.js +92 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-verify.d.ts +33 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-verify.js +26 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-worker.d.ts +23 -0
- package/v3/@claude-flow/cli/dist/src/services/harness-worker.js +66 -0
- package/v3/@claude-flow/cli/dist/src/services/memory-backup.d.ts +24 -0
- package/v3/@claude-flow/cli/dist/src/services/memory-backup.js +94 -0
- package/v3/@claude-flow/cli/dist/src/services/worker-daemon.d.ts +16 -1
- package/v3/@claude-flow/cli/dist/src/services/worker-daemon.js +84 -0
- package/v3/@claude-flow/cli/package.json +1 -1
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { type PromotionVerdict, type AcceptResult } from './harness-benchmark.js';
|
|
2
|
+
import { type ProvenConfigManifest } from '../config/proven-config.js';
|
|
3
|
+
/**
|
|
4
|
+
* The promotion rule is versioned so a receipt pins exactly which semantics
|
|
5
|
+
* decided it. v1+sig = the accept() conjunction AND a statistical-significance
|
|
6
|
+
* term: the per-held-out-task deltas must have a positive one-sided 95% bootstrap
|
|
7
|
+
* lower bound, so a small-N mean gain can't ride on noise. The canary term is a
|
|
8
|
+
* SEPARATE deployment-safety signal (a distinct slice), not held-out dominance.
|
|
9
|
+
*/
|
|
10
|
+
export declare const PROMOTION_RULE_VERSION = "accept/v1+sig";
|
|
11
|
+
export declare const PROOF_LABEL = "single-round proof-of-mechanism";
|
|
12
|
+
export declare const NOT_CLAIMS: readonly ["not flywheel proof", "not compounding learning", "not production learning"];
|
|
13
|
+
export interface HoldoutTask {
|
|
14
|
+
taskId: string;
|
|
15
|
+
baselineScore: number;
|
|
16
|
+
candidateScore: number;
|
|
17
|
+
}
|
|
18
|
+
export interface DecisionReceipt {
|
|
19
|
+
promotionRuleVersion: string;
|
|
20
|
+
verdictInputs: PromotionVerdict;
|
|
21
|
+
result: AcceptResult;
|
|
22
|
+
significant: boolean;
|
|
23
|
+
deltaCILow: number;
|
|
24
|
+
promoted: boolean;
|
|
25
|
+
reason: string;
|
|
26
|
+
}
|
|
27
|
+
export interface ShadowRegistration {
|
|
28
|
+
registrationId: string;
|
|
29
|
+
state: 'shadow';
|
|
30
|
+
served: false;
|
|
31
|
+
candidateManifestHash: string;
|
|
32
|
+
registeredAt: number;
|
|
33
|
+
}
|
|
34
|
+
export interface CostReceipt {
|
|
35
|
+
usd: number;
|
|
36
|
+
llmCalls: number;
|
|
37
|
+
tier: string;
|
|
38
|
+
notes: string;
|
|
39
|
+
}
|
|
40
|
+
/** Multi-dimensional deltas vs the parent — distinguishes *why* a change is (un)safe. */
|
|
41
|
+
export interface ChangeDeltas {
|
|
42
|
+
benchmark: number;
|
|
43
|
+
security: number;
|
|
44
|
+
cost: number;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Causal promotion record (not just provenance). Answers *why* a candidate won —
|
|
48
|
+
* and, aggregated across the lineage, *which mutation classes* reliably pay off.
|
|
49
|
+
* This is what turns the lineage from an audit trail into a knowledge base.
|
|
50
|
+
*/
|
|
51
|
+
export interface PromotionRecord {
|
|
52
|
+
parentManifestHash: string | null;
|
|
53
|
+
candidateManifestHash: string;
|
|
54
|
+
mutationClass: string;
|
|
55
|
+
mutationSummary: string;
|
|
56
|
+
deltas: ChangeDeltas;
|
|
57
|
+
decisionReceipt: DecisionReceipt;
|
|
58
|
+
}
|
|
59
|
+
/** Regression ancestry — a rejected candidate records the gate that killed it + its ancestor. */
|
|
60
|
+
export interface RegressionRecord {
|
|
61
|
+
candidateManifestHash: string;
|
|
62
|
+
ancestor: string | null;
|
|
63
|
+
mutationClass: string;
|
|
64
|
+
failureCause: 'holdout' | 'security' | 'drift' | 'replay' | 'governance' | 'canary' | 'significance';
|
|
65
|
+
failedTerms: string[];
|
|
66
|
+
}
|
|
67
|
+
export interface EvolveReceiptBundle {
|
|
68
|
+
label: typeof PROOF_LABEL;
|
|
69
|
+
disclaimers: typeof NOT_CLAIMS;
|
|
70
|
+
generation: number;
|
|
71
|
+
parent: string | null;
|
|
72
|
+
branch: string;
|
|
73
|
+
kind: 'synthetic' | 'real';
|
|
74
|
+
createdAt: number;
|
|
75
|
+
inputHoldoutHash: string;
|
|
76
|
+
baselineManifestHash: string;
|
|
77
|
+
candidateManifestHash: string;
|
|
78
|
+
meetsPromotionRule: {
|
|
79
|
+
version: string;
|
|
80
|
+
result: boolean;
|
|
81
|
+
};
|
|
82
|
+
decisionReceipt: DecisionReceipt;
|
|
83
|
+
shadow: ShadowRegistration | null;
|
|
84
|
+
costReceipt: CostReceipt;
|
|
85
|
+
mutationClass: string;
|
|
86
|
+
mutationSummary: string;
|
|
87
|
+
deltas: ChangeDeltas;
|
|
88
|
+
promotion: PromotionRecord | null;
|
|
89
|
+
regression: RegressionRecord | null;
|
|
90
|
+
holdout: HoldoutTask[];
|
|
91
|
+
baselineManifest: ProvenConfigManifest;
|
|
92
|
+
candidateManifest: ProvenConfigManifest;
|
|
93
|
+
}
|
|
94
|
+
/** Classify a policy mutation by which fields changed → a repeatable mutation CLASS + a diff summary. */
|
|
95
|
+
export declare function classifyMutation(baseline: Record<string, number>, candidate: Record<string, number>): {
|
|
96
|
+
mutationClass: string;
|
|
97
|
+
mutationSummary: string;
|
|
98
|
+
};
|
|
99
|
+
export interface AssembleOpts {
|
|
100
|
+
generation: number;
|
|
101
|
+
parent: string | null;
|
|
102
|
+
branch: string;
|
|
103
|
+
now: number;
|
|
104
|
+
kind: 'synthetic' | 'real';
|
|
105
|
+
cost: {
|
|
106
|
+
tier: string;
|
|
107
|
+
notes: string;
|
|
108
|
+
};
|
|
109
|
+
redblue?: 'PASS' | 'FAIL' | 'SKIPPED';
|
|
110
|
+
drift?: number;
|
|
111
|
+
canaryRollbackRate?: number;
|
|
112
|
+
layer?: string;
|
|
113
|
+
corpus?: string;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Assemble a receipt bundle from a holdout + configs. This is the SHARED core:
|
|
117
|
+
* the synthetic proof and a REAL measured round produce byte-identical bundle
|
|
118
|
+
* structure and run the SAME versioned accept() — so `verifyReceiptBundle`
|
|
119
|
+
* replays a real bundle exactly as it replays the synthetic fixture.
|
|
120
|
+
*/
|
|
121
|
+
export declare function assembleBundle(baseline: Record<string, number>, candidate: Record<string, number>, holdout: HoldoutTask[], o: AssembleOpts): EvolveReceiptBundle;
|
|
122
|
+
/**
|
|
123
|
+
* Build a REAL evolve-round receipt bundle from MEASURED holdout scores (live
|
|
124
|
+
* retrieval over a frozen anchor). Same gate, same replayability as the
|
|
125
|
+
* synthetic proof — but `kind: 'real'` and the scores come from actual runs.
|
|
126
|
+
* `redblue` should be 'FAIL' if the candidate regressed a frozen security/anchor
|
|
127
|
+
* slice; drift from real distribution shift. $0 (no LLM/network on this path).
|
|
128
|
+
*/
|
|
129
|
+
export declare function runRealEvolveRound(opts: {
|
|
130
|
+
baseline: Record<string, number>;
|
|
131
|
+
candidate: Record<string, number>;
|
|
132
|
+
holdout: HoldoutTask[];
|
|
133
|
+
generation: number;
|
|
134
|
+
parent: string | null;
|
|
135
|
+
branch?: string;
|
|
136
|
+
now: number;
|
|
137
|
+
redblue?: 'PASS' | 'FAIL' | 'SKIPPED';
|
|
138
|
+
drift?: number;
|
|
139
|
+
canaryRollbackRate?: number;
|
|
140
|
+
corpus: string;
|
|
141
|
+
}): EvolveReceiptBundle;
|
|
142
|
+
/**
|
|
143
|
+
* Run ONE deterministic synthetic evolve round and produce the receipt bundle.
|
|
144
|
+
* `now` is injected (no Date in the pure path) for reproducible fixtures.
|
|
145
|
+
* The default scenario is a strict Pareto improvement (candidate ≥ baseline on
|
|
146
|
+
* every task, > on the mean) so the full PROMOTE→SHADOW path is exercised; pass
|
|
147
|
+
* `regress: true` to exercise the REJECT path instead.
|
|
148
|
+
*/
|
|
149
|
+
export declare function runSyntheticProofRound(opts?: {
|
|
150
|
+
now: number;
|
|
151
|
+
generation?: number;
|
|
152
|
+
regress?: boolean;
|
|
153
|
+
parent?: string | null;
|
|
154
|
+
branch?: string;
|
|
155
|
+
baseline?: Record<string, number>;
|
|
156
|
+
candidate?: Record<string, number>;
|
|
157
|
+
}): EvolveReceiptBundle;
|
|
158
|
+
export interface VerifyReport {
|
|
159
|
+
valid: boolean;
|
|
160
|
+
hashChecks: {
|
|
161
|
+
inputHoldout: boolean;
|
|
162
|
+
baselineManifest: boolean;
|
|
163
|
+
candidateManifest: boolean;
|
|
164
|
+
};
|
|
165
|
+
recomputed: {
|
|
166
|
+
baselineHeldOut: number;
|
|
167
|
+
candidateHeldOut: number;
|
|
168
|
+
canaryRollbackRate: number;
|
|
169
|
+
decision: AcceptResult;
|
|
170
|
+
};
|
|
171
|
+
decisionMatches: boolean;
|
|
172
|
+
ruleVersionMatches: boolean;
|
|
173
|
+
noAutoServe: boolean;
|
|
174
|
+
causalConsistent: boolean;
|
|
175
|
+
explanation: string;
|
|
176
|
+
mismatches: string[];
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Independently verify a receipt bundle WITHOUT trusting any service log: rehash
|
|
180
|
+
* the embedded holdout + manifests, recompute the held-out means + canary rate
|
|
181
|
+
* from the embedded per-task scores, RE-RUN the same versioned accept(), and
|
|
182
|
+
* confirm the recomputed decision equals the recorded one. Also confirms the
|
|
183
|
+
* SHADOW registration is not served (no auto-serve). Pure; never throws.
|
|
184
|
+
*/
|
|
185
|
+
export declare function verifyReceiptBundle(bundle: EvolveReceiptBundle): VerifyReport;
|
|
186
|
+
export interface LineageTelemetry {
|
|
187
|
+
generations: number;
|
|
188
|
+
candidatesEvaluated: number;
|
|
189
|
+
promotions: number;
|
|
190
|
+
rejections: number;
|
|
191
|
+
cumulativeHeldOutImprovement: number;
|
|
192
|
+
rootHash: string | null;
|
|
193
|
+
branches: string[];
|
|
194
|
+
lineageIntact: boolean;
|
|
195
|
+
allReplayable: boolean;
|
|
196
|
+
nodes: Array<{
|
|
197
|
+
generation: number;
|
|
198
|
+
branch: string;
|
|
199
|
+
promoted: boolean;
|
|
200
|
+
parent: string | null;
|
|
201
|
+
candidateManifestHash: string;
|
|
202
|
+
mutationClass: string;
|
|
203
|
+
delta: number;
|
|
204
|
+
replayable: boolean;
|
|
205
|
+
}>;
|
|
206
|
+
problems: string[];
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Reconstruct + audit a lineage DAG. Independently replays every bundle and
|
|
210
|
+
* checks the graph invariants back to the immutable root. Pure; never throws.
|
|
211
|
+
*/
|
|
212
|
+
export declare function reconstructLineage(bundles: EvolveReceiptBundle[]): LineageTelemetry;
|
|
213
|
+
export interface MutationStat {
|
|
214
|
+
mutationClass: string;
|
|
215
|
+
attempts: number;
|
|
216
|
+
promotions: number;
|
|
217
|
+
meanDelta: number;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Aggregate per-mutation-class effectiveness across a lineage. After enough
|
|
221
|
+
* generations the optimizer can bias toward classes with higher historical
|
|
222
|
+
* payoff — meta-learning grounded in evidence, not intuition.
|
|
223
|
+
*/
|
|
224
|
+
export declare function mutationEffectiveness(bundles: EvolveReceiptBundle[]): MutationStat[];
|
|
225
|
+
export type PlateauStatus = 'insufficient-data' | 'active' | 'local-optimum' | 'noisy-benchmark' | 'optimizer-failure';
|
|
226
|
+
export interface PlateauReport {
|
|
227
|
+
status: PlateauStatus;
|
|
228
|
+
window: number;
|
|
229
|
+
medianImprovement: number;
|
|
230
|
+
promotionRate: number;
|
|
231
|
+
varianceShrinking: boolean;
|
|
232
|
+
candidateVariance: number;
|
|
233
|
+
rationale: string;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Distinguish local optimum vs noisy benchmark vs optimizer failure — rigorously,
|
|
237
|
+
* not by intuition. Over a rolling window: near-zero median improvement AND low
|
|
238
|
+
* promotion rate is a plateau; shrinking candidate variance ⇒ converged
|
|
239
|
+
* (local optimum); high, non-shrinking variance ⇒ the benchmark is noisy; low
|
|
240
|
+
* variance with no promotions ⇒ the optimizer stopped exploring (failure).
|
|
241
|
+
*/
|
|
242
|
+
export declare function detectPlateau(bundles: EvolveReceiptBundle[], opts?: {
|
|
243
|
+
window?: number;
|
|
244
|
+
epsilon?: number;
|
|
245
|
+
maxPromotionRate?: number;
|
|
246
|
+
}): PlateauReport;
|
|
247
|
+
//# sourceMappingURL=evolve-proof.d.ts.map
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* single-round proof-of-mechanism (ADR-176)
|
|
3
|
+
*
|
|
4
|
+
* SCOPE — read this before citing any output:
|
|
5
|
+
* This is a SINGLE-ROUND PROOF-OF-MECHANISM. It is NOT flywheel proof, NOT
|
|
6
|
+
* compounding learning, and NOT production learning. Its only purpose is to
|
|
7
|
+
* prove, on ONE deterministic synthetic round:
|
|
8
|
+
* (a) gate wiring — the real versioned accept() decides promotion,
|
|
9
|
+
* (b) receipt persistence — a self-contained bundle is written to disk,
|
|
10
|
+
* (c) SHADOW registration — a passing candidate is registered in shadow,
|
|
11
|
+
* (d) no auto-serve path — nothing is applied to the active/served policy.
|
|
12
|
+
* A synthetic PASS here is NOT evidence of real improvement.
|
|
13
|
+
*
|
|
14
|
+
* Independently verifiable: the bundle embeds the holdout, both manifests, the
|
|
15
|
+
* exact PromotionVerdict inputs, and their hashes. verifyReceiptBundle() rehashes
|
|
16
|
+
* everything and RE-RUNS the same versioned accept() — so a third party can
|
|
17
|
+
* confirm *why* the candidate passed/failed without trusting any service log.
|
|
18
|
+
*
|
|
19
|
+
* Pure Node, $0, no LLM, no network, no real store. Deterministic.
|
|
20
|
+
*/
|
|
21
|
+
import { createHash } from 'node:crypto';
|
|
22
|
+
import { accept } from './harness-benchmark.js';
|
|
23
|
+
import { bootstrapDeltaCILow } from './harness-improvement-ledger.js';
|
|
24
|
+
import { canonicalManifestBytes } from '../config/proven-config.js';
|
|
25
|
+
/**
|
|
26
|
+
* The promotion rule is versioned so a receipt pins exactly which semantics
|
|
27
|
+
* decided it. v1+sig = the accept() conjunction AND a statistical-significance
|
|
28
|
+
* term: the per-held-out-task deltas must have a positive one-sided 95% bootstrap
|
|
29
|
+
* lower bound, so a small-N mean gain can't ride on noise. The canary term is a
|
|
30
|
+
* SEPARATE deployment-safety signal (a distinct slice), not held-out dominance.
|
|
31
|
+
*/
|
|
32
|
+
export const PROMOTION_RULE_VERSION = 'accept/v1+sig';
|
|
33
|
+
export const PROOF_LABEL = 'single-round proof-of-mechanism';
|
|
34
|
+
export const NOT_CLAIMS = ['not flywheel proof', 'not compounding learning', 'not production learning'];
|
|
35
|
+
function sha256(s) { return 'sha256:' + createHash('sha256').update(s).digest('hex'); }
|
|
36
|
+
function canon(v) {
|
|
37
|
+
const c = (x) => Array.isArray(x) ? x.map(c)
|
|
38
|
+
: (x && typeof x === 'object') ? Object.fromEntries(Object.keys(x).sort().map((k) => [k, c(x[k])])) : x;
|
|
39
|
+
return JSON.stringify(c(v));
|
|
40
|
+
}
|
|
41
|
+
function manifestHash(m) { return sha256(canonicalManifestBytes(m).toString('utf-8')); }
|
|
42
|
+
const FAILURE_CAUSE = {
|
|
43
|
+
held_out_improves: 'holdout', redblue_pass: 'security', drift_within: 'drift',
|
|
44
|
+
replay_deterministic: 'replay', receipt_coverage_full: 'governance', canary_no_worse: 'canary',
|
|
45
|
+
};
|
|
46
|
+
/** Classify a policy mutation by which fields changed → a repeatable mutation CLASS + a diff summary. */
|
|
47
|
+
export function classifyMutation(baseline, candidate) {
|
|
48
|
+
const changed = Object.keys(candidate).filter((k) => candidate[k] !== baseline[k]).sort();
|
|
49
|
+
if (changed.length === 0)
|
|
50
|
+
return { mutationClass: 'none', mutationSummary: 'no change' };
|
|
51
|
+
const summary = changed.map((k) => `${k}:${baseline[k]}→${candidate[k]}`).join(', ');
|
|
52
|
+
const mutationClass = changed.length === 1 ? `retrieval:${changed[0]}` : 'retrieval:multi';
|
|
53
|
+
return { mutationClass, mutationSummary: summary };
|
|
54
|
+
}
|
|
55
|
+
function mean(xs) { return xs.length ? xs.reduce((a, b) => a + b, 0) / xs.length : 0; }
|
|
56
|
+
function mkManifest(policyValue, layer = 'synthetic/proof', corpus = 'synthetic-proof-v1') {
|
|
57
|
+
const ref = sha256(canon(policyValue));
|
|
58
|
+
return { schema: 'ruflo.proven-config/v1', policy: { ref, value: policyValue }, layer, benchmark: { corpus, corpusHash: sha256(corpus) } };
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Assemble a receipt bundle from a holdout + configs. This is the SHARED core:
|
|
62
|
+
* the synthetic proof and a REAL measured round produce byte-identical bundle
|
|
63
|
+
* structure and run the SAME versioned accept() — so `verifyReceiptBundle`
|
|
64
|
+
* replays a real bundle exactly as it replays the synthetic fixture.
|
|
65
|
+
*/
|
|
66
|
+
export function assembleBundle(baseline, candidate, holdout, o) {
|
|
67
|
+
const baselineHeldOut = mean(holdout.map((h) => h.baselineScore));
|
|
68
|
+
const candidateHeldOut = mean(holdout.map((h) => h.candidateScore));
|
|
69
|
+
// Canary is a SEPARATE deployment-safety signal (default: strict per-held-out
|
|
70
|
+
// regression, preserving synthetic behavior; real rounds pass a distinct slice).
|
|
71
|
+
const canaryRollbackRate = o.canaryRollbackRate ?? holdout.filter((h) => h.candidateScore < h.baselineScore - 1e-9).length / holdout.length;
|
|
72
|
+
// Statistical-significance term (small-N noise guard): bootstrap lower bound on
|
|
73
|
+
// the per-task deltas must be > 0.
|
|
74
|
+
const deltaCILow = bootstrapDeltaCILow(holdout.map((h) => h.candidateScore - h.baselineScore));
|
|
75
|
+
const significant = deltaCILow > 0;
|
|
76
|
+
const verdictInputs = {
|
|
77
|
+
heldOutScore: candidateHeldOut, baselineHeldOutScore: baselineHeldOut,
|
|
78
|
+
redblue: o.redblue ?? 'PASS', drift: o.drift ?? 0, driftThreshold: 0.05,
|
|
79
|
+
replayDeterministic: true, receiptCoverage: 1, canaryRollbackRate, baselineRollbackRate: 0,
|
|
80
|
+
};
|
|
81
|
+
const result = accept(verdictInputs);
|
|
82
|
+
const promoted = result.accept && significant;
|
|
83
|
+
const baselineManifest = mkManifest(baseline, o.layer, o.corpus);
|
|
84
|
+
const candidateManifest = mkManifest(candidate, o.layer, o.corpus);
|
|
85
|
+
const baselineManifestHash = manifestHash(baselineManifest);
|
|
86
|
+
const candidateManifestHash = manifestHash(candidateManifest);
|
|
87
|
+
const inputHoldoutHash = sha256(canon(holdout));
|
|
88
|
+
const failed = [...result.failed, ...(!significant ? ['significant'] : [])];
|
|
89
|
+
const decisionReceipt = {
|
|
90
|
+
promotionRuleVersion: PROMOTION_RULE_VERSION, verdictInputs, result, significant, deltaCILow, promoted,
|
|
91
|
+
reason: promoted ? `promoted (all ${PROMOTION_RULE_VERSION} terms held)` : `rejected — ${failed.join(', ')}`,
|
|
92
|
+
};
|
|
93
|
+
const shadow = promoted ? {
|
|
94
|
+
registrationId: sha256(`${candidateManifestHash}|gen${o.generation}|shadow`).replace('sha256:', 'shadow:'),
|
|
95
|
+
state: 'shadow', served: false, candidateManifestHash, registeredAt: o.now,
|
|
96
|
+
} : null;
|
|
97
|
+
const { mutationClass, mutationSummary } = classifyMutation(baseline, candidate);
|
|
98
|
+
const deltas = { benchmark: candidateHeldOut - baselineHeldOut, security: o.redblue === 'FAIL' ? -1 : 0, cost: 0 };
|
|
99
|
+
const promotion = promoted ? { parentManifestHash: o.parent, candidateManifestHash, mutationClass, mutationSummary, deltas, decisionReceipt } : null;
|
|
100
|
+
const regression = promoted ? null : {
|
|
101
|
+
candidateManifestHash, ancestor: o.parent ?? baselineManifestHash, mutationClass,
|
|
102
|
+
failureCause: !significant && result.accept ? 'significance' : (FAILURE_CAUSE[result.failed[0]] ?? 'holdout'), failedTerms: failed,
|
|
103
|
+
};
|
|
104
|
+
return {
|
|
105
|
+
label: PROOF_LABEL, disclaimers: NOT_CLAIMS, generation: o.generation, parent: o.parent, branch: o.branch, kind: o.kind, createdAt: o.now,
|
|
106
|
+
inputHoldoutHash, baselineManifestHash, candidateManifestHash,
|
|
107
|
+
meetsPromotionRule: { version: PROMOTION_RULE_VERSION, result: promoted },
|
|
108
|
+
decisionReceipt, shadow,
|
|
109
|
+
costReceipt: { usd: 0, llmCalls: 0, tier: o.cost.tier, notes: o.cost.notes },
|
|
110
|
+
mutationClass, mutationSummary, deltas, promotion, regression,
|
|
111
|
+
holdout, baselineManifest, candidateManifest,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Build a REAL evolve-round receipt bundle from MEASURED holdout scores (live
|
|
116
|
+
* retrieval over a frozen anchor). Same gate, same replayability as the
|
|
117
|
+
* synthetic proof — but `kind: 'real'` and the scores come from actual runs.
|
|
118
|
+
* `redblue` should be 'FAIL' if the candidate regressed a frozen security/anchor
|
|
119
|
+
* slice; drift from real distribution shift. $0 (no LLM/network on this path).
|
|
120
|
+
*/
|
|
121
|
+
export function runRealEvolveRound(opts) {
|
|
122
|
+
return assembleBundle(opts.baseline, opts.candidate, opts.holdout, {
|
|
123
|
+
generation: opts.generation, parent: opts.parent, branch: opts.branch ?? 'main', now: opts.now, kind: 'real',
|
|
124
|
+
cost: { tier: 'real-local', notes: 'measured on the frozen anchor via live retrieval — no LLM, no network' },
|
|
125
|
+
redblue: opts.redblue, drift: opts.drift, canaryRollbackRate: opts.canaryRollbackRate, layer: 'real/retrieval', corpus: opts.corpus,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Run ONE deterministic synthetic evolve round and produce the receipt bundle.
|
|
130
|
+
* `now` is injected (no Date in the pure path) for reproducible fixtures.
|
|
131
|
+
* The default scenario is a strict Pareto improvement (candidate ≥ baseline on
|
|
132
|
+
* every task, > on the mean) so the full PROMOTE→SHADOW path is exercised; pass
|
|
133
|
+
* `regress: true` to exercise the REJECT path instead.
|
|
134
|
+
*/
|
|
135
|
+
export function runSyntheticProofRound(opts = { now: 0 }) {
|
|
136
|
+
const generation = opts.generation ?? 0;
|
|
137
|
+
const parent = opts.parent ?? null;
|
|
138
|
+
const branch = opts.branch ?? 'main';
|
|
139
|
+
const baseline = opts.baseline ?? { alpha: 0.5, subjectWeight: 2, mmrLambda: 0.7, bodyWeight: 1, typePenaltyFactor: 1 };
|
|
140
|
+
const candidate = opts.candidate ?? { alpha: 0.3, subjectWeight: 1, mmrLambda: 0.5, bodyWeight: 1.5, typePenaltyFactor: 0.5 };
|
|
141
|
+
// Deterministic synthetic holdout. Default: candidate never worse, sometimes
|
|
142
|
+
// better (Pareto). regress: candidate worse on one task (drives a REJECT).
|
|
143
|
+
const holdout = [
|
|
144
|
+
{ taskId: 't0', baselineScore: 0.60, candidateScore: 0.70 },
|
|
145
|
+
{ taskId: 't1', baselineScore: 0.80, candidateScore: 0.86 },
|
|
146
|
+
{ taskId: 't2', baselineScore: 0.50, candidateScore: 0.62 },
|
|
147
|
+
{ taskId: 't3', baselineScore: 0.72, candidateScore: 0.80 },
|
|
148
|
+
{ taskId: 't4', baselineScore: 0.66, candidateScore: opts.regress ? 0.50 : 0.78 },
|
|
149
|
+
];
|
|
150
|
+
return assembleBundle(baseline, candidate, holdout, {
|
|
151
|
+
generation, parent, branch, now: opts.now, kind: 'synthetic',
|
|
152
|
+
cost: { tier: 'synthetic', notes: 'deterministic synthetic round — no model, no network, no real store' },
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Independently verify a receipt bundle WITHOUT trusting any service log: rehash
|
|
157
|
+
* the embedded holdout + manifests, recompute the held-out means + canary rate
|
|
158
|
+
* from the embedded per-task scores, RE-RUN the same versioned accept(), and
|
|
159
|
+
* confirm the recomputed decision equals the recorded one. Also confirms the
|
|
160
|
+
* SHADOW registration is not served (no auto-serve). Pure; never throws.
|
|
161
|
+
*/
|
|
162
|
+
export function verifyReceiptBundle(bundle) {
|
|
163
|
+
const mismatches = [];
|
|
164
|
+
const hashChecks = {
|
|
165
|
+
inputHoldout: sha256(canon(bundle.holdout)) === bundle.inputHoldoutHash,
|
|
166
|
+
baselineManifest: manifestHash(bundle.baselineManifest) === bundle.baselineManifestHash,
|
|
167
|
+
candidateManifest: manifestHash(bundle.candidateManifest) === bundle.candidateManifestHash,
|
|
168
|
+
};
|
|
169
|
+
if (!hashChecks.inputHoldout)
|
|
170
|
+
mismatches.push('input holdout hash mismatch');
|
|
171
|
+
if (!hashChecks.baselineManifest)
|
|
172
|
+
mismatches.push('baseline manifest hash mismatch');
|
|
173
|
+
if (!hashChecks.candidateManifest)
|
|
174
|
+
mismatches.push('candidate manifest hash mismatch');
|
|
175
|
+
const baselineHeldOut = mean(bundle.holdout.map((h) => h.baselineScore));
|
|
176
|
+
const candidateHeldOut = mean(bundle.holdout.map((h) => h.candidateScore));
|
|
177
|
+
// The canary is a SEPARATE slice (not embedded) — trust the recorded rate; the
|
|
178
|
+
// held-out means + significance ARE independently recomputed from the holdout,
|
|
179
|
+
// so holdout tampering is still caught (via means/significance/decision).
|
|
180
|
+
const canaryRollbackRate = bundle.decisionReceipt.verdictInputs.canaryRollbackRate;
|
|
181
|
+
const deltaCILow = bootstrapDeltaCILow(bundle.holdout.map((h) => h.candidateScore - h.baselineScore));
|
|
182
|
+
const significant = deltaCILow > 0;
|
|
183
|
+
// Re-run the SAME versioned rule on independently-recomputed inputs.
|
|
184
|
+
const ruleVersionMatches = bundle.decisionReceipt.promotionRuleVersion === PROMOTION_RULE_VERSION
|
|
185
|
+
&& bundle.meetsPromotionRule.version === PROMOTION_RULE_VERSION;
|
|
186
|
+
if (!ruleVersionMatches)
|
|
187
|
+
mismatches.push(`promotion rule version != ${PROMOTION_RULE_VERSION}`);
|
|
188
|
+
const decision = accept({
|
|
189
|
+
...bundle.decisionReceipt.verdictInputs,
|
|
190
|
+
heldOutScore: candidateHeldOut, baselineHeldOutScore: baselineHeldOut,
|
|
191
|
+
});
|
|
192
|
+
const promotedRecomputed = decision.accept && significant;
|
|
193
|
+
const decisionMatches = promotedRecomputed === bundle.decisionReceipt.promoted && promotedRecomputed === bundle.meetsPromotionRule.result;
|
|
194
|
+
if (!decisionMatches)
|
|
195
|
+
mismatches.push('recomputed decision != recorded decision');
|
|
196
|
+
// no-auto-serve: a shadow registration must never be marked served.
|
|
197
|
+
const noAutoServe = bundle.shadow === null || bundle.shadow.served === false;
|
|
198
|
+
if (!noAutoServe)
|
|
199
|
+
mismatches.push('candidate was auto-served (served=true) — violates shadow-only');
|
|
200
|
+
// causal record consistency: a pass carries a promotion record (with a delta
|
|
201
|
+
// that matches the recompute) and no regression; a reject carries the inverse.
|
|
202
|
+
const benchmarkDeltaMatches = Math.abs(bundle.deltas.benchmark - (candidateHeldOut - baselineHeldOut)) < 1e-9;
|
|
203
|
+
const causalConsistent = promotedRecomputed
|
|
204
|
+
? (bundle.promotion !== null && bundle.regression === null && benchmarkDeltaMatches)
|
|
205
|
+
: (bundle.promotion === null && bundle.regression !== null);
|
|
206
|
+
if (!causalConsistent)
|
|
207
|
+
mismatches.push('causal record inconsistent with the decision (promotion/regression/delta)');
|
|
208
|
+
const valid = hashChecks.inputHoldout && hashChecks.baselineManifest && hashChecks.candidateManifest
|
|
209
|
+
&& ruleVersionMatches && decisionMatches && noAutoServe && causalConsistent;
|
|
210
|
+
const why = promotedRecomputed
|
|
211
|
+
? `PASS under ${PROMOTION_RULE_VERSION}: held_out ${candidateHeldOut.toFixed(4)} > ${baselineHeldOut.toFixed(4)} (Δ CI-low ${deltaCILow.toFixed(4)} > 0, significant), canary rollback ${canaryRollbackRate} ≤ 0, all terms held`
|
|
212
|
+
: `FAIL under ${PROMOTION_RULE_VERSION}: ${[...decision.failed, ...(!significant ? ['significant'] : [])].join(', ')}`;
|
|
213
|
+
return {
|
|
214
|
+
valid, hashChecks,
|
|
215
|
+
recomputed: { baselineHeldOut, candidateHeldOut, canaryRollbackRate, decision },
|
|
216
|
+
decisionMatches, ruleVersionMatches, noAutoServe, causalConsistent,
|
|
217
|
+
explanation: `independently recomputed from the bundle (no service logs) → ${why}`,
|
|
218
|
+
mismatches,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Reconstruct + audit a lineage DAG. Independently replays every bundle and
|
|
223
|
+
* checks the graph invariants back to the immutable root. Pure; never throws.
|
|
224
|
+
*/
|
|
225
|
+
export function reconstructLineage(bundles) {
|
|
226
|
+
const problems = [];
|
|
227
|
+
const ordered = [...bundles].sort((a, b) => a.generation - b.generation);
|
|
228
|
+
const byCandidate = new Map(ordered.map((b) => [b.candidateManifestHash, b]));
|
|
229
|
+
let allReplayable = true;
|
|
230
|
+
const nodes = ordered.map((b) => {
|
|
231
|
+
const rep = verifyReceiptBundle(b);
|
|
232
|
+
if (!rep.valid) {
|
|
233
|
+
allReplayable = false;
|
|
234
|
+
problems.push(`gen ${b.generation}: not independently replayable (${rep.mismatches.join('; ')})`);
|
|
235
|
+
}
|
|
236
|
+
return { generation: b.generation, branch: b.branch, promoted: b.decisionReceipt.promoted, parent: b.parent, candidateManifestHash: b.candidateManifestHash, mutationClass: b.mutationClass, delta: b.deltas.benchmark, replayable: rep.valid };
|
|
237
|
+
});
|
|
238
|
+
// DAG invariants.
|
|
239
|
+
let lineageIntact = true;
|
|
240
|
+
const roots = ordered.filter((b) => b.parent === null);
|
|
241
|
+
if (roots.length !== 1) {
|
|
242
|
+
lineageIntact = false;
|
|
243
|
+
problems.push(`expected exactly one immutable root, found ${roots.length}`);
|
|
244
|
+
}
|
|
245
|
+
for (const b of ordered) {
|
|
246
|
+
if (b.parent === null)
|
|
247
|
+
continue;
|
|
248
|
+
const parent = byCandidate.get(b.parent);
|
|
249
|
+
if (!parent) {
|
|
250
|
+
lineageIntact = false;
|
|
251
|
+
problems.push(`gen ${b.generation} (${b.branch}): parent ${b.parent.slice(0, 20)}… not found in graph`);
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
if (parent.generation >= b.generation) {
|
|
255
|
+
lineageIntact = false;
|
|
256
|
+
problems.push(`gen ${b.generation}: parent generation ${parent.generation} not older (cycle risk)`);
|
|
257
|
+
}
|
|
258
|
+
if (b.baselineManifestHash !== parent.candidateManifestHash) {
|
|
259
|
+
lineageIntact = false;
|
|
260
|
+
problems.push(`gen ${b.generation} (${b.branch}): baseline != parent's promoted candidate (did not inherit the verified policy)`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return {
|
|
264
|
+
generations: ordered.length,
|
|
265
|
+
candidatesEvaluated: ordered.length,
|
|
266
|
+
promotions: ordered.filter((b) => b.decisionReceipt.promoted).length,
|
|
267
|
+
rejections: ordered.filter((b) => !b.decisionReceipt.promoted).length,
|
|
268
|
+
cumulativeHeldOutImprovement: ordered.reduce((s, b) => s + (b.decisionReceipt.promoted ? b.deltas.benchmark : 0), 0),
|
|
269
|
+
rootHash: roots[0]?.candidateManifestHash ?? null,
|
|
270
|
+
branches: [...new Set(ordered.map((b) => b.branch))],
|
|
271
|
+
lineageIntact: lineageIntact && allReplayable, allReplayable, nodes, problems,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Aggregate per-mutation-class effectiveness across a lineage. After enough
|
|
276
|
+
* generations the optimizer can bias toward classes with higher historical
|
|
277
|
+
* payoff — meta-learning grounded in evidence, not intuition.
|
|
278
|
+
*/
|
|
279
|
+
export function mutationEffectiveness(bundles) {
|
|
280
|
+
const by = new Map();
|
|
281
|
+
for (const b of bundles) {
|
|
282
|
+
const e = by.get(b.mutationClass) ?? { attempts: 0, promotions: 0, deltas: [] };
|
|
283
|
+
e.attempts++;
|
|
284
|
+
if (b.decisionReceipt.promoted) {
|
|
285
|
+
e.promotions++;
|
|
286
|
+
e.deltas.push(b.deltas.benchmark);
|
|
287
|
+
}
|
|
288
|
+
by.set(b.mutationClass, e);
|
|
289
|
+
}
|
|
290
|
+
return [...by.entries()]
|
|
291
|
+
.map(([mutationClass, e]) => ({ mutationClass, attempts: e.attempts, promotions: e.promotions, meanDelta: mean(e.deltas) }))
|
|
292
|
+
.sort((a, b) => b.meanDelta - a.meanDelta);
|
|
293
|
+
}
|
|
294
|
+
function median(xs) { if (!xs.length)
|
|
295
|
+
return 0; const s = [...xs].sort((a, b) => a - b); const m = s.length >> 1; return s.length % 2 ? s[m] : (s[m - 1] + s[m]) / 2; }
|
|
296
|
+
function variance(xs) { if (xs.length < 2)
|
|
297
|
+
return 0; const mu = mean(xs); return mean(xs.map((x) => (x - mu) ** 2)); }
|
|
298
|
+
/**
|
|
299
|
+
* Distinguish local optimum vs noisy benchmark vs optimizer failure — rigorously,
|
|
300
|
+
* not by intuition. Over a rolling window: near-zero median improvement AND low
|
|
301
|
+
* promotion rate is a plateau; shrinking candidate variance ⇒ converged
|
|
302
|
+
* (local optimum); high, non-shrinking variance ⇒ the benchmark is noisy; low
|
|
303
|
+
* variance with no promotions ⇒ the optimizer stopped exploring (failure).
|
|
304
|
+
*/
|
|
305
|
+
export function detectPlateau(bundles, opts = {}) {
|
|
306
|
+
const window = opts.window ?? 20;
|
|
307
|
+
const epsilon = opts.epsilon ?? 0.001;
|
|
308
|
+
const maxPromotionRate = opts.maxPromotionRate ?? 0.1;
|
|
309
|
+
const ordered = [...bundles].sort((a, b) => a.generation - b.generation);
|
|
310
|
+
if (ordered.length < window) {
|
|
311
|
+
return { status: 'insufficient-data', window, medianImprovement: 0, promotionRate: 0, varianceShrinking: false, candidateVariance: 0, rationale: `need ${window} generations, have ${ordered.length}` };
|
|
312
|
+
}
|
|
313
|
+
const recent = ordered.slice(-window);
|
|
314
|
+
const promotedDeltas = recent.filter((b) => b.decisionReceipt.promoted).map((b) => b.deltas.benchmark);
|
|
315
|
+
const medianImprovement = median(promotedDeltas);
|
|
316
|
+
const promotionRate = promotedDeltas.length / window;
|
|
317
|
+
const candScores = recent.map((b) => mean(b.holdout.map((h) => h.candidateScore)));
|
|
318
|
+
const firstHalfVar = variance(candScores.slice(0, window >> 1));
|
|
319
|
+
const secondHalfVar = variance(candScores.slice(window >> 1));
|
|
320
|
+
const candidateVariance = variance(candScores);
|
|
321
|
+
const varianceShrinking = secondHalfVar < firstHalfVar * 0.5;
|
|
322
|
+
let status = 'active';
|
|
323
|
+
let rationale = `median Δ ${medianImprovement.toFixed(4)} ≥ ε or promotion rate ${(promotionRate * 100).toFixed(0)}% ≥ ${(maxPromotionRate * 100).toFixed(0)}% — still improving`;
|
|
324
|
+
const plateaued = medianImprovement < epsilon && promotionRate < maxPromotionRate;
|
|
325
|
+
if (plateaued) {
|
|
326
|
+
if (varianceShrinking) {
|
|
327
|
+
status = 'local-optimum';
|
|
328
|
+
rationale = 'no gains + candidate variance shrinking → converged to a local optimum';
|
|
329
|
+
}
|
|
330
|
+
else if (candidateVariance > epsilon) {
|
|
331
|
+
status = 'noisy-benchmark';
|
|
332
|
+
rationale = 'no gains but candidates vary widely → benchmark noise is masking signal';
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
status = 'optimizer-failure';
|
|
336
|
+
rationale = 'no gains + candidates barely vary → optimizer stopped exploring';
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return { status, window, medianImprovement, promotionRate, varianceShrinking, candidateVariance, rationale };
|
|
340
|
+
}
|
|
341
|
+
//# sourceMappingURL=evolve-proof.js.map
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export interface BenchTask {
|
|
2
|
+
id: string;
|
|
3
|
+
input: unknown;
|
|
4
|
+
expected: unknown;
|
|
5
|
+
weight?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface HarnessBenchmarkCorpus {
|
|
8
|
+
version: string;
|
|
9
|
+
corpusHash: string;
|
|
10
|
+
tasks: BenchTask[];
|
|
11
|
+
}
|
|
12
|
+
/** Run a candidate policy on a task's input, producing an output. */
|
|
13
|
+
export type EvalFn<C> = (input: unknown, candidate: C) => unknown;
|
|
14
|
+
/** Grade an output against expected → [0,1]. */
|
|
15
|
+
export type GradeFn = (output: unknown, expected: unknown) => number;
|
|
16
|
+
/** Content hash of a corpus's tasks — the tamper-evident pin for the held-out set. */
|
|
17
|
+
export declare function hashCorpus(tasks: BenchTask[]): string;
|
|
18
|
+
/** Verify a corpus's declared hash matches its tasks (integrity of the benchmark). */
|
|
19
|
+
export declare function verifyCorpus(corpus: HarnessBenchmarkCorpus): boolean;
|
|
20
|
+
export interface ScoreResult {
|
|
21
|
+
fitness: number;
|
|
22
|
+
passRate: number;
|
|
23
|
+
n: number;
|
|
24
|
+
}
|
|
25
|
+
/** Score a candidate over a task set, on isolated tasks (no shared state). */
|
|
26
|
+
export declare function scoreOnTasks<C>(tasks: BenchTask[], candidate: C, evalFn: EvalFn<C>, gradeFn: GradeFn): ScoreResult;
|
|
27
|
+
export interface HeldOutSplit {
|
|
28
|
+
train: BenchTask[];
|
|
29
|
+
heldOut: BenchTask[];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Deterministic held-out split. Tasks are stably ordered by id, and the last
|
|
33
|
+
* `holdoutFrac` become the held-out set — disjoint from train, reproducible
|
|
34
|
+
* (so two runs converge, per the ADR-176 acceptance test).
|
|
35
|
+
*/
|
|
36
|
+
export declare function computeHeldOutSplit(tasks: BenchTask[], holdoutFrac?: number): HeldOutSplit;
|
|
37
|
+
/** Independently-measured verdicts fed to accept(). Each comes from a different mechanism. */
|
|
38
|
+
export interface PromotionVerdict {
|
|
39
|
+
heldOutScore: number;
|
|
40
|
+
baselineHeldOutScore: number;
|
|
41
|
+
redblue: 'PASS' | 'FAIL' | 'SKIPPED';
|
|
42
|
+
drift: number;
|
|
43
|
+
driftThreshold: number;
|
|
44
|
+
replayDeterministic: boolean;
|
|
45
|
+
receiptCoverage: number;
|
|
46
|
+
canaryRollbackRate: number;
|
|
47
|
+
baselineRollbackRate: number;
|
|
48
|
+
}
|
|
49
|
+
export interface AcceptResult {
|
|
50
|
+
accept: boolean;
|
|
51
|
+
terms: Record<string, {
|
|
52
|
+
value: unknown;
|
|
53
|
+
pass: boolean;
|
|
54
|
+
}>;
|
|
55
|
+
failed: string[];
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* accept(candidate) ⟺
|
|
59
|
+
* held_out_score > baseline
|
|
60
|
+
* AND redblue == PASS
|
|
61
|
+
* AND drift <= threshold
|
|
62
|
+
* AND replay == deterministic
|
|
63
|
+
* AND receipt_cov == 100%
|
|
64
|
+
* AND canary.rollback_rate <= baseline
|
|
65
|
+
* Every term is externally measurable; ANY failure → reject.
|
|
66
|
+
*/
|
|
67
|
+
export declare function accept(v: PromotionVerdict): AcceptResult;
|
|
68
|
+
//# sourceMappingURL=harness-benchmark.d.ts.map
|