ruvnet-brain 4.2.2-dev → 4.3.1-dev

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 (73) hide show
  1. package/README.md +15 -14
  2. package/bin/install.mjs +11 -2
  3. package/console/architecture.html +12 -14
  4. package/console/install-mockup.html +3 -3
  5. package/kb/zip-extract.mjs +8 -2
  6. package/package.json +7 -1
  7. package/plugin/.claude-plugin/plugin.json +1 -1
  8. package/plugin/.codex-plugin/plugin.json +1 -1
  9. package/plugin/hooks/codex-hooks.json +1 -1
  10. package/plugin/hooks/hooks.json +10 -0
  11. package/plugin/host-adapters/claude.json +11 -0
  12. package/plugin/host-adapters/codex.json +11 -0
  13. package/plugin/mcp/managed-cli-interface.mjs +77 -10
  14. package/plugin/mcp/server.mjs +2 -1
  15. package/plugin/scripts/anticipate.sh +30 -1
  16. package/plugin/scripts/capability-claim-evidence.mjs +431 -0
  17. package/plugin/scripts/capability-inventory-receipt.mjs +225 -0
  18. package/plugin/scripts/capability-registry.mjs +6 -0
  19. package/plugin/scripts/capability-routing.mjs +70 -0
  20. package/plugin/scripts/continuation-gate.mjs +68 -2
  21. package/plugin/scripts/coverage-integrity.mjs +575 -0
  22. package/plugin/scripts/ground-ruvnet.sh +2 -2
  23. package/plugin/scripts/nightly-controller.mjs +19 -2
  24. package/plugin/scripts/project-progression-contract.mjs +409 -0
  25. package/plugin/scripts/project-progression-hook.mjs +196 -0
  26. package/plugin/scripts/project-progression-outbox.mjs +95 -0
  27. package/plugin/scripts/project-progression-session-start.mjs +183 -0
  28. package/plugin/scripts/project-progression-store.mjs +248 -0
  29. package/plugin/scripts/project-store-resolver.mjs +106 -0
  30. package/plugin/scripts/session-snapshot-hook.mjs +29 -1
  31. package/plugin/scripts/session-start-core.mjs +22 -18
  32. package/plugin/scripts/spend-guard.mjs +6 -1
  33. package/scripts/adr-072-completion.mjs +98 -0
  34. package/scripts/behavioral-l1-l4.mjs +11 -8
  35. package/scripts/brain-score.mjs +7 -2
  36. package/scripts/build-bundle.mjs +82 -6
  37. package/scripts/card-from-source.mjs +32 -6
  38. package/scripts/corpus-aggregates.mjs +194 -0
  39. package/scripts/corpus-candidate.mjs +56 -36
  40. package/scripts/corpus-reconcile.mjs +295 -28
  41. package/scripts/corpus-seed-publish.mjs +3 -3
  42. package/scripts/coverage-integrity.mjs +3 -0
  43. package/scripts/gist-receipts.mjs +183 -0
  44. package/scripts/git-hooks/pre-push +21 -0
  45. package/scripts/host-registry.mjs +118 -0
  46. package/scripts/independent-review-receipt.mjs +499 -0
  47. package/scripts/ingest-gists.mjs +34 -5
  48. package/scripts/ingest-new-repos.mjs +5 -0
  49. package/scripts/metaharness-gate.mjs +58 -0
  50. package/scripts/nightly-gists.sh +3 -3
  51. package/scripts/nightly-wrapper.sh +75 -69
  52. package/scripts/product-integrity-contract.mjs +156 -0
  53. package/scripts/public-inventory.mjs +4 -0
  54. package/scripts/public-verification-aggregate.mjs +342 -0
  55. package/scripts/public-verification-finalizer.mjs +59 -0
  56. package/scripts/public-verification-inputs.mjs +473 -0
  57. package/scripts/public-verification-lane.mjs +244 -0
  58. package/scripts/publication-receipt.mjs +50 -6
  59. package/scripts/rebuild-gists-from-receipts.mjs +50 -5
  60. package/scripts/release-projection.mjs +86 -0
  61. package/scripts/release-transaction-provider.mjs +43 -102
  62. package/scripts/release-transaction.mjs +139 -51
  63. package/scripts/release.mjs +15 -7
  64. package/scripts/restore-local-ingests.mjs +2 -2
  65. package/scripts/retrieval-canary.mjs +533 -0
  66. package/scripts/routing-flywheel.mjs +12 -1
  67. package/scripts/rvf-generation.mjs +9 -3
  68. package/scripts/self-update.mjs +19 -44
  69. package/scripts/source-coverage.mjs +415 -0
  70. package/scripts/source-scope-receipt.mjs +84 -0
  71. package/scripts/sync-census.mjs +0 -0
  72. package/scripts/wired-check.mjs +114 -24
  73. package/scripts/worktree-integrity.mjs +123 -0
@@ -0,0 +1,575 @@
1
+ import crypto from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+
5
+ const HEX40 = /^[a-f0-9]{40}$/;
6
+ const HEX64 = /^[a-f0-9]{64}$/;
7
+ const HEX_GIST = /^[a-f0-9]{20,64}$/;
8
+ export const PUBLIC_GENERATIONS_FILE = 'PUBLIC-RVF-GENERATIONS.json';
9
+ export const RUNTIME_GENERATIONS_FILE = 'RVF-GENERATIONS.json';
10
+ export const INSTALLED_PROFILE_FILE = 'INSTALLED-PROFILE.json';
11
+
12
+ export function canonicalJson(value) {
13
+ if (Array.isArray(value)) return `[${value.map((entry) => canonicalJson(entry ?? null)).join(',')}]`;
14
+ if (value && typeof value === 'object') {
15
+ return `{${Object.keys(value).filter((key) => value[key] !== undefined).sort()
16
+ .map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(',')}}`;
17
+ }
18
+ return JSON.stringify(value);
19
+ }
20
+
21
+ export function digest(value) {
22
+ return crypto.createHash('sha256').update(typeof value === 'string' ? value : canonicalJson(value)).digest('hex');
23
+ }
24
+
25
+ function validateLegacyGistAggregateReceipt({ receipt, passagesFile, expectedIds }) {
26
+ const ids = Object.keys(receipt?.gists || {});
27
+ if (receipt?.schemaVersion !== 2 || receipt?.owner !== 'ruvnet' || !ids.length
28
+ || (expectedIds && canonicalJson([...ids].sort()) !== canonicalJson([...expectedIds].map(String).sort()))) {
29
+ throw new Error('legacy gist aggregate receipt is malformed or has the wrong gist set');
30
+ }
31
+ for (const id of ids) {
32
+ const row = receipt.gists[id];
33
+ const files = row?.files;
34
+ if (!HEX_GIST.test(id) || !/^[a-f0-9]{40}$/.test(String(row?.versionSha || ''))
35
+ || !Array.isArray(files) || !files.length
36
+ || files.some((file) => typeof file?.filename !== 'string' || !file.filename
37
+ || (file.included === true
38
+ ? !HEX64.test(String(file.sha256 || '')) || !Number.isSafeInteger(file.bytes) || file.bytes < 0
39
+ : file.included !== false || typeof file.reason !== 'string' || !file.reason
40
+ || !Number.isSafeInteger(file.size) || file.size < 0))
41
+ // Schema 2 was generated with JSON.stringify(array), before the
42
+ // canonical-json contract was introduced for schema 3. Preserve that
43
+ // historical receipt format while keeping the stricter contract below.
44
+ || row.contentDigest !== crypto.createHash('sha256').update(JSON.stringify(files)).digest('hex')) {
45
+ throw new Error(`legacy gist ${id} receipt is incomplete or internally inconsistent`);
46
+ }
47
+ }
48
+ // v2 receipts predate shipping the source JSONL alongside the seed RVF.
49
+ // The recorded passage digest is retained as historical metadata only;
50
+ // schema 3 is the first format whose source bytes are a release gate.
51
+ return receipt;
52
+ }
53
+
54
+ export function validateGistAggregateReceipt({ receipt, passagesFile, expectedIds = null,
55
+ sourceObservationSha256 = null }) {
56
+ if (receipt?.schemaVersion === 2 && sourceObservationSha256 === null) {
57
+ return validateLegacyGistAggregateReceipt({ receipt, passagesFile, expectedIds });
58
+ }
59
+ if (receipt?.schemaVersion !== 3 || receipt?.kind !== 'ruvnet-brain-gist-source-receipts'
60
+ || !receipt.gists || typeof receipt.gists !== 'object' || Array.isArray(receipt.gists)
61
+ || !HEX64.test(String(receipt.sourceObservationSha256 || ''))) {
62
+ throw new Error('gist aggregate receipt schema or observation identity is invalid');
63
+ }
64
+ if (sourceObservationSha256 !== null && receipt.sourceObservationSha256 !== sourceObservationSha256) {
65
+ throw new Error('gist aggregate receipt observation differs from coverage');
66
+ }
67
+ const ids = Object.keys(receipt.gists);
68
+ if (!ids.length || ids.some((id) => !HEX_GIST.test(id))
69
+ || canonicalJson(ids) !== canonicalJson([...ids].sort())
70
+ || (expectedIds && canonicalJson(ids) !== canonicalJson([...expectedIds].map(String).sort()))
71
+ || receipt.gistSet?.count !== ids.length || receipt.gistSet?.idsSha256 !== digest(ids)
72
+ || receipt.gistSet?.observationSha256 !== receipt.sourceObservationSha256) {
73
+ throw new Error('gist aggregate receipt does not bind the exact sorted gist set');
74
+ }
75
+ for (const id of ids) {
76
+ const row = receipt.gists[id];
77
+ const files = row?.files;
78
+ const filenames = Array.isArray(files) ? files.map((file) => file?.filename) : [];
79
+ if (row?.schemaVersion !== 3 || row?.kind !== 'ruvnet-brain-gist-source-receipt'
80
+ || row.gistId !== id || row.complete !== true || !/^[a-f0-9]{40}$/.test(String(row.versionSha || ''))
81
+ || !Array.isArray(files) || !files.length || row.fileCount !== files.length
82
+ || canonicalJson(filenames) !== canonicalJson([...filenames].sort())
83
+ || new Set(filenames).size !== filenames.length
84
+ || files.some((file) => typeof file?.filename !== 'string' || !file.filename
85
+ || (file.included === true
86
+ ? !HEX64.test(String(file.sha256 || '')) || !Number.isSafeInteger(file.bytes) || file.bytes < 0
87
+ : file.included !== false || typeof file.reason !== 'string' || !file.reason
88
+ || !(file.size === null || (Number.isSafeInteger(file.size) && file.size >= 0))))
89
+ || row.contentDigest !== digest(files)) {
90
+ throw new Error(`gist ${id} receipt is incomplete or internally inconsistent`);
91
+ }
92
+ const { receiptSha256, ...payload } = row;
93
+ if (!HEX64.test(String(receiptSha256 || '')) || receiptSha256 !== digest(payload)) {
94
+ throw new Error(`gist ${id} receipt digest differs`);
95
+ }
96
+ }
97
+ const sourceSet = ids.map((id) => ({ id, receiptSha256: receipt.gists[id].receiptSha256 }));
98
+ if (receipt.sourceSetSha256 !== digest(sourceSet)) throw new Error('gist source set digest differs');
99
+ let passagesStat = null;
100
+ try { passagesStat = fs.lstatSync(passagesFile); } catch { /* handled below */ }
101
+ if (!passagesFile || !passagesStat?.isFile() || passagesStat.isSymbolicLink()
102
+ || !HEX64.test(String(receipt.passagesSha256 || ''))
103
+ || receipt.passagesSha256 !== sha256File(passagesFile)) {
104
+ throw new Error('gist aggregate receipt does not bind its passage bytes');
105
+ }
106
+ const { receiptSha256, ...payload } = receipt;
107
+ if (!HEX64.test(String(receiptSha256 || '')) || receiptSha256 !== digest(payload)) {
108
+ throw new Error('gist aggregate receipt digest differs');
109
+ }
110
+ return receipt;
111
+ }
112
+
113
+ export function sha256File(file) {
114
+ const hash = crypto.createHash('sha256');
115
+ const fd = fs.openSync(file, 'r');
116
+ const buffer = Buffer.allocUnsafe(1024 * 1024);
117
+ try {
118
+ let bytes;
119
+ while ((bytes = fs.readSync(fd, buffer, 0, buffer.length, null)) > 0) hash.update(buffer.subarray(0, bytes));
120
+ } finally { fs.closeSync(fd); }
121
+ return hash.digest('hex');
122
+ }
123
+
124
+ const readJson = (file, label) => {
125
+ let stat;
126
+ try { stat = fs.lstatSync(file); } catch { throw new Error(`${label} is missing`); }
127
+ if (!stat.isFile() || stat.isSymbolicLink()) throw new Error(`${label} is not a trusted regular file`);
128
+ try { return JSON.parse(fs.readFileSync(file, 'utf8')); }
129
+ catch (error) { throw new Error(`${label} is unreadable: ${error.message}`); }
130
+ };
131
+
132
+ function containedRegular(root, relative, label) {
133
+ if (typeof relative !== 'string' || !relative || path.isAbsolute(relative)
134
+ || relative.split(/[\\/]/).includes('..')) throw new Error(`${label} path escapes the public inventory root`);
135
+ const resolved = path.resolve(root, relative);
136
+ const within = path.relative(root, resolved);
137
+ if (!within || within.startsWith('..') || path.isAbsolute(within)) {
138
+ throw new Error(`${label} path escapes the public inventory root`);
139
+ }
140
+ let stat;
141
+ try { stat = fs.lstatSync(resolved); } catch { throw new Error(`${label} is missing`); }
142
+ if (!stat.isFile() || stat.isSymbolicLink()) throw new Error(`${label} is not a trusted regular file`);
143
+ const realRoot = fs.realpathSync(root);
144
+ const realFile = fs.realpathSync(resolved);
145
+ const realWithin = path.relative(realRoot, realFile);
146
+ if (!realWithin || realWithin.startsWith('..') || path.isAbsolute(realWithin)) {
147
+ throw new Error(`${label} resolves outside the public inventory root`);
148
+ }
149
+ return resolved;
150
+ }
151
+
152
+ function evidenceIdentity(root, file, kind) {
153
+ return {
154
+ kind,
155
+ path: path.relative(root, file).split(path.sep).join('/'),
156
+ sha256: sha256File(file),
157
+ bytes: fs.statSync(file).size,
158
+ };
159
+ }
160
+
161
+ const canonicalStores = (root) => {
162
+ const stores = [];
163
+ for (const name of fs.readdirSync(root).filter((entry) => !entry.startsWith('._'))) {
164
+ const store = name.match(/^(.+)\.big\.rvf$/)?.[1];
165
+ if (!store) continue;
166
+ const stat = fs.lstatSync(path.join(root, name));
167
+ if (!stat.isFile() || stat.isSymbolicLink()) throw new Error(`${store} canonical RVF is not a trusted regular file`);
168
+ stores.push(store);
169
+ }
170
+ const folded = stores.map((store) => store.toLowerCase());
171
+ if (new Set(folded).size !== folded.length) throw new Error('canonical RVF store names have case-fold aliases');
172
+ return stores.sort();
173
+ };
174
+
175
+ export function projectPublicGenerationLedger({ ledger, publicStores, version, sourceSnapshot }) {
176
+ if (!ledger?.stores || typeof ledger.stores !== 'object' || Array.isArray(ledger.stores)) {
177
+ throw new Error('generation ledger stores are malformed');
178
+ }
179
+ if (typeof version !== 'string' || !version) throw new Error('public generation ledger version is missing');
180
+ if (!HEX40.test(String(sourceSnapshot || ''))) throw new Error('public generation ledger source snapshot is invalid');
181
+ const names = [...publicStores].map(String).sort();
182
+ if (!names.length || new Set(names).size !== names.length) {
183
+ throw new Error('public generation ledger store set is empty or duplicated');
184
+ }
185
+ const stores = {};
186
+ for (const name of names) {
187
+ if (!ledger.stores[name]) throw new Error(`generation ledger is missing public store ${name}`);
188
+ stores[name] = structuredClone(ledger.stores[name]);
189
+ }
190
+ return {
191
+ schemaVersion: 2,
192
+ kind: 'ruvnet-brain-public-generation-ledger',
193
+ brainVersion: version,
194
+ releaseTag: `v${version}`,
195
+ sourceSnapshot,
196
+ stores,
197
+ };
198
+ }
199
+
200
+ export function generationLedgerBytes(ledger) {
201
+ return Buffer.from(`${JSON.stringify(ledger, null, 2)}\n`);
202
+ }
203
+
204
+ export function validatePublicInventory({ assetsDir, coverage, ledger, installedPublicStores = null }) {
205
+ const root = path.resolve(assetsDir);
206
+ const selected = installedPublicStores === null ? null : [...installedPublicStores].map((store) => String(store).toLowerCase());
207
+ if (selected && (selected.some((store) => !store) || new Set(selected).size !== selected.length)) {
208
+ throw new Error('installed public store selection is missing or duplicated');
209
+ }
210
+ const selectedSet = selected === null ? null : new Set(selected);
211
+ if (!Array.isArray(coverage?.rows) || !ledger?.stores || typeof ledger.stores !== 'object' || Array.isArray(ledger.stores)) {
212
+ throw new Error('coverage rows or generation ledger stores are malformed');
213
+ }
214
+ const fence = readJson(path.join(root, 'PRIVATE-STORES.json'), 'private fence');
215
+ if (!Array.isArray(fence.privateStores)) throw new Error('private fence has no privateStores array');
216
+ const privateStores = fence.privateStores.map((store) => String(store).toLowerCase());
217
+ if (privateStores.some((store) => !store) || new Set(privateStores).size !== privateStores.length) {
218
+ throw new Error('private fence stores are missing or duplicated');
219
+ }
220
+ const privateSet = new Set(privateStores);
221
+ const evidenceFiles = [];
222
+ const uniqueStores = (rows, family) => {
223
+ const stores = rows.map((row) => String(row?.artifact?.store || '').toLowerCase());
224
+ if (stores.some((store) => !store) || new Set(stores).size !== stores.length) throw new Error(`${family} stores are missing or duplicated`);
225
+ return stores.sort();
226
+ };
227
+ const repos = coverage.rows.filter((row) => row.kind === 'repository' && row.disposition === 'eligible');
228
+ if (repos.some((row) => row.status !== 'CURRENT')) throw new Error('an eligible repository is not CURRENT');
229
+ const repositories = uniqueStores(repos, 'repository');
230
+ const excludedRepositories = uniqueStores(coverage.rows.filter((row) => row.kind === 'repository'
231
+ && row.disposition !== 'eligible'), 'excluded repository');
232
+ const excludedSet = new Set(excludedRepositories);
233
+ if (repositories.some((store) => excludedSet.has(store))) throw new Error('eligible and excluded repository stores overlap');
234
+ const gists = coverage.rows.filter((row) => row.kind === 'gist' && row.disposition === 'eligible');
235
+ if (gists.some((row) => row.status !== 'CURRENT')) throw new Error('an eligible gist is not CURRENT');
236
+ let gistAggregate = null;
237
+ if (gists.length) {
238
+ const gistStores = new Set(gists.map((row) => String(row?.artifact?.store || '').toLowerCase()));
239
+ if (gistStores.size !== 1 || !gistStores.has('ruv-gists')) throw new Error('eligible gists must use the ruv-gists aggregate');
240
+ if (!selectedSet || selectedSet.has('ruv-gists')) {
241
+ const receiptFile = path.join(root, 'ruv-gists.sources.json');
242
+ const receipt = readJson(receiptFile, 'gist aggregate receipt');
243
+ const passages = path.join(root, 'ruv-gists.passages.jsonl');
244
+ const ids = gists.map((row) => String(row.key || '').replace(/^gist:/, ''));
245
+ if (ids.some((id) => !id)) throw new Error('gist coverage row identity is missing');
246
+ validateGistAggregateReceipt({ receipt, passagesFile: passages, expectedIds: ids,
247
+ sourceObservationSha256: coverage.sourceObservationSha256 });
248
+ if (receipt.schemaVersion === 3) evidenceFiles.push(evidenceIdentity(root, passages, 'gist-passages'));
249
+ evidenceFiles.push(evidenceIdentity(root, receiptFile, 'gist-receipt'));
250
+ }
251
+ gistAggregate = 'ruv-gists';
252
+ }
253
+ const classesFile = path.join(root, 'public-store-classes.json');
254
+ const classes = readJson(classesFile, 'public store classes');
255
+ evidenceFiles.push(evidenceIdentity(root, classesFile, 'class-registry'));
256
+ if (classes.schemaVersion !== 1 || !Array.isArray(classes.derived)) throw new Error('derived store classes are malformed');
257
+ const derived = classes.derived.map((entry) => String(entry?.store || '').toLowerCase()).sort();
258
+ if (derived.some((store) => !store) || new Set(derived).size !== derived.length) throw new Error('derived stores are missing or duplicated');
259
+ for (const entry of classes.derived) {
260
+ const store = String(entry.store || '').toLowerCase();
261
+ if (selectedSet && !selectedSet.has(store)) continue;
262
+ const receiptFile = containedRegular(root, entry.receipt, `derived ${store} receipt`);
263
+ const receipt = readJson(receiptFile, `derived ${store} receipt`);
264
+ const passages = containedRegular(root, `${store}.passages.jsonl`, `derived ${store} passages`);
265
+ if (receipt.schemaVersion !== 1 || receipt.kind !== 'ruvnet-brain-derived-store-receipt'
266
+ || String(receipt.store || '').toLowerCase() !== store || !Array.isArray(receipt.inputs) || receipt.inputs.length === 0
267
+ || !HEX64.test(String(receipt.passagesSha256 || '')) || receipt.passagesSha256 !== sha256File(passages)) {
268
+ throw new Error(`derived ${store} receipt does not bind its passage bytes`);
269
+ }
270
+ for (const input of receipt.inputs) {
271
+ let inputFile = null;
272
+ try { inputFile = containedRegular(root, input?.path, `derived ${store} input`); } catch { /* handled below */ }
273
+ if (!inputFile || !HEX64.test(String(input.sha256 || '')) || input.sha256 !== sha256File(inputFile)) {
274
+ throw new Error(`derived ${store} input receipt differs from ${input?.path || '(missing)'}`);
275
+ }
276
+ evidenceFiles.push(evidenceIdentity(root, inputFile, 'derived-input'));
277
+ }
278
+ evidenceFiles.push(evidenceIdentity(root, passages, 'derived-passages'));
279
+ evidenceFiles.push(evidenceIdentity(root, receiptFile, 'derived-receipt'));
280
+ }
281
+ const expected = [...repositories, ...(gistAggregate ? [gistAggregate] : []), ...derived].sort();
282
+ if (new Set(expected).size !== expected.length) throw new Error('public store classes overlap');
283
+ const excludedCollision = expected.filter((store) => excludedSet.has(store));
284
+ if (excludedCollision.length) throw new Error(`excluded/public store collision: ${excludedCollision.join(', ')}`);
285
+ const privateCollision = expected.filter((store) => privateSet.has(store));
286
+ if (privateCollision.length) throw new Error(`private/public store collision: ${privateCollision.join(', ')}`);
287
+ const installedExpected = selected === null ? expected : [...selected].sort();
288
+ if (installedExpected.some((store) => !expected.includes(store))) throw new Error('installed profile selects an unknown public store');
289
+ const actual = canonicalStores(root).filter((store) => !privateSet.has(store.toLowerCase())
290
+ && !excludedSet.has(store.toLowerCase())).sort();
291
+ const missing = installedExpected.filter((store) => !actual.includes(store));
292
+ const extras = actual.filter((store) => !installedExpected.includes(store));
293
+ if (missing.length) throw new Error(`${missing.join(', ')} public store is missing`);
294
+ if (extras.length) throw new Error(`unclassified public stores: ${extras.join(', ')}`);
295
+ const ledgerStores = Object.keys(ledger.stores);
296
+ const foldedLedgerStores = ledgerStores.map((store) => store.toLowerCase());
297
+ if (new Set(foldedLedgerStores).size !== foldedLedgerStores.length) {
298
+ throw new Error('generation ledger store names have case-fold aliases');
299
+ }
300
+ const publicLedgerStores = ledgerStores.filter((store) => !privateSet.has(store.toLowerCase())
301
+ && !excludedSet.has(store.toLowerCase())).sort();
302
+ if (canonicalJson(publicLedgerStores) !== canonicalJson(expected)) throw new Error('public generation ledger store set differs from the inventory partition');
303
+ for (const store of expected) {
304
+ const generation = ledger.stores[store];
305
+ const filename = `${store}.big.rvf`;
306
+ const file = path.join(root, filename);
307
+ const selected = installedExpected.includes(store);
308
+ if (generation?.file !== filename || (selected && (generation.bytes !== fs.statSync(file).size || generation.sha256 !== sha256File(file)))) {
309
+ throw new Error(`${store} generation record does not bind the RVF bytes`);
310
+ }
311
+ if (typeof generation.model !== 'string' || !generation.model.trim()
312
+ || !Number.isInteger(generation.dimensions) || generation.dimensions <= 0
313
+ || (generation.sourceCommit !== null && !/^[a-f0-9]{7,64}$/i.test(String(generation.sourceCommit || '')))
314
+ || typeof generation.builtUtc !== 'string' || !Number.isFinite(Date.parse(generation.builtUtc))) {
315
+ throw new Error(`${store} generation record provenance is incomplete`);
316
+ }
317
+ }
318
+ const publicStores = expected;
319
+ evidenceFiles.sort((a, b) => a.path.localeCompare(b.path) || a.kind.localeCompare(b.kind));
320
+ return { repositories, excludedRepositories, gistAggregate, derived, publicStores,
321
+ installedPublicStores: installedExpected,
322
+ evidenceFiles,
323
+ partitionSha256: digest({ repositories, excludedRepositories, gistAggregate, derived, publicStores, evidenceFiles }) };
324
+ }
325
+
326
+ export function coverageGenerationFor({ generatorSourceSha, snapshotRoot, sourceObservationSha256 = null, rows, enumerationReceipt,
327
+ policyDispositionDigests = [], exemptionDigests = [] }) {
328
+ const orderedRows = [...rows].sort((a, b) => a.key.localeCompare(b.key));
329
+ return digest({ schemaVersion: 1, generatorSourceSha, snapshotRoot, sourceObservationSha256, orderedRows,
330
+ enumerationReceiptDigest: digest(enumerationReceipt), policyDispositionDigests, exemptionDigests });
331
+ }
332
+
333
+ export function releaseCoverageGenerationFor(coverage) {
334
+ const orderedRows = [...(coverage.rows || [])].sort((a, b) => a.key.localeCompare(b.key));
335
+ return digest({
336
+ schemaVersion: 1,
337
+ releaseIdentity: coverage.releaseIdentity,
338
+ corpusSeed: coverage.corpusSeed,
339
+ corpusCoverage: coverage.corpusCoverage,
340
+ generationLedger: coverage.generationLedger,
341
+ publicInventoryPartitionSha256: coverage.publicInventoryPartitionSha256,
342
+ installedProjectionSchema: coverage.installedProjectionSchema,
343
+ generatorSourceSha: coverage.generatorSourceSha,
344
+ snapshotRoot: coverage.snapshotRoot,
345
+ sourceObservationSha256: coverage.sourceObservationSha256,
346
+ orderedRows,
347
+ totals: coverage.totals,
348
+ enumerationReceiptDigest: digest(coverage.enumerationReceipt),
349
+ policy: coverage.policy,
350
+ });
351
+ }
352
+
353
+ function validateRowsAndTotals(coverage, failures) {
354
+ const rows = Array.isArray(coverage?.rows) ? coverage.rows : [];
355
+ if (!Array.isArray(coverage?.rows)) failures.push('rows are missing');
356
+ const keys = rows.map((row) => row?.key);
357
+ if (keys.some((key) => typeof key !== 'string' || !key)) failures.push('a row key is missing');
358
+ if (new Set(keys).size !== keys.length) failures.push('row keys are duplicated');
359
+ if (rows.some((row) => !['repository', 'gist'].includes(row?.kind)
360
+ || typeof row?.name !== 'string' || typeof row?.url !== 'string'
361
+ || typeof row?.status !== 'string' || typeof row?.disposition !== 'string'
362
+ || !row?.upstream || !row?.artifact || !Array.isArray(row?.reasons))) failures.push('row shape is invalid');
363
+ const repositoryCount = rows.filter((row) => row.kind === 'repository').length;
364
+ const gistCount = rows.filter((row) => row.kind === 'gist').length;
365
+ const byStatus = Object.fromEntries([...new Set(rows.map((row) => row.status))].sort()
366
+ .map((status) => [status, rows.filter((row) => row.status === status).length]));
367
+ if (coverage?.totals?.rows !== rows.length || coverage?.totals?.repositories !== repositoryCount
368
+ || coverage?.totals?.gists !== gistCount || canonicalJson(coverage?.totals?.byStatus || {}) !== canonicalJson(byStatus)) {
369
+ failures.push('totals do not match rows');
370
+ }
371
+ return { rows, repositoryCount, gistCount };
372
+ }
373
+
374
+ export function validateCoverageLedger(coverage) {
375
+ const failures = [];
376
+ if (coverage?.schemaVersion !== 1) failures.push('schemaVersion is unsupported');
377
+ const isCorpus = coverage?.kind === 'ruvnet-brain-corpus-coverage';
378
+ const isRelease = coverage?.kind === 'ruvnet-brain-release-coverage';
379
+ if ((isCorpus || isRelease) && !HEX64.test(String(coverage?.sourceObservationSha256 || ''))) {
380
+ failures.push('source observation digest is missing');
381
+ }
382
+ if (!HEX64.test(String(coverage?.generatorSourceSha || ''))) failures.push('generator source digest is missing');
383
+ if (!HEX64.test(String(coverage?.snapshotRoot || ''))) failures.push('snapshot root is missing');
384
+ const { rows, repositoryCount, gistCount } = validateRowsAndTotals(coverage, failures);
385
+ const enumeration = coverage?.enumerationReceipt;
386
+ if (enumeration?.schemaVersion !== 1 || enumeration?.terminal !== true || enumeration?.duplicateKeys !== 0
387
+ || enumeration?.repositories?.expected !== repositoryCount || enumeration?.gists?.expected !== gistCount
388
+ || !Array.isArray(enumeration?.repositories?.pages) || !Array.isArray(enumeration?.gists?.pages)) {
389
+ failures.push('enumeration receipt is incomplete');
390
+ }
391
+ const policyDispositionDigests = coverage?.policy?.policyDispositionDigests;
392
+ const exemptionDigests = coverage?.policy?.exemptionDigests;
393
+ if (!Array.isArray(policyDispositionDigests) || !Array.isArray(exemptionDigests)
394
+ || [...policyDispositionDigests, ...exemptionDigests].some((value) => !HEX64.test(String(value)))) {
395
+ failures.push('policy digests are invalid');
396
+ }
397
+ if (isRelease) {
398
+ const identity = coverage.releaseIdentity;
399
+ if (!identity || !identity.version || identity.tag !== `v${identity.version}` || !HEX40.test(String(identity.sourceSnapshot || ''))) {
400
+ failures.push('release identity is invalid');
401
+ }
402
+ const seed = coverage.corpusSeed;
403
+ if (!seed || typeof seed.tag !== 'string' || !seed.tag || /latest/i.test(seed.tag)
404
+ || !HEX64.test(String(seed.archiveSha256 || '')) || !Number.isSafeInteger(seed.archiveBytes) || seed.archiveBytes < 1
405
+ || !HEX64.test(String(seed.receiptSha256 || ''))) failures.push('corpus seed identity is invalid');
406
+ if (!HEX64.test(String(coverage.corpusCoverage?.sha256 || ''))
407
+ || !HEX64.test(String(coverage.corpusCoverage?.coverageGeneration || ''))) failures.push('corpus coverage link is invalid');
408
+ if (coverage.generationLedger?.file !== PUBLIC_GENERATIONS_FILE
409
+ || !HEX64.test(String(coverage.generationLedger?.sha256 || ''))
410
+ || !Number.isSafeInteger(coverage.generationLedger?.bytes) || coverage.generationLedger.bytes < 1
411
+ || !Number.isSafeInteger(coverage.generationLedger?.storeCount) || coverage.generationLedger.storeCount < 1) {
412
+ failures.push('generation ledger identity is invalid');
413
+ }
414
+ if (!HEX64.test(String(coverage.publicInventoryPartitionSha256 || ''))) failures.push('public inventory partition is invalid');
415
+ if (coverage.installedProjectionSchema !== 2) failures.push('installed projection schema is unsupported');
416
+ if (!failures.length && coverage.releaseCoverageGeneration !== releaseCoverageGenerationFor(coverage)) {
417
+ failures.push('release coverage generation digest does not match the ledger');
418
+ }
419
+ } else if (!failures.length) {
420
+ const expected = coverageGenerationFor({
421
+ generatorSourceSha: coverage.generatorSourceSha,
422
+ snapshotRoot: coverage.snapshotRoot,
423
+ sourceObservationSha256: coverage.sourceObservationSha256 || null,
424
+ rows,
425
+ enumerationReceipt: enumeration,
426
+ policyDispositionDigests,
427
+ exemptionDigests,
428
+ });
429
+ if (coverage.coverageGeneration !== expected) failures.push('coverage generation digest does not match the ledger');
430
+ }
431
+ return { valid: failures.length === 0, failures };
432
+ }
433
+
434
+ export function validateCoverageLink({ releaseCoverage, corpusCoverage, corpusCoverageSha256 }) {
435
+ const failures = [];
436
+ const release = validateCoverageLedger(releaseCoverage);
437
+ const corpus = validateCoverageLedger(corpusCoverage);
438
+ if (!release.valid) failures.push(...release.failures.map((failure) => `release: ${failure}`));
439
+ if (!corpus.valid || corpusCoverage?.kind !== 'ruvnet-brain-corpus-coverage') {
440
+ failures.push(...corpus.failures.map((failure) => `corpus: ${failure}`));
441
+ if (corpusCoverage?.kind !== 'ruvnet-brain-corpus-coverage') failures.push('corpus: wrong coverage kind');
442
+ }
443
+ if (releaseCoverage?.corpusCoverage?.sha256 !== corpusCoverageSha256) failures.push('corpus coverage byte digest differs');
444
+ if (releaseCoverage?.corpusCoverage?.coverageGeneration !== corpusCoverage?.coverageGeneration) {
445
+ failures.push('corpus coverage generation differs');
446
+ }
447
+ return { valid: failures.length === 0, failures };
448
+ }
449
+
450
+ /**
451
+ * Validate the exact installed/staged projection from disk. This is the single activation-boundary
452
+ * reader used by install, update, Console, and public proof; callers never reconstruct the linkage.
453
+ */
454
+ export function validateCoverageDirectory(root, {
455
+ expectedVersion = null,
456
+ expectedSourceSnapshot = null,
457
+ requireCompleteProfile = false,
458
+ } = {}) {
459
+ const coverageFile = path.join(root, 'COVERAGE.json');
460
+ const corpusFile = path.join(root, 'CORPUS-COVERAGE.json');
461
+ const failures = [];
462
+ if (!fs.existsSync(coverageFile)) failures.push('COVERAGE.json is missing');
463
+ if (!fs.existsSync(corpusFile)) failures.push('CORPUS-COVERAGE.json is missing');
464
+ if (failures.length) return { valid: false, failures, coverageFile, corpusFile };
465
+ try {
466
+ const coverageBytes = fs.readFileSync(coverageFile);
467
+ const corpusBytes = fs.readFileSync(corpusFile);
468
+ const coverage = JSON.parse(coverageBytes);
469
+ const corpusCoverage = JSON.parse(corpusBytes);
470
+ if (coverage.kind !== 'ruvnet-brain-release-coverage') {
471
+ failures.push('COVERAGE.json is not bound to a product release identity');
472
+ }
473
+ const linked = validateCoverageLink({
474
+ releaseCoverage: coverage,
475
+ corpusCoverage,
476
+ corpusCoverageSha256: crypto.createHash('sha256').update(corpusBytes).digest('hex'),
477
+ });
478
+ failures.push(...linked.failures);
479
+ if (expectedVersion && coverage?.releaseIdentity?.version !== expectedVersion) {
480
+ failures.push(`release version ${coverage?.releaseIdentity?.version || '(missing)'} differs from ${expectedVersion}`);
481
+ }
482
+ if (expectedSourceSnapshot && coverage?.releaseIdentity?.sourceSnapshot !== expectedSourceSnapshot) {
483
+ failures.push(`release source snapshot ${coverage?.releaseIdentity?.sourceSnapshot || '(missing)'} differs from ${expectedSourceSnapshot}`);
484
+ }
485
+ let generationLedger = null;
486
+ let runtimeGenerationLedger = null;
487
+ let publicInventory = null;
488
+ let installedProfile = null;
489
+ let coverageState = 'COMPLETE';
490
+ try {
491
+ const ledgerFile = path.join(root, PUBLIC_GENERATIONS_FILE);
492
+ const ledgerBytes = fs.readFileSync(ledgerFile);
493
+ generationLedger = JSON.parse(ledgerBytes);
494
+ if (crypto.createHash('sha256').update(ledgerBytes).digest('hex') !== coverage?.generationLedger?.sha256
495
+ || ledgerBytes.length !== coverage?.generationLedger?.bytes) failures.push('generation ledger bytes differ from ReleaseCoverage');
496
+ if (generationLedger.brainVersion !== coverage?.releaseIdentity?.version
497
+ || generationLedger.releaseTag !== coverage?.releaseIdentity?.tag
498
+ || generationLedger.sourceSnapshot !== coverage?.releaseIdentity?.sourceSnapshot
499
+ || generationLedger.schemaVersion !== 2
500
+ || generationLedger.kind !== 'ruvnet-brain-public-generation-ledger') {
501
+ failures.push('generation ledger release identity differs');
502
+ }
503
+ const installedProfileFile = path.join(root, INSTALLED_PROFILE_FILE);
504
+ if (fs.existsSync(installedProfileFile)) {
505
+ installedProfile = readJson(installedProfileFile, 'installed profile receipt');
506
+ const runtimeBytes = fs.readFileSync(path.join(root, RUNTIME_GENERATIONS_FILE));
507
+ const selected = [...(installedProfile.selectedPublicStores || [])].sort();
508
+ if (installedProfile.schemaVersion !== 1 || installedProfile.kind !== 'ruvnet-brain-installed-profile'
509
+ || installedProfile.profile !== 'ruvector' || installedProfile.state !== 'PROFILED'
510
+ || canonicalJson(selected) !== canonicalJson(['ruvector'])
511
+ || installedProfile.immutablePublicLedger?.file !== PUBLIC_GENERATIONS_FILE
512
+ || installedProfile.immutablePublicLedger?.sha256 !== crypto.createHash('sha256').update(ledgerBytes).digest('hex')
513
+ || installedProfile.immutablePublicLedger?.bytes !== ledgerBytes.length
514
+ || installedProfile.runtimeLedger?.file !== RUNTIME_GENERATIONS_FILE
515
+ || installedProfile.runtimeLedger?.sha256 !== crypto.createHash('sha256').update(runtimeBytes).digest('hex')
516
+ || installedProfile.runtimeLedger?.bytes !== runtimeBytes.length) {
517
+ failures.push('installed profile receipt is invalid or does not bind the installed ledgers');
518
+ } else {
519
+ coverageState = 'PROFILED';
520
+ if (requireCompleteProfile) failures.push('complete public corpus is required but the installed profile is ruvector');
521
+ }
522
+ }
523
+ const selectedPublicStores = installedProfile?.selectedPublicStores || null;
524
+ publicInventory = validatePublicInventory({ assetsDir: root, coverage, ledger: generationLedger, installedPublicStores: selectedPublicStores });
525
+ if (publicInventory.publicStores.length !== coverage?.generationLedger?.storeCount) failures.push('generation ledger public store count differs');
526
+ if (publicInventory.partitionSha256 !== coverage?.publicInventoryPartitionSha256) failures.push('installed public inventory partition digest differs');
527
+
528
+ const immutableNames = Object.keys(generationLedger.stores || {}).sort();
529
+ if (canonicalJson(immutableNames) !== canonicalJson(publicInventory.publicStores)) {
530
+ failures.push('immutable public generation ledger contains non-public stores');
531
+ }
532
+ runtimeGenerationLedger = readJson(path.join(root, RUNTIME_GENERATIONS_FILE), 'runtime generation ledger');
533
+ if (runtimeGenerationLedger.brainVersion !== coverage?.releaseIdentity?.version
534
+ || runtimeGenerationLedger.releaseTag !== coverage?.releaseIdentity?.tag
535
+ || runtimeGenerationLedger.sourceSnapshot !== coverage?.releaseIdentity?.sourceSnapshot
536
+ || runtimeGenerationLedger.schemaVersion !== 2
537
+ || runtimeGenerationLedger.kind !== 'ruvnet-brain-runtime-generation-ledger') {
538
+ failures.push('runtime generation ledger release identity differs');
539
+ }
540
+ const fence = readJson(path.join(root, 'PRIVATE-STORES.json'), 'private fence');
541
+ const privateSet = new Set((fence.privateStores || []).map((name) => String(name).toLowerCase()));
542
+ const runtimeNames = Object.keys(runtimeGenerationLedger.stores || {});
543
+ const runtimePublicNames = runtimeNames.filter((name) => !privateSet.has(name.toLowerCase())).sort();
544
+ const undeclaredExtras = runtimeNames.filter((name) => !immutableNames.includes(name) && !privateSet.has(name.toLowerCase()));
545
+ if (undeclaredExtras.length) failures.push(`runtime generation ledger has unclassified stores: ${undeclaredExtras.sort().join(', ')}`);
546
+ const expectedRuntimePublicNames = (selectedPublicStores || immutableNames).slice().sort();
547
+ if (canonicalJson(runtimePublicNames) !== canonicalJson(expectedRuntimePublicNames)) {
548
+ failures.push('runtime public generation store set differs from immutable public ledger');
549
+ } else {
550
+ for (const name of expectedRuntimePublicNames) {
551
+ if (canonicalJson(runtimeGenerationLedger.stores[name]) !== canonicalJson(generationLedger.stores[name])) {
552
+ failures.push(`runtime public generation record differs for ${name}`);
553
+ }
554
+ }
555
+ }
556
+ } catch (error) { failures.push(`installed inventory: ${error.message}`); }
557
+ return {
558
+ valid: failures.length === 0,
559
+ failures,
560
+ coverage,
561
+ corpusCoverage,
562
+ generationLedger,
563
+ runtimeGenerationLedger,
564
+ publicInventory,
565
+ installedProfile,
566
+ coverageState,
567
+ coverageFile,
568
+ corpusFile,
569
+ coverageSha256: crypto.createHash('sha256').update(coverageBytes).digest('hex'),
570
+ corpusCoverageSha256: crypto.createHash('sha256').update(corpusBytes).digest('hex'),
571
+ };
572
+ } catch (error) {
573
+ return { valid: false, failures: [`coverage projection is unreadable: ${error.message}`], coverageFile, corpusFile };
574
+ }
575
+ }
@@ -440,7 +440,7 @@ EOF
440
440
  fi
441
441
 
442
442
  # ── Gate 3 output — a ≤12-line pointer; the FULL playbook is injected once by session-start.sh ───
443
- if [ "$BUILD" -eq 1 ]; then
443
+ if false; then
444
444
  cat <<'EOF'
445
445
  [RuvNet Brain — build turn: APPLY THE PLAYBOOK injected at session start]
446
446
  This is a build / change request — run THE PLAYBOOK (the standing build playbook from session start), beats A–D. DO FIRST, silently:
@@ -506,7 +506,7 @@ fi
506
506
  # the user cannot tell grounding from a confident guess, which is the exact failure this whole
507
507
  # project exists to kill. So the footer either names the source that was actually read, or openly
508
508
  # says none was read. A fabricated path would be strictly worse than silence.
509
- if [ "$RUVNET" -eq 1 ] || [ "$DRIFT" -eq 1 ] || [ "$BUILD" -eq 1 ] || [ "$HARNESS_QE" -eq 1 ] || [ "$AUTON" -eq 1 ]; then
509
+ if false; then
510
510
  cat <<EOF
511
511
  [RuvNet Brain — engaged on this prompt]
512
512
  End your response with exactly ONE dim line, nothing after it. Pick the form that is TRUE:
@@ -46,8 +46,25 @@ const INSTALLER = path.join(ROOT, 'bin', 'install.mjs');
46
46
  */
47
47
  export const NIGHTLY_LABEL = 'com.ruvnet.brain-update';
48
48
 
49
+ function schedulerEnvironment(env) {
50
+ const fixtureRoot = env.RUVNET_CONSOLE_ROOT;
51
+ if (!fixtureRoot) return env;
52
+ if (env.RUVNET_BRAIN_TEST !== '1') {
53
+ throw new Error('RUVNET_CONSOLE_ROOT scheduler isolation requires RUVNET_BRAIN_TEST=1');
54
+ }
55
+ if (!path.isAbsolute(fixtureRoot)) {
56
+ throw new Error('RUVNET_CONSOLE_ROOT must be an absolute path');
57
+ }
58
+ const isolatedHome = path.resolve(fixtureRoot);
59
+ if (isolatedHome === path.resolve(os.homedir())) {
60
+ throw new Error('RUVNET_CONSOLE_ROOT must not be the real user home in test mode');
61
+ }
62
+ return { ...env, HOME: isolatedHome, USERPROFILE: isolatedHome };
63
+ }
64
+
49
65
  export function nightlyArtifact({ env = process.env, platform = process.platform } = {}) {
50
- const home = env.HOME || os.homedir();
66
+ const schedulerEnv = schedulerEnvironment(env);
67
+ const home = schedulerEnv.HOME || os.homedir();
51
68
  return {
52
69
  supported: platform === 'darwin',
53
70
  platform,
@@ -71,7 +88,7 @@ export function nightlyStatus(options = {}) {
71
88
 
72
89
  export function applyNightlyChoice(enabled, options = {}) {
73
90
  if (typeof enabled !== 'boolean') return { ok: false, log: 'nightly must be true or false' };
74
- const env = options.env || process.env;
91
+ const env = schedulerEnvironment(options.env || process.env);
75
92
  const before = nightlyStatus({ ...options, env });
76
93
  if (!before.artifact.supported) return { ok: false, state: before, log: before.evidence };
77
94
  const run = spawnSync(process.execPath, [