ruvnet-brain 4.3.27 → 4.3.28
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/README.md +1 -1
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/scripts/candidate-host-evidence.mjs +44 -16
- package/scripts/host-install-matrix.mjs +54 -11
- package/scripts/prepublication-evidence.mjs +8 -0
- package/scripts/publication-receipt.mjs +71 -30
- package/scripts/qe/ux-suite.mjs +17 -5
- package/scripts/release-qualification-contract.mjs +11 -1
- package/scripts/staged-host-verifier.mjs +2 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ Created: 2026-06-29 22:36:38 EDT
|
|
|
7
7
|
|
|
8
8
|
# 🧠 RuvNet Brain
|
|
9
9
|
|
|
10
|
-
### 🧠 RuvNet Brain — [](https://github.com/stuinfla/ruvnet-brain/blob/main/plugin/.claude-plugin/plugin.json)
|
|
11
11
|
|
|
12
12
|
**A portable, source-grounded brain over Reuven Cohen's (rUv's) RuvNet stack — delivered as a Claude Code plugin that makes Claude _use_ the stack instead of fighting it.**
|
|
13
13
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ruvnet-brain",
|
|
3
|
-
"version": "4.3.
|
|
3
|
+
"version": "4.3.28",
|
|
4
4
|
"description": "One-command installer for RuvNet Brain — a portable, source-grounded brain over rUv's RuvNet building blocks, delivered as a Claude Code plugin so Claude uses the stack instead of fighting it.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ruvnet-brain",
|
|
3
3
|
"description": "RuvNet brain transplant for Claude Code — grounds every RuvNet decision in real source across 77 rUv repositories, prefers Ruflo / RuVector-RVF / AgentDB over training-prior defaults (pgvector, Pinecone, hand-rolled cosine), and can pull in any RuvNet repo on demand. Ships a UserPromptSubmit retrieve-and-inject grounding hook and a PreToolUse write gate that refuses ungrounded rUv-product code until search_ruvnet has been consulted (ADR-0012 / ADR-067).",
|
|
4
|
-
"version": "4.3.
|
|
4
|
+
"version": "4.3.28",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Stuart Kerr"
|
|
7
7
|
},
|
|
@@ -5,6 +5,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
5
5
|
import { stagedHostVerifier, readCandidateRetrieval, verifyCandidateRetrievalAssets } from './staged-host-verifier.mjs';
|
|
6
6
|
import { payloadIdFor } from './release-payload.mjs';
|
|
7
7
|
import { validateRetrievalCanaryReceipt } from './retrieval-canary.mjs';
|
|
8
|
+
import { HOST_WARMUP_TIMEOUT_MS, RELEASE_SEARCH_DEADLINE_MS } from './host-install-matrix.mjs';
|
|
8
9
|
|
|
9
10
|
const arg = (name) => {
|
|
10
11
|
const index = process.argv.indexOf(name);
|
|
@@ -12,13 +13,13 @@ const arg = (name) => {
|
|
|
12
13
|
};
|
|
13
14
|
|
|
14
15
|
export async function buildCandidateHostEvidence({ manifestFile, packagePath, bundlePath, planFile, coverageFile, failureFile },
|
|
15
|
-
{ createVerifier = stagedHostVerifier } = {}) {
|
|
16
|
+
{ createVerifier = stagedHostVerifier, sequentialSearches = false } = {}) {
|
|
16
17
|
const manifest = JSON.parse(fs.readFileSync(manifestFile, 'utf8'));
|
|
17
18
|
const payloadId = payloadIdFor(manifest);
|
|
18
19
|
const identity = { version: manifest.version, candidateSha: manifest.candidateSha, payloadId };
|
|
19
20
|
const retrieval = readCandidateRetrieval({ manifest, planFile, coverageFile });
|
|
20
21
|
verifyCandidateRetrievalAssets({ retrieval, assets: { packagePath, bundlePath } });
|
|
21
|
-
const result = await createVerifier({ assets: { packagePath, bundlePath }, identity, retrieval })
|
|
22
|
+
const result = await createVerifier({ assets: { packagePath, bundlePath }, identity, retrieval, sequentialSearches })
|
|
22
23
|
.verify({ source: 'candidate', assets: { packagePath, bundlePath } });
|
|
23
24
|
verifyCandidateRetrievalAssets({ retrieval, assets: { packagePath, bundlePath } });
|
|
24
25
|
if (result.verdict !== 'PASS') {
|
|
@@ -32,34 +33,60 @@ export async function buildCandidateHostEvidence({ manifestFile, packagePath, bu
|
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
const modeNames = { claude: 'claude-only', codex: 'codex-only', dual: 'dual-host' };
|
|
35
|
-
const leaves =
|
|
36
|
+
const leaves = [];
|
|
37
|
+
const failures = [];
|
|
38
|
+
for (const [mode, name] of Object.entries(modeNames)) {
|
|
36
39
|
const fixture = result.fixtures?.[mode];
|
|
37
40
|
const grounding = fixture?.grounding;
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
const warmupGrounding = fixture?.warmupGrounding;
|
|
42
|
+
const hasReceipt = (receipt) => receipt && ['repo', 'path', 'file', 'storedPath']
|
|
43
|
+
.every((field) => typeof receipt[field] === 'string' && receipt[field].trim());
|
|
44
|
+
if (!Number.isFinite(fixture?.warmupMs) || fixture.warmupMs < 0 || fixture.warmupMs > HOST_WARMUP_TIMEOUT_MS
|
|
45
|
+
|| !hasReceipt(warmupGrounding) || warmupGrounding.repo !== 'ruvnet-brain') {
|
|
46
|
+
failures.push(`${name} did not produce a grounded worker warmup within ${HOST_WARMUP_TIMEOUT_MS}ms (${fixture?.warmupMs ?? 'unmeasured'}ms)`);
|
|
42
47
|
}
|
|
43
|
-
|
|
44
|
-
|
|
48
|
+
if (!Number.isFinite(fixture?.searchMs) || fixture.searchMs < 0 || fixture.searchMs > RELEASE_SEARCH_DEADLINE_MS) {
|
|
49
|
+
failures.push(`${name} first measured cited search exceeded the ${RELEASE_SEARCH_DEADLINE_MS}ms candidate deadline (${fixture?.searchMs ?? 'unmeasured'}ms)`);
|
|
50
|
+
}
|
|
51
|
+
if (fixture?.status !== 'PASS' || fixture?.process?.status !== 0 || !hasReceipt(grounding)) {
|
|
52
|
+
failures.push(`${name} did not produce a clean installed-search grounding receipt`);
|
|
53
|
+
}
|
|
54
|
+
try { validateRetrievalCanaryReceipt(fixture?.retrieval, { plan: retrieval.plan }); }
|
|
55
|
+
catch (error) { failures.push(`${name} retrieval canary rejected: ${error.message}`); }
|
|
56
|
+
leaves.push({
|
|
45
57
|
name,
|
|
46
58
|
sha: manifest.candidateSha,
|
|
47
59
|
payloadId,
|
|
48
|
-
status: fixture
|
|
49
|
-
conclusion: 'success',
|
|
60
|
+
status: fixture?.status === 'PASS' && fixture?.process?.status === 0 && hasReceipt(grounding) ? 'completed' : 'failed',
|
|
61
|
+
conclusion: failures.length ? 'failure' : 'success',
|
|
50
62
|
verdict: 'PASS',
|
|
51
63
|
source: 'candidate-host-evidence',
|
|
52
64
|
mode,
|
|
53
|
-
functionalSearch:
|
|
54
|
-
searchExit: fixture
|
|
65
|
+
functionalSearch: fixture?.status === 'PASS',
|
|
66
|
+
searchExit: fixture?.process?.status ?? null,
|
|
67
|
+
warmupMs: fixture?.warmupMs,
|
|
68
|
+
warmupGrounding,
|
|
69
|
+
searchMs: fixture?.searchMs,
|
|
55
70
|
grounding,
|
|
56
|
-
retrieval: fixture
|
|
71
|
+
retrieval: fixture?.retrieval,
|
|
57
72
|
artifactSha256: retrieval.artifactSha256,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
if (failures.length) {
|
|
76
|
+
const failure = {
|
|
77
|
+
schemaVersion: 1, kind: 'ruvnet-brain-candidate-host-failure', verdict: 'FAIL',
|
|
78
|
+
sha: manifest.candidateSha, payloadId, artifactSha256: retrieval.artifactSha256,
|
|
79
|
+
candidateArchiveSha256: retrieval.candidateArchiveSha256, planSha256: retrieval.plan.planSha256,
|
|
80
|
+
failures, result,
|
|
58
81
|
};
|
|
59
|
-
|
|
82
|
+
if (failureFile) fs.writeFileSync(path.resolve(failureFile), JSON.stringify(failure, null, 2) + '\n',
|
|
83
|
+
{ flag: 'wx', mode: 0o600 });
|
|
84
|
+
throw new Error(`candidate host matrix failed: ${failures.join('; ')}`);
|
|
85
|
+
}
|
|
60
86
|
return {
|
|
61
87
|
schemaVersion: 1,
|
|
62
88
|
sha: manifest.candidateSha,
|
|
89
|
+
hostPlatform: process.platform,
|
|
63
90
|
payloadId,
|
|
64
91
|
artifactSha256: retrieval.artifactSha256,
|
|
65
92
|
leaves,
|
|
@@ -68,7 +95,8 @@ export async function buildCandidateHostEvidence({ manifestFile, packagePath, bu
|
|
|
68
95
|
|
|
69
96
|
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
70
97
|
const evidence = await buildCandidateHostEvidence({ manifestFile: arg('--manifest'),
|
|
71
|
-
packagePath: arg('--package'), bundlePath: arg('--bundle'), planFile: arg('--plan'), coverageFile: arg('--coverage'), failureFile: arg('--out') ? `${arg('--out')}.failure.json` : null }
|
|
98
|
+
packagePath: arg('--package'), bundlePath: arg('--bundle'), planFile: arg('--plan'), coverageFile: arg('--coverage'), failureFile: arg('--out') ? `${arg('--out')}.failure.json` : null },
|
|
99
|
+
{ sequentialSearches: process.argv.includes('--sequential-searches') });
|
|
72
100
|
fs.writeFileSync(path.resolve(arg('--out')), `${JSON.stringify(evidence, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
|
|
73
101
|
console.log(JSON.stringify({ verdict: 'PASS', payloadId: evidence.payloadId, leaves: evidence.leaves.map(({ name }) => name) }));
|
|
74
102
|
}
|
|
@@ -32,6 +32,11 @@ import { runRetrievalCanaries, validateRetrievalCanaryReceipt, resolveInstalledC
|
|
|
32
32
|
|
|
33
33
|
/** The three host shapes a release must survive. ONE name each, for every consumer. */
|
|
34
34
|
export const HOST_MODES = Object.freeze(['claude', 'codex', 'dual']);
|
|
35
|
+
export const SELF_STORE_PROOF_QUERY = 'repo:ruvnet-brain What is the RuvNet Brain release evidence workflow?';
|
|
36
|
+
export const SELF_STORE_PROOF_K = 1;
|
|
37
|
+
export const RELEASE_SEARCH_QUERY = 'repo:ruvnet-brain How does RuvNet Brain prove a public release artifact?';
|
|
38
|
+
export const HOST_WARMUP_TIMEOUT_MS = 300_000;
|
|
39
|
+
export const RELEASE_SEARCH_DEADLINE_MS = 30_000;
|
|
35
40
|
|
|
36
41
|
/** Which CLIs each mode is allowed to see. A codex-only box genuinely has no `claude`. */
|
|
37
42
|
export const MODE_HOSTS = Object.freeze({
|
|
@@ -204,6 +209,7 @@ export async function runHostMatrixAsync({
|
|
|
204
209
|
verifyGrounding = verifyInstalledGrounding,
|
|
205
210
|
resolveMcpServer = resolveInstalledMcpServer,
|
|
206
211
|
retrieval,
|
|
212
|
+
sequentialSearches = false,
|
|
207
213
|
}) {
|
|
208
214
|
const spec = VARIANTS[variant];
|
|
209
215
|
if (!spec) throw new Error(`unknown host-matrix variant: ${variant}`);
|
|
@@ -251,32 +257,57 @@ export async function runHostMatrixAsync({
|
|
|
251
257
|
|
|
252
258
|
const prewarmContext = contexts[0];
|
|
253
259
|
const prewarmReader = path.join(prewarmContext.env.RUVNET_BRAIN_KB, 'forge-ask-all.mjs');
|
|
260
|
+
// This query only primes the shared model cache. Keep it in the Brain's own bounded store so
|
|
261
|
+
// Mac/Windows/Linux runner speed does not determine whether the later real host searches run.
|
|
262
|
+
// The following MCP matrix remains the measured, source-grounded candidate acceptance.
|
|
254
263
|
const prewarm = await runCommand(process.execPath, [prewarmReader, '--dir', prewarmContext.env.RUVNET_BRAIN_KB,
|
|
255
|
-
'--q', '
|
|
264
|
+
'--q', RELEASE_SEARCH_QUERY, '--k', '1', '--pool', '8',
|
|
265
|
+
'--repos', 'ruvnet-brain', '--bounded'], {
|
|
256
266
|
cwd: prewarmContext.env.RUVNET_BRAIN_KB, env: prewarmContext.env, timeout: 300_000,
|
|
257
267
|
});
|
|
258
268
|
if (prewarm.error || prewarm.status !== 0) {
|
|
259
|
-
|
|
269
|
+
const detail = String(prewarm.stderr || prewarm.stdout || '').trim().split('\n').slice(-8).join(' | ').slice(-1600);
|
|
270
|
+
return { verdict: 'FAIL', fixtures: {}, error: `shared model prewarm failed (${processDiagnostic(prewarm)})${detail ? `; ${detail}` : ''}` };
|
|
260
271
|
}
|
|
261
272
|
const prewarmGrounding = await verifyGrounding(String(prewarm.stdout || ''), prewarmContext.env.RUVNET_BRAIN_KB);
|
|
262
273
|
if (!prewarmGrounding?.grounded) {
|
|
263
274
|
return { verdict: 'FAIL', fixtures: {}, error: 'shared model prewarm returned no grounded source receipt' };
|
|
264
275
|
}
|
|
265
276
|
|
|
266
|
-
const
|
|
277
|
+
const searchOne = async (context) => {
|
|
267
278
|
let session;
|
|
268
279
|
try {
|
|
269
280
|
const serverPath = resolveMcpServer(context);
|
|
270
281
|
// One installed worker per host: model/store state survives smoke and every sealed case.
|
|
271
282
|
session = runMcpSearch === runInstalledMcpSearch ? createInstalledMcpSession({ serverPath, env: context.env }) : null;
|
|
272
283
|
const searchMcp = session ? (args) => session.search(args) : runMcpSearch;
|
|
273
|
-
|
|
284
|
+
// The installed shell warms its in-process models and stores asynchronously from MCP
|
|
285
|
+
// initialize. Candidate qualification must give that same worker the same cited readiness
|
|
286
|
+
// probe used by the public verifier before timing the first user search; a separate CLI
|
|
287
|
+
// prewarm only primes disk cache and cannot warm this worker's process memory.
|
|
288
|
+
const warmupResult = await searchMcp({ mode: context.mode, serverPath, env: context.env,
|
|
289
|
+
query: SELF_STORE_PROOF_QUERY, k: SELF_STORE_PROOF_K, timeoutMs: HOST_WARMUP_TIMEOUT_MS });
|
|
290
|
+
const warmupOutput = `${warmupResult.stdout || ''}${warmupResult.stderr || ''}`;
|
|
291
|
+
if (warmupResult.error || warmupResult.status !== 0) {
|
|
292
|
+
return { context, processResult: warmupResult,
|
|
293
|
+
error: `MCP warmup failed for ${context.mode} (${processDiagnostic(warmupResult)}): ${warmupOutput.slice(-2000)}` };
|
|
294
|
+
}
|
|
295
|
+
const warmupGrounding = await verifyGrounding(warmupOutput, context.env.RUVNET_BRAIN_KB);
|
|
296
|
+
if (!warmupGrounding?.grounded || warmupGrounding.receipt?.repo !== 'ruvnet-brain') {
|
|
297
|
+
return { context, processResult: warmupResult, warmupMs: warmupResult.broadMs,
|
|
298
|
+
error: `MCP self-store warmup grounding unproven for ${context.mode}` };
|
|
299
|
+
}
|
|
300
|
+
const processResult = await searchMcp({ mode: context.mode, serverPath, env: context.env,
|
|
301
|
+
query: RELEASE_SEARCH_QUERY, k: 5,
|
|
302
|
+
timeoutMs: RELEASE_SEARCH_DEADLINE_MS });
|
|
274
303
|
const output = `${processResult.stdout || ''}${processResult.stderr || ''}`;
|
|
275
304
|
if (processResult.error || processResult.status !== 0) {
|
|
276
|
-
return { context, processResult,
|
|
305
|
+
return { context, processResult, warmupMs: warmupResult.broadMs, warmupGrounding,
|
|
306
|
+
error: `MCP search failed for ${context.mode} (${processDiagnostic(processResult)}): ${output.slice(-2000)}` };
|
|
277
307
|
}
|
|
278
308
|
const grounding = await verifyGrounding(output, context.env.RUVNET_BRAIN_KB);
|
|
279
|
-
if (!grounding?.grounded) return { context, processResult,
|
|
309
|
+
if (!grounding?.grounded) return { context, processResult, warmupMs: warmupResult.broadMs,
|
|
310
|
+
warmupGrounding, error: `MCP search grounding unproven for ${context.mode}` };
|
|
280
311
|
let receipt;
|
|
281
312
|
if (retrieval) {
|
|
282
313
|
receipt = await runRetrievalCanaries({ ...retrieval,
|
|
@@ -288,17 +319,29 @@ export async function runHostMatrixAsync({
|
|
|
288
319
|
citationResolver: (matched, expected) => resolveInstalledCanaryCitation({ kbDir: context.env.RUVNET_BRAIN_KB, matched, expected }),
|
|
289
320
|
});
|
|
290
321
|
try { validateRetrievalCanaryReceipt(receipt, { plan: retrieval.plan }); }
|
|
291
|
-
catch (error) { return { context, processResult,
|
|
322
|
+
catch (error) { return { context, processResult, warmupMs: warmupResult.broadMs,
|
|
323
|
+
warmupGrounding, grounding, retrieval: receipt, error: `${context.mode} canary rejected: ${error.message}` }; }
|
|
292
324
|
}
|
|
293
|
-
return { context, processResult,
|
|
325
|
+
return { context, processResult, warmupMs: warmupResult.broadMs, warmupGrounding,
|
|
326
|
+
grounding, ...(receipt ? { retrieval: receipt } : {}) };
|
|
294
327
|
} catch (error) {
|
|
295
328
|
return { context, processResult: { status: null, error }, error: `${context.mode} host search failed: ${error.message}` };
|
|
296
329
|
} finally { await session?.close(); }
|
|
297
|
-
}
|
|
298
|
-
const
|
|
330
|
+
};
|
|
331
|
+
const searches = [];
|
|
332
|
+
if (sequentialSearches) {
|
|
333
|
+
for (const context of contexts) searches.push(await searchOne(context));
|
|
334
|
+
} else {
|
|
335
|
+
searches.push(...await Promise.all(contexts.map(searchOne)));
|
|
336
|
+
}
|
|
337
|
+
const fixtures = Object.fromEntries(searches.map(({ context, processResult, warmupMs, warmupGrounding,
|
|
338
|
+
grounding, retrieval, error }) => [context.mode, {
|
|
299
339
|
status: error ? 'FAIL' : 'PASS',
|
|
300
340
|
version,
|
|
301
341
|
process: processIdentity(processResult),
|
|
342
|
+
warmupMs: Number.isFinite(warmupMs) ? warmupMs : null,
|
|
343
|
+
...(warmupGrounding?.receipt ? { warmupGrounding: warmupGrounding.receipt } : {}),
|
|
344
|
+
searchMs: Number.isFinite(processResult.broadMs) ? processResult.broadMs : null,
|
|
302
345
|
...(grounding?.receipt ? { grounding: grounding.receipt } : {}),
|
|
303
346
|
...(retrieval ? { retrieval } : {}),
|
|
304
347
|
...(error ? { error } : {}),
|
|
@@ -395,7 +438,7 @@ export function createInstalledMcpSession({ serverPath, env, timeout = 300_000,
|
|
|
395
438
|
pending.set(id, { resolve, reject });
|
|
396
439
|
child.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', id, method, params })}\n`);
|
|
397
440
|
});
|
|
398
|
-
const execute = async ({ query =
|
|
441
|
+
const execute = async ({ query = RELEASE_SEARCH_QUERY, k = 5,
|
|
399
442
|
timeoutMs = timeout } = {}) => {
|
|
400
443
|
if (terminalError) return { status: null, error: terminalError, signal: exitSignal, stdout: '', stderr };
|
|
401
444
|
stderr = '';
|
|
@@ -9,6 +9,7 @@ import { RELEASE_QUALIFICATION_POLICY } from './integration-evidence.mjs';
|
|
|
9
9
|
import { readCandidateRetrieval } from './staged-host-verifier.mjs';
|
|
10
10
|
import { validateRetrievalCanaryReceipt } from './retrieval-canary.mjs';
|
|
11
11
|
import { payloadIdFor } from './release-payload.mjs';
|
|
12
|
+
import { HOST_WARMUP_TIMEOUT_MS, RELEASE_SEARCH_DEADLINE_MS } from './host-install-matrix.mjs';
|
|
12
13
|
|
|
13
14
|
const sha256 = (bytes) => crypto.createHash('sha256').update(bytes).digest('hex');
|
|
14
15
|
|
|
@@ -95,11 +96,18 @@ export function buildPrepublicationEvidence({
|
|
|
95
96
|
requireExactSet(host.value.leaves.map(({ name }) => name), ['claude-only', 'codex-only', 'dual-host'], 'candidate host leaves');
|
|
96
97
|
for (const leaf of host.value.leaves) {
|
|
97
98
|
const grounding = leaf.grounding;
|
|
99
|
+
const warmupGrounding = leaf.warmupGrounding;
|
|
98
100
|
const grounded = grounding && ['repo', 'path', 'file', 'storedPath']
|
|
99
101
|
.every((field) => typeof grounding[field] === 'string' && grounding[field].trim());
|
|
102
|
+
const warmupGrounded = warmupGrounding && ['repo', 'path', 'file', 'storedPath']
|
|
103
|
+
.every((field) => typeof warmupGrounding[field] === 'string' && warmupGrounding[field].trim());
|
|
104
|
+
const selfStoreWarmup = warmupGrounding?.repo === 'ruvnet-brain';
|
|
100
105
|
if (leaf.sha !== sha || leaf.payloadId !== payload.payloadId || leaf.status !== 'completed'
|
|
101
106
|
|| leaf.conclusion !== 'success' || leaf.verdict !== 'PASS'
|
|
102
107
|
|| leaf.functionalSearch !== true || leaf.searchExit !== 0 || !grounded
|
|
108
|
+
|| !Number.isFinite(leaf.warmupMs) || leaf.warmupMs < 0 || leaf.warmupMs > HOST_WARMUP_TIMEOUT_MS
|
|
109
|
+
|| !warmupGrounded || !selfStoreWarmup
|
|
110
|
+
|| !Number.isFinite(leaf.searchMs) || leaf.searchMs < 0 || leaf.searchMs > RELEASE_SEARCH_DEADLINE_MS
|
|
103
111
|
|| leaf.artifactSha256 !== host.value.artifactSha256) {
|
|
104
112
|
throw new Error(`candidate host leaf is not an exact PASS: ${leaf.name || '(missing)'}`);
|
|
105
113
|
}
|
|
@@ -15,7 +15,9 @@ import { spawn, spawnSync } from 'node:child_process';
|
|
|
15
15
|
// `fixtures.claude` described the same fixture and nothing could tell. The richer
|
|
16
16
|
// post-publication proofs below (payload assertions, MCP wiring, SOURCE.json, rpcSearch)
|
|
17
17
|
// stay here — they are this side's job, not duplication.
|
|
18
|
-
import { HOST_MODES, RECEIPT_MODE_NAMES, MODE_FROM_RECEIPT_NAME, classifyDoctor, VARIANTS,
|
|
18
|
+
import { HOST_MODES, RECEIPT_MODE_NAMES, MODE_FROM_RECEIPT_NAME, classifyDoctor, VARIANTS,
|
|
19
|
+
createInstalledMcpSession, HOST_WARMUP_TIMEOUT_MS, RELEASE_SEARCH_DEADLINE_MS, SELF_STORE_PROOF_QUERY,
|
|
20
|
+
SELF_STORE_PROOF_K } from './host-install-matrix.mjs';
|
|
19
21
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
20
22
|
import { evaluateCandidateReceipt, evaluatePublicationReceipt } from './release-proof.mjs';
|
|
21
23
|
import { verifyPayload } from './release-payload.mjs';
|
|
@@ -30,8 +32,21 @@ import { parseRetrievalResult } from '../kb/retrieval-result.mjs';
|
|
|
30
32
|
|
|
31
33
|
const REPO = 'stuinfla/ruvnet-brain';
|
|
32
34
|
const PACKAGE = 'ruvnet-brain';
|
|
33
|
-
const DEADLINE_MS =
|
|
34
|
-
const WARMUP_TIMEOUT_MS =
|
|
35
|
+
const DEADLINE_MS = RELEASE_SEARCH_DEADLINE_MS;
|
|
36
|
+
const WARMUP_TIMEOUT_MS = HOST_WARMUP_TIMEOUT_MS;
|
|
37
|
+
|
|
38
|
+
export async function runMeasuredHostSearches(hosts, search, { warmup, after } = {}) {
|
|
39
|
+
const results = new Map();
|
|
40
|
+
for (const host of hosts) {
|
|
41
|
+
try {
|
|
42
|
+
await warmup?.(host);
|
|
43
|
+
results.set(host.mode, await search(host, DEADLINE_MS));
|
|
44
|
+
} finally {
|
|
45
|
+
await after?.(host);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return results;
|
|
49
|
+
}
|
|
35
50
|
|
|
36
51
|
const sha256 = (file) => crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
|
|
37
52
|
const readJson = (file) => JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
@@ -524,40 +539,55 @@ export function livePublicationAdapter({ root = process.cwd(), candidateRoot = r
|
|
|
524
539
|
};
|
|
525
540
|
}));
|
|
526
541
|
|
|
527
|
-
// Keep one MCP worker per host. The first search on each worker may load the local model,
|
|
528
|
-
// but all later proofs reuse the initialized process instead of paying that startup cost
|
|
529
|
-
// again. The fixed deadline remains strict for each actual search operation.
|
|
530
|
-
for (const { mode, context } of hostResults) {
|
|
531
|
-
const publicMode = MODE_FROM_RECEIPT_NAME[mode];
|
|
532
|
-
mcpSessions.set(publicMode, createInstalledMcpSession({
|
|
533
|
-
serverPath: findMcpServer(context.home), env: context.env, timeout: WARMUP_TIMEOUT_MS,
|
|
534
|
-
}));
|
|
535
|
-
}
|
|
536
542
|
const searched = new Map();
|
|
537
543
|
const searchInstalledHost = async ({ mode }, timeoutMs) => {
|
|
538
544
|
const publicMode = MODE_FROM_RECEIPT_NAME[mode];
|
|
539
545
|
const session = mcpSessions.get(publicMode);
|
|
546
|
+
const phase = timeoutMs > DEADLINE_MS ? 'warmup' : 'measured search';
|
|
547
|
+
console.log(`Public verification: ${mode} ${phase} (limit ${timeoutMs}ms)`);
|
|
540
548
|
const result = await session.search({
|
|
541
|
-
query:
|
|
549
|
+
query: SELF_STORE_PROOF_QUERY, k: SELF_STORE_PROOF_K,
|
|
542
550
|
timeoutMs,
|
|
543
551
|
});
|
|
544
552
|
if (result.error || !result.mcpResult || (Object.hasOwn(result, 'status') && result.status !== 0)) {
|
|
545
|
-
throw new Error(`installed Brain
|
|
553
|
+
throw new Error(`installed Brain ${phase} failed for ${mode} after ${timeoutMs}ms: ${result.error?.message || 'no MCP result'}`);
|
|
546
554
|
}
|
|
547
|
-
if (!/repo
|
|
555
|
+
if (!/repo\s*=\s*ruvnet-brain/i.test(result.stdout) || !/path\s*:/i.test(result.stdout)) {
|
|
548
556
|
throw new Error(`installed Brain search returned no source citation for ${mode}`);
|
|
549
557
|
}
|
|
550
558
|
return result;
|
|
551
559
|
};
|
|
552
|
-
// Warm
|
|
553
|
-
//
|
|
554
|
-
//
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
560
|
+
// Warm and measure one isolated host at a time, then retire its model-backed worker before
|
|
561
|
+
// moving to the next host. Serial RPCs alone are insufficient: keeping three independent
|
|
562
|
+
// workers resident still competes for memory and CPU during the measured query on macOS.
|
|
563
|
+
const measuredSearches = await runMeasuredHostSearches(hostResults, searchInstalledHost, {
|
|
564
|
+
warmup: async ({ mode, context }) => {
|
|
565
|
+
for (const [openMode, session] of mcpSessions) {
|
|
566
|
+
if (openMode !== MODE_FROM_RECEIPT_NAME[mode]) {
|
|
567
|
+
await session.close();
|
|
568
|
+
mcpSessions.delete(openMode);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
const publicMode = MODE_FROM_RECEIPT_NAME[mode];
|
|
572
|
+
if (!mcpSessions.has(publicMode)) {
|
|
573
|
+
mcpSessions.set(publicMode, createInstalledMcpSession({
|
|
574
|
+
serverPath: findMcpServer(context.home), env: context.env, timeout: WARMUP_TIMEOUT_MS,
|
|
575
|
+
}));
|
|
576
|
+
}
|
|
577
|
+
// The generous bound is for one-time readiness/model warmup only; the following
|
|
578
|
+
// measured query still has the unchanged strict 30-second deadline.
|
|
579
|
+
await searchInstalledHost({ mode }, WARMUP_TIMEOUT_MS);
|
|
580
|
+
},
|
|
581
|
+
after: async ({ mode }) => {
|
|
582
|
+
const publicMode = MODE_FROM_RECEIPT_NAME[mode];
|
|
583
|
+
if (mode !== 'dual') {
|
|
584
|
+
const session = mcpSessions.get(publicMode);
|
|
585
|
+
if (session) await session.close();
|
|
586
|
+
mcpSessions.delete(publicMode);
|
|
587
|
+
}
|
|
588
|
+
},
|
|
589
|
+
});
|
|
590
|
+
for (const [mode, result] of measuredSearches) searched.set(mode, result);
|
|
561
591
|
await Promise.all(hostResults.map(async ({ context, installer }) => {
|
|
562
592
|
await commandAsync(process.execPath, [installer, '--doctor', '--hooks'], {
|
|
563
593
|
env: context.env, cwd: packageRoot, timeout: 300_000, stdio: 'inherit',
|
|
@@ -590,9 +620,9 @@ export function livePublicationAdapter({ root = process.cwd(), candidateRoot = r
|
|
|
590
620
|
// installed process that passed the host canary instead of starting a cold verifier child.
|
|
591
621
|
const session = mcpSessions.get(mode);
|
|
592
622
|
const result = session
|
|
593
|
-
? await session.search({ query:
|
|
623
|
+
? await session.search({ query: SELF_STORE_PROOF_QUERY, k: SELF_STORE_PROOF_K, timeoutMs: DEADLINE_MS })
|
|
594
624
|
: await rpcSearch(server, installContext.env,
|
|
595
|
-
|
|
625
|
+
SELF_STORE_PROOF_QUERY, SELF_STORE_PROOF_K, DEADLINE_MS);
|
|
596
626
|
if (result.error || !result.mcpResult || (Object.hasOwn(result, 'status') && result.status !== 0)) {
|
|
597
627
|
throw new Error(`installed Brain search failed for ${mode}: ${result.error?.message || 'no MCP result'}`);
|
|
598
628
|
}
|
|
@@ -604,13 +634,24 @@ export function livePublicationAdapter({ root = process.cwd(), candidateRoot = r
|
|
|
604
634
|
async searchInstalled({ mode, query, k }) {
|
|
605
635
|
const context = installContexts.get(mode);
|
|
606
636
|
if (!context) throw new Error(`${mode} public host is not installed`);
|
|
607
|
-
//
|
|
608
|
-
//
|
|
609
|
-
|
|
637
|
+
// Retrieval canaries run mode-by-mode. Retire the prior mode before opening this host so
|
|
638
|
+
// only one model-backed MCP worker can consume resources at a time.
|
|
639
|
+
for (const [openMode, openSession] of mcpSessions) {
|
|
640
|
+
if (openMode !== mode) {
|
|
641
|
+
await openSession.close();
|
|
642
|
+
mcpSessions.delete(openMode);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
610
645
|
let session = mcpSessions.get(mode);
|
|
611
646
|
if (!session) {
|
|
612
|
-
session = createInstalledMcpSession({ serverPath: findMcpServer(context.home), env: context.env, timeout:
|
|
647
|
+
session = createInstalledMcpSession({ serverPath: findMcpServer(context.home), env: context.env, timeout: WARMUP_TIMEOUT_MS });
|
|
613
648
|
mcpSessions.set(mode, session);
|
|
649
|
+
const warmed = await session.search({
|
|
650
|
+
query: SELF_STORE_PROOF_QUERY, k: SELF_STORE_PROOF_K, timeoutMs: WARMUP_TIMEOUT_MS,
|
|
651
|
+
});
|
|
652
|
+
if (warmed.error || !warmed.mcpResult || (Object.hasOwn(warmed, 'status') && warmed.status !== 0)) {
|
|
653
|
+
throw new Error(`installed Brain warmup failed for ${mode}: ${warmed.error?.message || 'no MCP result'}`);
|
|
654
|
+
}
|
|
614
655
|
}
|
|
615
656
|
const result = await session.search({ query, k, timeoutMs: DEADLINE_MS });
|
|
616
657
|
return parseRetrievalResult(result.mcpResult, { query, k });
|
package/scripts/qe/ux-suite.mjs
CHANGED
|
@@ -175,13 +175,22 @@ export function overBudgetRows(results, budgets) {
|
|
|
175
175
|
return (results || []).filter((r) => timingFailure(r.label, r.ms, budgets[r.label]) !== null);
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
+
/** Failed or missing acceptance rows are hard failures and must participate in attempt ranking. */
|
|
179
|
+
export function failedAcceptanceRows(acceptance) {
|
|
180
|
+
if (!Array.isArray(acceptance) || acceptance.length === 0) return [{ label: 'acceptance: NOT RUN' }];
|
|
181
|
+
return acceptance.filter((row) => row?.pass !== true);
|
|
182
|
+
}
|
|
183
|
+
|
|
178
184
|
/**
|
|
179
|
-
* Rank two attempts: fewer
|
|
180
|
-
*
|
|
185
|
+
* Rank two attempts: fewer failed hard acceptance checks wins first, then fewer over-budget
|
|
186
|
+
* timings, with the lower measured total breaking remaining ties.
|
|
181
187
|
*/
|
|
182
188
|
export function betterAttempt(a, b, budgets) {
|
|
183
189
|
if (!a) return b;
|
|
184
190
|
if (!b) return a;
|
|
191
|
+
const aa = failedAcceptanceRows(a.acceptance).length;
|
|
192
|
+
const ba = failedAcceptanceRows(b.acceptance).length;
|
|
193
|
+
if (aa !== ba) return aa < ba ? a : b;
|
|
185
194
|
const oa = overBudgetRows(a.results, budgets).length;
|
|
186
195
|
const ob = overBudgetRows(b.results, budgets).length;
|
|
187
196
|
if (oa !== ob) return oa < ob ? a : b;
|
|
@@ -190,8 +199,8 @@ export function betterAttempt(a, b, budgets) {
|
|
|
190
199
|
}
|
|
191
200
|
|
|
192
201
|
/**
|
|
193
|
-
* Run the render probe until
|
|
194
|
-
* best attempt seen, annotated with how many attempts it took.
|
|
202
|
+
* Run the render probe until timings, hard acceptance checks and harness notes are clean, or
|
|
203
|
+
* ATTEMPTS is exhausted; return the best attempt seen, annotated with how many attempts it took.
|
|
195
204
|
*/
|
|
196
205
|
export async function runRenderProbeBestOf(budgets, {
|
|
197
206
|
attempts = RENDER_ATTEMPTS,
|
|
@@ -200,9 +209,12 @@ export async function runRenderProbeBestOf(budgets, {
|
|
|
200
209
|
let best = null;
|
|
201
210
|
for (let i = 1; i <= attempts; i++) {
|
|
202
211
|
const attempt = await run();
|
|
212
|
+
const acceptanceFailures = failedAcceptanceRows(attempt.acceptance);
|
|
203
213
|
// `notes` means the probe could not produce a reading at all — a harness failure, not slowness.
|
|
204
214
|
// Retrying it is legitimate for the same reason, but it must never be silently swallowed.
|
|
205
|
-
if (!overBudgetRows(attempt.results, budgets).length
|
|
215
|
+
if (!overBudgetRows(attempt.results, budgets).length
|
|
216
|
+
&& acceptanceFailures.length === 0
|
|
217
|
+
&& !(attempt.notes || []).length) {
|
|
206
218
|
return { ...attempt, attemptsUsed: i, attemptsAllowed: attempts };
|
|
207
219
|
}
|
|
208
220
|
best = betterAttempt(best, attempt, budgets);
|
|
@@ -17,10 +17,13 @@ export const RELEASE_REQUIREMENTS = Object.freeze({
|
|
|
17
17
|
"tests/unit/release-identity-invariants.test.mjs",
|
|
18
18
|
"tests/unit/release-transaction.test.mjs",
|
|
19
19
|
"tests/unit/prepublication-evidence.test.mjs",
|
|
20
|
+
"tests/unit/candidate-host-evidence.test.mjs",
|
|
21
|
+
"tests/unit/host-install-matrix-concurrency.test.mjs",
|
|
20
22
|
"tests/unit/integration-evidence.test.mjs",
|
|
21
23
|
"tests/unit/qualified-candidate-check.test.mjs",
|
|
22
24
|
"tests/unit/release-qualification.test.mjs",
|
|
23
|
-
"tests/unit/development-push-boundary.test.mjs"
|
|
25
|
+
"tests/unit/development-push-boundary.test.mjs",
|
|
26
|
+
"tests/unit/protected-release-workflow.test.mjs"
|
|
24
27
|
]
|
|
25
28
|
},
|
|
26
29
|
{
|
|
@@ -52,6 +55,13 @@ export const RELEASE_REQUIREMENTS = Object.freeze({
|
|
|
52
55
|
"tests/unit/nightly-refresh-launcher.test.mjs",
|
|
53
56
|
"tests/unit/nightly-two-run-proof.test.mjs"
|
|
54
57
|
]
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"id": "ux-hard-acceptance",
|
|
61
|
+
"reason": "Retry accounting preserves hard UI acceptance failures and chooses only a clean measured attempt",
|
|
62
|
+
"files": [
|
|
63
|
+
"tests/unit/ux-render-best-of-n.test.mjs"
|
|
64
|
+
]
|
|
55
65
|
}
|
|
56
66
|
],
|
|
57
67
|
"integration": [
|
|
@@ -84,7 +84,7 @@ const preparePackage = async ({ packagePath, bundlePath }) => {
|
|
|
84
84
|
}
|
|
85
85
|
};
|
|
86
86
|
|
|
87
|
-
export function stagedHostVerifier({ assets, identity, retrieval }, { runMatrix = runHostMatrixAsync } = {}) {
|
|
87
|
+
export function stagedHostVerifier({ assets, identity, retrieval, sequentialSearches = false }, { runMatrix = runHostMatrixAsync } = {}) {
|
|
88
88
|
return {
|
|
89
89
|
async verify({ source, assets: observedAssets = assets }) {
|
|
90
90
|
if (retrieval) verifyCandidateRetrievalAssets({ retrieval, assets: observedAssets });
|
|
@@ -104,6 +104,7 @@ export function stagedHostVerifier({ assets, identity, retrieval }, { runMatrix
|
|
|
104
104
|
locate,
|
|
105
105
|
temp: prepared.temp,
|
|
106
106
|
retrieval,
|
|
107
|
+
sequentialSearches,
|
|
107
108
|
});
|
|
108
109
|
if (retrieval) verifyCandidateRetrievalAssets({ retrieval, assets: observedAssets });
|
|
109
110
|
if (matrix.verdict !== 'PASS') {
|