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 CHANGED
@@ -7,7 +7,7 @@ Created: 2026-06-29 22:36:38 EDT
7
7
 
8
8
  # 🧠 RuvNet Brain
9
9
 
10
- ### 🧠 RuvNet Brain — [![RuvNet Brain version 4.3.27 — updated 2026-07-30 03:24 EDT](https://img.shields.io/badge/version_4.3.27-updated_2026--07--30_03:24_EDT-1E90FF?style=for-the-badge&labelColor=0757BA)](https://github.com/stuinfla/ruvnet-brain/blob/main/plugin/.claude-plugin/plugin.json)
10
+ ### 🧠 RuvNet Brain — [![RuvNet Brain version 4.3.28 — updated 2026-07-30 03:24 EDT](https://img.shields.io/badge/version_4.3.28-updated_2026--07--30_03:24_EDT-1E90FF?style=for-the-badge&labelColor=0757BA)](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.27",
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.27",
4
+ "version": "4.3.28",
5
5
  "author": {
6
6
  "name": "Stuart Kerr"
7
7
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ruvnet-brain",
3
- "version": "4.3.27",
3
+ "version": "4.3.28",
4
4
  "description": "Source-grounded RuvNet knowledge, lifecycle enforcement, and learning for Codex.",
5
5
  "author": {
6
6
  "name": "Stuart Kerr"
@@ -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 = Object.entries(modeNames).map(([mode, name]) => {
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 grounded = grounding && ['repo', 'path', 'file', 'storedPath']
39
- .every((field) => typeof grounding[field] === 'string' && grounding[field].trim());
40
- if (fixture?.status !== 'PASS' || fixture?.process?.status !== 0 || !grounded) {
41
- throw new Error(`${name} did not produce a clean installed-search grounding receipt`);
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
- validateRetrievalCanaryReceipt(fixture.retrieval, { plan: retrieval.plan });
44
- return {
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.status === 'PASS' ? 'completed' : 'failed',
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: true,
54
- searchExit: fixture.process.status,
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.retrieval,
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', 'How does RuvNet Brain prove a public release artifact?', '--k', '1'], {
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
- return { verdict: 'FAIL', fixtures: {}, error: `shared model prewarm failed (${processDiagnostic(prewarm)})` };
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 searches = await Promise.all(contexts.map(async (context) => {
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
- const processResult = await searchMcp({ mode: context.mode, serverPath, env: context.env });
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, error: `MCP search failed for ${context.mode} (${processDiagnostic(processResult)}): ${output.slice(-2000)}` };
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, error: `MCP search grounding unproven for ${context.mode}` };
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, grounding, retrieval: receipt, error: `${context.mode} canary rejected: ${error.message}` }; }
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, grounding, ...(receipt ? { retrieval: receipt } : {}) };
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 fixtures = Object.fromEntries(searches.map(({ context, processResult, grounding, retrieval, error }) => [context.mode, {
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 = 'How does RuvNet Brain prove a public release artifact?', k = 5,
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, createInstalledMcpSession } from './host-install-matrix.mjs';
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 = 30_000;
34
- const WARMUP_TIMEOUT_MS = 300_000;
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: 'How does RuvNet Brain prove a public release artifact?', k: 5,
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 search failed for ${mode}: ${result.error?.message || 'no MCP result'}`);
553
+ throw new Error(`installed Brain ${phase} failed for ${mode} after ${timeoutMs}ms: ${result.error?.message || 'no MCP result'}`);
546
554
  }
547
- if (!/repo=/i.test(result.stdout) || !/path\s*:/i.test(result.stdout)) {
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 each worker serially. The model cache is shared, but simultaneous first-loads can
553
- // contend on slower runners (the macOS Codex-only timeout that motivated this path). The
554
- // warm-up is bounded generously and is never used as the release latency measurement.
555
- for (const host of hostResults) await searchInstalledHost(host, WARMUP_TIMEOUT_MS);
556
- // Once initialized, the three steady-state checks can run in parallel and are each held to
557
- // the strict public deadline that the receipt and aggregate validators enforce.
558
- await Promise.all(hostResults.map(async (host) => {
559
- searched.set(host.mode, await searchInstalledHost(host, DEADLINE_MS));
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: 'How does RuvNet Brain prove a public release artifact?', k: 5, timeoutMs: DEADLINE_MS })
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
- 'How does RuvNet Brain prove a public release artifact?', 5, DEADLINE_MS);
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
- // Keep one MCP worker per installed host. Starting a fresh worker for every canary reloads
608
- // the local embedding model repeatedly and can exceed the fixed 30-second public deadline
609
- // on macOS, even when the installed Brain itself is healthy.
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: DEADLINE_MS });
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 });
@@ -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 over-budget rows wins; ties break on lower total measured ms, so a
180
- * genuinely faster run is preferred over a marginally-less-bad one.
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 an attempt clears every budget, or ATTEMPTS is exhausted; return the
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 && !(attempt.notes || []).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') {