@claude-flow/cli 3.32.24 → 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 +81 -2
- package/dist/src/services/distill-oracle.d.ts +1 -0
- package/dist/src/services/distill-oracle.js +11 -3
- 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 +4 -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/.
|
|
@@ -399,7 +401,7 @@ export const metaharnessTools = [
|
|
|
399
401
|
},
|
|
400
402
|
// ───────────────────────────────────────────────────────────────────────
|
|
401
403
|
// ADR-153 — @metaharness/darwin integration (3 tools).
|
|
402
|
-
// Backed by the separate `@metaharness/darwin@~0.
|
|
404
|
+
// Backed by the separate `@metaharness/darwin@~0.8.0` npm package, NOT
|
|
403
405
|
// the umbrella `metaharness`. Plugin scripts shell out via _darwin.mjs.
|
|
404
406
|
// Same {success, data, degraded, exitCode} contract.
|
|
405
407
|
// ───────────────────────────────────────────────────────────────────────
|
|
@@ -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
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
*/
|
|
37
37
|
import { type VerifyTaskKind } from '../ruvector/output-verifier.js';
|
|
38
38
|
import { FableHarness, type ReflectItem, type ReflectResult } from './fable-harness.js';
|
|
39
|
+
export declare const MH_DARWIN_PIN = "0.8.0";
|
|
39
40
|
export type ResolvedProvenance = 'oracle:test-exec' | 'judge:fable' | 'proxy:structural';
|
|
40
41
|
/** SWE-bench-shaped / bench-suite-mapped test spec that Tier 1 can execute. */
|
|
41
42
|
export interface TestSpec {
|
|
@@ -37,6 +37,14 @@
|
|
|
37
37
|
import { spawn } from 'child_process';
|
|
38
38
|
import { verifyAndEscalate } from '../ruvector/output-verifier.js';
|
|
39
39
|
import { FableHarness, } from './fable-harness.js';
|
|
40
|
+
// Pinned @metaharness/darwin version for the Tier-1 mechanical oracle's `npx`
|
|
41
|
+
// invocations. Bare `npx --yes @metaharness/darwin` floats to whatever npm
|
|
42
|
+
// `latest` is, so a breaking darwin release could silently change eval
|
|
43
|
+
// behavior mid-run (the #142 pin-drift failure mode). Pin it, and let
|
|
44
|
+
// scripts/check-metaharness-pins.mjs watch this constant for drift. Kept in
|
|
45
|
+
// lock-step with the optionalDependencies pin in package.json and the plugin
|
|
46
|
+
// darwin cache (darwin-cache-0.8.0).
|
|
47
|
+
export const MH_DARWIN_PIN = '0.8.0';
|
|
40
48
|
// ── Public API ───────────────────────────────────────────────────────────
|
|
41
49
|
/**
|
|
42
50
|
* Label each trajectory with `resolved` + honest provenance, trying the tiers
|
|
@@ -198,7 +206,7 @@ export function buildOraclePlan(spec, remote) {
|
|
|
198
206
|
const probeCommands = [
|
|
199
207
|
`ssh -o BatchMode=yes -o ConnectTimeout=8 ${r} 'echo ok'`,
|
|
200
208
|
`${sshLocal}'command -v docker >/dev/null 2>&1 && echo docker-present || echo docker-missing'`,
|
|
201
|
-
`${sshLocal}'command -v darwin >/dev/null 2>&1 || npx --yes @metaharness/darwin --version'`,
|
|
209
|
+
`${sshLocal}'command -v darwin >/dev/null 2>&1 || npx --yes @metaharness/darwin@${MH_DARWIN_PIN} --version'`,
|
|
202
210
|
];
|
|
203
211
|
let kind;
|
|
204
212
|
const evalCommands = [];
|
|
@@ -207,7 +215,7 @@ export function buildOraclePlan(spec, remote) {
|
|
|
207
215
|
kind = 'ssh-darwin-bench';
|
|
208
216
|
const suite = spec.benchSuite ? `--suite ${shq(spec.benchSuite)}` : '';
|
|
209
217
|
const kase = spec.benchCase ? `--case ${shq(spec.benchCase)}` : '';
|
|
210
|
-
evalCommands.push(`${sshLocal}'cd ${workdir} && npx --yes @metaharness/darwin bench run ${suite} ${kase} --json'`.replace(/\s+/g, ' ').trim());
|
|
218
|
+
evalCommands.push(`${sshLocal}'cd ${workdir} && npx --yes @metaharness/darwin@${MH_DARWIN_PIN} bench run ${suite} ${kase} --json'`.replace(/\s+/g, ' ').trim());
|
|
211
219
|
parseHint = 'resolved = darwin bench run reports the case scored PASS (exit 0, json.passed=true)';
|
|
212
220
|
}
|
|
213
221
|
else if (spec.evalCommand) {
|
|
@@ -222,7 +230,7 @@ export function buildOraclePlan(spec, remote) {
|
|
|
222
230
|
}
|
|
223
231
|
const f2p = (spec.failToPass ?? []).map(shq).join(' ');
|
|
224
232
|
const p2p = (spec.passToPass ?? []).map(shq).join(' ');
|
|
225
|
-
evalCommands.push(`${sshLocal}'cd ${workdir} && npx --yes @metaharness/darwin swebench-eval` +
|
|
233
|
+
evalCommands.push(`${sshLocal}'cd ${workdir} && npx --yes @metaharness/darwin@${MH_DARWIN_PIN} swebench-eval` +
|
|
226
234
|
`${spec.repo ? ` --repo ${shq(spec.repo)}` : ''}` +
|
|
227
235
|
`${spec.baseCommit ? ` --base ${shq(spec.baseCommit)}` : ''}` +
|
|
228
236
|
`${f2p ? ` --fail-to-pass ${f2p}` : ''}` +
|
|
@@ -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
|