ruvnet-brain 4.3.9 → 4.3.11

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.
Files changed (87) hide show
  1. package/README.md +26 -9
  2. package/bin/install.mjs +646 -366
  3. package/bin/nightly-refresh.mjs +115 -0
  4. package/kb/brain-profile.mjs +118 -32
  5. package/kb/lifecycle-evidence-retention.mjs +237 -0
  6. package/kb/refresh-run.mjs +367 -0
  7. package/kb/retrieval-result.mjs +39 -0
  8. package/kb/update-storage-transaction.mjs +439 -0
  9. package/package.json +12 -3
  10. package/plugin/.claude-plugin/plugin.json +1 -1
  11. package/plugin/.codex-plugin/plugin.json +1 -1
  12. package/plugin/hooks/codex-hooks.json +2 -165
  13. package/plugin/hooks/hook-contracts.json +4 -65
  14. package/plugin/hooks/hooks.json +2 -208
  15. package/plugin/scripts/adr-currency-gate.mjs +29 -19
  16. package/plugin/scripts/capability-registry.mjs +10 -83
  17. package/plugin/scripts/codex-hook-adapter.mjs +62 -9
  18. package/plugin/scripts/codex-hook-wrapper.mjs +19 -1
  19. package/plugin/scripts/continuation-gate.mjs +30 -28
  20. package/plugin/scripts/continuation-objective.mjs +34 -0
  21. package/plugin/scripts/development-maintenance.mjs +49 -0
  22. package/plugin/scripts/hook-shim.mjs +3 -0
  23. package/plugin/scripts/learn-flush.mjs +6 -1
  24. package/plugin/scripts/lesson-gate.mjs +5 -2
  25. package/plugin/scripts/lesson-presentation.mjs +5 -0
  26. package/plugin/scripts/md-stamp.mjs +109 -6
  27. package/plugin/scripts/memory-doctor.mjs +3 -7
  28. package/plugin/scripts/nightly-controller.mjs +9 -27
  29. package/plugin/scripts/nightly-scheduler.mjs +494 -0
  30. package/plugin/scripts/project-progression-contract.mjs +4 -4
  31. package/plugin/scripts/project-progression-store.mjs +35 -6
  32. package/plugin/scripts/ruflo-bin.mjs +20 -0
  33. package/plugin/scripts/session-snapshot-contract.mjs +6 -1
  34. package/plugin/scripts/version-bump-gate.sh +14 -2
  35. package/plugin/skills/release-proof/SKILL.md +6 -2
  36. package/plugin/skills/release-proof/references/receipt-contract.md +31 -2
  37. package/plugin/skills/release-proof/scripts/release-proof.mjs +11 -2
  38. package/plugin/skills/ruvnet-brain/PLAYBOOK.md +15 -23
  39. package/scripts/build-bundle.mjs +8 -0
  40. package/scripts/build-primer.mjs +2 -3
  41. package/scripts/candidate-host-evidence.mjs +59 -41
  42. package/scripts/ci/build-fixture-kb.mjs +59 -2
  43. package/scripts/ci/mutate-hook-timeout.mjs +24 -47
  44. package/scripts/ci/stranger-scenario.mjs +13 -28
  45. package/scripts/claims-verify.mjs +219 -21
  46. package/scripts/console-engine.mjs +4 -18
  47. package/scripts/console-runtime-identity.mjs +6 -0
  48. package/scripts/development-maintenance.mjs +47 -0
  49. package/scripts/development-push-check.mjs +35 -0
  50. package/scripts/distill-project.mjs +24 -47
  51. package/scripts/doc-currency.mjs +91 -19
  52. package/scripts/git-hooks/pre-push +4 -162
  53. package/scripts/health-repair.mjs +22 -4
  54. package/scripts/hook-retirement-check.mjs +22 -0
  55. package/scripts/host-install-matrix.mjs +125 -61
  56. package/scripts/integration-evidence.mjs +11 -6
  57. package/scripts/learning-replay-fixture.mjs +23 -2
  58. package/scripts/nightly-package-observation.mjs +67 -0
  59. package/scripts/nightly-two-run-proof.mjs +489 -0
  60. package/scripts/nightly-watchdog.mjs +17 -2
  61. package/scripts/npm-invocation.mjs +20 -0
  62. package/scripts/prepublication-evidence.mjs +42 -6
  63. package/scripts/primer-grounding.mjs +20 -0
  64. package/scripts/product-integrity-contract.mjs +22 -2
  65. package/scripts/public-verification-abandon.mjs +101 -0
  66. package/scripts/public-verification-aggregate.mjs +61 -19
  67. package/scripts/public-verification-finalizer.mjs +8 -2
  68. package/scripts/public-verification-lane.mjs +64 -8
  69. package/scripts/publication-receipt.mjs +156 -50
  70. package/scripts/published-surface-probe.mjs +10 -2
  71. package/scripts/qa-contract.mjs +57 -0
  72. package/scripts/qa-lanes.mjs +40 -0
  73. package/scripts/qa-runner.mjs +54 -64
  74. package/scripts/qe/ux-suite.mjs +10 -34
  75. package/scripts/qualified-candidate-check.mjs +124 -0
  76. package/scripts/release-abort-stale.mjs +2 -2
  77. package/scripts/release-projection.mjs +20 -1
  78. package/scripts/release-qualification-contract.mjs +87 -0
  79. package/scripts/release-qualification.mjs +135 -0
  80. package/scripts/release-transaction.mjs +81 -1
  81. package/scripts/remedy-registry.mjs +6 -13
  82. package/scripts/retrieval-canary.mjs +91 -18
  83. package/scripts/selfcheck.mjs +12 -4
  84. package/scripts/snapshot-freshness.mjs +58 -0
  85. package/scripts/stack-sync.mjs +41 -43
  86. package/scripts/staged-host-verifier.mjs +63 -14
  87. package/scripts/wired-check.mjs +31 -2
@@ -3,11 +3,12 @@ import crypto from 'node:crypto';
3
3
  import fs from 'node:fs';
4
4
  import osModule from 'node:os';
5
5
  import path from 'node:path';
6
+ import { execFileSync } from 'node:child_process';
6
7
  import { fileURLToPath } from 'node:url';
7
8
  import { canonicalJson, digest, validateCoverageLedger } from './coverage-integrity.mjs';
8
9
  import { validateHostRegistry } from './host-registry.mjs';
9
10
  import { RECEIPT_MODE_NAMES } from './host-install-matrix.mjs';
10
- import { generatePublicationReceipt, livePublicationAdapter } from './publication-receipt.mjs';
11
+ import { generatePublicationReceipt, livePublicationAdapter, validateCandidateSource } from './publication-receipt.mjs';
11
12
  import {
12
13
  createPublicVerificationLeaf,
13
14
  PUBLIC_VERIFICATION_MODES,
@@ -29,12 +30,13 @@ function failedCanaryCases(retrieval) {
29
30
  status !== 'COMPLETED' || retrievalHit !== true || (cohort === 'delta' && citationResolved !== true));
30
31
  }
31
32
 
32
- function publicVerificationFailure({ os, identity, failures, completedLeaves }) {
33
+ function publicVerificationFailure({ os, identity, verifierSha, failures, completedLeaves }) {
33
34
  const payload = {
34
35
  schemaVersion: 1,
35
36
  kind: 'ruvnet-brain-public-verification-failure',
36
37
  os,
37
38
  sourceSha: identity.candidateSha,
39
+ ...(verifierSha === undefined ? {} : { verifierSha }),
38
40
  artifactSha256: identity.packageSha256,
39
41
  bundleSha256: identity.bundleSha256,
40
42
  failures: failures.map(({ mode, reason, retrieval }) => ({
@@ -102,6 +104,9 @@ function coverageProjection(coverage) {
102
104
 
103
105
  export async function createPublicVerificationLane({
104
106
  os,
107
+ verifierSha,
108
+ workflowRunId,
109
+ publicKey,
105
110
  candidate,
106
111
  publication,
107
112
  identity,
@@ -112,6 +117,8 @@ export async function createPublicVerificationLane({
112
117
  adapter,
113
118
  } = {}) {
114
119
  if (!PUBLIC_VERIFICATION_OS.includes(os)) throw new Error(`unsupported public verification OS: ${os || '(missing)'}`);
120
+ if (verifierSha !== undefined && !HEX40.test(String(verifierSha))) throw new Error('verifier SHA must be a full commit identity');
121
+ if (!/^[1-9][0-9]*$/.test(String(workflowRunId || ''))) throw new Error('workflow run ID is required');
115
122
  validateReleaseIdentity(identity);
116
123
  validatePublicEvidence({ candidate, publication, identity });
117
124
  const registry = validateHostRegistry(hostRegistry);
@@ -132,7 +139,9 @@ export async function createPublicVerificationLane({
132
139
  throw new Error('public verification lane adapter is incomplete');
133
140
  }
134
141
  const common = {
142
+ workflowRunId: String(workflowRunId),
135
143
  sourceSha: identity.candidateSha,
144
+ ...(verifierSha === undefined ? {} : { verifierSha }),
136
145
  version: identity.version,
137
146
  tag: identity.tag,
138
147
  artifactSha256: identity.packageSha256,
@@ -169,8 +178,15 @@ export async function createPublicVerificationLane({
169
178
  failures.push({ mode, reason: error.message, retrieval });
170
179
  continue;
171
180
  }
181
+ let nativeNightly;
182
+ if (mode === 'dual') {
183
+ if (typeof adapter.runNativeNightly !== 'function') throw new Error('native nightly proof adapter is required');
184
+ nativeNightly = await adapter.runNativeNightly({ identity, workflowRunId: String(workflowRunId) });
185
+ }
172
186
  leaves.push(createPublicVerificationLeaf({
173
187
  ...common,
188
+ ...(publication.acceptancePolicy ? { acceptancePolicy: publication.acceptancePolicy, searchTiming: { firstSearchMs: installed.searchMs, broadMs: publication.brain.broadMs, deadlineMs: publication.brain.deadlineMs } } : {}),
189
+ ...(nativeNightly === undefined ? {} : { nativeNightly }),
174
190
  os,
175
191
  mode,
176
192
  status: verifiedRetrieval.receiptSha256 === retrieval.receiptSha256 ? 'completed' : 'failed',
@@ -183,11 +199,11 @@ export async function createPublicVerificationLane({
183
199
  untested: [],
184
200
  skipped: 0,
185
201
  unknown: 0,
186
- }));
202
+ }, { publicKey }));
187
203
  }
188
204
  if (failures.length) {
189
205
  const error = new Error(`retrieval canary acceptance failed for ${failures.map(({ mode }) => mode).join(', ')}`);
190
- error.publicVerificationFailure = publicVerificationFailure({ os, identity, failures, completedLeaves: leaves });
206
+ error.publicVerificationFailure = publicVerificationFailure({ os, identity, verifierSha, failures, completedLeaves: leaves });
191
207
  throw error;
192
208
  }
193
209
  if (canonicalJson(leaves.map(({ mode }) => mode)) !== canonicalJson(PUBLIC_VERIFICATION_MODES)) {
@@ -206,8 +222,22 @@ function regularJson(file, label) {
206
222
  return { absolute, bytes: fs.readFileSync(absolute), value: JSON.parse(fs.readFileSync(absolute, 'utf8')) };
207
223
  }
208
224
 
225
+ export function resolveVerifierSha(root, expected) {
226
+ const options = { cwd: root, encoding: 'utf8' };
227
+ const actual = execFileSync('git', ['rev-parse', 'HEAD'], options).trim();
228
+ if (!HEX40.test(actual) || (expected !== undefined && expected !== actual)) {
229
+ throw new Error('verifier SHA differs from the executing checkout');
230
+ }
231
+ if (execFileSync('git', ['status', '--porcelain', '--untracked-files=no'], options).trim()) {
232
+ throw new Error('verifier checkout has tracked changes');
233
+ }
234
+ return actual;
235
+ }
236
+
209
237
  export async function generatePublicVerificationLane({
210
238
  os,
239
+ verifierSha: expectedVerifierSha,
240
+ workflowRunId,
211
241
  candidatePath,
212
242
  identityPath,
213
243
  coveragePath,
@@ -215,10 +245,19 @@ export async function generatePublicVerificationLane({
215
245
  registryPath,
216
246
  outPath,
217
247
  root = process.cwd(),
218
- adapter = livePublicationAdapter({ root }),
248
+ candidateRoot = root,
249
+ adapter = livePublicationAdapter({ root, candidateRoot }),
219
250
  } = {}) {
220
251
  if (os !== hostOperatingSystem()) throw new Error(`runner OS ${hostOperatingSystem() || process.platform} cannot produce ${os || '(missing)'}`);
252
+ const rootDirectory = fs.statSync(root, { bigint: true });
253
+ const scriptDirectory = fs.statSync(fileURLToPath(new URL('..', import.meta.url)), { bigint: true });
254
+ if (!rootDirectory.isDirectory() || rootDirectory.ino <= 0n
255
+ || rootDirectory.dev !== scriptDirectory.dev || rootDirectory.ino !== scriptDirectory.ino) {
256
+ throw new Error('verifier root differs from the executing script checkout');
257
+ }
258
+ const verifierSha = resolveVerifierSha(root, expectedVerifierSha);
221
259
  const candidate = regularJson(candidatePath, 'candidate receipt');
260
+ validateCandidateSource(candidateRoot, candidate.value);
222
261
  const identity = regularJson(identityPath, 'release identity');
223
262
  const releaseCoverage = regularJson(coveragePath, 'release coverage');
224
263
  const retrievalPlan = regularJson(planPath, 'retrieval plan');
@@ -234,6 +273,8 @@ export async function generatePublicVerificationLane({
234
273
  const publication = JSON.parse(fs.readFileSync(publicationPath, 'utf8'));
235
274
  const leaves = await createPublicVerificationLane({
236
275
  os,
276
+ verifierSha,
277
+ workflowRunId,
237
278
  candidate: candidate.value,
238
279
  publication,
239
280
  identity: identity.value,
@@ -244,15 +285,27 @@ export async function generatePublicVerificationLane({
244
285
  hostRegistry: hostRegistry.value,
245
286
  adapter,
246
287
  });
247
- const payload = { schemaVersion: 1, kind: 'ruvnet-brain-public-verification-os-lane', os, leaves };
288
+ resolveVerifierSha(root, verifierSha);
289
+ validateCandidateSource(candidateRoot, candidate.value);
290
+ const payload = { schemaVersion: 1, kind: 'ruvnet-brain-public-verification-os-lane', os,
291
+ verifierSha, sourceSha: identity.value.candidateSha, leaves };
248
292
  const receipt = { ...payload, laneSha256: digest(payload) };
249
293
  fs.mkdirSync(path.dirname(output), { recursive: true });
250
294
  fs.writeFileSync(output, `${JSON.stringify(receipt, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
251
295
  return receipt;
252
296
  } catch (error) {
253
- if (error.publicVerificationFailure && !fs.existsSync(output)) {
297
+ if (!fs.existsSync(output)) {
298
+ const failure = error.publicVerificationFailure || (() => {
299
+ const payload = { schemaVersion: 1, kind: 'ruvnet-brain-public-verification-failure',
300
+ os, verifierSha, sourceSha: identity.value.candidateSha,
301
+ artifactSha256: identity.value.packageSha256, bundleSha256: identity.value.bundleSha256,
302
+ workflowRunId: String(workflowRunId || ''),
303
+ failures: [{ stage: 'public-install-and-native-update', reason: String(error.message).slice(0, 2000) }],
304
+ completedLeaves: [] };
305
+ return { ...payload, failureSha256: digest(payload) };
306
+ })();
254
307
  fs.mkdirSync(path.dirname(output), { recursive: true });
255
- fs.writeFileSync(output, `${JSON.stringify(error.publicVerificationFailure, null, 2)}\n`,
308
+ fs.writeFileSync(output, `${JSON.stringify(failure, null, 2)}\n`,
256
309
  { flag: 'wx', mode: 0o600 });
257
310
  }
258
311
  throw error;
@@ -271,6 +324,9 @@ export async function main(args = process.argv.slice(2)) {
271
324
  try {
272
325
  const receipt = await generatePublicVerificationLane({
273
326
  os: argument(args, '--os'),
327
+ verifierSha: argument(args, '--verifier-sha') ?? undefined,
328
+ workflowRunId: argument(args, '--workflow-run-id'),
329
+ candidateRoot: argument(args, '--candidate-root') ?? undefined,
274
330
  candidatePath: argument(args, '--candidate'),
275
331
  identityPath: argument(args, '--identity'),
276
332
  coveragePath: argument(args, '--coverage'),
@@ -7,7 +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 readline from 'node:readline';
10
+ import { createRequire } from 'node:module';
11
11
  import { spawn, spawnSync } from 'node:child_process';
12
12
  // ONE doctor rule and ONE mode vocabulary, shared with the staged-side check in
13
13
  // scripts/staged-host-verifier.mjs. This file kept its own copies, spelled claudeOnly/
@@ -15,12 +15,13 @@ 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 } from './host-install-matrix.mjs';
19
- import { pathToFileURL } from 'node:url';
18
+ import { HOST_MODES, RECEIPT_MODE_NAMES, MODE_FROM_RECEIPT_NAME, classifyDoctor, VARIANTS, createInstalledMcpSession } from './host-install-matrix.mjs';
19
+ import { fileURLToPath, pathToFileURL } from 'node:url';
20
20
  import { evaluateCandidateReceipt, evaluatePublicationReceipt } from './release-proof.mjs';
21
21
  import { verifyPayload } from './release-payload.mjs';
22
- import { digest, sha256File } from './coverage-integrity.mjs';
23
- import { parseCitations } from '../kb/verify-citation.mjs';
22
+ import { resolveInstalledCanaryCitation } from './retrieval-canary.mjs';
23
+ import { runNightlyTwoRunProof, validateNightlyProofReceipt } from './nightly-two-run-proof.mjs';
24
+ import { parseRetrievalResult } from '../kb/retrieval-result.mjs';
24
25
 
25
26
  const REPO = 'stuinfla/ruvnet-brain';
26
27
  const PACKAGE = 'ruvnet-brain';
@@ -30,11 +31,24 @@ 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
  };
36
42
  return { executable: name, args };
37
43
  }
44
+ export function tarExtractionInvocation(artifactPath, destination, { platform = process.platform } = {}) {
45
+ const paths = platform === 'win32' ? path.win32 : path;
46
+ const archive = paths.resolve(artifactPath);
47
+ // A relative archive argument avoids GNU tar's drive-letter remote-host syntax and also
48
+ // works with Windows BSD tar, which does not support GNU's --force-local option.
49
+ return { args: ['-xzf', `./${paths.basename(archive)}`, '-C', paths.resolve(destination)],
50
+ cwd: paths.dirname(archive) };
51
+ }
38
52
  function command(name, args, options = {}) {
39
53
  const { platform = process.platform, ...spawnOptions } = options;
40
54
  const invocation = commandInvocation(name, args, { platform, env: spawnOptions.env || process.env });
@@ -45,13 +59,50 @@ function command(name, args, options = {}) {
45
59
  }
46
60
  return String(result.stdout || '').trim();
47
61
  }
62
+ export function validateCandidateSource(root, { sha, version }) {
63
+ const candidateRoot = fs.realpathSync(root);
64
+ const options = { cwd: candidateRoot };
65
+ const top = command('git', ['rev-parse', '--show-toplevel'], options);
66
+ const head = command('git', ['rev-parse', 'HEAD'], options);
67
+ // Git and Node can spell the same Windows directory with different casing/separators.
68
+ // Compare actual directory identity, never case-fold distinct source paths.
69
+ const candidateDirectory = fs.statSync(candidateRoot, { bigint: true });
70
+ const topDirectory = fs.statSync(top, { bigint: true });
71
+ if (!candidateDirectory.isDirectory() || !topDirectory.isDirectory()
72
+ || candidateDirectory.ino <= 0n || topDirectory.ino <= 0n
73
+ || candidateDirectory.dev !== topDirectory.dev || candidateDirectory.ino !== topDirectory.ino
74
+ || !/^[a-f0-9]{40}$/.test(String(sha)) || head !== sha) {
75
+ throw new Error(`candidate checkout ${head} does not match candidate ${sha}`);
76
+ }
77
+ if (command('git', ['status', '--porcelain', '--untracked-files=no'], options)) {
78
+ throw new Error('candidate checkout has tracked changes');
79
+ }
80
+ const manifest = path.join(candidateRoot, 'package.json');
81
+ const stat = fs.lstatSync(manifest);
82
+ if (!stat.isFile() || stat.isSymbolicLink() || !version || readJson(manifest).version !== version) {
83
+ throw new Error('candidate checkout package version differs from the released candidate');
84
+ }
85
+ return candidateRoot;
86
+ }
48
87
  function locate(name, { platform = process.platform } = {}) {
49
- 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;
50
89
  try {
51
90
  return command(platform === 'win32' ? 'where.exe' : 'which', [query], { platform })
52
91
  .split(/\r?\n/).find(Boolean) || null;
53
92
  } catch { return null; }
54
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
+ }
55
106
  export function createIsolatedPath(mode, temp, { platform = process.platform, env = process.env,
56
107
  resolve = (name) => locate(name, { platform }) } = {}) {
57
108
  const bin = path.join(temp, `bin-${mode}`);
@@ -70,7 +121,10 @@ export function createIsolatedPath(mode, temp, { platform = process.platform, en
70
121
  if (platform !== 'win32') return `${bin}:/usr/bin:/bin`;
71
122
  const node = resolve('node');
72
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`);
73
126
  return [bin, path.dirname(node), path.join(env.SystemRoot || 'C:\\Windows', 'System32'),
127
+ path.dirname(git),
74
128
  path.join(env.ProgramFiles || 'C:\\Program Files', 'Git', 'usr', 'bin')].join(path.win32.delimiter);
75
129
  }
76
130
  export function stageVerifiedBundle({ bundlePath, bundleSha256, packageRoot }) {
@@ -166,18 +220,35 @@ export function assertInstalledPayload(sourceRoot, installedRoot) {
166
220
  return checked;
167
221
  }
168
222
 
169
- export function rpcSearch(server, env, query, k = 5, timeoutMs = DEADLINE_MS) {
223
+ export function rpcSearch(server, env, query, k = 5, timeoutMs = DEADLINE_MS, {
224
+ requiredRepo = 'ruvnet-brain',
225
+ } = {}) {
170
226
  if (!Number.isSafeInteger(k) || k < 1 || k > 50) throw new Error(`invalid search result count: ${k}`);
171
227
  return new Promise((resolve, reject) => {
172
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');
173
232
  let stdout = '';
174
233
  let stderr = '';
175
234
  let timer;
235
+ let killTimer;
236
+ let closeTimer;
237
+ let outcome;
176
238
  const pending = new Map();
177
239
  const finish = (error, value) => {
240
+ if (outcome) return;
241
+ outcome = { error, value };
178
242
  clearTimeout(timer);
243
+ pending.clear();
244
+ // Do not let the next doctor/search probe contend with this process while it exits.
245
+ // A child that ignores graceful shutdown must not leave the verifier hanging either.
246
+ killTimer = setTimeout(() => {
247
+ outcome.error = new Error('installed Brain did not shut down within 5000ms');
248
+ child.kill('SIGKILL');
249
+ closeTimer = setTimeout(() => reject(new Error('installed Brain process did not close after SIGKILL')), 1000);
250
+ }, 5000);
179
251
  child.kill('SIGTERM');
180
- error ? reject(error) : resolve(value);
181
252
  };
182
253
  timer = setTimeout(() => finish(new Error(`installed Brain exceeded ${timeoutMs}ms deadline`)), timeoutMs);
183
254
  child.stderr.on('data', (chunk) => { stderr += chunk; });
@@ -195,8 +266,12 @@ export function rpcSearch(server, env, query, k = 5, timeoutMs = DEADLINE_MS) {
195
266
  }
196
267
  });
197
268
  child.on('error', (error) => finish(error));
198
- child.on('exit', (code) => {
199
- if (pending.size) finish(new Error(`installed Brain exited ${code}: ${stderr.slice(0, 400)}`));
269
+ child.on('close', (code) => {
270
+ clearTimeout(timer);
271
+ clearTimeout(killTimer);
272
+ clearTimeout(closeTimer);
273
+ const result = outcome || { error: new Error(`installed Brain exited ${code}: ${stderr.slice(0, 400)}`) };
274
+ result.error ? reject(result.error) : resolve(result.value);
200
275
  });
201
276
  const call = (id, method, params = {}) => new Promise((done) => {
202
277
  pending.set(id, done);
@@ -212,14 +287,19 @@ export function rpcSearch(server, env, query, k = 5, timeoutMs = DEADLINE_MS) {
212
287
  const broadMs = Math.round(performance.now() - started);
213
288
  const text = (searched.result?.content || []).map((item) => item.text || '').join('\n');
214
289
  if (searched.error || searched.result?.isError || /search_ruvnet error:/i.test(text)) throw new Error(`installed Brain search failed: ${text.slice(0, 400)}`);
215
- if (!/repo=ruvnet-brain/i.test(text) || !/path\s*:/i.test(text)) throw new Error('installed Brain search returned no ruvnet-brain source citation');
216
- finish(null, { broadMs, text });
290
+ if (!/repo=/i.test(text) || !/path\s*:/i.test(text)) throw new Error('installed Brain search returned no source citation');
291
+ const repos = [...text.matchAll(/repo=([\w.-]+)/gi)].map((match) => match[1].toLowerCase());
292
+ if (requiredRepo && !repos.includes(String(requiredRepo).toLowerCase())) {
293
+ throw new Error(`installed Brain search returned no ${requiredRepo} source citation`);
294
+ }
295
+ finish(null, { broadMs, text, mcpResult: searched.result });
217
296
  })().catch((error) => finish(error));
218
297
  });
219
298
  }
220
299
 
221
- export function livePublicationAdapter({ root = process.cwd() } = {}) {
300
+ export function livePublicationAdapter({ root = process.cwd(), candidateRoot = root } = {}) {
222
301
  const installContexts = new Map();
302
+ const mcpSessions = new Map();
223
303
  const passageFileDigests = new Map();
224
304
  let installTemp = null;
225
305
  return {
@@ -246,11 +326,40 @@ export function livePublicationAdapter({ root = process.cwd() } = {}) {
246
326
  return { path: destination, tag: release.tag_name, sha: tagCommit(root, tag) };
247
327
  },
248
328
 
329
+ async runNativeNightly({ identity, workflowRunId }) {
330
+ const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'ruvnet-public-nightly-'));
331
+ let completed = false;
332
+ try {
333
+ const npm = await this.downloadNpm({ version: identity.version,
334
+ destination: path.join(temp, 'package.tgz') });
335
+ const bundle = await this.downloadGithub({ tag: identity.tag, assetName: 'ruvnet-brain.zip',
336
+ destination: path.join(temp, 'ruvnet-brain.zip') });
337
+ const signature = await this.downloadGithub({ tag: identity.tag, assetName: 'ruvnet-brain.zip.sig',
338
+ destination: path.join(temp, 'ruvnet-brain.zip.sig') });
339
+ if (sha256(npm.path) !== identity.packageSha256 || sha256(bundle.path) !== identity.bundleSha256
340
+ || npm.version !== identity.version || bundle.sha !== identity.candidateSha
341
+ || signature.sha !== identity.candidateSha) throw new Error('nightly public artifact identity mismatch');
342
+ const proof = await runNightlyTwoRunProof({ packagePath: npm.path, bundlePath: bundle.path,
343
+ signaturePath: signature.path, sourceSha: identity.candidateSha, workflowRunId,
344
+ out: path.join(temp, 'nightly-proof.json') });
345
+ const validation = validateNightlyProofReceipt(proof, { platform: process.platform, version: identity.version,
346
+ packageSha256: identity.packageSha256, bundleSha256: identity.bundleSha256,
347
+ sourceSha: identity.candidateSha, workflowRunId });
348
+ if (!validation.ok) throw new Error(`public nightly proof failed: ${validation.failures.join('; ')}`);
349
+ completed = true;
350
+ return proof;
351
+ } finally {
352
+ if (completed) fs.rmSync(temp, { recursive: true, force: true });
353
+ else console.error(`Native public artifact diagnostics retained at ${temp}`);
354
+ }
355
+ },
356
+
249
357
  async installHosts({ artifactPath, artifactSha256, bundlePath, bundleSha256, version }) {
250
- const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'ruvnet-public-install-'));
358
+ const temp = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'ruvnet-public-install-')));
251
359
  installTemp = temp;
252
360
  const packageRoot = path.join(temp, 'package');
253
- command('tar', ['-xzf', artifactPath, '-C', temp]);
361
+ const extraction = tarExtractionInvocation(artifactPath, temp);
362
+ command('tar', extraction.args, { cwd: extraction.cwd });
254
363
  stageVerifiedBundle({ bundlePath, bundleSha256, packageRoot });
255
364
  const sealedPlugin = path.join(packageRoot, 'plugin');
256
365
  const results = {};
@@ -260,6 +369,7 @@ export function livePublicationAdapter({ root = process.cwd() } = {}) {
260
369
  // Derived from HOST_MODES, so a fourth host shape is added in ONE place and this loop
261
370
  // cannot fall behind the staged-side check the way it did.
262
371
  for (const mode of HOST_MODES.map((m) => RECEIPT_MODE_NAMES[m])) {
372
+ console.log(`Public verification: installing ${mode} on ${process.platform}`);
263
373
  const home = path.join(temp, `home-${mode}`);
264
374
  const codexHome = path.join(home, '.codex');
265
375
  const brainHome = path.join(home, '.cache', 'ruvnet-brain');
@@ -283,13 +393,20 @@ export function livePublicationAdapter({ root = process.cwd() } = {}) {
283
393
  RUVNET_STRICT_INSTALL: '0',
284
394
  CI: 'true',
285
395
  PATH: createIsolatedPath(mode, temp),
396
+ ...(process.platform === 'win32' && mode !== 'claudeOnly'
397
+ ? { CODEX_BIN: nativeCodexExecutable() } : {}),
286
398
  };
399
+ if (mode !== 'claudeOnly') {
400
+ command(env.CODEX_BIN || 'codex', ['--version'], { env, timeout: 30_000, stdio: 'inherit' });
401
+ command(env.CODEX_BIN || 'codex', ['plugin', 'list', '--json'], { env, timeout: 30_000, stdio: 'inherit' });
402
+ }
403
+ if (process.platform === 'win32') command('git', ['--version'], { env, timeout: 30_000, stdio: 'inherit' });
287
404
  const installer = path.join(packageRoot, 'bin', 'install.mjs');
288
405
  command(process.execPath, [
289
406
  installer, '--yes', '--force', '--version', `v${version}`,
290
407
  '--no-nightly-prompt', '--no-telemetry', '--no-stack', '--no-enhance', '--no-statusline',
291
408
  '--no-selfcheck', '--no-verify',
292
- ], { env, cwd: packageRoot, timeout: 1_200_000, maxBuffer: 32 * 1024 * 1024 });
409
+ ], { env, cwd: packageRoot, timeout: 1_200_000, stdio: 'inherit' });
293
410
 
294
411
  const verified = {};
295
412
  if (mode !== 'codexOnly') {
@@ -310,7 +427,7 @@ export function livePublicationAdapter({ root = process.cwd() } = {}) {
310
427
  const searched = await rpcSearch(findMcpServer(home), env,
311
428
  'How does RuvNet Brain prove a public release artifact?', 5, DEADLINE_MS);
312
429
  command(process.execPath, [installer, '--doctor', '--hooks'], {
313
- env, cwd: packageRoot, timeout: 300_000, maxBuffer: 32 * 1024 * 1024,
430
+ env, cwd: packageRoot, timeout: 300_000, stdio: 'inherit',
314
431
  });
315
432
  results[mode] = {
316
433
  status: 'PASS', doctorExit: 0, version, artifactSha256,
@@ -342,46 +459,32 @@ export function livePublicationAdapter({ root = process.cwd() } = {}) {
342
459
  async searchInstalled({ mode, query, k }) {
343
460
  const context = installContexts.get(mode);
344
461
  if (!context) throw new Error(`${mode} public host is not installed`);
345
- const result = await rpcSearch(findMcpServer(context.home), context.env, query, k, DEADLINE_MS);
346
- return parseCitations(result.text).map((citation) => ({
347
- repo: citation.repo.toLowerCase(),
348
- path: citation.docPath,
349
- }));
462
+ // Keep one MCP worker per installed host. Starting a fresh worker for every canary reloads
463
+ // the local embedding model repeatedly and can exceed the fixed 30-second public deadline
464
+ // on macOS, even when the installed Brain itself is healthy.
465
+ let session = mcpSessions.get(mode);
466
+ if (!session) {
467
+ session = createInstalledMcpSession({ serverPath: findMcpServer(context.home), env: context.env, timeout: DEADLINE_MS });
468
+ mcpSessions.set(mode, session);
469
+ }
470
+ const result = await session.search({ query, k });
471
+ return parseRetrievalResult(result.mcpResult, { query, k });
350
472
  },
351
473
 
352
474
  async resolveInstalledCitation({ mode, matched, expected }) {
353
475
  const context = installContexts.get(mode);
354
476
  if (!context) throw new Error(`${mode} public host is not installed`);
355
- if (String(matched?.repo || '').toLowerCase() !== expected.repo || matched?.path !== expected.path) {
356
- return { resolved: false };
357
- }
358
- const files = [
359
- path.join(context.kb, `${expected.repo}.passages.jsonl`),
360
- path.join(context.kb, `${expected.repo}.big.passages.jsonl`),
361
- ].filter((file) => fs.existsSync(file));
362
- for (const file of files) {
363
- const rows = readline.createInterface({ input: fs.createReadStream(file), crlfDelay: Infinity });
364
- try {
365
- for await (const line of rows) {
366
- let record;
367
- try { record = JSON.parse(line); } catch { continue; }
368
- if (record?.path !== expected.path) continue;
369
- const passageSha256 = digest(record);
370
- if (passageSha256 !== expected.passageSha256) return { resolved: false };
371
- if (!passageFileDigests.has(file)) passageFileDigests.set(file, sha256File(file));
372
- return { resolved: true, evidence: { passageSha256, passageFileSha256: passageFileDigests.get(file) } };
373
- }
374
- } finally { rows.close(); }
375
- }
376
- return { resolved: false };
477
+ return resolveInstalledCanaryCitation({ kbDir: context.kb, matched, expected, passageFileDigests });
377
478
  },
378
479
 
379
- async probePublishedSurface({ sha }) {
380
- const head = command('git', ['rev-parse', 'HEAD'], { cwd: root });
381
- if (head !== sha) throw new Error(`published-surface probe checkout ${head} does not match candidate ${sha}`);
382
- const result = spawnSync(process.execPath, ['scripts/published-surface-probe.mjs', '--json'], {
383
- cwd: root, env: process.env, encoding: 'utf8', timeout: 600_000,
480
+ async probePublishedSurface({ sha, version }) {
481
+ const source = validateCandidateSource(candidateRoot, { sha, version });
482
+ // The verifier supplies executable checks; the candidate supplies only the released source.
483
+ const script = fileURLToPath(new URL('./published-surface-probe.mjs', import.meta.url));
484
+ const result = spawnSync(process.execPath, [script, '--json'], {
485
+ cwd: source, env: process.env, encoding: 'utf8', timeout: 600_000,
384
486
  });
487
+ validateCandidateSource(candidateRoot, { sha, version });
385
488
  let parsed;
386
489
  try { parsed = JSON.parse(String(result.stdout || '')); } catch { throw new Error(`published-surface-probe emitted invalid JSON: ${String(result.stderr || '').slice(0, 300)}`); }
387
490
  if (result.status !== 0 || parsed.verdict !== 'PASS') throw new Error(`published-surface-probe is ${parsed.verdict || `exit ${result.status}`}`);
@@ -389,6 +492,8 @@ export function livePublicationAdapter({ root = process.cwd() } = {}) {
389
492
  },
390
493
 
391
494
  async dispose() {
495
+ await Promise.all([...mcpSessions.values()].map((session) => session.close()));
496
+ mcpSessions.clear();
392
497
  if (installTemp) fs.rmSync(installTemp, { recursive: true, force: true });
393
498
  installTemp = null;
394
499
  installContexts.clear();
@@ -449,6 +554,7 @@ export async function generatePublicationReceipt({
449
554
  const publication = {
450
555
  schemaVersion: 2,
451
556
  phase: 'publication',
557
+ acceptancePolicy: 'stabilization-public-deadline-v1',
452
558
  sha: candidate.sha,
453
559
  artifactSha256: digest,
454
560
  version: candidate.version,
@@ -468,7 +574,7 @@ export async function generatePublicationReceipt({
468
574
  postPublicationChecks: [surface],
469
575
  };
470
576
  const result = evaluatePublicationReceipt(candidate, publication);
471
- if (result.verdict !== 'PASS') throw new Error(`publication seal failed: ${result.failures.map(({ code }) => code).join(',')}`);
577
+ if (result.verdict !== 'PASS') throw new Error(`publication seal failed: ${JSON.stringify({ failures: result.failures, installed: publication.installed, brain: publication.brain })}`);
472
578
  fs.mkdirSync(path.dirname(outPath), { recursive: true });
473
579
  fs.writeFileSync(outPath, `${JSON.stringify(publication, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
474
580
  return result;
@@ -52,6 +52,7 @@ import fs from 'node:fs';
52
52
  import os from 'node:os';
53
53
  import path from 'node:path';
54
54
  import { spawnSync } from 'node:child_process';
55
+ import { fileURLToPath } from 'node:url';
55
56
 
56
57
  export const EXIT = Object.freeze({ PASS: 0, FAIL: 1, UNKNOWN: 4 });
57
58
 
@@ -127,6 +128,12 @@ async function probeRegistry() {
127
128
  }
128
129
 
129
130
  // ── B. EXECUTE the published package, from the registry ────────────────────────────────────────
131
+ export function npxInvocation(args, { platform = process.platform, env = process.env } = {}) {
132
+ if (platform === 'win32') return {
133
+ executable: env.ComSpec || 'cmd.exe', args: ['/d', '/c', 'npx.cmd', ...args],
134
+ };
135
+ return { executable: 'npx', args };
136
+ }
130
137
  function probeExec() {
131
138
  const home = fs.mkdtempSync(path.join(os.tmpdir(), 'ruvnet-brain-probe-'));
132
139
  try {
@@ -134,7 +141,8 @@ function probeExec() {
134
141
  // (npm resolve → download → unpack → bin mapping → node parses every module it imports) while
135
142
  // touching NOTHING on the machine. `--doctor`/`--plan` reach the network and the brain dir; this
136
143
  // probe must never be the reason a surface changes.
137
- const r = spawnSync('npx', ['-y', `${PKG}@latest`, '--help'], {
144
+ const invocation = npxInvocation(['-y', `${PKG}@latest`, '--help']);
145
+ const r = spawnSync(invocation.executable, invocation.args, {
138
146
  encoding: 'utf8',
139
147
  timeout: 300_000,
140
148
  cwd: home,
@@ -237,5 +245,5 @@ export async function main() {
237
245
  return EXIT[verdict];
238
246
  }
239
247
 
240
- const invokedDirectly = process.argv[1] && path.resolve(process.argv[1]) === path.resolve(new URL(import.meta.url).pathname);
248
+ const invokedDirectly = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
241
249
  if (invokedDirectly) process.exit(await main());
@@ -0,0 +1,57 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { createHash } from 'node:crypto';
4
+ import { execFileSync } from 'node:child_process';
5
+
6
+ export function verdictOf(results) {
7
+ if (results.some(({ status }) => ['FAIL', 'TIMEOUT', 'BLOCKED'].includes(status))) return 'FAIL';
8
+ return results.length && results.every(({ status }) => status === 'PASS') ? 'PASS' : 'UNKNOWN';
9
+ }
10
+
11
+ // A source digest includes dirty tracked files and untracked source, never ignored build outputs.
12
+ export function sourceIdentity(root) {
13
+ const git = (args) => execFileSync('git', args, { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
14
+ let sha = null;
15
+ try { sha = git(['rev-parse', 'HEAD']).trim(); } catch { /* unborn repository */ }
16
+ const files = [...new Set(git(['ls-files', '-z', '--cached', '--others', '--exclude-standard']).split('\0').filter(Boolean))].sort();
17
+ const hash = createHash('sha256');
18
+ for (const relative of files) {
19
+ const file = path.join(root, relative);
20
+ let bytes = Buffer.from('deleted');
21
+ try {
22
+ const stat = fs.lstatSync(file);
23
+ bytes = stat.isSymbolicLink() ? Buffer.from(`symlink:${fs.readlinkSync(file)}`)
24
+ : stat.isFile() ? Buffer.concat([Buffer.from(`${stat.mode & 0o777}:`), fs.readFileSync(file)]) : Buffer.from('directory');
25
+ } catch (error) { if (error.code !== 'ENOENT') throw error; }
26
+ hash.update(JSON.stringify([relative, createHash('sha256').update(bytes).digest('hex')]));
27
+ }
28
+ return { sha, dirty: Boolean(git(['status', '--porcelain']).trim()), digest: hash.digest('hex'), files: files.length, recipe: 'git-source-bytes-v1' };
29
+ }
30
+
31
+ export async function runLanes(lanes, execute, concurrency = 2) {
32
+ if (!Number.isInteger(concurrency) || concurrency < 1) throw new Error('invalid QA concurrency');
33
+ const names = new Set(lanes.map(({ name }) => name));
34
+ if (names.size !== lanes.length || lanes.some((lane) => (lane.dependsOn || []).some((name) => !names.has(name)))) throw new Error('invalid QA dependency');
35
+ const pending = [...lanes], completed = new Map();
36
+ while (pending.length) {
37
+ const resources = new Set(), batch = [];
38
+ for (const lane of pending) {
39
+ if (!(lane.dependsOn || []).every((name) => completed.has(name))) continue;
40
+ if ((lane.dependsOn || []).some((name) => completed.get(name).status !== 'PASS')) {
41
+ completed.set(lane.name, { name: lane.name, status: 'BLOCKED', reason: 'required prerequisite did not pass' });
42
+ continue;
43
+ }
44
+ if (batch.length >= concurrency || (lane.resource && resources.has(lane.resource))) continue;
45
+ batch.push(lane); resources.add(lane.resource);
46
+ }
47
+ if (!batch.length && !pending.some(({ name }) => completed.has(name))) throw new Error('QA dependency cycle');
48
+ await Promise.all(batch.map(async (lane) => {
49
+ let result;
50
+ try { result = await execute(lane); }
51
+ catch (error) { result = { name: lane.name, status: 'FAIL', reason: error.message }; }
52
+ completed.set(lane.name, result);
53
+ }));
54
+ for (let index = pending.length - 1; index >= 0; index--) if (completed.has(pending[index].name)) pending.splice(index, 1);
55
+ }
56
+ return lanes.map(({ name }) => completed.get(name));
57
+ }