@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
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ADR-322A promotion transaction.
|
|
3
|
+
*
|
|
4
|
+
* A single atomic state file is the authority for active champion, receipt
|
|
5
|
+
* consumption, lineage head, and serving epoch. Cross-process mutation is
|
|
6
|
+
* serialized with an O_EXCL lock; atomic rename commits all authoritative state
|
|
7
|
+
* at once. Runtime policy materialization is derived and recoverable.
|
|
8
|
+
*/
|
|
9
|
+
import * as fs from 'node:fs';
|
|
10
|
+
import * as path from 'node:path';
|
|
11
|
+
import { applyChampionParams } from '../config/harness-feedback-applier.js';
|
|
12
|
+
import { GENESIS_LEDGER_HEAD, canonicalizeJcs, sha256Ref, uuidV7, verifyFlywheelReceipt, } from './flywheel-receipt.js';
|
|
13
|
+
const STATE_VERSION = 1;
|
|
14
|
+
const STATE_DIR = ['.claude-flow', 'flywheel-v1'];
|
|
15
|
+
const STATE_FILE = 'transaction-state.json';
|
|
16
|
+
const LOCK_FILE = 'transaction-state.lock';
|
|
17
|
+
const RECEIPTS_DIR = 'receipts';
|
|
18
|
+
const LOCK_TIMEOUT_MS = 10_000;
|
|
19
|
+
const LOCK_STALE_MS = 60_000;
|
|
20
|
+
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
21
|
+
function stateDir(root) {
|
|
22
|
+
return path.join(root, ...STATE_DIR);
|
|
23
|
+
}
|
|
24
|
+
function statePath(root) {
|
|
25
|
+
return path.join(stateDir(root), STATE_FILE);
|
|
26
|
+
}
|
|
27
|
+
function lockPath(root) {
|
|
28
|
+
return path.join(stateDir(root), LOCK_FILE);
|
|
29
|
+
}
|
|
30
|
+
function receiptDir(root) {
|
|
31
|
+
return path.join(stateDir(root), RECEIPTS_DIR);
|
|
32
|
+
}
|
|
33
|
+
function assertSafeFile(file) {
|
|
34
|
+
try {
|
|
35
|
+
if (fs.lstatSync(file).isSymbolicLink())
|
|
36
|
+
throw new Error(`refusing symlink: ${file}`);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
if (error.code !== 'ENOENT')
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function ensureDir(root) {
|
|
44
|
+
fs.mkdirSync(receiptDir(root), { recursive: true, mode: 0o700 });
|
|
45
|
+
assertSafeFile(statePath(root));
|
|
46
|
+
assertSafeFile(lockPath(root));
|
|
47
|
+
}
|
|
48
|
+
function emptyState() {
|
|
49
|
+
return {
|
|
50
|
+
version: STATE_VERSION,
|
|
51
|
+
activeChampionRef: null,
|
|
52
|
+
activePolicy: null,
|
|
53
|
+
activeGateVersion: null,
|
|
54
|
+
activePolicySchemaVersion: null,
|
|
55
|
+
activeSafetyEnvelopeRef: null,
|
|
56
|
+
ledgerHead: GENESIS_LEDGER_HEAD,
|
|
57
|
+
servingEpoch: 0,
|
|
58
|
+
materializedServingEpoch: 0,
|
|
59
|
+
servedChampionRef: null,
|
|
60
|
+
receiptStates: {},
|
|
61
|
+
commits: [],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export function readFlywheelTransactionState(root) {
|
|
65
|
+
try {
|
|
66
|
+
assertSafeFile(statePath(root));
|
|
67
|
+
const parsed = JSON.parse(fs.readFileSync(statePath(root), 'utf8'));
|
|
68
|
+
if (parsed.version !== STATE_VERSION || !parsed.receiptStates || !Array.isArray(parsed.commits))
|
|
69
|
+
return emptyState();
|
|
70
|
+
return parsed;
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return emptyState();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function atomicWriteJson(file, value) {
|
|
77
|
+
assertSafeFile(file);
|
|
78
|
+
const tmp = `${file}.tmp.${process.pid}.${Date.now()}`;
|
|
79
|
+
const fd = fs.openSync(tmp, fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_WRONLY, 0o600);
|
|
80
|
+
try {
|
|
81
|
+
fs.writeFileSync(fd, `${canonicalizeJcs(value)}\n`, 'utf8');
|
|
82
|
+
fs.fsyncSync(fd);
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
fs.closeSync(fd);
|
|
86
|
+
}
|
|
87
|
+
fs.renameSync(tmp, file);
|
|
88
|
+
const dirFd = fs.openSync(path.dirname(file), fs.constants.O_RDONLY);
|
|
89
|
+
try {
|
|
90
|
+
fs.fsyncSync(dirFd);
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
fs.closeSync(dirFd);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async function withStateLock(root, fn) {
|
|
97
|
+
ensureDir(root);
|
|
98
|
+
const lock = lockPath(root);
|
|
99
|
+
const deadline = Date.now() + LOCK_TIMEOUT_MS;
|
|
100
|
+
for (;;) {
|
|
101
|
+
try {
|
|
102
|
+
const fd = fs.openSync(lock, fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_WRONLY, 0o600);
|
|
103
|
+
fs.writeFileSync(fd, JSON.stringify({ pid: process.pid, at: Date.now() }), 'utf8');
|
|
104
|
+
fs.closeSync(fd);
|
|
105
|
+
try {
|
|
106
|
+
return await fn();
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
try {
|
|
110
|
+
fs.unlinkSync(lock);
|
|
111
|
+
}
|
|
112
|
+
catch { /* lock already gone */ }
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
if (error.code !== 'EEXIST')
|
|
117
|
+
throw error;
|
|
118
|
+
try {
|
|
119
|
+
const stat = fs.lstatSync(lock);
|
|
120
|
+
if (Date.now() - stat.mtimeMs > LOCK_STALE_MS) {
|
|
121
|
+
fs.unlinkSync(lock);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
catch { /* raced with owner */ }
|
|
126
|
+
if (Date.now() >= deadline)
|
|
127
|
+
throw new Error('timed out acquiring flywheel transaction lock');
|
|
128
|
+
await delay(5);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function validateReceiptId(receiptId) {
|
|
133
|
+
if (!/^sha256:[a-f0-9]{64}$/.test(receiptId))
|
|
134
|
+
throw new Error('invalid receipt ID');
|
|
135
|
+
}
|
|
136
|
+
function receiptPath(root, receiptId) {
|
|
137
|
+
validateReceiptId(receiptId);
|
|
138
|
+
return path.join(receiptDir(root), `${receiptId.slice('sha256:'.length)}.json`);
|
|
139
|
+
}
|
|
140
|
+
export function readFlywheelReceipt(root, receiptId) {
|
|
141
|
+
try {
|
|
142
|
+
const file = receiptPath(root, receiptId);
|
|
143
|
+
assertSafeFile(file);
|
|
144
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
export async function registerFlywheelReceipt(root, receipt, now = Date.now()) {
|
|
151
|
+
ensureDir(root);
|
|
152
|
+
validateReceiptId(receipt.payload.receiptId);
|
|
153
|
+
const file = receiptPath(root, receipt.payload.receiptId);
|
|
154
|
+
if (!fs.existsSync(file))
|
|
155
|
+
atomicWriteJson(file, receipt);
|
|
156
|
+
return withStateLock(root, () => {
|
|
157
|
+
const state = readFlywheelTransactionState(root);
|
|
158
|
+
const existing = state.receiptStates[receipt.payload.receiptId];
|
|
159
|
+
if (existing)
|
|
160
|
+
return existing;
|
|
161
|
+
const record = {
|
|
162
|
+
receiptId: receipt.payload.receiptId,
|
|
163
|
+
status: 'evaluated',
|
|
164
|
+
registeredAt: now,
|
|
165
|
+
promotedAt: null,
|
|
166
|
+
promotionTransactionId: null,
|
|
167
|
+
};
|
|
168
|
+
state.receiptStates[receipt.payload.receiptId] = record;
|
|
169
|
+
atomicWriteJson(statePath(root), state);
|
|
170
|
+
return record;
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
function materialize(root, state, now, applyFn) {
|
|
174
|
+
if (!state.activeChampionRef || !state.activePolicy)
|
|
175
|
+
return { applied: false, reason: 'no active champion' };
|
|
176
|
+
const previous = state.servedChampionRef;
|
|
177
|
+
const apply = applyFn ?? ((projectRoot, policy, championRef, prior, appliedAt) => applyChampionParams(projectRoot, {
|
|
178
|
+
championId: championRef,
|
|
179
|
+
params: policy,
|
|
180
|
+
layer: 'repo/local',
|
|
181
|
+
previous: prior,
|
|
182
|
+
now: appliedAt,
|
|
183
|
+
}));
|
|
184
|
+
return apply(root, state.activePolicy, state.activeChampionRef, previous, now);
|
|
185
|
+
}
|
|
186
|
+
export async function recoverFlywheelMaterialization(root, opts = {}) {
|
|
187
|
+
const now = opts.now ?? Date.now();
|
|
188
|
+
const before = readFlywheelTransactionState(root);
|
|
189
|
+
if (!before.activeChampionRef || before.servingEpoch === before.materializedServingEpoch) {
|
|
190
|
+
return { success: true, idempotent: true, reason: 'materialization current', materialized: true };
|
|
191
|
+
}
|
|
192
|
+
const applied = materialize(root, before, now, opts.applyFn);
|
|
193
|
+
if (!applied.applied && applied.reason !== 'already active') {
|
|
194
|
+
return {
|
|
195
|
+
success: false,
|
|
196
|
+
idempotent: false,
|
|
197
|
+
reason: 'promotion committed; materialization pending',
|
|
198
|
+
championRef: before.activeChampionRef,
|
|
199
|
+
servingEpoch: before.servingEpoch,
|
|
200
|
+
materialized: false,
|
|
201
|
+
materializeReason: applied.reason,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
await withStateLock(root, () => {
|
|
205
|
+
const state = readFlywheelTransactionState(root);
|
|
206
|
+
if (state.activeChampionRef === before.activeChampionRef && state.servingEpoch === before.servingEpoch) {
|
|
207
|
+
state.materializedServingEpoch = state.servingEpoch;
|
|
208
|
+
state.servedChampionRef = state.activeChampionRef;
|
|
209
|
+
atomicWriteJson(statePath(root), state);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
return {
|
|
213
|
+
success: true,
|
|
214
|
+
idempotent: !applied.applied,
|
|
215
|
+
reason: 'materialized',
|
|
216
|
+
championRef: before.activeChampionRef,
|
|
217
|
+
servingEpoch: before.servingEpoch,
|
|
218
|
+
materialized: true,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
export async function promoteFlywheelCandidate(root, receiptId, options) {
|
|
222
|
+
if (!options.confirm)
|
|
223
|
+
return { success: false, idempotent: false, reason: 'explicit confirmation required' };
|
|
224
|
+
if (!options.trustedPublicKeys?.size) {
|
|
225
|
+
return { success: false, idempotent: false, reason: 'at least one trusted receipt signer is required' };
|
|
226
|
+
}
|
|
227
|
+
const receipt = readFlywheelReceipt(root, receiptId);
|
|
228
|
+
if (!receipt)
|
|
229
|
+
return { success: false, idempotent: false, reason: 'receipt not found' };
|
|
230
|
+
const verification = verifyFlywheelReceipt(receipt, options.trustedPublicKeys);
|
|
231
|
+
if (!verification.valid) {
|
|
232
|
+
return { success: false, idempotent: false, reason: `receipt verification failed: ${verification.errors.join(', ')}` };
|
|
233
|
+
}
|
|
234
|
+
const now = options.now ?? Date.now();
|
|
235
|
+
const transaction = await withStateLock(root, () => {
|
|
236
|
+
const state = readFlywheelTransactionState(root);
|
|
237
|
+
const receiptState = state.receiptStates[receiptId];
|
|
238
|
+
if (!receiptState)
|
|
239
|
+
return { success: false, idempotent: false, reason: 'receipt is not registered' };
|
|
240
|
+
if (receiptState.status === 'consumed' && receiptState.promotionTransactionId) {
|
|
241
|
+
const prior = state.commits.find((c) => c.transactionId === receiptState.promotionTransactionId);
|
|
242
|
+
return {
|
|
243
|
+
success: true,
|
|
244
|
+
idempotent: true,
|
|
245
|
+
reason: 'already promoted',
|
|
246
|
+
transactionId: prior?.transactionId,
|
|
247
|
+
receiptId,
|
|
248
|
+
championRef: prior?.candidateId ?? state.activeChampionRef ?? undefined,
|
|
249
|
+
ledgerHead: state.ledgerHead,
|
|
250
|
+
servingEpoch: prior?.servingEpoch ?? state.servingEpoch,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
if (receiptState.status !== 'evaluated' || receiptState.promotedAt !== null) {
|
|
254
|
+
return { success: false, idempotent: false, reason: `receipt state is ${receiptState.status}` };
|
|
255
|
+
}
|
|
256
|
+
if (receipt.payload.decision !== 'accepted')
|
|
257
|
+
return { success: false, idempotent: false, reason: 'receipt decision is not accepted' };
|
|
258
|
+
if (Date.parse(receipt.payload.expiresAt) <= now)
|
|
259
|
+
return { success: false, idempotent: false, reason: 'receipt expired' };
|
|
260
|
+
if (state.activeChampionRef === null) {
|
|
261
|
+
state.activeChampionRef = receipt.payload.baselineRef;
|
|
262
|
+
state.activeGateVersion = receipt.payload.gateVersion;
|
|
263
|
+
state.activePolicySchemaVersion = receipt.payload.policySchemaVersion;
|
|
264
|
+
state.activeSafetyEnvelopeRef = receipt.payload.safetyEnvelopeRef;
|
|
265
|
+
}
|
|
266
|
+
if (state.activeChampionRef !== receipt.payload.baselineRef)
|
|
267
|
+
return { success: false, idempotent: false, reason: 'stale baseline' };
|
|
268
|
+
if (state.ledgerHead !== receipt.payload.expectedLedgerHead)
|
|
269
|
+
return { success: false, idempotent: false, reason: 'stale ledger head' };
|
|
270
|
+
if (state.activeGateVersion !== receipt.payload.gateVersion)
|
|
271
|
+
return { success: false, idempotent: false, reason: 'gate version changed' };
|
|
272
|
+
if (state.activePolicySchemaVersion !== receipt.payload.policySchemaVersion)
|
|
273
|
+
return { success: false, idempotent: false, reason: 'policy schema changed' };
|
|
274
|
+
if (state.activeSafetyEnvelopeRef !== receipt.payload.safetyEnvelopeRef)
|
|
275
|
+
return { success: false, idempotent: false, reason: 'safety envelope changed' };
|
|
276
|
+
if (receipt.payload.proposerSubstitution
|
|
277
|
+
&& !options.allowedProposerSubstitutions?.has(receipt.payload.proposerSubstitution))
|
|
278
|
+
return { success: false, idempotent: false, reason: 'proposer substitution is evaluation-only' };
|
|
279
|
+
const verificationByTerm = new Map(receipt.payload.termVerification.map((term) => [term.term, term]));
|
|
280
|
+
for (const [term, passed] of Object.entries(receipt.payload.gates)) {
|
|
281
|
+
if (!passed)
|
|
282
|
+
continue;
|
|
283
|
+
const evidence = verificationByTerm.get(term);
|
|
284
|
+
if (!evidence)
|
|
285
|
+
return { success: false, idempotent: false, reason: `missing verification for gate: ${term}` };
|
|
286
|
+
if (evidence.verification === 'trusted-assertion'
|
|
287
|
+
&& (!evidence.attestor || !options.approvedAttestors?.has(evidence.attestor)))
|
|
288
|
+
return { success: false, idempotent: false, reason: `unapproved evidence attestor for gate: ${term}` };
|
|
289
|
+
}
|
|
290
|
+
if (options.faultAt === 'before-commit')
|
|
291
|
+
throw new Error('fault injection: before-commit');
|
|
292
|
+
const transactionId = uuidV7(now);
|
|
293
|
+
const commitCore = {
|
|
294
|
+
transactionId,
|
|
295
|
+
receiptId,
|
|
296
|
+
lineageId: receipt.payload.lineageId,
|
|
297
|
+
sequence: state.commits.length + 1,
|
|
298
|
+
previousLedgerHead: state.ledgerHead,
|
|
299
|
+
baselineRef: receipt.payload.baselineRef,
|
|
300
|
+
candidateId: receipt.payload.candidateId,
|
|
301
|
+
servingEpoch: state.servingEpoch + 1,
|
|
302
|
+
proposer: receipt.payload.effectiveProposer,
|
|
303
|
+
...(receipt.payload.proposerSubstitution ? { proposerSubstitution: receipt.payload.proposerSubstitution } : {}),
|
|
304
|
+
promotedAt: now,
|
|
305
|
+
};
|
|
306
|
+
const commit = { ...commitCore, commitId: sha256Ref(canonicalizeJcs(commitCore)) };
|
|
307
|
+
const nextHead = sha256Ref(canonicalizeJcs({ previous: state.ledgerHead, commitId: commit.commitId }));
|
|
308
|
+
state.commits.push(commit);
|
|
309
|
+
state.ledgerHead = nextHead;
|
|
310
|
+
state.activeChampionRef = receipt.payload.candidateId;
|
|
311
|
+
state.activePolicy = receipt.payload.candidatePolicy;
|
|
312
|
+
state.servingEpoch = commit.servingEpoch;
|
|
313
|
+
receiptState.status = 'consumed';
|
|
314
|
+
receiptState.promotedAt = now;
|
|
315
|
+
receiptState.promotionTransactionId = transactionId;
|
|
316
|
+
atomicWriteJson(statePath(root), state);
|
|
317
|
+
return {
|
|
318
|
+
success: true,
|
|
319
|
+
idempotent: false,
|
|
320
|
+
reason: 'promotion committed',
|
|
321
|
+
transactionId,
|
|
322
|
+
receiptId,
|
|
323
|
+
championRef: commit.candidateId,
|
|
324
|
+
ledgerHead: nextHead,
|
|
325
|
+
servingEpoch: commit.servingEpoch,
|
|
326
|
+
};
|
|
327
|
+
});
|
|
328
|
+
if (!transaction.success || transaction.idempotent) {
|
|
329
|
+
if (transaction.success) {
|
|
330
|
+
const recovered = await recoverFlywheelMaterialization(root, options);
|
|
331
|
+
return { ...transaction, materialized: recovered.materialized, materializeReason: recovered.materializeReason };
|
|
332
|
+
}
|
|
333
|
+
return transaction;
|
|
334
|
+
}
|
|
335
|
+
if (options.faultAt === 'after-commit-before-materialize') {
|
|
336
|
+
throw new Error('fault injection: after-commit-before-materialize');
|
|
337
|
+
}
|
|
338
|
+
const recovered = await recoverFlywheelMaterialization(root, options);
|
|
339
|
+
return { ...transaction, materialized: recovered.materialized, materializeReason: recovered.materializeReason };
|
|
340
|
+
}
|
|
341
|
+
export function listFlywheelReceipts(root) {
|
|
342
|
+
const state = readFlywheelTransactionState(root);
|
|
343
|
+
try {
|
|
344
|
+
return fs.readdirSync(receiptDir(root))
|
|
345
|
+
.filter((file) => /^[a-f0-9]{64}\.json$/.test(file))
|
|
346
|
+
.map((file) => {
|
|
347
|
+
const receipt = JSON.parse(fs.readFileSync(path.join(receiptDir(root), file), 'utf8'));
|
|
348
|
+
return { receipt, state: state.receiptStates[receipt.payload.receiptId] ?? null };
|
|
349
|
+
})
|
|
350
|
+
.sort((a, b) => a.receipt.payload.issuedAt.localeCompare(b.receipt.payload.issuedAt));
|
|
351
|
+
}
|
|
352
|
+
catch {
|
|
353
|
+
return [];
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
export function verifyFlywheelLedger(root) {
|
|
357
|
+
const state = readFlywheelTransactionState(root);
|
|
358
|
+
const errors = [];
|
|
359
|
+
let head = GENESIS_LEDGER_HEAD;
|
|
360
|
+
for (let i = 0; i < state.commits.length; i++) {
|
|
361
|
+
const commit = state.commits[i];
|
|
362
|
+
const { commitId, ...core } = commit;
|
|
363
|
+
if (commit.sequence !== i + 1)
|
|
364
|
+
errors.push(`sequence mismatch at ${i + 1}`);
|
|
365
|
+
if (commit.previousLedgerHead !== head)
|
|
366
|
+
errors.push(`parent mismatch at ${i + 1}`);
|
|
367
|
+
if (sha256Ref(canonicalizeJcs(core)) !== commitId)
|
|
368
|
+
errors.push(`commit hash mismatch at ${i + 1}`);
|
|
369
|
+
head = sha256Ref(canonicalizeJcs({ previous: head, commitId }));
|
|
370
|
+
}
|
|
371
|
+
if (head !== state.ledgerHead)
|
|
372
|
+
errors.push('ledger head mismatch');
|
|
373
|
+
if (state.commits.length && state.commits[state.commits.length - 1].candidateId !== state.activeChampionRef) {
|
|
374
|
+
errors.push('active champion does not match ledger head');
|
|
375
|
+
}
|
|
376
|
+
return { valid: errors.length === 0, errors, commits: state.commits.length, head };
|
|
377
|
+
}
|
|
378
|
+
//# sourceMappingURL=flywheel-transaction.js.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type FlywheelResult } from './harness-flywheel.js';
|
|
2
2
|
import { type GenerationResult } from './harness-flywheel-generations.js';
|
|
3
|
+
import { type DarwinInvoker, type ProposerMode } from './flywheel-proposer.js';
|
|
3
4
|
/**
|
|
4
5
|
* Run one live flywheel tick against `projectRoot`. Opt-in + $0 default: with
|
|
5
6
|
* RUFLO_HARNESS_LOOP unset it is a no-op. Best-effort; never throws.
|
|
@@ -8,6 +9,14 @@ export declare function runFlywheelWorker(projectRoot: string, opts?: {
|
|
|
8
9
|
sample?: number;
|
|
9
10
|
optInOverride?: boolean;
|
|
10
11
|
now?: number;
|
|
12
|
+
receiptPrivateKeyPem?: string;
|
|
13
|
+
receiptPublicKeyPem?: string;
|
|
14
|
+
lineageId?: string;
|
|
15
|
+
evaluationRunId?: string;
|
|
16
|
+
safetyEnvelopeRef?: string;
|
|
17
|
+
proposer?: ProposerMode;
|
|
18
|
+
darwinInvoker?: DarwinInvoker;
|
|
19
|
+
allowSubstitutionPromotion?: boolean;
|
|
11
20
|
}): Promise<FlywheelResult>;
|
|
12
21
|
/**
|
|
13
22
|
* Run ONE live COMPOUNDING generation against the persisted lineage (ADR-176
|
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
* pays the ONNX cost only when actually running a tick. Never throws.
|
|
6
6
|
*/
|
|
7
7
|
import { harnessLoopOptedIn } from './harness-worker.js';
|
|
8
|
-
import { runFlywheelTick } from './harness-flywheel.js';
|
|
8
|
+
import { DEFAULT_CONFIG, retrievalPolicyNeighbors, runFlywheelTick, } from './harness-flywheel.js';
|
|
9
9
|
import { runFlywheelGeneration, checkServedChampionDrift } from './harness-flywheel-generations.js';
|
|
10
10
|
import { loadFrozenHumanEval } from './harness-frozen-eval.js';
|
|
11
|
+
import { proposeFlywheelCandidates, } from './flywheel-proposer.js';
|
|
12
|
+
import { sha256Ref } from './flywheel-receipt.js';
|
|
11
13
|
/** The human-labeled ADR-081 anchor — the never-regress relevance set. */
|
|
12
14
|
const ANCHOR = [
|
|
13
15
|
['how was the Opus model alias fixed', ['opus 4.8', 'opus alias', 'opus model alias', '#2232']],
|
|
@@ -34,6 +36,60 @@ export async function runFlywheelWorker(projectRoot, opts = {}) {
|
|
|
34
36
|
const tool = neural.neuralTools.find((t) => t.name === 'neural_patterns');
|
|
35
37
|
if (!tool)
|
|
36
38
|
return { ran: false, reason: 'neural_patterns tool unavailable' };
|
|
39
|
+
const baseline = {
|
|
40
|
+
...DEFAULT_CONFIG,
|
|
41
|
+
...(applier.activeChampion(projectRoot)?.params ?? {}),
|
|
42
|
+
};
|
|
43
|
+
const safetyEnvelope = {
|
|
44
|
+
ref: opts.safetyEnvelopeRef ?? sha256Ref(JSON.stringify({
|
|
45
|
+
schema: 'ruflo.retrieval-safety-envelope/v1',
|
|
46
|
+
allowedPolicyKeys: ['alpha', 'topK', 'rerank', 'mmrLambda'],
|
|
47
|
+
numericBounds: {
|
|
48
|
+
alpha: { min: 0.1, max: 0.9 },
|
|
49
|
+
topK: { min: 5, max: 20 },
|
|
50
|
+
mmrLambda: { min: 0.3, max: 0.9 },
|
|
51
|
+
},
|
|
52
|
+
})),
|
|
53
|
+
allowedPolicyKeys: ['alpha', 'topK', 'rerank', 'mmrLambda'],
|
|
54
|
+
numericBounds: {
|
|
55
|
+
alpha: { min: 0.1, max: 0.9 },
|
|
56
|
+
topK: { min: 5, max: 20 },
|
|
57
|
+
mmrLambda: { min: 0.3, max: 0.9 },
|
|
58
|
+
},
|
|
59
|
+
// Retrieval evaluations are local and report zero provider spend today;
|
|
60
|
+
// finite ceilings make any future resource evidence fail closed.
|
|
61
|
+
maxP95LatencyMicros: 5_000_000,
|
|
62
|
+
maxCostMicrosPerTask: 10_000,
|
|
63
|
+
maxTokensPerTask: 100_000,
|
|
64
|
+
maxFailureRate: 0.01,
|
|
65
|
+
maxEvaluationCostMicros: 1_000_000,
|
|
66
|
+
};
|
|
67
|
+
const proposerMode = opts.proposer
|
|
68
|
+
?? (process.env.RUFLO_FLYWHEEL_PROPOSER ?? 'auto');
|
|
69
|
+
if (!['auto', 'local', 'darwin'].includes(proposerMode)) {
|
|
70
|
+
return { ran: false, reason: `invalid proposer mode: ${proposerMode}` };
|
|
71
|
+
}
|
|
72
|
+
const archive = await proposeFlywheelCandidates({
|
|
73
|
+
mode: proposerMode,
|
|
74
|
+
baselinePolicy: baseline,
|
|
75
|
+
safetyEnvelope,
|
|
76
|
+
seed: opts.now ?? Date.now(),
|
|
77
|
+
localProposer: () => retrievalPolicyNeighbors(baseline).map((policy) => ({
|
|
78
|
+
policy: policy,
|
|
79
|
+
resources: {
|
|
80
|
+
p95LatencyMicros: 0,
|
|
81
|
+
costMicrosPerTask: 0,
|
|
82
|
+
tokensPerTask: 0,
|
|
83
|
+
failureRate: 0,
|
|
84
|
+
evaluationCostMicros: 0,
|
|
85
|
+
},
|
|
86
|
+
})),
|
|
87
|
+
darwinInvoker: opts.darwinInvoker,
|
|
88
|
+
allowSubstitutionPromotion: opts.allowSubstitutionPromotion,
|
|
89
|
+
});
|
|
90
|
+
const candidatePolicies = archive.paretoCandidates.map((candidate) => candidate.policy);
|
|
91
|
+
if (!candidatePolicies.length)
|
|
92
|
+
return { ran: false, reason: 'proposer archive contained no admissible candidates' };
|
|
37
93
|
const deps = {
|
|
38
94
|
getPatterns: () => neural.getStorePatterns(),
|
|
39
95
|
search: async (query, cfg) => {
|
|
@@ -41,9 +97,18 @@ export async function runFlywheelWorker(projectRoot, opts = {}) {
|
|
|
41
97
|
return (r.results || []).slice(0, 5).map((m) => ({ id: m?.id ?? '', name: m?.name ?? '' }));
|
|
42
98
|
},
|
|
43
99
|
anchorTasks: ANCHOR,
|
|
44
|
-
activeParams: () =>
|
|
100
|
+
activeParams: () => baseline,
|
|
45
101
|
sample: opts.sample ?? 40,
|
|
46
102
|
now: opts.now,
|
|
103
|
+
receiptPrivateKeyPem: opts.receiptPrivateKeyPem,
|
|
104
|
+
receiptPublicKeyPem: opts.receiptPublicKeyPem,
|
|
105
|
+
lineageId: opts.lineageId,
|
|
106
|
+
evaluationRunId: opts.evaluationRunId,
|
|
107
|
+
safetyEnvelopeRef: safetyEnvelope.ref,
|
|
108
|
+
requestedProposer: archive.requestedProposer,
|
|
109
|
+
effectiveProposer: archive.effectiveProposer,
|
|
110
|
+
proposerSubstitution: archive.proposerSubstitution,
|
|
111
|
+
candidatePolicies,
|
|
47
112
|
};
|
|
48
113
|
return await runFlywheelTick(projectRoot, deps);
|
|
49
114
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type HarvestPattern } from './harness-corpus-harvester.js';
|
|
2
|
+
import { type FlywheelEvaluationReceipt } from './flywheel-receipt.js';
|
|
2
3
|
export interface RetrievalConfig {
|
|
3
4
|
alpha: number;
|
|
4
5
|
subjectWeight: number;
|
|
@@ -26,6 +27,17 @@ export interface FlywheelDeps {
|
|
|
26
27
|
activeParams?: () => Partial<RetrievalConfig> | null;
|
|
27
28
|
sample?: number;
|
|
28
29
|
now?: number;
|
|
30
|
+
lineageId?: string;
|
|
31
|
+
evaluationRunId?: string;
|
|
32
|
+
safetyEnvelopeRef?: string;
|
|
33
|
+
requestedProposer?: 'auto' | 'local' | 'darwin';
|
|
34
|
+
effectiveProposer?: 'local' | 'darwin';
|
|
35
|
+
proposerSubstitution?: string;
|
|
36
|
+
receiptPrivateKeyPem?: string;
|
|
37
|
+
receiptPublicKeyPem?: string;
|
|
38
|
+
bootstrapIterations?: number;
|
|
39
|
+
/** Candidate policies supplied by an external proposer archive (ADR-322B). */
|
|
40
|
+
candidatePolicies?: RetrievalConfig[];
|
|
29
41
|
}
|
|
30
42
|
export interface FlywheelResult {
|
|
31
43
|
ran: boolean;
|
|
@@ -38,11 +50,22 @@ export interface FlywheelResult {
|
|
|
38
50
|
anchorRegressed?: boolean;
|
|
39
51
|
championRef?: string;
|
|
40
52
|
corpusVersion?: string;
|
|
53
|
+
candidateConfig?: RetrievalConfig;
|
|
54
|
+
receiptId?: string;
|
|
55
|
+
receipt?: FlywheelEvaluationReceipt;
|
|
56
|
+
promotable?: boolean;
|
|
57
|
+
legacyDeprecation?: boolean;
|
|
41
58
|
}
|
|
59
|
+
export declare function retrievalPolicyNeighbors(base: RetrievalConfig): RetrievalConfig[];
|
|
42
60
|
/**
|
|
43
61
|
* Run one flywheel tick against `projectRoot`. Best-effort; never throws.
|
|
44
62
|
* Returns a rich result AND (as a side effect) appends to the improvement ledger
|
|
45
63
|
* and — on accept — applies the champion locally + chains it.
|
|
46
64
|
*/
|
|
65
|
+
export declare function evaluateFlywheelCandidate(projectRoot: string, deps: FlywheelDeps): Promise<FlywheelResult>;
|
|
66
|
+
/**
|
|
67
|
+
* Compatibility wrapper. New callers get evaluation-only semantics. The legacy
|
|
68
|
+
* implicit apply path exists for one release behind an explicit opt-in flag.
|
|
69
|
+
*/
|
|
47
70
|
export declare function runFlywheelTick(projectRoot: string, deps: FlywheelDeps): Promise<FlywheelResult>;
|
|
48
71
|
//# sourceMappingURL=harness-flywheel.d.ts.map
|