ruvnet-brain 4.0.1 → 4.0.4

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 (195) hide show
  1. package/.claude-plugin/marketplace.json +1 -0
  2. package/README.md +4 -4
  3. package/bin/install.mjs +303 -24
  4. package/console/CONTRACT.md +172 -0
  5. package/console/activity.js +753 -0
  6. package/console/app.js +4189 -0
  7. package/console/architecture.html +1221 -0
  8. package/console/assets/depth-1.webp +0 -0
  9. package/console/assets/depth-2.webp +0 -0
  10. package/console/assets/depth-3.webp +0 -0
  11. package/console/assets/harness-vs-plain.svg +259 -0
  12. package/console/assets/hero.webp +0 -0
  13. package/console/assets/memory.webp +0 -0
  14. package/console/assets/metaharness.svg +247 -0
  15. package/console/index.html +777 -0
  16. package/console/install-architecture.html +162 -0
  17. package/console/install-mockup.html +543 -0
  18. package/console/style.css +2144 -0
  19. package/console/tips.css +926 -0
  20. package/console/tips.html +858 -0
  21. package/console/tips.js +128 -0
  22. package/docs/RELEASE-NOTES-4.0.md +88 -0
  23. package/kb/model-requirements.mjs +37 -6
  24. package/keys/ruvnet-brain-signing.pub.pem +3 -0
  25. package/package.json +8 -22
  26. package/plugin/.claude-plugin/marketplace.json +1 -0
  27. package/plugin/.claude-plugin/plugin.json +2 -3
  28. package/plugin/.codex-plugin/plugin.json +1 -1
  29. package/plugin/commands/brain-console.md +2 -2
  30. package/plugin/commands/configure.md +3 -2
  31. package/plugin/commands/rvbc.md +4 -3
  32. package/plugin/commands/rvcb.md +2 -2
  33. package/plugin/commands/whats-new.md +6 -6
  34. package/plugin/docs/RELEASE-NOTES-4.0.md +88 -0
  35. package/plugin/hooks/hooks.json +1 -2
  36. package/plugin/mcp/managed-cli-interface.mjs +47 -4
  37. package/plugin/mcp/server.mjs +90 -32
  38. package/plugin/scripts/detach.mjs +14 -0
  39. package/plugin/scripts/first-session-worker.mjs +38 -0
  40. package/plugin/scripts/ground-ruvnet.sh +16 -6
  41. package/plugin/scripts/hook-shim.mjs +34 -29
  42. package/plugin/scripts/learn-capture.sh +22 -3
  43. package/plugin/scripts/learn-flush.mjs +21 -4
  44. package/plugin/scripts/runtime-preferences.mjs +269 -0
  45. package/plugin/scripts/session-start-core.mjs +503 -0
  46. package/plugin/scripts/session-start.sh +3 -858
  47. package/plugin/scripts/whats-new.mjs +42 -0
  48. package/plugin/skills/brain-console/SKILL.md +4 -2
  49. package/plugin/skills/release-proof/SKILL.md +98 -0
  50. package/plugin/skills/release-proof/agents/openai.yaml +4 -0
  51. package/plugin/skills/release-proof/references/receipt-contract.md +44 -0
  52. package/plugin/skills/release-proof/scripts/release-proof.mjs +286 -0
  53. package/plugin/skills/ruvnet-brain/PLAYBOOK.md +5 -1
  54. package/plugin/skills/ruvnet-brain/SKILL.md +22 -7
  55. package/plugin/skills/rvbc/SKILL.md +9 -6
  56. package/plugin/skills/whats-new/SKILL.md +4 -4
  57. package/scripts/adr-backfill.mjs +107 -0
  58. package/scripts/advocacy-outcomes.mjs +808 -0
  59. package/scripts/agentdb-context.mjs +216 -0
  60. package/scripts/agentdb-fleet-doctor.mjs +101 -0
  61. package/scripts/ascii-drift.mjs +236 -0
  62. package/scripts/behavioral-l1-l4.mjs +210 -0
  63. package/scripts/brain-capability-check.mjs +72 -0
  64. package/scripts/brain-grade-groundtruth.mjs +100 -0
  65. package/scripts/brain-latency-50.mjs +227 -0
  66. package/scripts/brain-novice-50.mjs +189 -0
  67. package/scripts/brain-stamp.mjs +94 -0
  68. package/scripts/brain-state.mjs +212 -0
  69. package/scripts/build-bundle.mjs +531 -0
  70. package/scripts/build-concepts.mjs +132 -0
  71. package/scripts/build-l2.mjs +71 -0
  72. package/scripts/build-primer.mjs +73 -0
  73. package/scripts/build-symbols.mjs +68 -0
  74. package/scripts/calibrate-router.mjs +97 -0
  75. package/scripts/capability-audit.mjs +321 -0
  76. package/scripts/capability-registry.mjs +876 -0
  77. package/scripts/check-indexation.mjs +108 -0
  78. package/scripts/check-legibility.mjs +189 -0
  79. package/scripts/ci/build-fixture-kb.mjs +67 -0
  80. package/scripts/ci/learning-replay-codex-adapter.mjs +62 -0
  81. package/scripts/ci/learning-replay-recorder.mjs +59 -0
  82. package/scripts/ci/mutate-hook-timeout.mjs +70 -0
  83. package/scripts/ci/stranger-fixture-stage.mjs +17 -0
  84. package/scripts/ci/stranger-scenario.mjs +228 -0
  85. package/scripts/ci/stranger-timeout.mjs +25 -0
  86. package/scripts/ci-verdict.mjs +29 -0
  87. package/scripts/claims-verify.mjs +710 -0
  88. package/scripts/clear-claude-tmp.sh +31 -0
  89. package/scripts/console-engine.mjs +434 -0
  90. package/scripts/console-engine.test.mjs +125 -0
  91. package/scripts/corpus-qa.mjs +250 -0
  92. package/scripts/correction-detect-embed.mjs +346 -0
  93. package/scripts/correction-detect-measure.mjs +270 -0
  94. package/scripts/correction-detect.mjs +686 -0
  95. package/scripts/count-chunks.mjs +54 -0
  96. package/scripts/described-questions.json +30 -0
  97. package/scripts/design-grade.mjs +58 -0
  98. package/scripts/dev-plugin-link.sh +105 -0
  99. package/scripts/distill-project.mjs +200 -0
  100. package/scripts/doc-currency.mjs +801 -0
  101. package/scripts/eval-brain.mjs +244 -0
  102. package/scripts/fix-metaharness-memretrieve.mjs +121 -0
  103. package/scripts/fix-workstream.mjs +291 -0
  104. package/scripts/full-hints.mjs +87 -0
  105. package/scripts/gate.sh +39 -0
  106. package/scripts/gates.mjs +146 -0
  107. package/scripts/gen-console-images.mjs +54 -0
  108. package/scripts/gen-images.mjs +47 -0
  109. package/scripts/git-clone-refresh.mjs +52 -0
  110. package/scripts/git-hooks/pre-push +126 -0
  111. package/scripts/goal-match.mjs +398 -0
  112. package/scripts/goldie-research.mjs +223 -0
  113. package/scripts/goldie-weekly.sh +67 -0
  114. package/scripts/health-repair.mjs +237 -0
  115. package/scripts/helix-scenario-questions.json +10 -0
  116. package/scripts/ingest-gists.mjs +230 -0
  117. package/scripts/ingest-meeting.mjs +115 -0
  118. package/scripts/ingest-repo.mjs +79 -0
  119. package/scripts/install-npx-witness.sh +49 -0
  120. package/scripts/issue-fix.mjs +558 -0
  121. package/scripts/issue-watch.mjs +276 -0
  122. package/scripts/issue4-close-note.md +31 -0
  123. package/scripts/key-canary.mjs +91 -0
  124. package/scripts/latency-to-surface.mjs +233 -0
  125. package/scripts/learning-enable.mjs +380 -0
  126. package/scripts/learning-replay.mjs +1570 -0
  127. package/scripts/learnings.mjs +62 -0
  128. package/scripts/lesson-gate.mjs +680 -0
  129. package/scripts/lesson-lifecycle.mjs +449 -0
  130. package/scripts/lesson-promote.mjs +262 -0
  131. package/scripts/lesson-ratify.mjs +98 -0
  132. package/scripts/lesson-seed.mjs +252 -0
  133. package/scripts/lesson-store.mjs +447 -0
  134. package/scripts/loop-checkpoint.mjs +86 -0
  135. package/scripts/memdb-health.sh +14 -0
  136. package/scripts/memory-doctor.mjs +326 -0
  137. package/scripts/model-catalog.mjs +79 -0
  138. package/scripts/nightly-controller.mjs +66 -0
  139. package/scripts/nightly-gists.sh +72 -0
  140. package/scripts/nightly-wrapper.sh +172 -0
  141. package/scripts/notify.sh +12 -0
  142. package/scripts/npx-witness.sh +56 -0
  143. package/scripts/onboarding-console.mjs +2922 -0
  144. package/scripts/private-fence.mjs +69 -0
  145. package/scripts/proactivity-metrics.mjs +118 -0
  146. package/scripts/proof-questions.json +56 -0
  147. package/scripts/protected-release-invocation.mjs +76 -0
  148. package/scripts/prove.mjs +95 -0
  149. package/scripts/proxy/claude-proxied.sh +57 -0
  150. package/scripts/proxy/proxy-revert.sh +59 -0
  151. package/scripts/proxy/proxy-up.sh +60 -0
  152. package/scripts/proxy/proxy-verify.mjs +142 -0
  153. package/scripts/publication-receipt.mjs +307 -0
  154. package/scripts/published-surface-probe.mjs +241 -0
  155. package/scripts/qe/card-lane-gate.mjs +162 -0
  156. package/scripts/qe/session-start-gate.mjs +229 -0
  157. package/scripts/qe/ux-suite.mjs +323 -0
  158. package/scripts/reconcile-project.mjs +0 -0
  159. package/scripts/record-lesson.mjs +113 -0
  160. package/scripts/refresh-model-catalog.mjs +99 -0
  161. package/scripts/release-authority.mjs +93 -0
  162. package/scripts/release-proof.mjs +9 -0
  163. package/scripts/release-vector.mjs +281 -0
  164. package/scripts/release.mjs +439 -0
  165. package/scripts/remedy-registry.mjs +247 -0
  166. package/scripts/rerank-cap-eval.mjs +265 -0
  167. package/scripts/rerank-cap-warm-ab.mjs +129 -0
  168. package/scripts/route-cheap.mjs +20 -15
  169. package/scripts/router-utilization.mjs +182 -0
  170. package/scripts/routing-flywheel.mjs +596 -0
  171. package/scripts/rvf-generation.mjs +104 -0
  172. package/scripts/rvf-index-audit.mjs +138 -0
  173. package/scripts/self-update.mjs +296 -0
  174. package/scripts/selfcheck.mjs +7 -1
  175. package/scripts/sign-bundle.mjs +69 -0
  176. package/scripts/signal-watch.mjs +171 -0
  177. package/scripts/stabilization-receipt.mjs +108 -0
  178. package/scripts/stack-sync.mjs +469 -0
  179. package/scripts/stamp-existing-rvf-generations.mjs +53 -0
  180. package/scripts/stamp-sweep.mjs +144 -0
  181. package/scripts/status-honesty.mjs +102 -0
  182. package/scripts/sync-version.mjs +217 -0
  183. package/scripts/token-report.mjs +102 -0
  184. package/scripts/top100-benchmark.mjs +479 -0
  185. package/scripts/top100-corpus.mjs +112 -0
  186. package/scripts/top100-semantic-assertions.mjs +449 -0
  187. package/scripts/update-apply.mjs +9 -0
  188. package/scripts/upgrade-notice.mjs +14 -0
  189. package/scripts/verify-bundle.mjs +51 -0
  190. package/scripts/verify-channels.mjs +184 -0
  191. package/scripts/verify-model-catalog.mjs +104 -0
  192. package/scripts/verify-nightly-close-issue4.sh +31 -0
  193. package/scripts/version.mjs +40 -0
  194. package/scripts/wired-check.mjs +867 -0
  195. package/plugin/scripts/finalize-token-meter.mjs +0 -25
@@ -0,0 +1,142 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * proxy-verify.mjs — trial-specific verification for the Meta LLM Proxy.
4
+ *
5
+ * DELIBERATELY THIN. rUv already ships the health check:
6
+ *
7
+ * ruflo doctor --component proxy
8
+ *
9
+ * which covers binary signature, version, process liveness (via the proxy's
10
+ * own /status), bind address, and sponsored-consent state (ADR-307/313).
11
+ * This script SHELLS OUT to that rather than reimplementing it — an earlier
12
+ * draft of this file hand-rolled all of it before the grounding gate caught
13
+ * that the real tool existed.
14
+ *
15
+ * It adds exactly two things rUv's doctor does not do, both specific to this
16
+ * trial rather than to the proxy in general:
17
+ *
18
+ * 1. A real end-to-end completion through /v1/messages, checked against the
19
+ * PASSTHROUGH ORACLE. Per ADR-313's addendum, a genuine Anthropic
20
+ * response carries `service_tier` and `cache_creation` in usage; Cognitum's
21
+ * gateway never returns them. Their presence proves the request reached
22
+ * real Anthropic on the user's own subscription rather than being quietly
23
+ * served by a cheap-tier substitute. The doctor confirms the process is
24
+ * healthy; only this proves the round-trip is real.
25
+ *
26
+ * 2. That the trial has NOT been wired globally. This trial is opt-in per
27
+ * session by design; an ANTHROPIC_BASE_URL in ~/.claude/settings.json
28
+ * would put the proxy in the hot path of every Claude Code window on the
29
+ * machine, which is exactly what this trial promised not to do.
30
+ *
31
+ * Exit 0 only if every check passes.
32
+ */
33
+ import { execFileSync } from 'node:child_process';
34
+ import fs from 'node:fs';
35
+ import os from 'node:os';
36
+ import path from 'node:path';
37
+
38
+ const BASE = 'http://127.0.0.1:11435';
39
+ const TOKEN_PATH = path.join(os.homedir(), '.ruflo', 'proxy-token');
40
+ const PROBE_MODEL = process.env.PROXY_VERIFY_MODEL || 'claude-haiku-4-5-20251001';
41
+
42
+ const checks = [];
43
+ const record = (name, ok, detail) => {
44
+ checks.push({ name, ok });
45
+ console.log(` ${ok ? 'PASS' : 'FAIL'} ${name}${detail ? ` — ${detail}` : ''}`);
46
+ };
47
+
48
+ console.log('Meta LLM Proxy — trial verification\n');
49
+
50
+ // ---------------------------------------------------------------------------
51
+ // 1. rUv's shipped health check. We do not reimplement any of it.
52
+ // ---------------------------------------------------------------------------
53
+ console.log('[1] ruflo doctor --component proxy (rUv\'s own check)');
54
+ let doctorOut = '';
55
+ let doctorOk = false;
56
+ try {
57
+ doctorOut = execFileSync('ruflo', ['doctor', '--component', 'proxy'], {
58
+ encoding: 'utf8',
59
+ timeout: 60_000,
60
+ });
61
+ doctorOk = /Summary:.*passed/.test(doctorOut) && !/\b\d+ failed/.test(doctorOut);
62
+ } catch (err) {
63
+ doctorOut = err.stdout || String(err);
64
+ }
65
+ for (const line of doctorOut.split('\n')) {
66
+ if (/^[✓✗⚠]/.test(line.trim())) console.log(` ${line.trim()}`);
67
+ }
68
+ record('ruflo proxy doctor reports healthy', doctorOk);
69
+
70
+ // The doctor prints the resolved plane; surface it as its own assertion so a
71
+ // silent flip away from passthrough can never pass unnoticed.
72
+ record('data plane is passthrough', /data plane:\s*passthrough/i.test(doctorOut));
73
+
74
+ // ---------------------------------------------------------------------------
75
+ // 2. The round-trip the doctor does not perform.
76
+ // ---------------------------------------------------------------------------
77
+ console.log('\n[2] live round-trip + passthrough oracle');
78
+ let token = '';
79
+ try {
80
+ token = fs.readFileSync(TOKEN_PATH, 'utf8').trim();
81
+ } catch {
82
+ /* handled below */
83
+ }
84
+
85
+ if (!token) {
86
+ record('live round-trip returns correct content', false, 'no proxy token');
87
+ record('response proves REAL Anthropic', false, 'skipped');
88
+ } else {
89
+ try {
90
+ const res = await fetch(`${BASE}/v1/messages`, {
91
+ method: 'POST',
92
+ headers: {
93
+ authorization: `Bearer ${token}`,
94
+ 'content-type': 'application/json',
95
+ 'anthropic-version': '2023-06-01',
96
+ },
97
+ body: JSON.stringify({
98
+ model: PROBE_MODEL,
99
+ max_tokens: 16,
100
+ messages: [{ role: 'user', content: 'Reply with exactly: PROXY_OK' }],
101
+ }),
102
+ signal: AbortSignal.timeout(60_000),
103
+ });
104
+ const body = await res.json();
105
+ const text = body?.content?.[0]?.text ?? '';
106
+ record('live round-trip returns correct content', res.ok && text.includes('PROXY_OK'), `HTTP ${res.status}`);
107
+
108
+ const usage = body?.usage ?? {};
109
+ const oracleOk = 'service_tier' in usage && 'cache_creation' in usage;
110
+ record(
111
+ 'response proves REAL Anthropic (service_tier + cache_creation)',
112
+ oracleOk,
113
+ oracleOk ? `service_tier=${usage.service_tier}` : 'oracle fields ABSENT — did not reach Anthropic',
114
+ );
115
+ } catch (err) {
116
+ record('live round-trip returns correct content', false, String(err?.message || err));
117
+ record('response proves REAL Anthropic', false, 'no response');
118
+ }
119
+ }
120
+
121
+ // ---------------------------------------------------------------------------
122
+ // 3. Trial safety: opt-in per session, never global.
123
+ // ---------------------------------------------------------------------------
124
+ console.log('\n[3] trial safety');
125
+ let noGlobalWiring = true;
126
+ const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
127
+ try {
128
+ if (fs.existsSync(settingsPath)) {
129
+ noGlobalWiring = !fs.readFileSync(settingsPath, 'utf8').includes('ANTHROPIC_BASE_URL');
130
+ }
131
+ } catch {
132
+ /* absent file is fine */
133
+ }
134
+ record('NOT wired globally (~/.claude/settings.json clean)', noGlobalWiring);
135
+
136
+ const failed = checks.filter((c) => !c.ok);
137
+ console.log(`\n${checks.length - failed.length}/${checks.length} checks passed`);
138
+ if (failed.length) {
139
+ console.log(`FAILED: ${failed.map((f) => f.name).join(', ')}`);
140
+ process.exit(1);
141
+ }
142
+ console.log('Verified: proxy healthy, passthrough, real Anthropic round-trip, not wired globally.');
@@ -0,0 +1,307 @@
1
+ #!/usr/bin/env node
2
+ // Post-publication evidence producer. It only observes public bytes and isolated installs, then
3
+ // writes one append-only receipt. It has no publish, tag, release, push, or deployment capability.
4
+ import crypto from 'node:crypto';
5
+ import fs from 'node:fs';
6
+ import os from 'node:os';
7
+ import path from 'node:path';
8
+ import { pipeline } from 'node:stream/promises';
9
+ import { Readable } from 'node:stream';
10
+ import { spawn, spawnSync } from 'node:child_process';
11
+ import { pathToFileURL } from 'node:url';
12
+ import { evaluateCandidateReceipt, evaluatePublicationReceipt } from './release-proof.mjs';
13
+
14
+ const REPO = 'stuinfla/ruvnet-brain';
15
+ const PACKAGE = 'ruvnet-brain';
16
+ const DEADLINE_MS = 30_000;
17
+
18
+ const sha256 = (file) => crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
19
+ const readJson = (file) => JSON.parse(fs.readFileSync(file, 'utf8'));
20
+ const receiptDigest = (candidate) => String(candidate?.artifact?.sha256 || '').replace(/^sha256:/, '');
21
+
22
+ function command(name, args, options = {}) {
23
+ const result = spawnSync(name, args, { encoding: 'utf8', ...options });
24
+ if (result.error || result.status !== 0) {
25
+ const detail = String(result.stderr || result.stdout || result.error?.message || '').trim();
26
+ throw new Error(`${name} ${args.join(' ')} failed: ${detail || `exit ${result.status}`}`);
27
+ }
28
+ return String(result.stdout || '').trim();
29
+ }
30
+
31
+ async function download(url, destination, headers = {}) {
32
+ const response = await fetch(url, {
33
+ redirect: 'follow',
34
+ headers: { 'user-agent': 'ruvnet-brain-publication-receipt', ...headers },
35
+ signal: AbortSignal.timeout(300_000),
36
+ });
37
+ if (!response.ok || !response.body) throw new Error(`download failed: ${url} -> HTTP ${response.status}`);
38
+ await pipeline(Readable.fromWeb(response.body), fs.createWriteStream(destination, { flags: 'wx', mode: 0o600 }));
39
+ return destination;
40
+ }
41
+
42
+ function tagCommit(root, tag) {
43
+ const rows = command('git', ['ls-remote', 'origin', `refs/tags/${tag}`, `refs/tags/${tag}^{}`], { cwd: root })
44
+ .split('\n').filter(Boolean).map((line) => line.trim().split(/\s+/));
45
+ const sha = rows.find(([, ref]) => ref?.endsWith('^{}'))?.[0] || rows[0]?.[0] || '';
46
+ if (!/^[a-f0-9]{40}$/i.test(sha)) throw new Error(`GitHub tag ${tag} has no full commit SHA`);
47
+ return sha;
48
+ }
49
+
50
+ function findManifest(root, relative, version) {
51
+ const found = [];
52
+ const visit = (directory, depth = 0) => {
53
+ if (depth > 9 || !fs.existsSync(directory)) return;
54
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
55
+ const child = path.join(directory, entry.name);
56
+ if (entry.isSymbolicLink()) continue;
57
+ if (entry.isDirectory()) visit(child, depth + 1);
58
+ else if (entry.isFile() && child.endsWith(relative)) found.push(child);
59
+ }
60
+ };
61
+ visit(root);
62
+ const match = found.find((file) => {
63
+ try { return readJson(file).version === version; } catch { return false; }
64
+ });
65
+ if (!match) throw new Error(`installed ${relative} at version ${version} not found in virgin home`);
66
+ return match;
67
+ }
68
+
69
+ export function assertInstalledPayload(sourceRoot, installedRoot) {
70
+ let checked = 0;
71
+ const visit = (source, relative = '') => {
72
+ const stat = fs.lstatSync(source);
73
+ if (stat.isSymbolicLink()) throw new Error(`sealed plugin payload contains symlink: ${relative || '.'}`);
74
+ if (stat.isDirectory()) {
75
+ for (const name of fs.readdirSync(source)) visit(path.join(source, name), path.join(relative, name));
76
+ return;
77
+ }
78
+ if (!stat.isFile()) throw new Error(`sealed plugin payload contains unsupported entry: ${relative}`);
79
+ const installed = path.join(installedRoot, relative);
80
+ let installedStat;
81
+ try { installedStat = fs.lstatSync(installed); } catch { throw new Error(`installed plugin payload is missing ${relative}`); }
82
+ if (!installedStat.isFile() || installedStat.isSymbolicLink()) {
83
+ throw new Error(`installed plugin payload is not a regular file: ${relative}`);
84
+ }
85
+ if (sha256(source) !== sha256(installed)) throw new Error(`installed plugin payload byte mismatch: ${relative}`);
86
+ checked += 1;
87
+ };
88
+ visit(sourceRoot);
89
+ if (checked === 0) throw new Error('sealed plugin payload is empty');
90
+ return checked;
91
+ }
92
+
93
+ function rpcSearch(server, env, query, timeoutMs = DEADLINE_MS) {
94
+ return new Promise((resolve, reject) => {
95
+ const child = spawn(process.execPath, [server], { env, stdio: ['pipe', 'pipe', 'pipe'] });
96
+ let stdout = '';
97
+ let stderr = '';
98
+ let timer;
99
+ const pending = new Map();
100
+ const finish = (error, value) => {
101
+ clearTimeout(timer);
102
+ child.kill('SIGTERM');
103
+ error ? reject(error) : resolve(value);
104
+ };
105
+ timer = setTimeout(() => finish(new Error(`installed Brain exceeded ${timeoutMs}ms deadline`)), timeoutMs);
106
+ child.stderr.on('data', (chunk) => { stderr += chunk; });
107
+ child.stdout.on('data', (chunk) => {
108
+ stdout += chunk;
109
+ let newline;
110
+ while ((newline = stdout.indexOf('\n')) >= 0) {
111
+ const line = stdout.slice(0, newline).trim();
112
+ stdout = stdout.slice(newline + 1);
113
+ if (!line) continue;
114
+ let message;
115
+ try { message = JSON.parse(line); } catch { continue; }
116
+ const handler = pending.get(message.id);
117
+ if (handler) { pending.delete(message.id); handler(message); }
118
+ }
119
+ });
120
+ child.on('error', (error) => finish(error));
121
+ child.on('exit', (code) => {
122
+ if (pending.size) finish(new Error(`installed Brain exited ${code}: ${stderr.slice(0, 400)}`));
123
+ });
124
+ const call = (id, method, params = {}) => new Promise((done) => {
125
+ pending.set(id, done);
126
+ child.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', id, method, params })}\n`);
127
+ });
128
+ (async () => {
129
+ const initialized = await call(1, 'initialize', { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'release-proof', version: '1' } });
130
+ if (initialized.error) throw new Error(`MCP initialize failed: ${JSON.stringify(initialized.error)}`);
131
+ const listed = await call(2, 'tools/list');
132
+ if (!listed.result?.tools?.some((tool) => tool.name === 'search_ruvnet')) throw new Error('installed MCP does not advertise search_ruvnet');
133
+ const started = performance.now();
134
+ const searched = await call(3, 'tools/call', { name: 'search_ruvnet', arguments: { query, k: 5 } });
135
+ const broadMs = Math.round(performance.now() - started);
136
+ const text = (searched.result?.content || []).map((item) => item.text || '').join('\n');
137
+ if (searched.error || searched.result?.isError || /search_ruvnet error:/i.test(text)) throw new Error(`installed Brain search failed: ${text.slice(0, 400)}`);
138
+ if (!/repo=ruvnet-brain/i.test(text) || !/path\s*:/i.test(text)) throw new Error('installed Brain search returned no ruvnet-brain source citation');
139
+ finish(null, { broadMs, text });
140
+ })().catch((error) => finish(error));
141
+ });
142
+ }
143
+
144
+ export function livePublicationAdapter({ root = process.cwd() } = {}) {
145
+ let installContext = null;
146
+ return {
147
+ async downloadNpm({ version, destination }) {
148
+ const metadata = JSON.parse(command('npm', ['view', `${PACKAGE}@${version}`, '--json']));
149
+ if (metadata.version !== version || !metadata.dist?.tarball) {
150
+ throw new Error('npm metadata is missing exact version or tarball');
151
+ }
152
+ await download(metadata.dist.tarball, destination);
153
+ return { path: destination, version: metadata.version, sha: metadata.gitHead || null };
154
+ },
155
+
156
+ async downloadGithub({ tag, assetName, destination }) {
157
+ const headers = process.env.GITHUB_TOKEN ? { authorization: `Bearer ${process.env.GITHUB_TOKEN}` } : {};
158
+ const response = await fetch(`https://api.github.com/repos/${REPO}/releases/tags/${encodeURIComponent(tag)}`, {
159
+ headers: { 'user-agent': 'ruvnet-brain-publication-receipt', ...headers },
160
+ signal: AbortSignal.timeout(30_000),
161
+ });
162
+ if (!response.ok) throw new Error(`GitHub release ${tag} lookup failed: HTTP ${response.status}`);
163
+ const release = await response.json();
164
+ const asset = release.assets?.find((item) => item.name === assetName);
165
+ if (!asset?.browser_download_url) throw new Error(`GitHub release ${tag} is missing sealed artifact ${assetName}`);
166
+ await download(asset.browser_download_url, destination, headers);
167
+ return { path: destination, tag: release.tag_name, sha: tagCommit(root, tag) };
168
+ },
169
+
170
+ async installHosts({ artifactPath, artifactSha256, version }) {
171
+ const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'ruvnet-public-install-'));
172
+ const packageRoot = path.join(temp, 'package');
173
+ command('tar', ['-xzf', artifactPath, '-C', temp]);
174
+ const home = path.join(temp, 'home');
175
+ const codexHome = path.join(home, '.codex');
176
+ const brainHome = path.join(home, '.cache', 'ruvnet-brain');
177
+ const kb = path.join(brainHome, 'kb');
178
+ fs.mkdirSync(codexHome, { recursive: true });
179
+ const env = {
180
+ ...process.env,
181
+ HOME: home,
182
+ CODEX_HOME: codexHome,
183
+ RUVNET_BRAIN_HOME: brainHome,
184
+ RUVNET_BRAIN_KB: kb,
185
+ RUVNET_STRICT_INSTALL: '1',
186
+ RUVNET_BRAIN_PROFILE: 'complete',
187
+ CI: 'true',
188
+ };
189
+ command(process.execPath, [
190
+ path.join(packageRoot, 'bin', 'install.mjs'), '--yes', '--force', '--version', `v${version}`,
191
+ '--no-nightly-prompt', '--no-telemetry', '--no-stack', '--no-enhance', '--no-statusline',
192
+ ], { env, cwd: packageRoot, timeout: 1_200_000, maxBuffer: 32 * 1024 * 1024 });
193
+
194
+ const claudeManifest = findManifest(path.join(home, '.claude'), path.join('.claude-plugin', 'plugin.json'), version);
195
+ const codexManifest = findManifest(path.join(home, '.codex'), path.join('.codex-plugin', 'plugin.json'), version);
196
+ const sealedPlugin = path.join(packageRoot, 'plugin');
197
+ const claudeFiles = assertInstalledPayload(sealedPlugin, path.dirname(path.dirname(claudeManifest)));
198
+ const codexFiles = assertInstalledPayload(sealedPlugin, path.dirname(path.dirname(codexManifest)));
199
+ const config = fs.readFileSync(path.join(codexHome, 'config.toml'), 'utf8');
200
+ if (!/\[mcp_servers\.ruvnet-brain\]/.test(config)) throw new Error('virgin Codex home is not wired to ruvnet-brain');
201
+ const source = readJson(path.join(kb, 'SOURCE.json'));
202
+ if (source.brainVersion !== version || source.releaseTag !== `v${version}`) throw new Error('installed public Brain bundle version mismatch');
203
+ installContext = { temp, home, codexHome, brainHome, kb, env, packageRoot };
204
+ return {
205
+ claude: { status: 'PASS', version, artifactSha256, payloadFilesVerified: claudeFiles },
206
+ codex: { status: 'PASS', version, artifactSha256, payloadFilesVerified: codexFiles },
207
+ bundle: { brainVersion: source.brainVersion, releaseTag: source.releaseTag },
208
+ };
209
+ },
210
+
211
+ async probeBrain() {
212
+ if (!installContext) throw new Error('public hosts must be installed before Brain proof');
213
+ const rvfs = fs.readdirSync(installContext.kb).filter((name) => /ruvnet-brain.*\.rvf$/i.test(name));
214
+ if (rvfs.length === 0) throw new Error('installed public Brain has no ruvnet-brain self RVF');
215
+ const server = path.join(installContext.home, '.claude', 'ruvnet-brain', 'mcp', 'server.mjs');
216
+ if (!fs.existsSync(server)) throw new Error('installed persistent MCP server is missing');
217
+ const result = await rpcSearch(server, installContext.env, 'How does RuvNet Brain prove a public release artifact?', DEADLINE_MS);
218
+ const readiness = readJson(path.join(installContext.brainHome, 'mcp-readiness.json'));
219
+ if (readiness.state !== 'ready') throw new Error(`installed Brain readiness is ${readiness.state || 'missing'}`);
220
+ return { status: 'PASS', selfStore: true, broadMs: result.broadMs, deadlineMs: DEADLINE_MS };
221
+ },
222
+
223
+ async probePublishedSurface({ sha }) {
224
+ const head = command('git', ['rev-parse', 'HEAD'], { cwd: root });
225
+ if (head !== sha) throw new Error(`published-surface probe checkout ${head} does not match candidate ${sha}`);
226
+ const result = spawnSync(process.execPath, ['scripts/published-surface-probe.mjs', '--json'], {
227
+ cwd: root, env: process.env, encoding: 'utf8', timeout: 600_000,
228
+ });
229
+ let parsed;
230
+ try { parsed = JSON.parse(String(result.stdout || '')); } catch { throw new Error(`published-surface-probe emitted invalid JSON: ${String(result.stderr || '').slice(0, 300)}`); }
231
+ if (result.status !== 0 || parsed.verdict !== 'PASS') throw new Error(`published-surface-probe is ${parsed.verdict || `exit ${result.status}`}`);
232
+ return { name: 'published-surface-probe', status: 'completed', conclusion: 'success', sha };
233
+ },
234
+ };
235
+ }
236
+
237
+ export async function generatePublicationReceipt({
238
+ root = process.cwd(),
239
+ candidatePath,
240
+ outPath,
241
+ adapter = livePublicationAdapter({ root }),
242
+ } = {}) {
243
+ if (!candidatePath || !outPath) throw new Error('candidatePath and outPath are required');
244
+ if (fs.existsSync(outPath)) throw new Error(`refusing to overwrite existing publication receipt: ${outPath}`);
245
+ const candidate = readJson(candidatePath);
246
+ const candidateResult = evaluateCandidateReceipt(candidate);
247
+ if (candidateResult.verdict !== 'PASS') throw new Error(`candidate seal failed: ${candidateResult.failures.map(({ code }) => code).join(',')}`);
248
+ const digest = receiptDigest(candidate);
249
+ const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'ruvnet-public-evidence-'));
250
+ try {
251
+ const assetName = path.basename(candidate.artifact.path);
252
+ const npm = await adapter.downloadNpm({ version: candidate.version, destination: path.join(temp, `npm-${assetName}`) });
253
+ const github = await adapter.downloadGithub({ tag: candidate.tag, assetName, destination: path.join(temp, `github-${assetName}`) });
254
+ const npmDigest = sha256(npm.path);
255
+ const githubDigest = sha256(github.path);
256
+ if (npmDigest !== digest) throw new Error(`npm public byte mismatch: ${npmDigest} != ${digest}`);
257
+ if (githubDigest !== digest) throw new Error(`GitHub public byte mismatch: ${githubDigest} != ${digest}`);
258
+ if (npm.version !== candidate.version || (npm.sha && npm.sha !== candidate.sha)) throw new Error('npm public identity mismatch');
259
+ if (github.tag !== candidate.tag || github.sha !== candidate.sha) throw new Error('GitHub public identity mismatch');
260
+
261
+ const installed = await adapter.installHosts({ artifactPath: npm.path, artifactSha256: digest, version: candidate.version });
262
+ const brain = await adapter.probeBrain({ sha: candidate.sha, artifactSha256: digest, version: candidate.version });
263
+ const surface = await adapter.probePublishedSurface({ sha: candidate.sha, artifactSha256: digest, version: candidate.version });
264
+ const publication = {
265
+ schemaVersion: 1,
266
+ phase: 'publication',
267
+ sha: candidate.sha,
268
+ artifactSha256: digest,
269
+ version: candidate.version,
270
+ // npm does not guarantee gitHead metadata. Exact public-byte equality with the candidate's
271
+ // SHA-bound sealed artifact is the identity proof; a present but conflicting gitHead is red.
272
+ npm: { version: npm.version, sha: candidate.sha, artifactSha256: npmDigest },
273
+ githubRelease: { tag: github.tag, sha: github.sha, artifactSha256: githubDigest },
274
+ bundle: installed.bundle,
275
+ installed: { claude: installed.claude, codex: installed.codex },
276
+ brain,
277
+ postPublicationChecks: [surface],
278
+ };
279
+ const result = evaluatePublicationReceipt(candidate, publication);
280
+ if (result.verdict !== 'PASS') throw new Error(`publication seal failed: ${result.failures.map(({ code }) => code).join(',')}`);
281
+ fs.mkdirSync(path.dirname(outPath), { recursive: true });
282
+ fs.writeFileSync(outPath, `${JSON.stringify(publication, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
283
+ return result;
284
+ } finally {
285
+ fs.rmSync(temp, { recursive: true, force: true });
286
+ }
287
+ }
288
+
289
+ function argument(args, flag) {
290
+ const index = args.indexOf(flag);
291
+ return index >= 0 ? args[index + 1] : null;
292
+ }
293
+
294
+ export async function main(args = process.argv.slice(2)) {
295
+ try {
296
+ const candidatePath = argument(args, '--candidate');
297
+ const outPath = argument(args, '--out');
298
+ const result = await generatePublicationReceipt({ candidatePath, outPath });
299
+ console.log(JSON.stringify(result, null, 2));
300
+ return 0;
301
+ } catch (error) {
302
+ console.error(`publication-receipt: ${error.message}`);
303
+ return 1;
304
+ }
305
+ }
306
+
307
+ if (process.argv[1] && pathToFileURL(path.resolve(process.argv[1])).href === import.meta.url) process.exitCode = await main();