@claude-flow/cli 3.32.25 → 3.32.26
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 -1
- package/.claude/helpers/auto-memory-hook.mjs +430 -430
- package/.claude/helpers/helpers.manifest.json +6 -6
- package/.claude/helpers/hook-handler.cjs +565 -565
- package/.claude/helpers/intelligence.cjs +1058 -1058
- package/.claude/helpers/statusline.cjs +1060 -1060
- package/catalog-manifest.json +4 -4
- package/dist/src/commands/metaharness.js +80 -2
- package/dist/src/mcp-tools/metaharness-tools.js +80 -1
- package/dist/src/services/flywheel-proposer.d.ts +87 -0
- package/dist/src/services/flywheel-proposer.js +165 -0
- package/dist/src/services/flywheel-receipt.d.ts +136 -0
- package/dist/src/services/flywheel-receipt.js +309 -0
- package/dist/src/services/flywheel-transaction.d.ts +77 -0
- package/dist/src/services/flywheel-transaction.js +378 -0
- package/dist/src/services/harness-flywheel-runtime.d.ts +9 -0
- package/dist/src/services/harness-flywheel-runtime.js +67 -2
- package/dist/src/services/harness-flywheel.d.ts +23 -0
- package/dist/src/services/harness-flywheel.js +111 -21
- package/package.json +3 -1
- package/.claude/.proven-config-version +0 -1
- package/.claude/proven-config.json +0 -42
package/catalog-manifest.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"generation":
|
|
4
|
-
"generatedAt": "2026-07-
|
|
5
|
-
"gitSha": "
|
|
3
|
+
"generation": 2,
|
|
4
|
+
"generatedAt": "2026-07-28T19:25:02.626Z",
|
|
5
|
+
"gitSha": "80634fa0",
|
|
6
6
|
"catalog": {
|
|
7
7
|
"agents": 164,
|
|
8
|
-
"tools":
|
|
8
|
+
"tools": 388,
|
|
9
9
|
"skills": 34
|
|
10
10
|
},
|
|
11
11
|
"benchmark": null
|
|
@@ -35,9 +35,11 @@
|
|
|
35
35
|
*/
|
|
36
36
|
import { output } from '../output.js';
|
|
37
37
|
import { spawnSync } from 'child_process';
|
|
38
|
-
import { existsSync } from 'fs';
|
|
38
|
+
import { existsSync, readFileSync } from 'fs';
|
|
39
39
|
import { join, dirname, resolve } from 'path';
|
|
40
40
|
import { fileURLToPath } from 'url';
|
|
41
|
+
import { runFlywheelWorker } from '../services/harness-flywheel-runtime.js';
|
|
42
|
+
import { listFlywheelReceipts, promoteFlywheelCandidate, readFlywheelTransactionState, verifyFlywheelLedger, } from '../services/flywheel-transaction.js';
|
|
41
43
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
42
44
|
// Subcommand → plugin script filename
|
|
43
45
|
const SUBCOMMANDS = {
|
|
@@ -65,7 +67,77 @@ const SUBCOMMANDS = {
|
|
|
65
67
|
// @metaharness/darwin@0.8.0 — GEPA library surface (genome ops; the
|
|
66
68
|
// gepaOptimize loop itself stays library-only / behind evolve)
|
|
67
69
|
gepa: 'gepa.mjs',
|
|
70
|
+
// ADR-153 Darwin evolution and stable benchmark-corpus operations.
|
|
71
|
+
evolve: 'evolve.mjs',
|
|
72
|
+
bench: 'bench.mjs',
|
|
73
|
+
// ADR-322 evaluation receipt + atomic promotion loop; handled in-process.
|
|
74
|
+
flywheel: '__internal__',
|
|
68
75
|
};
|
|
76
|
+
function flywheelFlag(flags, name, fallback) {
|
|
77
|
+
const value = flags[name];
|
|
78
|
+
return (value === undefined ? fallback : value);
|
|
79
|
+
}
|
|
80
|
+
async function dispatchFlywheel(operation, positional, flags) {
|
|
81
|
+
const projectRoot = resolve(String(flywheelFlag(flags, 'projectRoot', process.cwd())));
|
|
82
|
+
let data;
|
|
83
|
+
if (!operation || operation === 'status') {
|
|
84
|
+
data = {
|
|
85
|
+
state: readFlywheelTransactionState(projectRoot),
|
|
86
|
+
ledger: verifyFlywheelLedger(projectRoot),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
else if (operation === 'run') {
|
|
90
|
+
const privateKeyPath = flywheelFlag(flags, 'privateKey');
|
|
91
|
+
const publicKeyPath = flywheelFlag(flags, 'publicKey');
|
|
92
|
+
if (!!privateKeyPath !== !!publicKeyPath) {
|
|
93
|
+
data = { success: false, reason: '--private-key and --public-key must be supplied together' };
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
data = await runFlywheelWorker(projectRoot, {
|
|
97
|
+
optInOverride: true,
|
|
98
|
+
sample: Number(flywheelFlag(flags, 'sample', 40)),
|
|
99
|
+
proposer: String(flywheelFlag(flags, 'proposer', 'auto')),
|
|
100
|
+
receiptPrivateKeyPem: privateKeyPath ? readFileSync(resolve(privateKeyPath), 'utf8') : undefined,
|
|
101
|
+
receiptPublicKeyPem: publicKeyPath ? readFileSync(resolve(publicKeyPath), 'utf8') : undefined,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
else if (operation === 'receipts') {
|
|
106
|
+
data = listFlywheelReceipts(projectRoot).map(({ receipt, state }) => ({
|
|
107
|
+
receiptId: receipt.payload.receiptId,
|
|
108
|
+
candidateId: receipt.payload.candidateId,
|
|
109
|
+
baselineRef: receipt.payload.baselineRef,
|
|
110
|
+
decision: receipt.payload.decision,
|
|
111
|
+
signed: !!receipt.signature,
|
|
112
|
+
issuedAt: receipt.payload.issuedAt,
|
|
113
|
+
state,
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
else if (operation === 'history') {
|
|
117
|
+
const state = readFlywheelTransactionState(projectRoot);
|
|
118
|
+
data = { ledgerHead: state.ledgerHead, commits: state.commits };
|
|
119
|
+
}
|
|
120
|
+
else if (operation === 'promote') {
|
|
121
|
+
const receiptId = positional[0];
|
|
122
|
+
const publicKeyPath = flywheelFlag(flags, 'publicKey');
|
|
123
|
+
if (!receiptId || !publicKeyPath) {
|
|
124
|
+
data = { success: false, reason: 'promote requires <receipt-id> and --public-key <PEM path>' };
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
const publicKey = readFileSync(resolve(publicKeyPath), 'utf8');
|
|
128
|
+
data = await promoteFlywheelCandidate(projectRoot, receiptId, {
|
|
129
|
+
confirm: flywheelFlag(flags, 'confirm', false) === true,
|
|
130
|
+
trustedPublicKeys: new Set([publicKey]),
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
data = { success: false, reason: `unknown flywheel operation: ${operation}` };
|
|
136
|
+
}
|
|
137
|
+
output.writeln(JSON.stringify(data, null, 2));
|
|
138
|
+
const success = !(data && typeof data === 'object' && data.success === false);
|
|
139
|
+
return { success, exitCode: success ? 0 : 1, data };
|
|
140
|
+
}
|
|
69
141
|
/**
|
|
70
142
|
* Walk up from the current dirname to find the ruflo repo root that
|
|
71
143
|
* contains plugins/ruflo-metaharness/. Handles three install layouts:
|
|
@@ -127,7 +199,7 @@ export const metaharnessCommand = {
|
|
|
127
199
|
// iter 73 — list reflects all dispatchable subcommands (was
|
|
128
200
|
// stale at the iter-3 list of 5). Keep this synced with the
|
|
129
201
|
// SUBCOMMANDS map above.
|
|
130
|
-
description: 'One of: score | genome | mcp-scan | threat-model | oia-audit | audit-list | audit-trend | similarity | drift-from-history | mint | redblue | learn | gepa',
|
|
202
|
+
description: 'One of: score | genome | mcp-scan | threat-model | oia-audit | audit-list | audit-trend | similarity | drift-from-history | mint | redblue | learn | gepa | evolve | bench | flywheel',
|
|
131
203
|
type: 'string',
|
|
132
204
|
},
|
|
133
205
|
],
|
|
@@ -190,6 +262,9 @@ export const metaharnessCommand = {
|
|
|
190
262
|
output.writeln(' drift-from-history iter 53 — diff current state against most recent audit (1-command drift)');
|
|
191
263
|
output.writeln(' mint scaffold a custom harness (dry-run by default)');
|
|
192
264
|
output.writeln(' redblue adversarial red/blue LLM testing (init|run|patch|attack|report)');
|
|
265
|
+
output.writeln(' evolve Darwin candidate evolution');
|
|
266
|
+
output.writeln(' bench create or verify a stable benchmark suite');
|
|
267
|
+
output.writeln(' flywheel receipt loop: run | status | receipts | history | promote');
|
|
193
268
|
output.writeln('');
|
|
194
269
|
output.writeln('Each subcommand accepts --format json|table and --help.');
|
|
195
270
|
output.writeln('');
|
|
@@ -201,6 +276,9 @@ export const metaharnessCommand = {
|
|
|
201
276
|
output.writeln(`Valid: ${Object.keys(SUBCOMMANDS).join(', ')}`);
|
|
202
277
|
return { success: false, exitCode: 2, data: { subcommand } };
|
|
203
278
|
}
|
|
279
|
+
if (subcommand === 'flywheel') {
|
|
280
|
+
return dispatchFlywheel(positionalRest[0], positionalRest.slice(1), ctxFlags);
|
|
281
|
+
}
|
|
204
282
|
const scriptDir = locatePluginScripts(SUBCOMMANDS[subcommand]);
|
|
205
283
|
if (!scriptDir) {
|
|
206
284
|
output.writeln(output.warning('metaharness: plugins/ruflo-metaharness/scripts/ not found. Install ruflo with `npm i ruflo` or run from the ruflo repo.'));
|
|
@@ -48,9 +48,11 @@
|
|
|
48
48
|
*/
|
|
49
49
|
import { getProjectCwd } from './types.js';
|
|
50
50
|
import { spawn } from 'node:child_process';
|
|
51
|
-
import { existsSync } from 'node:fs';
|
|
51
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
52
52
|
import { join, dirname, resolve } from 'node:path';
|
|
53
53
|
import { fileURLToPath } from 'node:url';
|
|
54
|
+
import { runFlywheelWorker } from '../services/harness-flywheel-runtime.js';
|
|
55
|
+
import { listFlywheelReceipts, promoteFlywheelCandidate, readFlywheelTransactionState, verifyFlywheelLedger, } from '../services/flywheel-transaction.js';
|
|
54
56
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
55
57
|
/**
|
|
56
58
|
* Walk up from this module to find plugins/ruflo-metaharness/scripts/.
|
|
@@ -680,5 +682,82 @@ export const metaharnessTools = [
|
|
|
680
682
|
return { success: r.success, data: r.json, degraded: r.degraded, exitCode: r.exitCode };
|
|
681
683
|
},
|
|
682
684
|
},
|
|
685
|
+
{
|
|
686
|
+
name: 'metaharness_flywheel',
|
|
687
|
+
description: 'ADR-322 — evaluate candidates into immutable receipts, inspect the append-only promotion ledger, and explicitly promote one signed receipt with atomic compare-and-swap semantics. Evaluation never mutates the active champion. Promotion requires confirm=true and a local approved Ed25519 public-key PEM path; explicit trust is mandatory.',
|
|
688
|
+
category: 'metaharness',
|
|
689
|
+
inputSchema: {
|
|
690
|
+
type: 'object',
|
|
691
|
+
properties: {
|
|
692
|
+
operation: { type: 'string', enum: ['run', 'status', 'receipts', 'history', 'promote'], description: 'Flywheel operation', default: 'status' },
|
|
693
|
+
projectRoot: { type: 'string', description: 'Target project root (default: active project cwd)' },
|
|
694
|
+
receiptId: { type: 'string', description: 'Receipt content ID for promote' },
|
|
695
|
+
sample: { type: 'number', description: 'Maximum harvested evaluation sample', default: 40 },
|
|
696
|
+
proposer: { type: 'string', enum: ['local', 'auto', 'darwin'], description: 'Candidate proposer. Auto fallback is evaluation-only; explicit darwin fails closed when no compatible adapter is installed.', default: 'auto' },
|
|
697
|
+
privateKeyPath: { type: 'string', description: 'Local Ed25519 private-key PEM path for signing run receipts; must be paired with publicKeyPath' },
|
|
698
|
+
publicKeyPath: { type: 'string', description: 'Local Ed25519 public-key PEM path used to sign a run or approve a promotion' },
|
|
699
|
+
confirm: { type: 'boolean', description: 'Required for promote; never inferred', default: false },
|
|
700
|
+
},
|
|
701
|
+
required: ['operation'],
|
|
702
|
+
},
|
|
703
|
+
handler: async (input) => {
|
|
704
|
+
const operation = String(input.operation ?? 'status');
|
|
705
|
+
const projectRoot = resolve(String(input.projectRoot ?? getProjectCwd()));
|
|
706
|
+
if (operation === 'status') {
|
|
707
|
+
return {
|
|
708
|
+
success: true,
|
|
709
|
+
data: {
|
|
710
|
+
state: readFlywheelTransactionState(projectRoot),
|
|
711
|
+
ledger: verifyFlywheelLedger(projectRoot),
|
|
712
|
+
},
|
|
713
|
+
degraded: false,
|
|
714
|
+
exitCode: 0,
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
if (operation === 'receipts') {
|
|
718
|
+
const data = listFlywheelReceipts(projectRoot).map(({ receipt, state }) => ({
|
|
719
|
+
receiptId: receipt.payload.receiptId,
|
|
720
|
+
candidateId: receipt.payload.candidateId,
|
|
721
|
+
baselineRef: receipt.payload.baselineRef,
|
|
722
|
+
decision: receipt.payload.decision,
|
|
723
|
+
signed: !!receipt.signature,
|
|
724
|
+
issuedAt: receipt.payload.issuedAt,
|
|
725
|
+
state,
|
|
726
|
+
}));
|
|
727
|
+
return { success: true, data, degraded: false, exitCode: 0 };
|
|
728
|
+
}
|
|
729
|
+
if (operation === 'history') {
|
|
730
|
+
const state = readFlywheelTransactionState(projectRoot);
|
|
731
|
+
return { success: true, data: { ledgerHead: state.ledgerHead, commits: state.commits }, degraded: false, exitCode: 0 };
|
|
732
|
+
}
|
|
733
|
+
if (operation === 'run') {
|
|
734
|
+
const privateKeyPath = input.privateKeyPath ? resolve(String(input.privateKeyPath)) : undefined;
|
|
735
|
+
const publicKeyPath = input.publicKeyPath ? resolve(String(input.publicKeyPath)) : undefined;
|
|
736
|
+
if (!!privateKeyPath !== !!publicKeyPath) {
|
|
737
|
+
return { success: false, data: { reason: 'privateKeyPath and publicKeyPath must be supplied together' }, degraded: false, exitCode: 2 };
|
|
738
|
+
}
|
|
739
|
+
const data = await runFlywheelWorker(projectRoot, {
|
|
740
|
+
optInOverride: true,
|
|
741
|
+
sample: Number(input.sample ?? 40),
|
|
742
|
+
proposer: String(input.proposer ?? 'auto'),
|
|
743
|
+
receiptPrivateKeyPem: privateKeyPath ? readFileSync(privateKeyPath, 'utf8') : undefined,
|
|
744
|
+
receiptPublicKeyPem: publicKeyPath ? readFileSync(publicKeyPath, 'utf8') : undefined,
|
|
745
|
+
});
|
|
746
|
+
return { success: data.ran, data, degraded: false, exitCode: data.ran ? 0 : 1 };
|
|
747
|
+
}
|
|
748
|
+
if (operation === 'promote') {
|
|
749
|
+
if (!input.receiptId || !input.publicKeyPath) {
|
|
750
|
+
return { success: false, data: { reason: 'receiptId and publicKeyPath are required' }, degraded: false, exitCode: 2 };
|
|
751
|
+
}
|
|
752
|
+
const publicKey = readFileSync(resolve(String(input.publicKeyPath)), 'utf8');
|
|
753
|
+
const data = await promoteFlywheelCandidate(projectRoot, String(input.receiptId), {
|
|
754
|
+
confirm: input.confirm === true,
|
|
755
|
+
trustedPublicKeys: new Set([publicKey]),
|
|
756
|
+
});
|
|
757
|
+
return { success: data.success, data, degraded: false, exitCode: data.success ? 0 : 1 };
|
|
758
|
+
}
|
|
759
|
+
return { success: false, data: { reason: `unsupported operation: ${operation}` }, degraded: false, exitCode: 2 };
|
|
760
|
+
},
|
|
761
|
+
},
|
|
683
762
|
];
|
|
684
763
|
//# sourceMappingURL=metaharness-tools.js.map
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ADR-322B proposer adapter.
|
|
3
|
+
*
|
|
4
|
+
* Proposers are untrusted candidate generators. This adapter normalizes local
|
|
5
|
+
* and Darwin output, enforces hard policy/resource admissibility before Pareto
|
|
6
|
+
* ranking, and makes auto-mode substitution evaluation-only by default.
|
|
7
|
+
*/
|
|
8
|
+
import { type ProposerName } from './flywheel-receipt.js';
|
|
9
|
+
export type ProposerMode = 'auto' | ProposerName;
|
|
10
|
+
export interface SafetyEnvelope {
|
|
11
|
+
ref: string;
|
|
12
|
+
allowedPolicyKeys: string[];
|
|
13
|
+
numericBounds?: Record<string, {
|
|
14
|
+
min?: number;
|
|
15
|
+
max?: number;
|
|
16
|
+
}>;
|
|
17
|
+
maxP95LatencyMicros: number;
|
|
18
|
+
maxCostMicrosPerTask: number;
|
|
19
|
+
maxTokensPerTask: number;
|
|
20
|
+
maxFailureRate: number;
|
|
21
|
+
maxEvaluationCostMicros: number;
|
|
22
|
+
}
|
|
23
|
+
export interface CandidateResources {
|
|
24
|
+
p95LatencyMicros: number;
|
|
25
|
+
costMicrosPerTask: number;
|
|
26
|
+
tokensPerTask: number;
|
|
27
|
+
failureRate: number;
|
|
28
|
+
evaluationCostMicros: number;
|
|
29
|
+
}
|
|
30
|
+
export interface ProposedCandidate {
|
|
31
|
+
policy: Record<string, unknown>;
|
|
32
|
+
resources: CandidateResources;
|
|
33
|
+
score?: number;
|
|
34
|
+
mutation?: string;
|
|
35
|
+
parentIds?: string[];
|
|
36
|
+
}
|
|
37
|
+
export interface NormalizedCandidate extends ProposedCandidate {
|
|
38
|
+
candidateId: string;
|
|
39
|
+
admissible: boolean;
|
|
40
|
+
rejectionReasons: string[];
|
|
41
|
+
}
|
|
42
|
+
export interface CandidateArchive {
|
|
43
|
+
archiveVersion: 'ruflo.candidate-archive/v1';
|
|
44
|
+
archiveId: string;
|
|
45
|
+
requestedProposer: ProposerMode;
|
|
46
|
+
effectiveProposer: ProposerName;
|
|
47
|
+
proposerSubstitution?: string;
|
|
48
|
+
promotionAllowed: boolean;
|
|
49
|
+
baselineRef: string;
|
|
50
|
+
safetyEnvelopeRef: string;
|
|
51
|
+
seed: number;
|
|
52
|
+
candidates: NormalizedCandidate[];
|
|
53
|
+
admissibleCandidates: NormalizedCandidate[];
|
|
54
|
+
paretoCandidates: NormalizedCandidate[];
|
|
55
|
+
completed: boolean;
|
|
56
|
+
}
|
|
57
|
+
export interface DarwinRunInput {
|
|
58
|
+
baselinePolicy: Record<string, unknown>;
|
|
59
|
+
seed: number;
|
|
60
|
+
budget: {
|
|
61
|
+
maxEvaluationCostMicros: number;
|
|
62
|
+
maxConcurrency: number;
|
|
63
|
+
maxWallTimeMs: number;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
export type DarwinInvoker = (input: DarwinRunInput) => Promise<{
|
|
67
|
+
completed: boolean;
|
|
68
|
+
candidates: ProposedCandidate[];
|
|
69
|
+
reason?: string;
|
|
70
|
+
}>;
|
|
71
|
+
export interface ProposeCandidatesInput {
|
|
72
|
+
mode: ProposerMode;
|
|
73
|
+
baselinePolicy: Record<string, unknown>;
|
|
74
|
+
safetyEnvelope: SafetyEnvelope;
|
|
75
|
+
seed: number;
|
|
76
|
+
localProposer: (baseline: Record<string, unknown>, seed: number) => ProposedCandidate[] | Promise<ProposedCandidate[]>;
|
|
77
|
+
darwinInvoker?: DarwinInvoker;
|
|
78
|
+
allowSubstitutionPromotion?: boolean;
|
|
79
|
+
maxConcurrency?: number;
|
|
80
|
+
maxWallTimeMs?: number;
|
|
81
|
+
maxCandidates?: number;
|
|
82
|
+
}
|
|
83
|
+
export declare class DarwinUnavailableError extends Error {
|
|
84
|
+
constructor(message: string);
|
|
85
|
+
}
|
|
86
|
+
export declare function proposeFlywheelCandidates(input: ProposeCandidatesInput): Promise<CandidateArchive>;
|
|
87
|
+
//# sourceMappingURL=flywheel-proposer.d.ts.map
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ADR-322B proposer adapter.
|
|
3
|
+
*
|
|
4
|
+
* Proposers are untrusted candidate generators. This adapter normalizes local
|
|
5
|
+
* and Darwin output, enforces hard policy/resource admissibility before Pareto
|
|
6
|
+
* ranking, and makes auto-mode substitution evaluation-only by default.
|
|
7
|
+
*/
|
|
8
|
+
import { canonicalizeJcs, policyCandidateId, sha256Ref } from './flywheel-receipt.js';
|
|
9
|
+
export class DarwinUnavailableError extends Error {
|
|
10
|
+
constructor(message) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = 'DarwinUnavailableError';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function validateCandidate(candidate, envelope) {
|
|
16
|
+
const reasons = [];
|
|
17
|
+
const allowed = new Set(envelope.allowedPolicyKeys);
|
|
18
|
+
for (const [key, value] of Object.entries(candidate.policy)) {
|
|
19
|
+
if (!allowed.has(key)) {
|
|
20
|
+
reasons.push(`policy key not allowed: ${key}`);
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
const bounds = envelope.numericBounds?.[key];
|
|
24
|
+
if (bounds && typeof value === 'number') {
|
|
25
|
+
if (bounds.min !== undefined && value < bounds.min)
|
|
26
|
+
reasons.push(`${key} below minimum`);
|
|
27
|
+
if (bounds.max !== undefined && value > bounds.max)
|
|
28
|
+
reasons.push(`${key} above maximum`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const r = candidate.resources;
|
|
32
|
+
if (r.p95LatencyMicros > envelope.maxP95LatencyMicros)
|
|
33
|
+
reasons.push('p95 latency exceeds envelope');
|
|
34
|
+
if (r.costMicrosPerTask > envelope.maxCostMicrosPerTask)
|
|
35
|
+
reasons.push('cost per task exceeds envelope');
|
|
36
|
+
if (r.tokensPerTask > envelope.maxTokensPerTask)
|
|
37
|
+
reasons.push('tokens per task exceeds envelope');
|
|
38
|
+
if (r.failureRate > envelope.maxFailureRate)
|
|
39
|
+
reasons.push('failure rate exceeds envelope');
|
|
40
|
+
if (r.evaluationCostMicros > envelope.maxEvaluationCostMicros)
|
|
41
|
+
reasons.push('evaluation cost exceeds envelope');
|
|
42
|
+
return {
|
|
43
|
+
...candidate,
|
|
44
|
+
candidateId: policyCandidateId(candidate.policy),
|
|
45
|
+
admissible: reasons.length === 0,
|
|
46
|
+
rejectionReasons: reasons,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function finalizeArchive(input) {
|
|
50
|
+
const normalized = input.candidates.map((candidate) => validateCandidate(candidate, input.safetyEnvelope));
|
|
51
|
+
const admissible = normalized.filter((candidate) => candidate.admissible);
|
|
52
|
+
const dominates = (a, b) => {
|
|
53
|
+
const av = [
|
|
54
|
+
a.score ?? 0,
|
|
55
|
+
-a.resources.p95LatencyMicros,
|
|
56
|
+
-a.resources.costMicrosPerTask,
|
|
57
|
+
-a.resources.tokensPerTask,
|
|
58
|
+
-a.resources.failureRate,
|
|
59
|
+
-a.resources.evaluationCostMicros,
|
|
60
|
+
];
|
|
61
|
+
const bv = [
|
|
62
|
+
b.score ?? 0,
|
|
63
|
+
-b.resources.p95LatencyMicros,
|
|
64
|
+
-b.resources.costMicrosPerTask,
|
|
65
|
+
-b.resources.tokensPerTask,
|
|
66
|
+
-b.resources.failureRate,
|
|
67
|
+
-b.resources.evaluationCostMicros,
|
|
68
|
+
];
|
|
69
|
+
return av.every((value, index) => value >= bv[index])
|
|
70
|
+
&& av.some((value, index) => value > bv[index]);
|
|
71
|
+
};
|
|
72
|
+
const paretoCandidates = admissible.filter((candidate, index) => !admissible.some((other, otherIndex) => otherIndex !== index && dominates(other, candidate)));
|
|
73
|
+
const core = {
|
|
74
|
+
archiveVersion: 'ruflo.candidate-archive/v1',
|
|
75
|
+
requestedProposer: input.requested,
|
|
76
|
+
effectiveProposer: input.effective,
|
|
77
|
+
...(input.substitution ? { proposerSubstitution: input.substitution } : {}),
|
|
78
|
+
promotionAllowed: input.promotionAllowed,
|
|
79
|
+
baselineRef: input.baselineRef,
|
|
80
|
+
safetyEnvelopeRef: input.safetyEnvelope.ref,
|
|
81
|
+
seed: input.seed,
|
|
82
|
+
candidates: normalized,
|
|
83
|
+
completed: input.completed,
|
|
84
|
+
};
|
|
85
|
+
return {
|
|
86
|
+
...core,
|
|
87
|
+
archiveId: sha256Ref(canonicalizeJcs(core)),
|
|
88
|
+
admissibleCandidates: admissible,
|
|
89
|
+
paretoCandidates,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
export async function proposeFlywheelCandidates(input) {
|
|
93
|
+
const baselineRef = policyCandidateId(input.baselinePolicy);
|
|
94
|
+
const runLocal = async (substitution) => finalizeArchive({
|
|
95
|
+
requested: input.mode,
|
|
96
|
+
effective: 'local',
|
|
97
|
+
substitution,
|
|
98
|
+
promotionAllowed: substitution ? input.allowSubstitutionPromotion === true : true,
|
|
99
|
+
baselineRef,
|
|
100
|
+
safetyEnvelope: input.safetyEnvelope,
|
|
101
|
+
seed: input.seed,
|
|
102
|
+
candidates: await input.localProposer(input.baselinePolicy, input.seed),
|
|
103
|
+
completed: true,
|
|
104
|
+
});
|
|
105
|
+
if (input.mode === 'local')
|
|
106
|
+
return runLocal();
|
|
107
|
+
if (!input.darwinInvoker) {
|
|
108
|
+
if (input.mode === 'darwin')
|
|
109
|
+
throw new DarwinUnavailableError('Darwin explicitly requested but unavailable');
|
|
110
|
+
return runLocal('darwin-unavailable');
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
const maxWallTimeMs = input.maxWallTimeMs ?? 60_000;
|
|
114
|
+
let timeout;
|
|
115
|
+
const result = await Promise.race([
|
|
116
|
+
input.darwinInvoker({
|
|
117
|
+
baselinePolicy: input.baselinePolicy,
|
|
118
|
+
seed: input.seed,
|
|
119
|
+
budget: {
|
|
120
|
+
maxEvaluationCostMicros: input.safetyEnvelope.maxEvaluationCostMicros,
|
|
121
|
+
maxConcurrency: input.maxConcurrency ?? 2,
|
|
122
|
+
maxWallTimeMs,
|
|
123
|
+
},
|
|
124
|
+
}),
|
|
125
|
+
new Promise((_, reject) => {
|
|
126
|
+
timeout = setTimeout(() => reject(new DarwinUnavailableError('Darwin exceeded wall-time budget')), maxWallTimeMs);
|
|
127
|
+
timeout.unref?.();
|
|
128
|
+
}),
|
|
129
|
+
]).finally(() => {
|
|
130
|
+
if (timeout)
|
|
131
|
+
clearTimeout(timeout);
|
|
132
|
+
});
|
|
133
|
+
if (!result.completed) {
|
|
134
|
+
if (input.mode === 'darwin')
|
|
135
|
+
throw new DarwinUnavailableError(result.reason ?? 'Darwin archive incomplete');
|
|
136
|
+
return runLocal(`darwin-incomplete:${result.reason ?? 'unknown'}`);
|
|
137
|
+
}
|
|
138
|
+
if (result.candidates.length > (input.maxCandidates ?? 256)) {
|
|
139
|
+
throw new DarwinUnavailableError('Darwin archive exceeded candidate-count budget');
|
|
140
|
+
}
|
|
141
|
+
const evaluationCost = result.candidates.reduce((sum, candidate) => sum + candidate.resources.evaluationCostMicros, 0);
|
|
142
|
+
if (evaluationCost > input.safetyEnvelope.maxEvaluationCostMicros) {
|
|
143
|
+
throw new DarwinUnavailableError('Darwin archive exceeded evaluation-cost budget');
|
|
144
|
+
}
|
|
145
|
+
return finalizeArchive({
|
|
146
|
+
requested: input.mode,
|
|
147
|
+
effective: 'darwin',
|
|
148
|
+
promotionAllowed: true,
|
|
149
|
+
baselineRef,
|
|
150
|
+
safetyEnvelope: input.safetyEnvelope,
|
|
151
|
+
seed: input.seed,
|
|
152
|
+
candidates: result.candidates,
|
|
153
|
+
completed: true,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
if (input.mode === 'darwin') {
|
|
158
|
+
if (error instanceof DarwinUnavailableError)
|
|
159
|
+
throw error;
|
|
160
|
+
throw new DarwinUnavailableError(`Darwin failed closed: ${error.message}`);
|
|
161
|
+
}
|
|
162
|
+
return runLocal(`darwin-error:${error.message}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=flywheel-proposer.js.map
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
export declare const RECEIPT_SCHEMA = "ruflo.flywheel-receipt/v1";
|
|
2
|
+
export declare const RECEIPT_DOMAIN = "ruflo/flywheel-receipt/v1";
|
|
3
|
+
export declare const GENESIS_LEDGER_HEAD: string;
|
|
4
|
+
export type VerificationKind = 'recomputed' | 'signature-verified' | 'trusted-assertion';
|
|
5
|
+
export type ProposerName = 'local' | 'darwin';
|
|
6
|
+
export interface ReceiptSignature {
|
|
7
|
+
algorithm: 'ed25519';
|
|
8
|
+
domain: typeof RECEIPT_DOMAIN;
|
|
9
|
+
publicKeyPem: string;
|
|
10
|
+
signatureBase64: string;
|
|
11
|
+
}
|
|
12
|
+
export interface ResourceEvidence {
|
|
13
|
+
p95LatencyMicros: number;
|
|
14
|
+
costMicrosPerTask: number;
|
|
15
|
+
tokensPerTask: number;
|
|
16
|
+
failureRate: string;
|
|
17
|
+
evaluationCostMicros: number;
|
|
18
|
+
energyMicrojoules?: number;
|
|
19
|
+
currency: string;
|
|
20
|
+
}
|
|
21
|
+
export interface PromotionStatistics {
|
|
22
|
+
ruleVersion: 'ruflo.flywheel-gate/v1';
|
|
23
|
+
relativeLift: string;
|
|
24
|
+
pairedBootstrapProbability: string;
|
|
25
|
+
pairedBootstrapDeltaCILow95: string;
|
|
26
|
+
frozenAnchorRegression: string;
|
|
27
|
+
iterations: number;
|
|
28
|
+
seedHex: string;
|
|
29
|
+
significant: boolean;
|
|
30
|
+
accepted: boolean;
|
|
31
|
+
}
|
|
32
|
+
export interface TermVerification {
|
|
33
|
+
term: string;
|
|
34
|
+
verification: VerificationKind;
|
|
35
|
+
evidenceRef: string;
|
|
36
|
+
attestor?: string;
|
|
37
|
+
}
|
|
38
|
+
export interface EvaluationEvidence {
|
|
39
|
+
corpusRoles: {
|
|
40
|
+
selectionTaskIds: string[];
|
|
41
|
+
promotionHoldoutTaskIds: string[];
|
|
42
|
+
guardTaskIds: string[];
|
|
43
|
+
};
|
|
44
|
+
verification: Record<string, unknown>;
|
|
45
|
+
canary: Record<string, unknown>;
|
|
46
|
+
}
|
|
47
|
+
export interface FlywheelReceiptPayload {
|
|
48
|
+
schemaVersion: typeof RECEIPT_SCHEMA;
|
|
49
|
+
receiptId: string;
|
|
50
|
+
lineageId: string;
|
|
51
|
+
candidateId: string;
|
|
52
|
+
evaluationRunId: string;
|
|
53
|
+
baselineRef: string;
|
|
54
|
+
expectedLedgerHead: string;
|
|
55
|
+
candidatePolicy: Record<string, unknown>;
|
|
56
|
+
gateVersion: string;
|
|
57
|
+
policySchemaVersion: string;
|
|
58
|
+
safetyEnvelopeRef: string;
|
|
59
|
+
requestedProposer: 'auto' | ProposerName;
|
|
60
|
+
effectiveProposer: ProposerName;
|
|
61
|
+
proposerSubstitution?: string;
|
|
62
|
+
corpusVersion: string;
|
|
63
|
+
corpusHash: string;
|
|
64
|
+
baselineScore: string;
|
|
65
|
+
candidateScore: string;
|
|
66
|
+
heldOutDeltas: string[];
|
|
67
|
+
statistics: PromotionStatistics;
|
|
68
|
+
gates: Record<string, boolean>;
|
|
69
|
+
resourceEvidence: ResourceEvidence;
|
|
70
|
+
evidence: EvaluationEvidence;
|
|
71
|
+
termVerification: TermVerification[];
|
|
72
|
+
decision: 'accepted' | 'rejected';
|
|
73
|
+
issuedAt: string;
|
|
74
|
+
expiresAt: string;
|
|
75
|
+
}
|
|
76
|
+
export interface FlywheelEvaluationReceipt {
|
|
77
|
+
payload: FlywheelReceiptPayload;
|
|
78
|
+
signature?: ReceiptSignature;
|
|
79
|
+
}
|
|
80
|
+
export interface CreateReceiptInput {
|
|
81
|
+
lineageId?: string;
|
|
82
|
+
evaluationRunId?: string;
|
|
83
|
+
baselineRef: string;
|
|
84
|
+
expectedLedgerHead?: string;
|
|
85
|
+
candidatePolicy: Record<string, unknown>;
|
|
86
|
+
gateVersion?: string;
|
|
87
|
+
policySchemaVersion?: string;
|
|
88
|
+
safetyEnvelopeRef: string;
|
|
89
|
+
requestedProposer?: 'auto' | ProposerName;
|
|
90
|
+
effectiveProposer?: ProposerName;
|
|
91
|
+
proposerSubstitution?: string;
|
|
92
|
+
corpusVersion: string;
|
|
93
|
+
corpusHash: string;
|
|
94
|
+
baselineScore: number;
|
|
95
|
+
candidateScore: number;
|
|
96
|
+
heldOutDeltas: number[];
|
|
97
|
+
frozenAnchorRegression: number;
|
|
98
|
+
gates: Record<string, boolean>;
|
|
99
|
+
resourceEvidence?: Partial<ResourceEvidence>;
|
|
100
|
+
evidence?: EvaluationEvidence;
|
|
101
|
+
termVerification?: TermVerification[];
|
|
102
|
+
now?: number;
|
|
103
|
+
ttlMs?: number;
|
|
104
|
+
privateKeyPem?: string;
|
|
105
|
+
publicKeyPem?: string;
|
|
106
|
+
bootstrapIterations?: number;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* RFC-8785-compatible for the JSON domain accepted above: ECMAScript primitive
|
|
110
|
+
* serialization plus recursively sorted UTF-16 property names.
|
|
111
|
+
*/
|
|
112
|
+
export declare function canonicalizeJcs(value: unknown): string;
|
|
113
|
+
export declare function sha256Ref(value: string | Buffer): string;
|
|
114
|
+
export declare function policyCandidateId(policy: Record<string, unknown>): string;
|
|
115
|
+
/** UUIDv7 with a 48-bit millisecond timestamp and RFC-4122 variant bits. */
|
|
116
|
+
export declare function uuidV7(now?: number): string;
|
|
117
|
+
export declare function computePromotionStatistics(input: {
|
|
118
|
+
baselineScore: number;
|
|
119
|
+
candidateScore: number;
|
|
120
|
+
heldOutDeltas: number[];
|
|
121
|
+
frozenAnchorRegression: number;
|
|
122
|
+
corpusHash: string;
|
|
123
|
+
candidateId: string;
|
|
124
|
+
baselineRef: string;
|
|
125
|
+
evaluationRunId: string;
|
|
126
|
+
iterations?: number;
|
|
127
|
+
metricEpsilon?: number;
|
|
128
|
+
}): PromotionStatistics;
|
|
129
|
+
export declare function createFlywheelReceipt(input: CreateReceiptInput): FlywheelEvaluationReceipt;
|
|
130
|
+
export interface ReceiptVerification {
|
|
131
|
+
valid: boolean;
|
|
132
|
+
signed: boolean;
|
|
133
|
+
errors: string[];
|
|
134
|
+
}
|
|
135
|
+
export declare function verifyFlywheelReceipt(receipt: FlywheelEvaluationReceipt, trustedPublicKeys?: Set<string>): ReceiptVerification;
|
|
136
|
+
//# sourceMappingURL=flywheel-receipt.d.ts.map
|