claude-flow 3.23.0 → 3.25.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.
Files changed (53) hide show
  1. package/.claude/.proven-config-version +1 -0
  2. package/.claude/helpers/.helpers-version +1 -1
  3. package/.claude/helpers/helpers.manifest.json +2 -2
  4. package/.claude/proven-config.json +42 -0
  5. package/package.json +1 -1
  6. package/v3/@claude-flow/cli/dist/src/config/harness-feedback-applier.d.ts +50 -0
  7. package/v3/@claude-flow/cli/dist/src/config/harness-feedback-applier.js +122 -0
  8. package/v3/@claude-flow/cli/dist/src/config/proven-config-refresh.d.ts +39 -0
  9. package/v3/@claude-flow/cli/dist/src/config/proven-config-refresh.js +154 -0
  10. package/v3/@claude-flow/cli/dist/src/config/proven-config-rvfa.d.ts +23 -0
  11. package/v3/@claude-flow/cli/dist/src/config/proven-config-rvfa.js +73 -0
  12. package/v3/@claude-flow/cli/dist/src/config/proven-config.d.ts +86 -0
  13. package/v3/@claude-flow/cli/dist/src/config/proven-config.js +176 -0
  14. package/v3/@claude-flow/cli/dist/src/index.js +35 -0
  15. package/v3/@claude-flow/cli/dist/src/mcp-tools/neural-tools.d.ts +10 -0
  16. package/v3/@claude-flow/cli/dist/src/mcp-tools/neural-tools.js +129 -20
  17. package/v3/@claude-flow/cli/dist/src/ruvector/lattice-wasm.d.ts +14 -0
  18. package/v3/@claude-flow/cli/dist/src/ruvector/lattice-wasm.js +144 -0
  19. package/v3/@claude-flow/cli/dist/src/services/daemon-autostart.d.ts +17 -0
  20. package/v3/@claude-flow/cli/dist/src/services/daemon-autostart.js +79 -0
  21. package/v3/@claude-flow/cli/dist/src/services/evolve-proof.d.ts +253 -0
  22. package/v3/@claude-flow/cli/dist/src/services/evolve-proof.js +343 -0
  23. package/v3/@claude-flow/cli/dist/src/services/harness-benchmark.d.ts +68 -0
  24. package/v3/@claude-flow/cli/dist/src/services/harness-benchmark.js +94 -0
  25. package/v3/@claude-flow/cli/dist/src/services/harness-canary.d.ts +60 -0
  26. package/v3/@claude-flow/cli/dist/src/services/harness-canary.js +69 -0
  27. package/v3/@claude-flow/cli/dist/src/services/harness-corpus-harvester.d.ts +51 -0
  28. package/v3/@claude-flow/cli/dist/src/services/harness-corpus-harvester.js +74 -0
  29. package/v3/@claude-flow/cli/dist/src/services/harness-flywheel-generations.d.ts +115 -0
  30. package/v3/@claude-flow/cli/dist/src/services/harness-flywheel-generations.js +360 -0
  31. package/v3/@claude-flow/cli/dist/src/services/harness-flywheel-runtime.d.ts +25 -0
  32. package/v3/@claude-flow/cli/dist/src/services/harness-flywheel-runtime.js +92 -0
  33. package/v3/@claude-flow/cli/dist/src/services/harness-flywheel.d.ts +48 -0
  34. package/v3/@claude-flow/cli/dist/src/services/harness-flywheel.js +207 -0
  35. package/v3/@claude-flow/cli/dist/src/services/harness-frozen-eval.d.ts +22 -0
  36. package/v3/@claude-flow/cli/dist/src/services/harness-frozen-eval.js +66 -0
  37. package/v3/@claude-flow/cli/dist/src/services/harness-hosts.d.ts +40 -0
  38. package/v3/@claude-flow/cli/dist/src/services/harness-hosts.js +88 -0
  39. package/v3/@claude-flow/cli/dist/src/services/harness-improvement-ledger.d.ts +63 -0
  40. package/v3/@claude-flow/cli/dist/src/services/harness-improvement-ledger.js +101 -0
  41. package/v3/@claude-flow/cli/dist/src/services/harness-loop.d.ts +53 -0
  42. package/v3/@claude-flow/cli/dist/src/services/harness-loop.js +85 -0
  43. package/v3/@claude-flow/cli/dist/src/services/harness-qualification.d.ts +66 -0
  44. package/v3/@claude-flow/cli/dist/src/services/harness-qualification.js +143 -0
  45. package/v3/@claude-flow/cli/dist/src/services/harness-replay.d.ts +37 -0
  46. package/v3/@claude-flow/cli/dist/src/services/harness-replay.js +92 -0
  47. package/v3/@claude-flow/cli/dist/src/services/harness-verify.d.ts +33 -0
  48. package/v3/@claude-flow/cli/dist/src/services/harness-verify.js +26 -0
  49. package/v3/@claude-flow/cli/dist/src/services/harness-worker.d.ts +23 -0
  50. package/v3/@claude-flow/cli/dist/src/services/harness-worker.js +66 -0
  51. package/v3/@claude-flow/cli/dist/src/services/worker-daemon.d.ts +8 -1
  52. package/v3/@claude-flow/cli/dist/src/services/worker-daemon.js +42 -0
  53. package/v3/@claude-flow/cli/package.json +1 -1
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Self-running daemon (auto-start).
3
+ *
4
+ * The self-optimizing loop's workers (distillation, backup, and the future
5
+ * evolve worker) are inert unless the daemon runs — but it required a manual
6
+ * `ruflo daemon start`. This ensures a daemon is running on CLI use, SAFELY:
7
+ *
8
+ * - single-instance: only starts when no live daemon holds the pidfile, and
9
+ * the spawned `daemon start` independently enforces single-instance via its
10
+ * own lock + checkExistingDaemon() — so a race spawns at most one survivor,
11
+ * - bounded lifetime: the daemon self-terminates on TTL/idle (12h default,
12
+ * RUFLO_DAEMON_TTL_SECS) — auto-start never means "runs forever",
13
+ * - opt-out: RUFLO_DAEMON_AUTOSTART=0|false|no disables it entirely,
14
+ * - cheap: a pidfile read + a signal-0 liveness check on the fast path,
15
+ * - best-effort + silent: never blocks or fails a command.
16
+ *
17
+ * Reuses `daemon start` verbatim (all its lock/TTL/worker machinery) — this
18
+ * module only decides WHETHER to spawn, never reimplements the daemon.
19
+ */
20
+ import * as fs from 'fs';
21
+ import * as path from 'path';
22
+ import { spawn } from 'child_process';
23
+ /** True if a live daemon already holds the project's pidfile. */
24
+ export function isDaemonAlive(projectRoot) {
25
+ const pidFile = path.join(projectRoot, '.claude-flow', 'daemon.pid');
26
+ try {
27
+ if (!fs.existsSync(pidFile))
28
+ return false;
29
+ const pid = parseInt(fs.readFileSync(pidFile, 'utf-8').trim(), 10);
30
+ if (Number.isNaN(pid) || pid === process.pid)
31
+ return false;
32
+ process.kill(pid, 0); // signal 0 = existence check
33
+ return true;
34
+ }
35
+ catch {
36
+ // Dead process → stale pidfile. Clean it so `daemon start` proceeds cleanly.
37
+ try {
38
+ fs.unlinkSync(pidFile);
39
+ }
40
+ catch { /* ignore */ }
41
+ return false;
42
+ }
43
+ }
44
+ function autostartDisabled() {
45
+ return /^(0|false|no|off)$/i.test(process.env.RUFLO_DAEMON_AUTOSTART ?? '');
46
+ }
47
+ const defaultSpawn = (projectRoot) => {
48
+ const cliBin = process.argv[1]; // the running bin/cli.js
49
+ const child = spawn(process.execPath, [cliBin, 'daemon', 'start', '--quiet'], {
50
+ cwd: projectRoot,
51
+ detached: true,
52
+ stdio: 'ignore',
53
+ env: { ...process.env },
54
+ });
55
+ child.unref();
56
+ };
57
+ /**
58
+ * Ensure a daemon is running for `projectRoot`. No-op when disabled or when one
59
+ * is already alive. Best-effort; never throws.
60
+ */
61
+ export function ensureDaemonRunning(projectRoot, opts = {}) {
62
+ try {
63
+ if (autostartDisabled())
64
+ return { started: false, reason: 'disabled (RUFLO_DAEMON_AUTOSTART=0)' };
65
+ // Only in an initialized project (avoid scaffolding a daemon in a random dir).
66
+ if (!fs.existsSync(path.join(projectRoot, '.claude-flow')) && !fs.existsSync(path.join(projectRoot, '.claude'))) {
67
+ return { started: false, reason: 'not a ruflo project' };
68
+ }
69
+ const alive = (opts.isAlive ?? isDaemonAlive)(projectRoot);
70
+ if (alive)
71
+ return { started: false, reason: 'already running' };
72
+ (opts.spawnFn ?? defaultSpawn)(projectRoot);
73
+ return { started: true };
74
+ }
75
+ catch (e) {
76
+ return { started: false, reason: `error: ${e?.message ?? e}` };
77
+ }
78
+ }
79
+ //# sourceMappingURL=daemon-autostart.js.map
@@ -0,0 +1,253 @@
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
+ humanRelevance?: number;
46
+ }
47
+ /**
48
+ * Causal promotion record (not just provenance). Answers *why* a candidate won —
49
+ * and, aggregated across the lineage, *which mutation classes* reliably pay off.
50
+ * This is what turns the lineage from an audit trail into a knowledge base.
51
+ */
52
+ export interface PromotionRecord {
53
+ parentManifestHash: string | null;
54
+ candidateManifestHash: string;
55
+ mutationClass: string;
56
+ mutationSummary: string;
57
+ deltas: ChangeDeltas;
58
+ decisionReceipt: DecisionReceipt;
59
+ }
60
+ /** Regression ancestry — a rejected candidate records the gate that killed it + its ancestor. */
61
+ export interface RegressionRecord {
62
+ candidateManifestHash: string;
63
+ ancestor: string | null;
64
+ mutationClass: string;
65
+ failureCause: 'holdout' | 'security' | 'drift' | 'replay' | 'governance' | 'canary' | 'significance';
66
+ failedTerms: string[];
67
+ }
68
+ export interface EvolveReceiptBundle {
69
+ label: typeof PROOF_LABEL;
70
+ disclaimers: typeof NOT_CLAIMS;
71
+ generation: number;
72
+ parent: string | null;
73
+ branch: string;
74
+ kind: 'synthetic' | 'real';
75
+ createdAt: number;
76
+ inputHoldoutHash: string;
77
+ baselineManifestHash: string;
78
+ candidateManifestHash: string;
79
+ meetsPromotionRule: {
80
+ version: string;
81
+ result: boolean;
82
+ };
83
+ decisionReceipt: DecisionReceipt;
84
+ shadow: ShadowRegistration | null;
85
+ costReceipt: CostReceipt;
86
+ mutationClass: string;
87
+ mutationSummary: string;
88
+ deltas: ChangeDeltas;
89
+ humanEvalHash?: string;
90
+ promotion: PromotionRecord | null;
91
+ regression: RegressionRecord | null;
92
+ holdout: HoldoutTask[];
93
+ baselineManifest: ProvenConfigManifest;
94
+ candidateManifest: ProvenConfigManifest;
95
+ }
96
+ /** Classify a policy mutation by which fields changed → a repeatable mutation CLASS + a diff summary. */
97
+ export declare function classifyMutation(baseline: Record<string, number>, candidate: Record<string, number>): {
98
+ mutationClass: string;
99
+ mutationSummary: string;
100
+ };
101
+ export interface AssembleOpts {
102
+ generation: number;
103
+ parent: string | null;
104
+ branch: string;
105
+ now: number;
106
+ kind: 'synthetic' | 'real';
107
+ cost: {
108
+ tier: string;
109
+ notes: string;
110
+ };
111
+ redblue?: 'PASS' | 'FAIL' | 'SKIPPED';
112
+ drift?: number;
113
+ canaryRollbackRate?: number;
114
+ humanRelevanceDelta?: number;
115
+ humanEvalHash?: string;
116
+ layer?: string;
117
+ corpus?: string;
118
+ }
119
+ /**
120
+ * Assemble a receipt bundle from a holdout + configs. This is the SHARED core:
121
+ * the synthetic proof and a REAL measured round produce byte-identical bundle
122
+ * structure and run the SAME versioned accept() — so `verifyReceiptBundle`
123
+ * replays a real bundle exactly as it replays the synthetic fixture.
124
+ */
125
+ export declare function assembleBundle(baseline: Record<string, number>, candidate: Record<string, number>, holdout: HoldoutTask[], o: AssembleOpts): EvolveReceiptBundle;
126
+ /**
127
+ * Build a REAL evolve-round receipt bundle from MEASURED holdout scores (live
128
+ * retrieval over a frozen anchor). Same gate, same replayability as the
129
+ * synthetic proof — but `kind: 'real'` and the scores come from actual runs.
130
+ * `redblue` should be 'FAIL' if the candidate regressed a frozen security/anchor
131
+ * slice; drift from real distribution shift. $0 (no LLM/network on this path).
132
+ */
133
+ export declare function runRealEvolveRound(opts: {
134
+ baseline: Record<string, number>;
135
+ candidate: Record<string, number>;
136
+ holdout: HoldoutTask[];
137
+ generation: number;
138
+ parent: string | null;
139
+ branch?: string;
140
+ now: number;
141
+ redblue?: 'PASS' | 'FAIL' | 'SKIPPED';
142
+ drift?: number;
143
+ canaryRollbackRate?: number;
144
+ humanRelevanceDelta?: number;
145
+ humanEvalHash?: string;
146
+ corpus: string;
147
+ }): EvolveReceiptBundle;
148
+ /**
149
+ * Run ONE deterministic synthetic evolve round and produce the receipt bundle.
150
+ * `now` is injected (no Date in the pure path) for reproducible fixtures.
151
+ * The default scenario is a strict Pareto improvement (candidate ≥ baseline on
152
+ * every task, > on the mean) so the full PROMOTE→SHADOW path is exercised; pass
153
+ * `regress: true` to exercise the REJECT path instead.
154
+ */
155
+ export declare function runSyntheticProofRound(opts?: {
156
+ now: number;
157
+ generation?: number;
158
+ regress?: boolean;
159
+ parent?: string | null;
160
+ branch?: string;
161
+ baseline?: Record<string, number>;
162
+ candidate?: Record<string, number>;
163
+ }): EvolveReceiptBundle;
164
+ export interface VerifyReport {
165
+ valid: boolean;
166
+ hashChecks: {
167
+ inputHoldout: boolean;
168
+ baselineManifest: boolean;
169
+ candidateManifest: boolean;
170
+ };
171
+ recomputed: {
172
+ baselineHeldOut: number;
173
+ candidateHeldOut: number;
174
+ canaryRollbackRate: number;
175
+ decision: AcceptResult;
176
+ };
177
+ decisionMatches: boolean;
178
+ ruleVersionMatches: boolean;
179
+ noAutoServe: boolean;
180
+ causalConsistent: boolean;
181
+ explanation: string;
182
+ mismatches: string[];
183
+ }
184
+ /**
185
+ * Independently verify a receipt bundle WITHOUT trusting any service log: rehash
186
+ * the embedded holdout + manifests, recompute the held-out means + canary rate
187
+ * from the embedded per-task scores, RE-RUN the same versioned accept(), and
188
+ * confirm the recomputed decision equals the recorded one. Also confirms the
189
+ * SHADOW registration is not served (no auto-serve). Pure; never throws.
190
+ */
191
+ export declare function verifyReceiptBundle(bundle: EvolveReceiptBundle): VerifyReport;
192
+ export interface LineageTelemetry {
193
+ generations: number;
194
+ candidatesEvaluated: number;
195
+ promotions: number;
196
+ rejections: number;
197
+ cumulativeHeldOutImprovement: number;
198
+ rootHash: string | null;
199
+ branches: string[];
200
+ lineageIntact: boolean;
201
+ allReplayable: boolean;
202
+ nodes: Array<{
203
+ generation: number;
204
+ branch: string;
205
+ promoted: boolean;
206
+ parent: string | null;
207
+ candidateManifestHash: string;
208
+ mutationClass: string;
209
+ delta: number;
210
+ replayable: boolean;
211
+ }>;
212
+ problems: string[];
213
+ }
214
+ /**
215
+ * Reconstruct + audit a lineage DAG. Independently replays every bundle and
216
+ * checks the graph invariants back to the immutable root. Pure; never throws.
217
+ */
218
+ export declare function reconstructLineage(bundles: EvolveReceiptBundle[]): LineageTelemetry;
219
+ export interface MutationStat {
220
+ mutationClass: string;
221
+ attempts: number;
222
+ promotions: number;
223
+ meanDelta: number;
224
+ }
225
+ /**
226
+ * Aggregate per-mutation-class effectiveness across a lineage. After enough
227
+ * generations the optimizer can bias toward classes with higher historical
228
+ * payoff — meta-learning grounded in evidence, not intuition.
229
+ */
230
+ export declare function mutationEffectiveness(bundles: EvolveReceiptBundle[]): MutationStat[];
231
+ export type PlateauStatus = 'insufficient-data' | 'active' | 'local-optimum' | 'noisy-benchmark' | 'optimizer-failure';
232
+ export interface PlateauReport {
233
+ status: PlateauStatus;
234
+ window: number;
235
+ medianImprovement: number;
236
+ promotionRate: number;
237
+ varianceShrinking: boolean;
238
+ candidateVariance: number;
239
+ rationale: string;
240
+ }
241
+ /**
242
+ * Distinguish local optimum vs noisy benchmark vs optimizer failure — rigorously,
243
+ * not by intuition. Over a rolling window: near-zero median improvement AND low
244
+ * promotion rate is a plateau; shrinking candidate variance ⇒ converged
245
+ * (local optimum); high, non-shrinking variance ⇒ the benchmark is noisy; low
246
+ * variance with no promotions ⇒ the optimizer stopped exploring (failure).
247
+ */
248
+ export declare function detectPlateau(bundles: EvolveReceiptBundle[], opts?: {
249
+ window?: number;
250
+ epsilon?: number;
251
+ maxPromotionRate?: number;
252
+ }): PlateauReport;
253
+ //# sourceMappingURL=evolve-proof.d.ts.map
@@ -0,0 +1,343 @@
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, humanRelevance: o.humanRelevanceDelta };
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, humanEvalHash: o.humanEvalHash, 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,
126
+ humanRelevanceDelta: opts.humanRelevanceDelta, humanEvalHash: opts.humanEvalHash,
127
+ layer: 'real/retrieval', corpus: opts.corpus,
128
+ });
129
+ }
130
+ /**
131
+ * Run ONE deterministic synthetic evolve round and produce the receipt bundle.
132
+ * `now` is injected (no Date in the pure path) for reproducible fixtures.
133
+ * The default scenario is a strict Pareto improvement (candidate ≥ baseline on
134
+ * every task, > on the mean) so the full PROMOTE→SHADOW path is exercised; pass
135
+ * `regress: true` to exercise the REJECT path instead.
136
+ */
137
+ export function runSyntheticProofRound(opts = { now: 0 }) {
138
+ const generation = opts.generation ?? 0;
139
+ const parent = opts.parent ?? null;
140
+ const branch = opts.branch ?? 'main';
141
+ const baseline = opts.baseline ?? { alpha: 0.5, subjectWeight: 2, mmrLambda: 0.7, bodyWeight: 1, typePenaltyFactor: 1 };
142
+ const candidate = opts.candidate ?? { alpha: 0.3, subjectWeight: 1, mmrLambda: 0.5, bodyWeight: 1.5, typePenaltyFactor: 0.5 };
143
+ // Deterministic synthetic holdout. Default: candidate never worse, sometimes
144
+ // better (Pareto). regress: candidate worse on one task (drives a REJECT).
145
+ const holdout = [
146
+ { taskId: 't0', baselineScore: 0.60, candidateScore: 0.70 },
147
+ { taskId: 't1', baselineScore: 0.80, candidateScore: 0.86 },
148
+ { taskId: 't2', baselineScore: 0.50, candidateScore: 0.62 },
149
+ { taskId: 't3', baselineScore: 0.72, candidateScore: 0.80 },
150
+ { taskId: 't4', baselineScore: 0.66, candidateScore: opts.regress ? 0.50 : 0.78 },
151
+ ];
152
+ return assembleBundle(baseline, candidate, holdout, {
153
+ generation, parent, branch, now: opts.now, kind: 'synthetic',
154
+ cost: { tier: 'synthetic', notes: 'deterministic synthetic round — no model, no network, no real store' },
155
+ });
156
+ }
157
+ /**
158
+ * Independently verify a receipt bundle WITHOUT trusting any service log: rehash
159
+ * the embedded holdout + manifests, recompute the held-out means + canary rate
160
+ * from the embedded per-task scores, RE-RUN the same versioned accept(), and
161
+ * confirm the recomputed decision equals the recorded one. Also confirms the
162
+ * SHADOW registration is not served (no auto-serve). Pure; never throws.
163
+ */
164
+ export function verifyReceiptBundle(bundle) {
165
+ const mismatches = [];
166
+ const hashChecks = {
167
+ inputHoldout: sha256(canon(bundle.holdout)) === bundle.inputHoldoutHash,
168
+ baselineManifest: manifestHash(bundle.baselineManifest) === bundle.baselineManifestHash,
169
+ candidateManifest: manifestHash(bundle.candidateManifest) === bundle.candidateManifestHash,
170
+ };
171
+ if (!hashChecks.inputHoldout)
172
+ mismatches.push('input holdout hash mismatch');
173
+ if (!hashChecks.baselineManifest)
174
+ mismatches.push('baseline manifest hash mismatch');
175
+ if (!hashChecks.candidateManifest)
176
+ mismatches.push('candidate manifest hash mismatch');
177
+ const baselineHeldOut = mean(bundle.holdout.map((h) => h.baselineScore));
178
+ const candidateHeldOut = mean(bundle.holdout.map((h) => h.candidateScore));
179
+ // The canary is a SEPARATE slice (not embedded) — trust the recorded rate; the
180
+ // held-out means + significance ARE independently recomputed from the holdout,
181
+ // so holdout tampering is still caught (via means/significance/decision).
182
+ const canaryRollbackRate = bundle.decisionReceipt.verdictInputs.canaryRollbackRate;
183
+ const deltaCILow = bootstrapDeltaCILow(bundle.holdout.map((h) => h.candidateScore - h.baselineScore));
184
+ const significant = deltaCILow > 0;
185
+ // Re-run the SAME versioned rule on independently-recomputed inputs.
186
+ const ruleVersionMatches = bundle.decisionReceipt.promotionRuleVersion === PROMOTION_RULE_VERSION
187
+ && bundle.meetsPromotionRule.version === PROMOTION_RULE_VERSION;
188
+ if (!ruleVersionMatches)
189
+ mismatches.push(`promotion rule version != ${PROMOTION_RULE_VERSION}`);
190
+ const decision = accept({
191
+ ...bundle.decisionReceipt.verdictInputs,
192
+ heldOutScore: candidateHeldOut, baselineHeldOutScore: baselineHeldOut,
193
+ });
194
+ const promotedRecomputed = decision.accept && significant;
195
+ const decisionMatches = promotedRecomputed === bundle.decisionReceipt.promoted && promotedRecomputed === bundle.meetsPromotionRule.result;
196
+ if (!decisionMatches)
197
+ mismatches.push('recomputed decision != recorded decision');
198
+ // no-auto-serve: a shadow registration must never be marked served.
199
+ const noAutoServe = bundle.shadow === null || bundle.shadow.served === false;
200
+ if (!noAutoServe)
201
+ mismatches.push('candidate was auto-served (served=true) — violates shadow-only');
202
+ // causal record consistency: a pass carries a promotion record (with a delta
203
+ // that matches the recompute) and no regression; a reject carries the inverse.
204
+ const benchmarkDeltaMatches = Math.abs(bundle.deltas.benchmark - (candidateHeldOut - baselineHeldOut)) < 1e-9;
205
+ const causalConsistent = promotedRecomputed
206
+ ? (bundle.promotion !== null && bundle.regression === null && benchmarkDeltaMatches)
207
+ : (bundle.promotion === null && bundle.regression !== null);
208
+ if (!causalConsistent)
209
+ mismatches.push('causal record inconsistent with the decision (promotion/regression/delta)');
210
+ const valid = hashChecks.inputHoldout && hashChecks.baselineManifest && hashChecks.candidateManifest
211
+ && ruleVersionMatches && decisionMatches && noAutoServe && causalConsistent;
212
+ const why = promotedRecomputed
213
+ ? `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`
214
+ : `FAIL under ${PROMOTION_RULE_VERSION}: ${[...decision.failed, ...(!significant ? ['significant'] : [])].join(', ')}`;
215
+ return {
216
+ valid, hashChecks,
217
+ recomputed: { baselineHeldOut, candidateHeldOut, canaryRollbackRate, decision },
218
+ decisionMatches, ruleVersionMatches, noAutoServe, causalConsistent,
219
+ explanation: `independently recomputed from the bundle (no service logs) → ${why}`,
220
+ mismatches,
221
+ };
222
+ }
223
+ /**
224
+ * Reconstruct + audit a lineage DAG. Independently replays every bundle and
225
+ * checks the graph invariants back to the immutable root. Pure; never throws.
226
+ */
227
+ export function reconstructLineage(bundles) {
228
+ const problems = [];
229
+ const ordered = [...bundles].sort((a, b) => a.generation - b.generation);
230
+ const byCandidate = new Map(ordered.map((b) => [b.candidateManifestHash, b]));
231
+ let allReplayable = true;
232
+ const nodes = ordered.map((b) => {
233
+ const rep = verifyReceiptBundle(b);
234
+ if (!rep.valid) {
235
+ allReplayable = false;
236
+ problems.push(`gen ${b.generation}: not independently replayable (${rep.mismatches.join('; ')})`);
237
+ }
238
+ 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 };
239
+ });
240
+ // DAG invariants.
241
+ let lineageIntact = true;
242
+ const roots = ordered.filter((b) => b.parent === null);
243
+ if (roots.length !== 1) {
244
+ lineageIntact = false;
245
+ problems.push(`expected exactly one immutable root, found ${roots.length}`);
246
+ }
247
+ for (const b of ordered) {
248
+ if (b.parent === null)
249
+ continue;
250
+ const parent = byCandidate.get(b.parent);
251
+ if (!parent) {
252
+ lineageIntact = false;
253
+ problems.push(`gen ${b.generation} (${b.branch}): parent ${b.parent.slice(0, 20)}… not found in graph`);
254
+ continue;
255
+ }
256
+ if (parent.generation >= b.generation) {
257
+ lineageIntact = false;
258
+ problems.push(`gen ${b.generation}: parent generation ${parent.generation} not older (cycle risk)`);
259
+ }
260
+ if (b.baselineManifestHash !== parent.candidateManifestHash) {
261
+ lineageIntact = false;
262
+ problems.push(`gen ${b.generation} (${b.branch}): baseline != parent's promoted candidate (did not inherit the verified policy)`);
263
+ }
264
+ }
265
+ return {
266
+ generations: ordered.length,
267
+ candidatesEvaluated: ordered.length,
268
+ promotions: ordered.filter((b) => b.decisionReceipt.promoted).length,
269
+ rejections: ordered.filter((b) => !b.decisionReceipt.promoted).length,
270
+ cumulativeHeldOutImprovement: ordered.reduce((s, b) => s + (b.decisionReceipt.promoted ? b.deltas.benchmark : 0), 0),
271
+ rootHash: roots[0]?.candidateManifestHash ?? null,
272
+ branches: [...new Set(ordered.map((b) => b.branch))],
273
+ lineageIntact: lineageIntact && allReplayable, allReplayable, nodes, problems,
274
+ };
275
+ }
276
+ /**
277
+ * Aggregate per-mutation-class effectiveness across a lineage. After enough
278
+ * generations the optimizer can bias toward classes with higher historical
279
+ * payoff — meta-learning grounded in evidence, not intuition.
280
+ */
281
+ export function mutationEffectiveness(bundles) {
282
+ const by = new Map();
283
+ for (const b of bundles) {
284
+ const e = by.get(b.mutationClass) ?? { attempts: 0, promotions: 0, deltas: [] };
285
+ e.attempts++;
286
+ if (b.decisionReceipt.promoted) {
287
+ e.promotions++;
288
+ e.deltas.push(b.deltas.benchmark);
289
+ }
290
+ by.set(b.mutationClass, e);
291
+ }
292
+ return [...by.entries()]
293
+ .map(([mutationClass, e]) => ({ mutationClass, attempts: e.attempts, promotions: e.promotions, meanDelta: mean(e.deltas) }))
294
+ .sort((a, b) => b.meanDelta - a.meanDelta);
295
+ }
296
+ function median(xs) { if (!xs.length)
297
+ 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; }
298
+ function variance(xs) { if (xs.length < 2)
299
+ return 0; const mu = mean(xs); return mean(xs.map((x) => (x - mu) ** 2)); }
300
+ /**
301
+ * Distinguish local optimum vs noisy benchmark vs optimizer failure — rigorously,
302
+ * not by intuition. Over a rolling window: near-zero median improvement AND low
303
+ * promotion rate is a plateau; shrinking candidate variance ⇒ converged
304
+ * (local optimum); high, non-shrinking variance ⇒ the benchmark is noisy; low
305
+ * variance with no promotions ⇒ the optimizer stopped exploring (failure).
306
+ */
307
+ export function detectPlateau(bundles, opts = {}) {
308
+ const window = opts.window ?? 20;
309
+ const epsilon = opts.epsilon ?? 0.001;
310
+ const maxPromotionRate = opts.maxPromotionRate ?? 0.1;
311
+ const ordered = [...bundles].sort((a, b) => a.generation - b.generation);
312
+ if (ordered.length < window) {
313
+ return { status: 'insufficient-data', window, medianImprovement: 0, promotionRate: 0, varianceShrinking: false, candidateVariance: 0, rationale: `need ${window} generations, have ${ordered.length}` };
314
+ }
315
+ const recent = ordered.slice(-window);
316
+ const promotedDeltas = recent.filter((b) => b.decisionReceipt.promoted).map((b) => b.deltas.benchmark);
317
+ const medianImprovement = median(promotedDeltas);
318
+ const promotionRate = promotedDeltas.length / window;
319
+ const candScores = recent.map((b) => mean(b.holdout.map((h) => h.candidateScore)));
320
+ const firstHalfVar = variance(candScores.slice(0, window >> 1));
321
+ const secondHalfVar = variance(candScores.slice(window >> 1));
322
+ const candidateVariance = variance(candScores);
323
+ const varianceShrinking = secondHalfVar < firstHalfVar * 0.5;
324
+ let status = 'active';
325
+ let rationale = `median Δ ${medianImprovement.toFixed(4)} ≥ ε or promotion rate ${(promotionRate * 100).toFixed(0)}% ≥ ${(maxPromotionRate * 100).toFixed(0)}% — still improving`;
326
+ const plateaued = medianImprovement < epsilon && promotionRate < maxPromotionRate;
327
+ if (plateaued) {
328
+ if (varianceShrinking) {
329
+ status = 'local-optimum';
330
+ rationale = 'no gains + candidate variance shrinking → converged to a local optimum';
331
+ }
332
+ else if (candidateVariance > epsilon) {
333
+ status = 'noisy-benchmark';
334
+ rationale = 'no gains but candidates vary widely → benchmark noise is masking signal';
335
+ }
336
+ else {
337
+ status = 'optimizer-failure';
338
+ rationale = 'no gains + candidates barely vary → optimizer stopped exploring';
339
+ }
340
+ }
341
+ return { status, window, medianImprovement, promotionRate, varianceShrinking, candidateVariance, rationale };
342
+ }
343
+ //# sourceMappingURL=evolve-proof.js.map