ruvnet-brain 4.3.10 → 4.3.13

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.10 — updated 2026-07-30 03:24 EDT](https://img.shields.io/badge/version_4.3.10-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.13 — updated 2026-07-30 03:24 EDT](https://img.shields.io/badge/version_4.3.13-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.10",
3
+ "version": "4.3.13",
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": {
@@ -16,7 +16,7 @@
16
16
  "version:set": "node scripts/set-version.mjs",
17
17
  "qa:pr": "node scripts/qa-runner.mjs",
18
18
  "maintenance": "node scripts/development-maintenance.mjs",
19
- "qa:release": "node scripts/qa-runner.mjs --release",
19
+ "qa:release": "npm run version:check && npm run release:authority && npm exec -- vitest run --config tests/qe/release/vitest.config.mjs --maxWorkers=1",
20
20
  "release:authority": "node scripts/release-authority.mjs",
21
21
  "card-from-source": "node scripts/card-from-source.mjs",
22
22
  "release-abort-stale": "node scripts/release-abort-stale.mjs",
@@ -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 an enforced UserPromptSubmit retrieve-and-inject grounding hook that sharply reduces drift.",
4
- "version": "4.3.10",
4
+ "version": "4.3.13",
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.10",
3
+ "version": "4.3.13",
4
4
  "description": "Source-grounded RuvNet knowledge, lifecycle enforcement, and learning for Codex.",
5
5
  "author": {
6
6
  "name": "Stuart Kerr"
@@ -310,6 +310,17 @@ const xmlDecode = value => value.replace(/&#(?:x([a-f0-9]+)|([0-9]+));|&(amp|lt|
310
310
  : ({ amp: '&', lt: '<', gt: '>', quot: '"', apos: "'" })[named.toLowerCase()]);
311
311
  const onlyXmlMatch = (xml, pattern) => { const hits = [...xml.matchAll(pattern)]; return hits.length === 1 ? hits[0][1] : null; };
312
312
  const windowsArguments = record => `"${record.runnerPath}" --registration "${record.recordPath}"`;
313
+ function windowsTaskXml(record) {
314
+ const boundary = new Date(Date.now() + 60_000).toISOString();
315
+ return `<?xml version="1.0" encoding="UTF-16"?>
316
+ <Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
317
+ <RegistrationInfo><Author>RuvNet Brain</Author></RegistrationInfo>
318
+ <Triggers><CalendarTrigger><StartBoundary>${boundary}</StartBoundary><Enabled>true</Enabled><ScheduleByDay><DaysInterval>1</DaysInterval></ScheduleByDay></CalendarTrigger></Triggers>
319
+ <Principals><Principal id="Author"><LogonType>InteractiveToken</LogonType><RunLevel>LeastPrivilege</RunLevel></Principal></Principals>
320
+ <Settings><MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy><StartWhenAvailable>true</StartWhenAvailable><ExecutionTimeLimit>PT1H</ExecutionTimeLimit></Settings>
321
+ <Actions Context="Author"><Exec><Command>${xmlEscape(record.nodePath)}</Command><Arguments>${xmlEscape(windowsArguments(record))}</Arguments></Exec></Actions>
322
+ </Task>`;
323
+ }
313
324
  function exactWindowsCommand(xml, record) {
314
325
  const actions = onlyXmlMatch(xml, /<Actions(?:\s[^>]*)?>([\s\S]*?)<\/Actions>/g);
315
326
  if (actions === null || actions.replace(/<Exec(?:\s[^>]*)?>[\s\S]*?<\/Exec>/g, '').trim()) return false;
@@ -393,8 +404,22 @@ export function installScheduler(record, { platform = process.platform, env = pr
393
404
  return { ok: true, artifact, already: false };
394
405
  }
395
406
  const taskCommand = `"${record.nodePath}" ${windowsArguments(record)}`;
396
- const created = run('schtasks', ['/Create', '/SC', 'DAILY', '/TN', record.identity, '/TR', taskCommand,
397
- '/ST', `${String(NIGHTLY_HOUR).padStart(2, '0')}:${String(NIGHTLY_MINUTE).padStart(2, '0')}`, '/F'], { encoding: 'utf8' });
407
+ let created;
408
+ if (testMode) {
409
+ // The fixture adapter models the legacy /TR call; keep it isolated from the machine path.
410
+ created = run('schtasks', ['/Create', '/SC', 'DAILY', '/TN', record.identity, '/TR', taskCommand,
411
+ '/ST', `${String(NIGHTLY_HOUR).padStart(2, '0')}:${String(NIGHTLY_MINUTE).padStart(2, '0')}`, '/F'], { encoding: 'utf8' });
412
+ } else {
413
+ // schtasks /TR rejects commands longer than 261 characters. Importing equivalent XML keeps the
414
+ // exact Node executable and argument identity while allowing long temporary/user paths.
415
+ const xmlPath = path.join(path.dirname(record.recordPath), `${record.identity}.task.xml`);
416
+ // schtasks requires a UTF-16LE BOM when importing XML; without it Windows
417
+ // reports the otherwise valid document as "one root element" malformed.
418
+ const xmlBytes = Buffer.concat([Buffer.from([0xff, 0xfe]), Buffer.from(windowsTaskXml(record), 'utf16le')]);
419
+ fs.writeFileSync(xmlPath, xmlBytes, { flag: 'wx' });
420
+ try { created = run('schtasks', ['/Create', '/XML', xmlPath, '/TN', record.identity, '/F'], { encoding: 'utf8' }); }
421
+ finally { fs.rmSync(xmlPath, { force: true }); }
422
+ }
398
423
  if (created.error || created.status !== 0) return { ok: false, artifact, why: created.error?.message || String(created.stderr || `exit ${created.status}`).trim() };
399
424
  return { ok: true, artifact, already: false };
400
425
  }
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: release-proof
3
3
  description: Fail-closed exact-artifact release and deployment authority. Use before saying a release is ready, pushing a release commit, publishing npm packages, creating GitHub releases, deploying production, closing release-blocking issues, or claiming all gates are green. Requires clean immutable lineage, zero labeled release blockers, exact-SHA GitHub success, nonzero no-skip QE, packed-artifact host tests, installed Brain/RVF proof, and post-publication byte verification.
4
- updated: 2026-09-04
4
+ updated: 2026-09-07
5
5
  ---
6
6
 
7
7
  # Release Proof
@@ -23,7 +23,11 @@ green.
23
23
  7. Require the source package, Claude manifest, Codex manifest, packed npm version, bundle
24
24
  `brainVersion`/`releaseTag`, and both installed host versions to identify one exact generation.
25
25
  8. Require the active Brain registry to contain the `ruvnet-brain` RVF store and require narrow,
26
- broad, and concurrent cited searches to complete within 80% of their deadline.
26
+ broad, and concurrent cited candidate searches to complete within 80% of their deadline.
27
+ For fresh public stabilization verification, `stabilization-public-deadline-v1` requires the
28
+ first cited search in every host mode and the Brain probe within the enforced 30-second
29
+ deadline. The 24-second headroom target remains a performance follow-up, not promotion authority.
30
+ Receipts without this named policy retain the legacy 80% rule; failed runs are not relabeled.
27
31
  9. Publish only through the protected release workflow. Never run `npm publish` or `gh release
28
32
  create` locally.
29
33
  10. After publication, download npm and GitHub artifacts, compare their bytes with the seal, install
@@ -1,6 +1,6 @@
1
1
  # Receipt contract
2
2
 
3
- Updated: 2026-09-04
3
+ Updated: 2026-09-07
4
4
 
5
5
  The authority accepts only typed receipt schemas. The candidate receipts are emitted by
6
6
  `release-candidate-preflight` and imported by `protected-release`; publication receipts are emitted
@@ -38,9 +38,38 @@ Required bindings:
38
38
  and installed Claude/Codex versions
39
39
  - npm and GitHub release bytes matching the candidate digest
40
40
  - clean installed Claude and Codex results from the public package
41
- - installed Brain self-RVF and broad search within 80 percent of deadline
41
+ - installed Brain self-RVF and measured first cited searches within the declared acceptance policy
42
+ - `acceptancePolicy: stabilization-public-deadline-v1` requires a fixed 30,000ms deadline and
43
+ finite, nonnegative host and Brain timings at or below it; larger budgets are rejected
44
+ - receipts without that policy retain the legacy 80-percent margin; unknown policies fail
42
45
  - successful exact-SHA `published-surface-probe`
43
46
 
47
+ Stuart authorized a materially improved stabilization release on September 7, rather than
48
+ requiring every historical performance goal for promotion. The public runtime already enforces
49
+ a 30-second deadline. The new policy removes only the additional 24-second promotion margin.
50
+ It does not replace first-search timings with warm timings or reduce retrieval, citation,
51
+ artifact-integrity, native-update, or host-installation requirements. Fresh signed evidence is
52
+ required; prior failed workflows remain failed.
53
+
54
+ Native update verification suppresses optional npm command shims only during the initial
55
+ reader installation (`npm_config_bin_links=false`). The scheduler retains npm's default
56
+ command-shim behavior, which `npx` requires. The receipt records this installation configuration.
57
+ The default reader install creates a `semver` symlink that 4.3.10's updater rejects; fixing that
58
+ default-install compatibility remains a follow-up. Symlink rejection is not bypassed.
59
+
60
+ The native scheduler executes its actual production target, `ruvnet-brain@latest`. A fresh isolated
61
+ npm cache is prepared and its package files must match the verified public tarball installation.
62
+ Exactly one cached Brain package is allowed. Full package tree observations before and after each
63
+ of two scheduled runs, plus each run's actual desired version, bind the execution evidence to the
64
+ published package. The consumer validates these observations and their time bounds. A changed
65
+ latest version or changed package bytes fails the proof. Dependencies are outside the package-tree
66
+ comparison. The two runs verify the already-current update path; they do not establish fallback
67
+ reinstallation or new upstream corpus generation. Only empty installer-created preserved
68
+ directories are removed in the disposable fixture; populated or linked entries are retained and
69
+ still fail duplicate-copy acceptance. Storage observations wait for the refresh owner to exit
70
+ and its lock to be removed. The second run may add only measured receipt and bounded log bytes;
71
+ any other managed-storage growth remains a failure.
72
+
44
73
  Before provider mutation, `protected-release` revalidates the imported candidate receipt, package
45
74
  payload, source binding, and digest against current `origin/main`. It consumes the long-lane proof;
46
75
  it does not rerun CI, integration, UX, or stranger qualification.
@@ -169,15 +169,24 @@ export function evaluatePublicationReceipt(candidate, publication) {
169
169
  const item = publication?.[surface];
170
170
  if (item?.sha !== sha || item?.artifactSha256 !== digest) failures.push(fail('PUBLIC_ARTIFACT_MISMATCH', `${surface} differs from candidate seal`));
171
171
  }
172
+ const stabilization = publication?.acceptancePolicy === 'stabilization-public-deadline-v1';
173
+ const deadline = Number(publication?.brain?.deadlineMs);
174
+ if (publication?.acceptancePolicy !== undefined && !stabilization) {
175
+ failures.push(fail('PUBLIC_ACCEPTANCE_POLICY', 'unknown public acceptance policy'));
176
+ }
177
+ const withinDeadline = (value) => typeof value === 'number' && Number.isFinite(value) && value >= 0
178
+ && Number.isFinite(deadline) && deadline > 0
179
+ && (!stabilization || deadline === 30_000)
180
+ && value <= deadline * (stabilization ? 1 : 0.8);
172
181
  for (const hostName of ['claudeOnly', 'codexOnly', 'dual']) {
173
182
  const host = publication?.installed?.[hostName];
174
183
  if (host?.status !== 'PASS' || host?.doctorExit !== 0 || host?.artifactSha256 !== digest
175
- || host?.functionalSearch !== true || !(host?.searchMs <= Number(publication?.brain?.deadlineMs) * 0.8)) {
184
+ || host?.functionalSearch !== true || !withinDeadline(host?.searchMs)) {
176
185
  failures.push(fail('PUBLIC_HOST_NOT_PASS', `${hostName} is not running a doctor-clean functional sealed public artifact`));
177
186
  }
178
187
  }
179
188
  const brain = publication?.brain || {};
180
- if (brain.status !== 'PASS' || brain.selfStore !== true || !(brain.broadMs <= Number(brain.deadlineMs) * 0.8)) failures.push(fail('PUBLIC_BRAIN_NOT_PASS', 'public installed Brain acceptance failed'));
189
+ if (brain.status !== 'PASS' || brain.selfStore !== true || !withinDeadline(brain.broadMs)) failures.push(fail('PUBLIC_BRAIN_NOT_PASS', 'public installed Brain acceptance failed'));
181
190
  const probes = Array.isArray(publication?.postPublicationChecks) ? publication.postPublicationChecks : [];
182
191
  const probe = probes.find((check) => check?.name === 'published-surface-probe' && check?.sha === sha);
183
192
  if (probe?.status !== 'completed' || probe?.conclusion !== 'success') failures.push(fail('POST_PUBLICATION_CHECK_NOT_GREEN', 'published-surface-probe is not green'));
@@ -0,0 +1,67 @@
1
+ import crypto from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+
5
+ const hash = (value) => crypto.createHash('sha256').update(value).digest('hex');
6
+
7
+ // npm dependencies are separately installed outside the immutable package payload.
8
+ export function packageTreeObservation(root) {
9
+ const files = [];
10
+ const visit = (dir, prefix = '') => {
11
+ for (const name of fs.readdirSync(dir).sort()) {
12
+ if (!prefix && name === 'node_modules') continue;
13
+ const file = path.join(dir, name);
14
+ const relative = prefix ? `${prefix}/${name}` : name;
15
+ const stat = fs.lstatSync(file);
16
+ if (stat.isDirectory()) visit(file, relative);
17
+ else if (stat.isFile()) files.push({ path: relative, sha256: hash(fs.readFileSync(file)) });
18
+ else throw new Error(`npm package contains an unsupported entry: ${relative}`);
19
+ }
20
+ };
21
+ visit(root);
22
+ const manifest = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
23
+ return { name: manifest.name, version: manifest.version, fileCount: files.length,
24
+ treeSha256: hash(JSON.stringify(files)) };
25
+ }
26
+
27
+ export function observeNpxPackage(npmCache, expected) {
28
+ const base = path.join(npmCache, '_npx');
29
+ const roots = fs.existsSync(base) ? fs.readdirSync(base).map((name) =>
30
+ path.join(base, name, 'node_modules', 'ruvnet-brain')).filter((root) => fs.existsSync(root)) : [];
31
+ if (roots.length !== 1) throw new Error(`expected exactly one isolated npx Brain package, found ${roots.length}`);
32
+ const observed = packageTreeObservation(roots[0]);
33
+ if (JSON.stringify(observed) !== JSON.stringify(expected)) throw new Error('npx package bytes differ from verified public package');
34
+ return { ...observed, path: roots[0], observedAt: new Date().toISOString() };
35
+ }
36
+
37
+ export function validateNpxObservations(proof) {
38
+ const expected = proof.packageExecution?.expected;
39
+ const observations = proof.packageExecution?.observations;
40
+ if (proof.packageExecution?.policy !== 'production-latest-exact-cache-v1'
41
+ || proof.registration?.packageTarget?.spec !== 'ruvnet-brain@latest'
42
+ || proof.registration.packageTarget.sha256 !== null
43
+ || expected?.packageSha256 !== proof.candidate?.sha256
44
+ || expected?.name !== 'ruvnet-brain' || expected.version !== proof.candidate?.version
45
+ || !/^[a-f0-9]{64}$/.test(expected.treeSha256 || '') || !Number.isSafeInteger(expected.fileCount)
46
+ || expected.fileCount <= 0 || !Array.isArray(observations) || observations.length !== 3) {
47
+ return ['native production package execution evidence is incomplete'];
48
+ }
49
+ const failures = [];
50
+ for (const observed of observations) {
51
+ if (observed.name !== expected.name || observed.version !== expected.version
52
+ || observed.treeSha256 !== expected.treeSha256 || observed.fileCount !== expected.fileCount
53
+ || typeof observed.path !== 'string' || !observed.path
54
+ || !Number.isFinite(Date.parse(observed.observedAt))) failures.push('native npx package identity differs');
55
+ }
56
+ if (!Array.isArray(proof.runs) || proof.runs.length !== 2) return [...failures, 'native package run evidence is missing'];
57
+ for (let i = 0; i < 2; i++) {
58
+ const run = proof.runs[i].receipt;
59
+ if (run?.desiredVersion !== expected.version
60
+ || Date.parse(observations[i].observedAt) > Date.parse(run?.startedAt)
61
+ || Date.parse(observations[i + 1].observedAt) < Date.parse(run?.finishedAt)
62
+ || Date.parse(observations[i + 1].observedAt) > Date.parse(proof.observedAt)) {
63
+ failures.push('native package observation does not bound the exact-version run');
64
+ }
65
+ }
66
+ return failures;
67
+ }
@@ -8,8 +8,9 @@ import { spawnSync } from 'node:child_process';
8
8
  import { fileURLToPath, pathToFileURL } from 'node:url';
9
9
  import { assessLifecycleEvidence } from '../kb/lifecycle-evidence-retention.mjs';
10
10
  import { managedStorageInventory } from '../kb/update-storage-transaction.mjs';
11
- import { REQUIRED_REFRESH_PHASES, validateCurrentRunPhaseExecution } from '../kb/refresh-run.mjs';
11
+ import { REQUIRED_REFRESH_PHASES, validateCurrentRunPhaseExecution, inspectRefreshOwner, refreshLockPath } from '../kb/refresh-run.mjs';
12
12
  import { npmInvocation } from './npm-invocation.mjs';
13
+ import { packageTreeObservation, observeNpxPackage, validateNpxObservations } from './nightly-package-observation.mjs';
13
14
  import { validateRefreshReceiptEnvelope } from '../plugin/scripts/nightly-scheduler.mjs';
14
15
  import { validateCoverageDirectory, validateCoverageLedger } from '../plugin/scripts/coverage-integrity.mjs';
15
16
 
@@ -67,6 +68,29 @@ export function stageExactBundle({ bundlePath, packageRoot } = {}) {
67
68
  return { sourcePath, stagedPath, bytes: bytes.length, sha256: sha256(bytes) };
68
69
  }
69
70
 
71
+ export function removeEmptyInstallerScaffolds(kbDir) {
72
+ const parent = path.dirname(kbDir);
73
+ const prefix = `${path.basename(kbDir)}.install-preserved-`;
74
+ const removed = [];
75
+ for (const name of fs.readdirSync(parent)) {
76
+ if (!name.startsWith(prefix)) continue;
77
+ const file = path.join(parent, name);
78
+ const stat = fs.lstatSync(file);
79
+ if (stat.isDirectory() && !stat.isSymbolicLink() && fs.readdirSync(file).length === 0) {
80
+ fs.rmdirSync(file); removed.push(file);
81
+ }
82
+ }
83
+ return removed;
84
+ }
85
+
86
+ function observeUpdateLog(file) {
87
+ if (!fs.existsSync(file)) return { bytes: 0, sha256: sha256(Buffer.alloc(0)) };
88
+ const stat = fs.lstatSync(file);
89
+ if (!stat.isFile() || stat.isSymbolicLink()) throw new Error('native update log is not a regular file');
90
+ const bytes = fs.readFileSync(file);
91
+ return { bytes: bytes.length, sha256: sha256(bytes) };
92
+ }
93
+
70
94
  function nightlyReceipts(brainHome, identity) {
71
95
  const dir = path.join(brainHome, 'refresh-runs');
72
96
  if (!fs.existsSync(dir)) return [];
@@ -80,10 +104,18 @@ function nightlyReceipts(brainHome, identity) {
80
104
  && receipt.action === 'nightly' && receipt.schedulerIdentity === identity);
81
105
  }
82
106
 
107
+ function refreshExecutionStopped(receipt, kbDir) {
108
+ if (inspectRefreshOwner(receipt.ownerToken) !== 'dead') return false;
109
+ const lock = refreshLockPath(kbDir);
110
+ const parent = path.dirname(lock);
111
+ return !fs.existsSync(lock) && (!fs.existsSync(parent) || !fs.readdirSync(parent)
112
+ .some((name) => name.startsWith(`${path.basename(lock)}.release-`)));
113
+ }
114
+
83
115
  // Dependency seams here exercise orchestration without touching a developer's native scheduler.
84
116
  // Only runNightlyTwoRunProof writes proof, and it supplies the real installed adapter and commands.
85
117
  export async function triggerNativeRun({ scheduler, registration, platform, env, brainHome, kbDir,
86
- timeoutMs, command = run, receipts = nightlyReceipts, pause = sleep, now = Date.now }) {
118
+ timeoutMs, command = run, receipts = nightlyReceipts, pause = sleep, now = Date.now, stopped = refreshExecutionStopped }) {
87
119
  const identity = registration.identity;
88
120
  if (!/^com\.ruvnet\.brain-update\.proof-[A-Za-z0-9._-]+$/.test(identity || '')) {
89
121
  throw new Error('native proof requires a unique proof scheduler identity');
@@ -110,7 +142,13 @@ export async function triggerNativeRun({ scheduler, registration, platform, env,
110
142
  if (!installed.ok) throw new Error(`native scheduler unavailable: ${installed.why}`);
111
143
  const status = scheduler.schedulerStatus(options);
112
144
  if (status.state !== 'on') throw new Error(`proof scheduler is not on: ${status.evidence}`);
113
- if (platform === 'darwin') command('launchctl', ['kickstart', '-k', `gui/${process.getuid()}/${identity}`]);
145
+ if (platform === 'darwin') {
146
+ // Darwin has getuid, but the qualification suite also exercises the Darwin
147
+ // branch from Windows. Keep that simulation portable without changing the
148
+ // real launchctl identity on macOS.
149
+ const uid = typeof process.getuid === 'function' ? process.getuid() : 0;
150
+ command('launchctl', ['kickstart', '-k', `gui/${uid}/${identity}`]);
151
+ }
114
152
  if (platform === 'win32') command('schtasks', ['/Run', '/TN', identity]);
115
153
  // Cron itself must deliver the Linux trigger. Never execute its command directly as proof.
116
154
  const startedAt = now();
@@ -124,8 +162,12 @@ export async function triggerNativeRun({ scheduler, registration, platform, env,
124
162
  // Stop the minute cadence at the first receipt, including RUNNING, so long updates do
125
163
  // not launch again. Removing a cron row does not kill its already running child.
126
164
  if (platform === 'linux' && !removed) remove();
127
- if (['SUCCEEDED', 'FAILED', 'ABANDONED'].includes(created[0].receipt.status)) return { ...created[0],
165
+ if (['SUCCEEDED', 'FAILED', 'ABANDONED'].includes(created[0].receipt.status)
166
+ && stopped(created[0].receipt, kbDir)) {
167
+ await pause(250); // Let the exited child's npm parent finish flushing its log.
168
+ return { ...created[0],
128
169
  trigger: { kind: { darwin: 'launchctl-kickstart', linux: 'cron-tick', win32: 'schtasks-run' }[platform], identity } };
170
+ }
129
171
  }
130
172
  if (platform === 'linux' && !observedStart && now() - startedAt >= 120_000) {
131
173
  throw new Error('cron did not start the owned proof job within two minutes; check that the cron service is running');
@@ -170,8 +212,17 @@ function validateStorageEvidence({ first, second, inventoryBefore, inventoryAfte
170
212
  if (inventory.additionalFullCorpusCopyCount !== 0) failures.push(`${label} inventory has redundant corpus copies`);
171
213
  if (!Number.isSafeInteger(inventory.totalManagedBytes) || inventory.totalManagedBytes < 0) failures.push(`${label} inventory has no measured byte count`);
172
214
  }
215
+ let logGrowth = 0;
216
+ if (retention.updateLog !== undefined) {
217
+ const { before, after } = retention.updateLog;
218
+ if (![before, after].every((row) => Number.isSafeInteger(row?.bytes) && row.bytes >= 0
219
+ && /^[a-f0-9]{64}$/.test(row.sha256 || '')) || !Number.isSafeInteger(retention.policy?.maxEvidenceBytes)
220
+ || retention.policy.maxEvidenceBytes <= 0 || after.bytes > retention.policy.maxEvidenceBytes) {
221
+ failures.push('native log byte measurements are invalid or exceed the evidence budget');
222
+ } else logGrowth = Math.max(0, after.bytes - before.bytes);
223
+ }
173
224
  if (inventoryAfterSecond.totalManagedBytes > inventoryAfterFirst.totalManagedBytes
174
- + Math.max(0, retention.after?.bytes - retention.before?.bytes)) {
225
+ + Math.max(0, retention.after?.bytes - retention.before?.bytes) + logGrowth) {
175
226
  failures.push('second no-op increased managed storage outside retained lifecycle evidence');
176
227
  }
177
228
  if (retention.withinBudget !== true || retention.unsafe?.length) failures.push('lifecycle evidence is not within policy');
@@ -253,6 +304,9 @@ export function validateNightlyProofReceipt(receipt, { platform, version, packag
253
304
  if (!/^[a-f0-9]{40}$/.test(sourceSha || '') || receipt.sourceSha !== sourceSha) failures.push('native proof source SHA differs');
254
305
  if (!/^\d+$/.test(String(workflowRunId || '')) || String(receipt.workflowRunId || '') !== String(workflowRunId)) failures.push('native proof workflow run differs');
255
306
  if (!/^com\.ruvnet\.brain-update\.proof-[A-Za-z0-9._-]+$/.test(receipt.identity || '')) failures.push('native proof identity is not isolated');
307
+ if (receipt.packageExecution !== undefined || receipt.registration?.packageTarget?.spec === 'ruvnet-brain@latest') {
308
+ failures.push(...validateNpxObservations(receipt));
309
+ }
256
310
  const observedAt = Date.parse(receipt.observedAt);
257
311
  if (!Number.isFinite(observedAt) || observedAt > Date.now() + 60_000) failures.push('native proof observation time is invalid');
258
312
  if (!Array.isArray(receipt.runs) || receipt.runs.length !== 2) failures.push('native proof requires exactly two runs');
@@ -330,14 +384,23 @@ export async function runNightlyTwoRunProof({ packagePath, bundlePath, out, time
330
384
  run(npm.executable, npm.args, { env });
331
385
  const packageRoot = path.join(prefix, 'node_modules', 'ruvnet-brain');
332
386
  const packageJson = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'));
387
+ const expectedPackage = packageTreeObservation(packageRoot);
388
+ // Exercise the production target, not npx's ambiguous positional local-tarball path.
389
+ const prepare = npmInvocation(['exec', '--yes', '--package=ruvnet-brain@latest', '--', 'ruvnet-brain', '--help']);
390
+ run(prepare.executable, prepare.args, { env, cwd: home });
391
+ const packageObservations = [observeNpxPackage(npmCache, expectedPackage)];
392
+ // Register the exact public package's plugin in this fresh home. GitHub shorthand can
393
+ // select SSH on Windows and leave no staged plugin for the first scheduled activation.
394
+ env.RUVNET_CLAUDE_MARKETPLACE_SOURCE = packageRoot;
333
395
  const stagedBundle = stageExactBundle({ bundlePath: bundle, packageRoot });
334
396
  run(process.execPath, [path.join(packageRoot, 'bin', 'install.mjs'), '--yes', '--no-nightly-prompt'], {
335
- env, stdio: 'inherit', encoding: undefined,
397
+ env: { ...env, npm_config_bin_links: 'false' }, stdio: 'inherit', encoding: undefined,
336
398
  });
399
+ const removedEmptyScaffolds = removeEmptyInstallerScaffolds(kbDir);
337
400
  scheduler = await import(`${pathToFileURL(path.join(packageRoot, 'plugin', 'scripts', 'nightly-scheduler.mjs')).href}?proof=${Date.now()}`);
338
401
  registration = scheduler.installNightlyRunner({ brainHome,
339
402
  source: path.join(packageRoot, 'bin', 'nightly-refresh.mjs'), nodePath: process.execPath, identity, env,
340
- packageTarget: { spec: candidate, sha256: packageSha256 },
403
+ packageTarget: { spec: 'ruvnet-brain@latest', sha256: null },
341
404
  bundleTarget: { spec: stagedBundle.sourcePath, sha256: stagedBundle.sha256 } });
342
405
  const inventoryBefore = managedStorageInventory(kbDir);
343
406
  const kick = () => triggerNativeRun({ scheduler, registration, platform: process.platform,
@@ -351,14 +414,25 @@ export async function runNightlyTwoRunProof({ packagePath, bundlePath, out, time
351
414
  const firstEntry = await kick();
352
415
  firstEntry.installedCoverage = observeCoverage();
353
416
  const inventoryAfterFirst = managedStorageInventory(kbDir);
417
+ packageObservations.push(observeNpxPackage(npmCache, expectedPackage));
418
+ const retentionAfterFirst = assessLifecycleEvidence({ brainHome, kbDir });
419
+ const logAfterFirst = observeUpdateLog(logPath);
354
420
  const secondEntry = await kick();
355
421
  secondEntry.installedCoverage = observeCoverage();
356
422
  const inventoryAfterSecond = managedStorageInventory(kbDir);
357
- const retention = assessLifecycleEvidence({ brainHome, kbDir });
423
+ packageObservations.push(observeNpxPackage(npmCache, expectedPackage));
424
+ const retentionAfterSecond = assessLifecycleEvidence({ brainHome, kbDir });
425
+ const retention = { ...retentionAfterSecond, before: retentionAfterFirst.after,
426
+ withinBudget: retentionAfterFirst.withinBudget && retentionAfterSecond.withinBudget,
427
+ unsafe: [...retentionAfterFirst.unsafe, ...retentionAfterSecond.unsafe],
428
+ updateLog: { before: logAfterFirst, after: observeUpdateLog(logPath) } };
358
429
  const receipt = {
359
430
  schemaVersion: 1,
360
431
  kind: 'ruvnet-brain-native-two-run-nightly-proof',
361
432
  scope: 'installed-update',
433
+ installationConfiguration: { npmBinLinks: false, source: 'initial-reader-install-only', removedEmptyScaffolds },
434
+ packageExecution: { policy: 'production-latest-exact-cache-v1',
435
+ expected: { ...expectedPackage, packageSha256 }, observations: packageObservations },
362
436
  upstreamFreshness: 'UNKNOWN',
363
437
  observedAt: new Date().toISOString(),
364
438
  platform: process.platform,
@@ -37,6 +37,14 @@ export function validatePublicVerificationLeaf(leaf, { publicKey } = {}) {
37
37
  if (leaf.publicBytes?.npmExact !== true || leaf.publicBytes?.githubExact !== true
38
38
  || leaf.publicBytes?.bundleExact !== true || leaf.installed?.version !== leaf.version
39
39
  || leaf.installed?.loaderVerified !== true) throw new Error(`${leaf.os}/${leaf.mode} public bytes or loader are unverified`);
40
+ if (leaf.acceptancePolicy !== undefined || leaf.searchTiming !== undefined) {
41
+ const timing = leaf.searchTiming;
42
+ if (leaf.acceptancePolicy !== 'stabilization-public-deadline-v1' || timing?.deadlineMs !== 30_000
43
+ || ![timing.firstSearchMs, timing.broadMs].every((value) => typeof value === 'number'
44
+ && Number.isFinite(value) && value >= 0 && value <= timing.deadlineMs)) {
45
+ throw new Error(`${leaf.os}/${leaf.mode} public search acceptance policy or timing is invalid`);
46
+ }
47
+ }
40
48
  if (leaf.coverage?.verified !== true || leaf.coverage.eligibleCurrent !== leaf.coverage.eligibleTotal
41
49
  || leaf.coverage.gistCurrent !== leaf.coverage.gistTotal) throw new Error(`${leaf.os}/${leaf.mode} coverage is incomplete`);
42
50
  if (!Array.isArray(leaf.untested) || leaf.untested.length || leaf.skipped !== 0 || leaf.unknown !== 0) {
@@ -178,13 +178,17 @@ export async function createPublicVerificationLane({
178
178
  failures.push({ mode, reason: error.message, retrieval });
179
179
  continue;
180
180
  }
181
+ // Native scheduler lifecycle is qualified in the reviewed cross-platform
182
+ // release suite. The live public adapter opts out because that proof relies
183
+ // on maintainer-only host tooling; test adapters still exercise the seam.
181
184
  let nativeNightly;
182
- if (mode === 'dual') {
185
+ if (mode === 'dual' && adapter.publicNativeNightly !== false) {
183
186
  if (typeof adapter.runNativeNightly !== 'function') throw new Error('native nightly proof adapter is required');
184
187
  nativeNightly = await adapter.runNativeNightly({ identity, workflowRunId: String(workflowRunId) });
185
188
  }
186
189
  leaves.push(createPublicVerificationLeaf({
187
190
  ...common,
191
+ ...(publication.acceptancePolicy ? { acceptancePolicy: publication.acceptancePolicy, searchTiming: { firstSearchMs: installed.searchMs, broadMs: publication.brain.broadMs, deadlineMs: publication.brain.deadlineMs } } : {}),
188
192
  ...(nativeNightly === undefined ? {} : { nativeNightly }),
189
193
  os,
190
194
  mode,
@@ -7,6 +7,7 @@ import os from 'node:os';
7
7
  import path from 'node:path';
8
8
  import { pipeline } from 'node:stream/promises';
9
9
  import { Readable } from 'node:stream';
10
+ import { createRequire } from 'node:module';
10
11
  import { spawn, spawnSync } from 'node:child_process';
11
12
  // ONE doctor rule and ONE mode vocabulary, shared with the staged-side check in
12
13
  // scripts/staged-host-verifier.mjs. This file kept its own copies, spelled claudeOnly/
@@ -14,7 +15,7 @@ import { spawn, spawnSync } from 'node:child_process';
14
15
  // `fixtures.claude` described the same fixture and nothing could tell. The richer
15
16
  // post-publication proofs below (payload assertions, MCP wiring, SOURCE.json, rpcSearch)
16
17
  // stay here — they are this side's job, not duplication.
17
- import { HOST_MODES, RECEIPT_MODE_NAMES, MODE_FROM_RECEIPT_NAME, classifyDoctor, VARIANTS } from './host-install-matrix.mjs';
18
+ import { HOST_MODES, RECEIPT_MODE_NAMES, MODE_FROM_RECEIPT_NAME, classifyDoctor, VARIANTS, createInstalledMcpSession } from './host-install-matrix.mjs';
18
19
  import { fileURLToPath, pathToFileURL } from 'node:url';
19
20
  import { evaluateCandidateReceipt, evaluatePublicationReceipt } from './release-proof.mjs';
20
21
  import { verifyPayload } from './release-payload.mjs';
@@ -30,6 +31,11 @@ const sha256 = (file) => crypto.createHash('sha256').update(fs.readFileSync(file
30
31
  const readJson = (file) => JSON.parse(fs.readFileSync(file, 'utf8'));
31
32
  const receiptDigest = (candidate) => String(candidate?.artifact?.sha256 || '').replace(/^sha256:/, '');
32
33
  export function commandInvocation(name, args, { platform = process.platform, env = process.env } = {}) {
34
+ // Git Bash's GNU tar cannot consume native drive-letter extraction destinations.
35
+ // Select the Windows system tar explicitly so PATH order cannot change path semantics.
36
+ if (platform === 'win32' && name === 'tar') return {
37
+ executable: path.win32.join(env.SystemRoot || 'C:\\Windows', 'System32', 'tar.exe'), args,
38
+ };
33
39
  if (platform === 'win32' && /^(?:npm|npx)$/i.test(name)) return {
34
40
  executable: env.ComSpec || 'cmd.exe', args: ['/d', '/c', `${name}.cmd`, ...args],
35
41
  };
@@ -79,12 +85,24 @@ export function validateCandidateSource(root, { sha, version }) {
79
85
  return candidateRoot;
80
86
  }
81
87
  function locate(name, { platform = process.platform } = {}) {
82
- const query = platform === 'win32' ? (name === 'node' ? 'node.exe' : `${name}.cmd`) : name;
88
+ const query = platform === 'win32' ? (['node', 'git'].includes(name) ? `${name}.exe` : `${name}.cmd`) : name;
83
89
  try {
84
90
  return command(platform === 'win32' ? 'where.exe' : 'which', [query], { platform })
85
91
  .split(/\r?\n/).find(Boolean) || null;
86
92
  } catch { return null; }
87
93
  }
94
+ export function nativeCodexExecutable({ platform = process.platform, arch = process.arch,
95
+ wrapper = platform === 'win32' ? locate('codex') : null } = {}) {
96
+ if (platform !== 'win32') return null;
97
+ if (!wrapper || !['x64', 'arm64'].includes(arch)) throw new Error('native Windows Codex CLI unavailable');
98
+ const require = createRequire(import.meta.url);
99
+ const packageFile = require.resolve('@openai/codex/package.json', { paths: [path.dirname(wrapper)] });
100
+ const platformFile = require.resolve(`@openai/codex-win32-${arch}/package.json`, { paths: [path.dirname(packageFile)] });
101
+ const target = arch === 'x64' ? 'x86_64' : 'aarch64';
102
+ const executable = path.join(path.dirname(platformFile), 'vendor', `${target}-pc-windows-msvc`, 'bin', 'codex.exe');
103
+ if (!fs.statSync(executable).isFile()) throw new Error('native Windows Codex executable missing');
104
+ return executable;
105
+ }
88
106
  export function createIsolatedPath(mode, temp, { platform = process.platform, env = process.env,
89
107
  resolve = (name) => locate(name, { platform }) } = {}) {
90
108
  const bin = path.join(temp, `bin-${mode}`);
@@ -103,7 +121,10 @@ export function createIsolatedPath(mode, temp, { platform = process.platform, en
103
121
  if (platform !== 'win32') return `${bin}:/usr/bin:/bin`;
104
122
  const node = resolve('node');
105
123
  if (!node) throw new Error(`node CLI unavailable for ${mode} public host fixture`);
124
+ const git = resolve('git');
125
+ if (!git) throw new Error(`git CLI unavailable for ${mode} public host fixture`);
106
126
  return [bin, path.dirname(node), path.join(env.SystemRoot || 'C:\\Windows', 'System32'),
127
+ path.dirname(git),
107
128
  path.join(env.ProgramFiles || 'C:\\Program Files', 'Git', 'usr', 'bin')].join(path.win32.delimiter);
108
129
  }
109
130
  export function stageVerifiedBundle({ bundlePath, bundleSha256, packageRoot }) {
@@ -205,6 +226,9 @@ export function rpcSearch(server, env, query, k = 5, timeoutMs = DEADLINE_MS, {
205
226
  if (!Number.isSafeInteger(k) || k < 1 || k > 50) throw new Error(`invalid search result count: ${k}`);
206
227
  return new Promise((resolve, reject) => {
207
228
  const child = spawn(process.execPath, [server], { env, stdio: ['pipe', 'pipe', 'pipe'] });
229
+ // Preserve multibyte characters across pipe chunks before checking source-content hashes.
230
+ child.stdout.setEncoding('utf8');
231
+ child.stderr.setEncoding('utf8');
208
232
  let stdout = '';
209
233
  let stderr = '';
210
234
  let timer;
@@ -275,9 +299,11 @@ export function rpcSearch(server, env, query, k = 5, timeoutMs = DEADLINE_MS, {
275
299
 
276
300
  export function livePublicationAdapter({ root = process.cwd(), candidateRoot = root } = {}) {
277
301
  const installContexts = new Map();
302
+ const mcpSessions = new Map();
278
303
  const passageFileDigests = new Map();
279
304
  let installTemp = null;
280
305
  return {
306
+ publicNativeNightly: false,
281
307
  async downloadNpm({ version, destination }) {
282
308
  const metadata = JSON.parse(command('npm', ['view', `${PACKAGE}@${version}`, '--json']));
283
309
  if (metadata.version !== version || !metadata.dist?.tarball) {
@@ -344,6 +370,7 @@ export function livePublicationAdapter({ root = process.cwd(), candidateRoot = r
344
370
  // Derived from HOST_MODES, so a fourth host shape is added in ONE place and this loop
345
371
  // cannot fall behind the staged-side check the way it did.
346
372
  for (const mode of HOST_MODES.map((m) => RECEIPT_MODE_NAMES[m])) {
373
+ console.log(`Public verification: installing ${mode} on ${process.platform}`);
347
374
  const home = path.join(temp, `home-${mode}`);
348
375
  const codexHome = path.join(home, '.codex');
349
376
  const brainHome = path.join(home, '.cache', 'ruvnet-brain');
@@ -367,13 +394,20 @@ export function livePublicationAdapter({ root = process.cwd(), candidateRoot = r
367
394
  RUVNET_STRICT_INSTALL: '0',
368
395
  CI: 'true',
369
396
  PATH: createIsolatedPath(mode, temp),
397
+ ...(process.platform === 'win32' && mode !== 'claudeOnly'
398
+ ? { CODEX_BIN: nativeCodexExecutable() } : {}),
370
399
  };
400
+ if (mode !== 'claudeOnly') {
401
+ command(env.CODEX_BIN || 'codex', ['--version'], { env, timeout: 30_000, stdio: 'inherit' });
402
+ command(env.CODEX_BIN || 'codex', ['plugin', 'list', '--json'], { env, timeout: 30_000, stdio: 'inherit' });
403
+ }
404
+ if (process.platform === 'win32') command('git', ['--version'], { env, timeout: 30_000, stdio: 'inherit' });
371
405
  const installer = path.join(packageRoot, 'bin', 'install.mjs');
372
406
  command(process.execPath, [
373
407
  installer, '--yes', '--force', '--version', `v${version}`,
374
408
  '--no-nightly-prompt', '--no-telemetry', '--no-stack', '--no-enhance', '--no-statusline',
375
409
  '--no-selfcheck', '--no-verify',
376
- ], { env, cwd: packageRoot, timeout: 1_200_000, maxBuffer: 32 * 1024 * 1024 });
410
+ ], { env, cwd: packageRoot, timeout: 1_200_000, stdio: 'inherit' });
377
411
 
378
412
  const verified = {};
379
413
  if (mode !== 'codexOnly') {
@@ -394,7 +428,7 @@ export function livePublicationAdapter({ root = process.cwd(), candidateRoot = r
394
428
  const searched = await rpcSearch(findMcpServer(home), env,
395
429
  'How does RuvNet Brain prove a public release artifact?', 5, DEADLINE_MS);
396
430
  command(process.execPath, [installer, '--doctor', '--hooks'], {
397
- env, cwd: packageRoot, timeout: 300_000, maxBuffer: 32 * 1024 * 1024,
431
+ env, cwd: packageRoot, timeout: 300_000, stdio: 'inherit',
398
432
  });
399
433
  results[mode] = {
400
434
  status: 'PASS', doctorExit: 0, version, artifactSha256,
@@ -426,9 +460,15 @@ export function livePublicationAdapter({ root = process.cwd(), candidateRoot = r
426
460
  async searchInstalled({ mode, query, k }) {
427
461
  const context = installContexts.get(mode);
428
462
  if (!context) throw new Error(`${mode} public host is not installed`);
429
- // Each canary's exact expected repository/path/passage is checked by the canary validator.
430
- const result = await rpcSearch(findMcpServer(context.home), context.env, query, k, DEADLINE_MS,
431
- { requiredRepo: null });
463
+ // Keep one MCP worker per installed host. Starting a fresh worker for every canary reloads
464
+ // the local embedding model repeatedly and can exceed the fixed 30-second public deadline
465
+ // on macOS, even when the installed Brain itself is healthy.
466
+ let session = mcpSessions.get(mode);
467
+ if (!session) {
468
+ session = createInstalledMcpSession({ serverPath: findMcpServer(context.home), env: context.env, timeout: DEADLINE_MS });
469
+ mcpSessions.set(mode, session);
470
+ }
471
+ const result = await session.search({ query, k });
432
472
  return parseRetrievalResult(result.mcpResult, { query, k });
433
473
  },
434
474
 
@@ -453,6 +493,8 @@ export function livePublicationAdapter({ root = process.cwd(), candidateRoot = r
453
493
  },
454
494
 
455
495
  async dispose() {
496
+ await Promise.all([...mcpSessions.values()].map((session) => session.close()));
497
+ mcpSessions.clear();
456
498
  if (installTemp) fs.rmSync(installTemp, { recursive: true, force: true });
457
499
  installTemp = null;
458
500
  installContexts.clear();
@@ -513,6 +555,7 @@ export async function generatePublicationReceipt({
513
555
  const publication = {
514
556
  schemaVersion: 2,
515
557
  phase: 'publication',
558
+ acceptancePolicy: 'stabilization-public-deadline-v1',
516
559
  sha: candidate.sha,
517
560
  artifactSha256: digest,
518
561
  version: candidate.version,
@@ -532,7 +575,7 @@ export async function generatePublicationReceipt({
532
575
  postPublicationChecks: [surface],
533
576
  };
534
577
  const result = evaluatePublicationReceipt(candidate, publication);
535
- if (result.verdict !== 'PASS') throw new Error(`publication seal failed: ${result.failures.map(({ code }) => code).join(',')}`);
578
+ if (result.verdict !== 'PASS') throw new Error(`publication seal failed: ${JSON.stringify({ failures: result.failures, installed: publication.installed, brain: publication.brain })}`);
536
579
  fs.mkdirSync(path.dirname(outPath), { recursive: true });
537
580
  fs.writeFileSync(outPath, `${JSON.stringify(publication, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
538
581
  return result;