ruvnet-brain 4.0.90-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 (74) hide show
  1. package/README.md +31 -7
  2. package/bin/install.mjs +153 -22
  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/decision-outcomes.mjs +0 -0
  23. package/plugin/scripts/ground-ruvnet.sh +2 -2
  24. package/plugin/scripts/nightly-controller.mjs +19 -2
  25. package/plugin/scripts/project-progression-contract.mjs +409 -0
  26. package/plugin/scripts/project-progression-hook.mjs +196 -0
  27. package/plugin/scripts/project-progression-outbox.mjs +95 -0
  28. package/plugin/scripts/project-progression-session-start.mjs +183 -0
  29. package/plugin/scripts/project-progression-store.mjs +248 -0
  30. package/plugin/scripts/project-store-resolver.mjs +106 -0
  31. package/plugin/scripts/session-snapshot-hook.mjs +29 -1
  32. package/plugin/scripts/session-start-core.mjs +22 -18
  33. package/plugin/scripts/spend-guard.mjs +6 -1
  34. package/scripts/adr-072-completion.mjs +98 -0
  35. package/scripts/behavioral-l1-l4.mjs +11 -8
  36. package/scripts/brain-score.mjs +23 -9
  37. package/scripts/build-bundle.mjs +94 -8
  38. package/scripts/card-from-source.mjs +140 -0
  39. package/scripts/corpus-aggregates.mjs +194 -0
  40. package/scripts/corpus-candidate.mjs +314 -0
  41. package/scripts/corpus-reconcile.mjs +540 -0
  42. package/scripts/corpus-seed-publish.mjs +110 -0
  43. package/scripts/coverage-integrity.mjs +3 -0
  44. package/scripts/gist-receipts.mjs +183 -0
  45. package/scripts/git-hooks/pre-push +21 -0
  46. package/scripts/host-registry.mjs +118 -0
  47. package/scripts/independent-review-receipt.mjs +499 -0
  48. package/scripts/ingest-gists.mjs +34 -5
  49. package/scripts/ingest-new-repos.mjs +127 -0
  50. package/scripts/metaharness-gate.mjs +58 -0
  51. package/scripts/nightly-gists.sh +3 -3
  52. package/scripts/nightly-wrapper.sh +105 -64
  53. package/scripts/product-integrity-contract.mjs +156 -0
  54. package/scripts/public-inventory.mjs +4 -0
  55. package/scripts/public-verification-aggregate.mjs +342 -0
  56. package/scripts/public-verification-finalizer.mjs +59 -0
  57. package/scripts/public-verification-inputs.mjs +473 -0
  58. package/scripts/public-verification-lane.mjs +244 -0
  59. package/scripts/publication-receipt.mjs +50 -6
  60. package/scripts/rebuild-gists-from-receipts.mjs +291 -0
  61. package/scripts/release-projection.mjs +86 -0
  62. package/scripts/release-transaction-provider.mjs +43 -102
  63. package/scripts/release-transaction.mjs +165 -49
  64. package/scripts/release.mjs +158 -3
  65. package/scripts/restore-local-ingests.mjs +2 -2
  66. package/scripts/retrieval-canary.mjs +533 -0
  67. package/scripts/routing-flywheel.mjs +12 -1
  68. package/scripts/rvf-generation.mjs +50 -0
  69. package/scripts/self-update.mjs +19 -44
  70. package/scripts/source-coverage.mjs +415 -0
  71. package/scripts/source-scope-receipt.mjs +84 -0
  72. package/scripts/sync-census.mjs +0 -0
  73. package/scripts/wired-check.mjs +114 -24
  74. package/scripts/worktree-integrity.mjs +123 -0
@@ -0,0 +1,533 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import crypto from 'node:crypto';
5
+ import { spawnSync } from 'node:child_process';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { canonicalJson, digest, validateCoverageLedger } from './coverage-integrity.mjs';
8
+
9
+ const HEX40 = /^[a-f0-9]{40}$/;
10
+ const HEX64 = /^[a-f0-9]{64}$/;
11
+
12
+ const ordered = (value) => [...value].sort((a, b) => a.localeCompare(b));
13
+ const storeOf = (row) => String(row?.artifact?.store || '').toLowerCase();
14
+ const setDigest = (values) => digest(ordered(values));
15
+
16
+ function checkedSet(values, expectedDigest, label) {
17
+ if (!Array.isArray(values) || values.some((value) => typeof value !== 'string' || !value)
18
+ || canonicalJson(values) !== canonicalJson(ordered(values)) || new Set(values).size !== values.length
19
+ || setDigest(values) !== expectedDigest) throw new Error(`${label} set is invalid`);
20
+ return values;
21
+ }
22
+
23
+ function defaultReadPassages(assetsDir, store) {
24
+ const file = path.join(assetsDir, `${store}.passages.jsonl`);
25
+ if (!fs.existsSync(file) || !fs.lstatSync(file).isFile()) throw new Error(`${store} passage inventory is missing`);
26
+ return fs.readFileSync(file, 'utf8').split('\n').filter(Boolean).map((line, index) => {
27
+ try { return JSON.parse(line); }
28
+ catch (error) { throw new Error(`${store} passage ${index + 1} is invalid: ${error.message}`); }
29
+ });
30
+ }
31
+
32
+ function planPayload(plan) {
33
+ const { planSha256: _planSha256, ...payload } = plan;
34
+ return payload;
35
+ }
36
+
37
+ function regular(file, label) {
38
+ const resolved = path.resolve(file || '');
39
+ const stat = fs.lstatSync(resolved);
40
+ if (!stat.isFile() || stat.isSymbolicLink()) throw new Error(`${label} is not a trusted regular file`);
41
+ return resolved;
42
+ }
43
+
44
+ function queryEvidenceSourcePayload(evidence) {
45
+ return {
46
+ schemaVersion: evidence?.schemaVersion,
47
+ kind: evidence?.kind,
48
+ queryStoreSetSha256: evidence?.queryStoreSetSha256,
49
+ queries: evidence?.queries,
50
+ };
51
+ }
52
+
53
+ const normalizedQuery = (query) => String(query || '').trim().replace(/\s+/g, ' ').toLowerCase();
54
+
55
+ function queryEvidenceReceiptPayload(evidence) {
56
+ const { receiptSha256: _receiptSha256, ...payload } = evidence;
57
+ return payload;
58
+ }
59
+
60
+ export function sealRetrievalQueryEvidence(evidence) {
61
+ const sourcePayload = queryEvidenceSourcePayload(evidence);
62
+ const payload = {
63
+ ...sourcePayload,
64
+ sourceCommit: evidence?.sourceCommit,
65
+ sourcePath: evidence?.sourcePath,
66
+ sourceBlobSha256: digest(sourcePayload),
67
+ };
68
+ return { ...payload, receiptSha256: digest(payload) };
69
+ }
70
+
71
+ export function validateRetrievalQueryEvidence(evidence) {
72
+ const keys = Object.keys(evidence || {}).sort();
73
+ const expectedKeys = ['kind', 'queries', 'queryStoreSetSha256', 'receiptSha256', 'schemaVersion',
74
+ 'sourceBlobSha256', 'sourceCommit', 'sourcePath'];
75
+ if (canonicalJson(keys) !== canonicalJson(expectedKeys)
76
+ || evidence?.schemaVersion !== 2 || evidence?.kind !== 'ruvnet-brain-retrieval-query-evidence'
77
+ || !HEX40.test(String(evidence.sourceCommit || '')) || typeof evidence.sourcePath !== 'string'
78
+ || !evidence.sourcePath || path.isAbsolute(evidence.sourcePath)
79
+ || evidence.sourcePath.split(/[\\/]/).includes('..')
80
+ || !HEX64.test(String(evidence.queryStoreSetSha256 || ''))
81
+ || !evidence.queries || typeof evidence.queries !== 'object' || Array.isArray(evidence.queries)
82
+ || Object.keys(evidence.queries).length === 0
83
+ || evidence.sourceBlobSha256 !== digest(queryEvidenceSourcePayload(evidence))
84
+ || evidence.receiptSha256 !== digest(queryEvidenceReceiptPayload(evidence))) {
85
+ throw new Error('independent retrieval query evidence is malformed');
86
+ }
87
+ const stores = ordered(Object.keys(evidence.queries));
88
+ if (digest(stores) !== evidence.queryStoreSetSha256) {
89
+ throw new Error('independent retrieval query evidence store set is invalid');
90
+ }
91
+ const seenQueries = new Set();
92
+ for (const store of stores) {
93
+ const row = evidence.queries[store];
94
+ const expected = row?.expected;
95
+ const normalized = normalizedQuery(row?.query);
96
+ if (Object.keys(row || {}).sort().join(',') !== 'expected,query,recordSha256'
97
+ || typeof row.query !== 'string' || row.query !== row.query.trim().replace(/\s+/g, ' ')
98
+ || normalized.length < 24 || seenQueries.has(normalized)
99
+ || !expected || Object.keys(expected).sort().join(',') !== 'passageSha256,path'
100
+ || typeof expected.path !== 'string' || !expected.path || path.isAbsolute(expected.path)
101
+ || expected.path.split(/[\\/]/).includes('..')
102
+ || !HEX64.test(String(expected.passageSha256 || ''))
103
+ || row.recordSha256 !== digest({ store, query: row.query, expected })) {
104
+ throw new Error(`independent retrieval query evidence for ${store} is malformed`);
105
+ }
106
+ seenQueries.add(normalized);
107
+ }
108
+ return evidence;
109
+ }
110
+
111
+ export function verifyQueryOracleSource(queryEvidence, candidateSourceSha, { cwd = process.cwd(), run = spawnSync } = {}) {
112
+ validateRetrievalQueryEvidence(queryEvidence);
113
+ if (!HEX40.test(String(candidateSourceSha || '')) || queryEvidence.sourceCommit === candidateSourceSha) {
114
+ throw new Error('query oracle source identity is invalid');
115
+ }
116
+ const ancestor = run('git', ['merge-base', '--is-ancestor', queryEvidence.sourceCommit, candidateSourceSha],
117
+ { cwd, encoding: 'utf8' });
118
+ if (ancestor.error || ancestor.status !== 0) throw new Error('query oracle source is not a strict candidate ancestor');
119
+ const shown = run('git', ['show', `${queryEvidence.sourceCommit}:${queryEvidence.sourcePath}`],
120
+ { cwd, encoding: null, maxBuffer: 64 * 1024 * 1024 });
121
+ let sourcePayload;
122
+ try { sourcePayload = queryEvidenceSourcePayload(JSON.parse(Buffer.from(shown.stdout || '').toString('utf8'))); }
123
+ catch { throw new Error('query oracle source payload is not valid JSON'); }
124
+ if (shown.error || shown.status !== 0 || digest(sourcePayload) !== queryEvidence.sourceBlobSha256) {
125
+ throw new Error('query oracle source payload differs from its immutable Git identity');
126
+ }
127
+ const candidateShown = run('git', ['show', `${candidateSourceSha}:${queryEvidence.sourcePath}`],
128
+ { cwd, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 });
129
+ let candidateEvidence;
130
+ try { candidateEvidence = JSON.parse(String(candidateShown.stdout || '')); }
131
+ catch { throw new Error('candidate query oracle evidence is not valid JSON'); }
132
+ if (candidateShown.error || candidateShown.status !== 0
133
+ || canonicalJson(candidateEvidence) !== canonicalJson(queryEvidence)) {
134
+ throw new Error('supplied query oracle evidence differs from candidate tracked bytes');
135
+ }
136
+ return queryEvidence;
137
+ }
138
+
139
+ const sha256Buffer = (bytes) => crypto.createHash('sha256').update(bytes).digest('hex');
140
+ export function validateRetrievalCanaryPlan(plan) {
141
+ const observedBaseline = plan?.baseline?.kind === 'ruvnet-brain-observed-failed-public-baseline';
142
+ const baselineReceiptValid = observedBaseline
143
+ ? plan.baseline.integrity === 'DEGRADED' && plan.baseline.historicalCorpusReceipt === false && plan.baseline.candidateVerificationEligible === false
144
+ && HEX64.test(String(plan.baseline.observationReceiptSha256 || '')) && HEX64.test(String(plan.baseline.discrepancyDigest || ''))
145
+ : plan?.baseline?.kind === 'ruvnet-brain-verified-public-baseline'
146
+ && HEX64.test(String(plan?.baseline?.verificationReceiptSha256 || ''));
147
+ if (plan?.schemaVersion !== 2 || plan?.kind !== 'ruvnet-brain-retrieval-canary-plan'
148
+ || !HEX64.test(String(plan.coverage?.sha256 || '')) || !Number.isSafeInteger(plan.coverage?.bytes)
149
+ || plan.coverage.bytes < 1 || !HEX64.test(String(plan.coverage?.releaseCoverageGeneration || ''))
150
+ || !HEX64.test(String(plan.baseline?.archiveSha256 || '')) || !baselineReceiptValid
151
+ || !HEX64.test(String(plan.baseline?.archiveManifestSha256 || '')) || !Number.isSafeInteger(plan.baseline?.archiveBytes)
152
+ || !Number.isSafeInteger(plan.baseline?.storeCount) || plan.baseline.storeCount < 1
153
+ || !Array.isArray(plan.baseline?.stores) || plan.baseline.stores.length !== plan.baseline.storeCount
154
+ || !HEX64.test(String(plan.baseline?.storeSetSha256 || ''))
155
+ || !HEX40.test(String(plan.candidate?.sourceSha || '')) || !HEX64.test(String(plan.candidate?.packageSha256 || ''))
156
+ || !HEX64.test(String(plan.candidate?.archiveSha256 || ''))
157
+ || !HEX64.test(String(plan.candidate?.coverageSha256 || '')) || !HEX64.test(String(plan.candidate?.publicLedgerSha256 || ''))
158
+ || !HEX64.test(String(plan.candidate?.publicInventoryPartitionSha256 || ''))
159
+ || !Number.isSafeInteger(plan.candidate?.publicLedgerBytes) || plan.candidate.publicLedgerBytes < 1
160
+ || !Number.isSafeInteger(plan.candidate?.publicStoreCount) || plan.candidate.publicStoreCount < 1
161
+ || !HEX64.test(String(plan.oracle?.receiptSha256 || ''))
162
+ || !HEX64.test(String(plan.oracle?.queryStoreSetSha256 || ''))
163
+ || !HEX40.test(String(plan.oracle?.sourceCommit || '')) || !HEX64.test(String(plan.oracle?.sourceBlobSha256 || ''))
164
+ || !plan.denominator || plan.k !== 10
165
+ || !Array.isArray(plan.cases) || plan.cases.length === 0) throw new Error('retrieval canary plan is malformed');
166
+ const ids = plan.cases.map((row) => row.id);
167
+ const evidence = validateRetrievalQueryEvidence(plan.oracle?.evidence);
168
+ if (evidence.receiptSha256 !== plan.oracle.receiptSha256
169
+ || evidence.queryStoreSetSha256 !== plan.oracle.queryStoreSetSha256
170
+ || evidence.sourceCommit !== plan.oracle.sourceCommit
171
+ || evidence.sourceBlobSha256 !== plan.oracle.sourceBlobSha256) {
172
+ throw new Error('retrieval canary oracle differs from its sealed query evidence');
173
+ }
174
+ const baselineStores = ordered(plan.baseline.stores.map((store) => String(store).toLowerCase()));
175
+ if (new Set(baselineStores).size !== baselineStores.length
176
+ || digest(baselineStores) !== plan.baseline.storeSetSha256) throw new Error('retrieval baseline store set is invalid');
177
+ checkedSet(plan.denominator.eligibleStores, plan.denominator.eligibleStoreSetSha256, 'eligible denominator');
178
+ checkedSet(plan.denominator.deltaStores, plan.denominator.deltaStoreSetSha256, 'delta denominator');
179
+ checkedSet(plan.denominator.legacyPopulationStores, plan.denominator.legacyPopulationStoreSetSha256,
180
+ 'legacy population denominator');
181
+ checkedSet(plan.denominator.legacySelectedStores, plan.denominator.legacySelectedStoreSetSha256,
182
+ 'legacy selected denominator');
183
+ if (setDigest(plan.denominator.eligibleStores) !== plan.oracle.queryStoreSetSha256) {
184
+ throw new Error('oracle denominator differs from eligible coverage');
185
+ }
186
+ if (new Set(ids).size !== ids.length) throw new Error('retrieval canary plan has duplicate case ids');
187
+ if (!plan.cases.some(({ cohort }) => cohort === 'delta') || !plan.cases.some(({ cohort }) => cohort === 'legacy')) {
188
+ throw new Error('retrieval canary plan requires delta and legacy cohorts');
189
+ }
190
+ for (const row of plan.cases) {
191
+ const oracleRow = evidence.queries[row.expected?.repo];
192
+ if (!['delta', 'legacy'].includes(row.cohort) || typeof row.query !== 'string' || row.query.length < 24
193
+ || typeof row.expected?.repo !== 'string' || !row.expected.repo
194
+ || typeof row.expected?.path !== 'string' || !row.expected.path
195
+ || row.id !== `${row.cohort}:${row.expected.repo}`
196
+ || !HEX64.test(String(row.expected?.passageSha256 || ''))
197
+ || !HEX64.test(String(row.oracleRecordSha256 || ''))
198
+ || !oracleRow || row.query !== oracleRow.query
199
+ || canonicalJson(row.expected) !== canonicalJson({ repo: row.expected.repo, ...oracleRow.expected })
200
+ || row.oracleRecordSha256 !== oracleRow.recordSha256) {
201
+ throw new Error(`retrieval canary ${row.id || '(missing)'} is malformed`);
202
+ }
203
+ }
204
+ const deltaCases = ordered(plan.cases.filter(({ cohort }) => cohort === 'delta').map(({ expected }) => expected.repo));
205
+ const legacyCases = ordered(plan.cases.filter(({ cohort }) => cohort === 'legacy').map(({ expected }) => expected.repo));
206
+ if (canonicalJson(deltaCases) !== canonicalJson(plan.denominator.deltaStores)
207
+ || canonicalJson(legacyCases) !== canonicalJson(plan.denominator.legacySelectedStores)
208
+ || plan.cohorts?.delta !== deltaCases.length || plan.cohorts?.legacy !== legacyCases.length) {
209
+ throw new Error('retrieval canary cases differ from the sealed denominator');
210
+ }
211
+ if (digest(planPayload(plan)) !== plan.planSha256) throw new Error('retrieval canary plan digest mismatch');
212
+ return plan;
213
+ }
214
+ export function validatePlanAgainstCoverage(plan, coverage, { allowObservedBaseline = false } = {}) {
215
+ validateRetrievalCanaryPlan(plan);
216
+ if (plan.baseline.kind === 'ruvnet-brain-observed-failed-public-baseline' && !allowObservedBaseline) {
217
+ throw new Error('observed failed-public baseline is not candidate-verification eligible');
218
+ }
219
+ const checked = validateCoverageLedger(coverage);
220
+ if (!checked.valid) throw new Error(`coverage ledger is invalid: ${checked.failures.join('; ')}`);
221
+ const generation = coverage.kind === 'ruvnet-brain-release-coverage'
222
+ ? coverage.releaseCoverageGeneration : coverage.coverageGeneration;
223
+ const eligible = ordered(coverage.rows.filter((row) => row.kind === 'repository'
224
+ && row.disposition === 'eligible' && row.status === 'CURRENT').map(storeOf));
225
+ if (!eligible.length || new Set(eligible).size !== eligible.length) throw new Error('eligible coverage denominator is invalid');
226
+ const baseline = new Set(plan.baseline.stores);
227
+ const delta = eligible.filter((store) => !baseline.has(store));
228
+ const legacy = eligible.filter((store) => baseline.has(store));
229
+ if (generation !== plan.coverage.releaseCoverageGeneration
230
+ || canonicalJson(eligible) !== canonicalJson(plan.denominator.eligibleStores)
231
+ || canonicalJson(delta) !== canonicalJson(plan.denominator.deltaStores)
232
+ || canonicalJson(legacy) !== canonicalJson(plan.denominator.legacyPopulationStores)
233
+ || plan.denominator.legacySelectedStores.some((store) => !legacy.includes(store))) {
234
+ throw new Error('retrieval canary plan differs from exact coverage denominator');
235
+ }
236
+ return plan;
237
+ }
238
+ export function buildRetrievalCanaryPlan({ coverage, baseline, candidate, coverageIdentity = null, queryEvidence, assetsDir = '.',
239
+ readPassages = defaultReadPassages, legacySampleSize } = {}) {
240
+ const checked = validateCoverageLedger(coverage);
241
+ if (!checked.valid) throw new Error(`coverage ledger is invalid: ${checked.failures.join('; ')}`);
242
+ const coverageGeneration = coverage.kind === 'ruvnet-brain-release-coverage'
243
+ ? coverage.releaseCoverageGeneration : coverage.coverageGeneration;
244
+ if (!HEX64.test(String(coverageGeneration || ''))) throw new Error('coverage has no exact generation identity');
245
+ const observedBaseline = baseline?.kind === 'ruvnet-brain-observed-failed-public-baseline';
246
+ const receiptValid = observedBaseline
247
+ ? baseline?.integrity === 'DEGRADED' && baseline?.historicalCorpusReceipt === false && baseline?.candidateVerificationEligible === false
248
+ && HEX64.test(String(baseline?.observationReceiptSha256 || '')) && HEX64.test(String(baseline?.discrepancyDigest || ''))
249
+ && Array.isArray(baseline?.ledgerDiscrepancies) && Array.isArray(baseline?.provenanceGaps)
250
+ : baseline?.kind === 'ruvnet-brain-verified-public-baseline'
251
+ && HEX64.test(String(baseline?.verificationReceiptSha256 || ''));
252
+ if (baseline?.schemaVersion !== 1 || !receiptValid
253
+ || !HEX64.test(String(baseline.archiveSha256 || '')) || !Number.isSafeInteger(baseline.archiveBytes) || baseline.archiveBytes < 1
254
+ || !HEX64.test(String(baseline.archiveManifestSha256 || ''))
255
+ || !Array.isArray(baseline.stores) || baseline.stores.length === 0 || baseline.storeCount !== baseline.stores.length
256
+ || new Set(baseline.stores.map((store) => String(store).toLowerCase())).size !== baseline.stores.length) {
257
+ throw new Error('verified failed-public baseline identity is malformed');
258
+ }
259
+ if (!candidate || (coverage.kind === 'ruvnet-brain-release-coverage'
260
+ && candidate.sourceSha !== coverage.releaseIdentity?.sourceSnapshot)) {
261
+ throw new Error('candidate source identity differs from release coverage');
262
+ }
263
+ if (!HEX40.test(String(candidate?.sourceSha || '')) || !HEX64.test(String(candidate?.packageSha256 || ''))
264
+ || !HEX64.test(String(candidate?.archiveSha256 || ''))
265
+ || !HEX64.test(String(candidate?.coverageSha256 || '')) || !HEX64.test(String(candidate?.publicLedgerSha256 || ''))
266
+ || !Number.isSafeInteger(candidate?.publicLedgerBytes) || candidate.publicLedgerBytes < 1
267
+ || !Number.isSafeInteger(candidate?.publicStoreCount) || candidate.publicStoreCount < 1
268
+ || !HEX64.test(String(candidate?.publicInventoryPartitionSha256 || ''))) {
269
+ throw new Error('candidate public artifact identity is malformed');
270
+ }
271
+ if (coverage.kind === 'ruvnet-brain-release-coverage') {
272
+ const baselineReceiptSha256 = observedBaseline ? baseline.observationReceiptSha256 : baseline.verificationReceiptSha256;
273
+ if (baseline.tag !== coverage.corpusSeed?.tag || baseline.archiveSha256 !== coverage.corpusSeed?.archiveSha256
274
+ || baseline.archiveBytes !== coverage.corpusSeed?.archiveBytes || baselineReceiptSha256 !== coverage.corpusSeed?.receiptSha256) {
275
+ throw new Error('failed-public baseline differs from the release corpus seed identity');
276
+ }
277
+ if (candidate.publicLedgerSha256 !== coverage.generationLedger?.sha256
278
+ || candidate.publicLedgerBytes !== coverage.generationLedger?.bytes
279
+ || candidate.publicStoreCount !== coverage.generationLedger?.storeCount
280
+ || candidate.publicInventoryPartitionSha256 !== coverage.publicInventoryPartitionSha256
281
+ || !coverageIdentity || candidate.coverageSha256 !== coverageIdentity.sha256
282
+ || !Number.isSafeInteger(coverageIdentity.bytes) || coverageIdentity.bytes < 1) {
283
+ throw new Error('candidate identity differs from exact release coverage evidence');
284
+ }
285
+ }
286
+ validateRetrievalQueryEvidence(queryEvidence);
287
+ if (queryEvidence.sourceCommit === candidate.sourceSha) throw new Error('independent query source is not pre-candidate');
288
+ const eligible = coverage.rows.filter((row) => row.kind === 'repository' && row.disposition === 'eligible');
289
+ if (!eligible.length || eligible.some((row) => row.status !== 'CURRENT' || !storeOf(row))) {
290
+ throw new Error('eligible repository coverage is incomplete');
291
+ }
292
+ const duplicateStores = eligible.map(storeOf).filter((store, index, stores) => stores.indexOf(store) !== index);
293
+ if (duplicateStores.length) throw new Error(`eligible repository stores are duplicated: ${ordered(new Set(duplicateStores)).join(', ')}`);
294
+ const eligibleStores = ordered(eligible.map(storeOf));
295
+ if (queryEvidence.queryStoreSetSha256 !== setDigest(eligibleStores)
296
+ || canonicalJson(ordered(Object.keys(queryEvidence.queries))) !== canonicalJson(eligibleStores)) {
297
+ throw new Error('independent query oracle does not cover the exact eligible store set');
298
+ }
299
+ const baselineStores = new Set(baseline.stores.map((name) => String(name).toLowerCase()));
300
+ const delta = eligible.filter((row) => !baselineStores.has(storeOf(row)));
301
+ const legacyPool = eligible.filter((row) => baselineStores.has(storeOf(row)));
302
+ if (!delta.length || !legacyPool.length) throw new Error('coverage does not establish both failed-seed delta and legacy cohorts');
303
+ const passages = new Map(eligible.map((row) => [storeOf(row), readPassages(assetsDir, storeOf(row))]));
304
+ const rankedLegacy = legacyPool.map((row) => ({ row, count: passages.get(storeOf(row)).length }))
305
+ .sort((a, b) => a.count - b.count || storeOf(a.row).localeCompare(storeOf(b.row)));
306
+ const strata = new Map();
307
+ rankedLegacy.forEach((entry, index) => {
308
+ const stratum = Math.min(3, Math.floor(index * 4 / rankedLegacy.length));
309
+ if (!strata.has(stratum)) strata.set(stratum, []);
310
+ strata.get(stratum).push(entry);
311
+ });
312
+ const sampleCount = Math.min(legacyPool.length, legacySampleSize ?? Math.max(10, Math.ceil(eligible.length * 0.1)));
313
+ if (sampleCount < strata.size) throw new Error(`legacy sample must cover all ${strata.size} passage-count strata`);
314
+ const selected = [];
315
+ for (const [stratum, entries] of strata) {
316
+ entries.sort((a, b) => digest(`${coverageGeneration}:${stratum}:${storeOf(a.row)}`)
317
+ .localeCompare(digest(`${coverageGeneration}:${stratum}:${storeOf(b.row)}`)));
318
+ selected.push({ ...entries.shift(), stratum });
319
+ }
320
+ while (selected.length < sampleCount) {
321
+ let advanced = false;
322
+ for (const [stratum, entries] of strata) if (entries.length && selected.length < sampleCount) {
323
+ selected.push({ ...entries.shift(), stratum }); advanced = true;
324
+ }
325
+ if (!advanced) break;
326
+ }
327
+ const legacy = selected.map(({ row, stratum, count }) => ({ row, stratum, passageCount: count }));
328
+ const cases = [...delta.map((row) => ({ row, cohort: 'delta' })),
329
+ ...legacy.map(({ row, stratum, passageCount }) => ({ row, cohort: 'legacy', stratum, passageCount }))]
330
+ .map(({ row, cohort, stratum = null, passageCount }) => {
331
+ const store = storeOf(row);
332
+ const observedPassageCount = passageCount ?? passages.get(store).length;
333
+ const evidence = queryEvidence.queries[store];
334
+ const matches = passages.get(store).filter((row) => row.path === evidence?.expected?.path
335
+ && digest(row) === evidence.expected.passageSha256);
336
+ if (!evidence || matches.length !== 1) {
337
+ throw new Error(`${store} has no sealed independent query evidence`);
338
+ }
339
+ return {
340
+ id: `${cohort}:${store}`,
341
+ cohort,
342
+ stratum,
343
+ passageCount: observedPassageCount,
344
+ query: evidence.query.trim(),
345
+ oracleRecordSha256: evidence.recordSha256,
346
+ expected: { repo: store, ...evidence.expected },
347
+ source: { key: row.key, upstreamSha: row.upstream?.sha || null, artifactSha256: row.artifact?.rvfSha256 || null,
348
+ queryEvidenceReceiptSha256: queryEvidence.receiptSha256 },
349
+ };
350
+ }).sort((a, b) => a.id.localeCompare(b.id));
351
+ const payload = {
352
+ schemaVersion: 2,
353
+ kind: 'ruvnet-brain-retrieval-canary-plan',
354
+ coverage: { sha256: coverageIdentity.sha256, bytes: coverageIdentity.bytes,
355
+ releaseCoverageGeneration: coverageGeneration },
356
+ baseline: { kind: baseline.kind, tag: baseline.tag, archiveSha256: baseline.archiveSha256, archiveBytes: baseline.archiveBytes,
357
+ archiveManifestSha256: baseline.archiveManifestSha256,
358
+ ...(observedBaseline ? { integrity: baseline.integrity, historicalCorpusReceipt: false,
359
+ candidateVerificationEligible: false, observationReceiptSha256: baseline.observationReceiptSha256,
360
+ discrepancyDigest: baseline.discrepancyDigest } : { verificationReceiptSha256: baseline.verificationReceiptSha256 }),
361
+ stores: ordered(baseline.stores.map((store) => String(store).toLowerCase())),
362
+ storeCount: baseline.storeCount,
363
+ storeSetSha256: digest(ordered(baseline.stores.map((store) => String(store).toLowerCase()))) },
364
+ candidate: structuredClone(candidate),
365
+ denominator: {
366
+ eligibleStores,
367
+ eligibleStoreSetSha256: setDigest(eligibleStores),
368
+ deltaStores: ordered(delta.map(storeOf)),
369
+ deltaStoreSetSha256: setDigest(delta.map(storeOf)),
370
+ legacyPopulationStores: ordered(legacyPool.map(storeOf)),
371
+ legacyPopulationStoreSetSha256: setDigest(legacyPool.map(storeOf)),
372
+ legacySelectedStores: ordered(legacy.map(({ row }) => storeOf(row))),
373
+ legacySelectedStoreSetSha256: setDigest(legacy.map(({ row }) => storeOf(row))),
374
+ },
375
+ oracle: { receiptSha256: queryEvidence.receiptSha256,
376
+ queryStoreSetSha256: queryEvidence.queryStoreSetSha256,
377
+ sourceCommit: queryEvidence.sourceCommit, sourceBlobSha256: queryEvidence.sourceBlobSha256,
378
+ evidence: structuredClone(queryEvidence) },
379
+ cohorts: { delta: delta.length, legacy: legacy.length,
380
+ legacyStrata: [...strata.keys()].sort().map((stratum) => ({ stratum,
381
+ population: rankedLegacy.filter((_entry, index) => Math.min(3, Math.floor(index * 4 / rankedLegacy.length)) === stratum).length,
382
+ selected: legacy.filter((entry) => entry.stratum === stratum).length })) },
383
+ k: 10,
384
+ cases,
385
+ };
386
+ const plan = validateRetrievalCanaryPlan({ ...payload, planSha256: digest(payload) });
387
+ return validatePlanAgainstCoverage(plan, coverage, { allowObservedBaseline: observedBaseline });
388
+ }
389
+ function resultRows(value) {
390
+ if (Array.isArray(value)) return value;
391
+ if (Array.isArray(value?.results)) return value.results;
392
+ throw new Error('search result has no results array');
393
+ }
394
+
395
+ function receiptPayload(receipt) {
396
+ const { receiptSha256: _receiptSha256, ...payload } = receipt;
397
+ return payload;
398
+ }
399
+ export function validateRetrievalCanaryReceipt(receipt, { plan, requireAcceptance = true } = {}) {
400
+ validateRetrievalCanaryPlan(plan);
401
+ if (receipt?.schemaVersion !== 1 || receipt?.kind !== 'ruvnet-brain-retrieval-canary-receipt'
402
+ || !HEX40.test(String(receipt.sourceSha || '')) || !HEX64.test(String(receipt.artifactSha256 || ''))
403
+ || !HEX64.test(String(receipt.candidateArchiveSha256 || ''))
404
+ || !HEX64.test(String(receipt.coverageGeneration || '')) || !HEX64.test(String(receipt.planSha256 || ''))
405
+ || !Array.isArray(receipt.cases) || !receipt.cases.length) throw new Error('retrieval canary receipt is malformed');
406
+ if (digest(receiptPayload(receipt)) !== receipt.receiptSha256) throw new Error('retrieval canary receipt digest mismatch');
407
+ if (receipt.sourceSha !== plan.candidate.sourceSha
408
+ || receipt.candidateArchiveSha256 !== plan.candidate.archiveSha256
409
+ || receipt.artifactSha256 !== plan.candidate.packageSha256
410
+ || receipt.coverageGeneration !== plan.coverage.releaseCoverageGeneration
411
+ || receipt.planSha256 !== plan.planSha256 || receipt.k !== 10 || plan.k !== 10
412
+ || canonicalJson(receipt.cases.map(({ id, cohort }) => ({ id, cohort })))
413
+ !== canonicalJson(plan.cases.map(({ id, cohort }) => ({ id, cohort })))) {
414
+ throw new Error('retrieval canary receipt denominator differs from the sealed plan');
415
+ }
416
+ if (receipt.cases.some((row) => !['COMPLETED', 'UNKNOWN', 'SKIPPED'].includes(row.status)
417
+ || (row.retrievalHit === true && (row.status !== 'COMPLETED' || !Number.isInteger(row.rank)
418
+ || row.rank < 1 || row.rank > 10))
419
+ || (row.retrievalHit !== true && (row.rank !== null || row.citationResolved === true)))) {
420
+ throw new Error('retrieval canary case state is invalid');
421
+ }
422
+ for (const row of receipt.cases) {
423
+ const expected = plan.cases.find(({ id }) => id === row.id)?.expected;
424
+ const ranked = row.retrievalHit ? row.citations?.[row.rank - 1] : null;
425
+ if (row.retrievalHit && (String(ranked?.repo || '').toLowerCase() !== expected.repo
426
+ || ranked?.path !== expected.path
427
+ || (row.citationResolved === true && (row.citationEvidence?.passageSha256 !== expected.passageSha256
428
+ || !HEX64.test(String(row.citationEvidence?.passageFileSha256 || '')))))) {
429
+ throw new Error(`retrieval canary ${row.id} hit or citation evidence differs from the sealed plan`);
430
+ }
431
+ }
432
+ const delta = receipt.cases.filter(({ cohort }) => cohort === 'delta');
433
+ const hits = receipt.cases.filter(({ retrievalHit }) => retrievalHit === true).length;
434
+ const unknown = receipt.cases.filter(({ status }) => status === 'UNKNOWN').length;
435
+ const skipped = receipt.cases.filter(({ status }) => status === 'SKIPPED').length;
436
+ const metrics = {
437
+ total: receipt.cases.length,
438
+ hits,
439
+ deltaTotal: delta.length,
440
+ deltaHits: delta.filter(({ retrievalHit }) => retrievalHit === true).length,
441
+ deltaCitations: delta.filter(({ retrievalHit, citationResolved }) => retrievalHit === true && citationResolved === true).length,
442
+ recallAt10: hits / receipt.cases.length,
443
+ deltaCitationRate: delta.length ? delta.filter(({ retrievalHit, citationResolved }) => retrievalHit === true && citationResolved === true).length / delta.length : 0,
444
+ unknown,
445
+ skipped,
446
+ };
447
+ if (canonicalJson(metrics) !== canonicalJson(receipt.metrics)) throw new Error('retrieval canary metrics were not derived from case evidence');
448
+ if (requireAcceptance && (metrics.deltaCitationRate !== 1 || metrics.recallAt10 < 0.98 || unknown || skipped)) {
449
+ throw new Error('retrieval canary acceptance failed');
450
+ }
451
+ return receipt;
452
+ }
453
+
454
+ export async function runRetrievalCanaries({ plan, sourceSha, artifactSha256, candidateArchiveSha256,
455
+ search, citationResolver, concurrency = 3 } = {}) {
456
+ validateRetrievalCanaryPlan(plan);
457
+ if (!HEX40.test(String(sourceSha || '')) || !HEX64.test(String(artifactSha256 || ''))
458
+ || !HEX64.test(String(candidateArchiveSha256 || '')) || sourceSha !== plan.candidate.sourceSha
459
+ || artifactSha256 !== plan.candidate.packageSha256
460
+ || candidateArchiveSha256 !== plan.candidate.archiveSha256
461
+ || typeof search !== 'function' || typeof citationResolver !== 'function') {
462
+ throw new Error('retrieval canary execution identity is invalid');
463
+ }
464
+ const cases = new Array(plan.cases.length);
465
+ let next = 0;
466
+ await Promise.all(Array.from({ length: Math.min(Math.max(1, concurrency), plan.cases.length) }, async () => {
467
+ for (let index = next++; index < plan.cases.length; index = next++) {
468
+ const canary = plan.cases[index];
469
+ try {
470
+ const rows = resultRows(await search({ query: canary.query, k: 10 }));
471
+ const top = rows.slice(0, 10);
472
+ const rank = top.findIndex((row) => String(row?.repo || '').toLowerCase() === canary.expected.repo
473
+ && row?.path === canary.expected.path);
474
+ const matched = rank >= 0 ? top[rank] : null;
475
+ const resolved = matched ? await citationResolver(matched, canary.expected) : { resolved: false };
476
+ cases[index] = { id: canary.id, cohort: canary.cohort, status: 'COMPLETED', retrievalHit: rank >= 0,
477
+ citationResolved: resolved?.resolved === true, citationEvidence: resolved?.evidence || null,
478
+ rank: rank >= 0 ? rank + 1 : null, citations: top.map(({ repo, path: hitPath }) => ({ repo, path: hitPath })) };
479
+ } catch (error) {
480
+ cases[index] = { id: canary.id, cohort: canary.cohort, status: 'UNKNOWN', retrievalHit: false,
481
+ citationResolved: false, citationEvidence: null, rank: null, citations: [], error: error.message };
482
+ }
483
+ }
484
+ }));
485
+ const delta = cases.filter(({ cohort }) => cohort === 'delta');
486
+ const hits = cases.filter(({ retrievalHit }) => retrievalHit).length;
487
+ const metrics = { total: cases.length, hits, deltaTotal: delta.length, deltaHits: delta.filter(({ retrievalHit }) => retrievalHit).length,
488
+ deltaCitations: delta.filter(({ retrievalHit, citationResolved }) => retrievalHit && citationResolved).length,
489
+ recallAt10: hits / cases.length, deltaCitationRate: delta.length ? delta.filter(({ retrievalHit, citationResolved }) => retrievalHit && citationResolved).length / delta.length : 0,
490
+ unknown: cases.filter(({ status }) => status === 'UNKNOWN').length,
491
+ skipped: cases.filter(({ status }) => status === 'SKIPPED').length };
492
+ const payload = { schemaVersion: 1, kind: 'ruvnet-brain-retrieval-canary-receipt', sourceSha, artifactSha256,
493
+ candidateArchiveSha256,
494
+ coverageGeneration: plan.coverage.releaseCoverageGeneration, planSha256: plan.planSha256, k: 10, cases, metrics };
495
+ return validateRetrievalCanaryReceipt({ ...payload, receiptSha256: digest(payload) }, { plan, requireAcceptance: false });
496
+ }
497
+
498
+ function arg(argv, name) {
499
+ const index = argv.indexOf(name);
500
+ return index >= 0 ? argv[index + 1] : null;
501
+ }
502
+
503
+ export function buildPlanFromFiles({ coverageFile, baselineFile, candidateFile, oracleFile, assetsDir,
504
+ outFile, cwd = process.cwd() }) {
505
+ const coveragePath = regular(coverageFile, 'coverage');
506
+ const coverageBytes = fs.readFileSync(coveragePath);
507
+ const coverage = JSON.parse(coverageBytes);
508
+ const baseline = JSON.parse(fs.readFileSync(regular(baselineFile, 'baseline'), 'utf8'));
509
+ const candidate = JSON.parse(fs.readFileSync(regular(candidateFile, 'candidate identity'), 'utf8'));
510
+ const queryEvidence = JSON.parse(fs.readFileSync(regular(oracleFile, 'query oracle'), 'utf8'));
511
+ verifyQueryOracleSource(queryEvidence, candidate.sourceSha, { cwd });
512
+ const plan = buildRetrievalCanaryPlan({ coverage, baseline, candidate, queryEvidence,
513
+ coverageIdentity: { sha256: sha256Buffer(coverageBytes), bytes: coverageBytes.length },
514
+ assetsDir: regular(path.join(path.resolve(assetsDir), 'PRIVATE-STORES.json'), 'assets private fence')
515
+ && path.resolve(assetsDir) });
516
+ const output = path.resolve(outFile || 'release-evidence/retrieval-canary-plan.json');
517
+ fs.mkdirSync(path.dirname(output), { recursive: true });
518
+ fs.writeFileSync(output, `${JSON.stringify(plan, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
519
+ return plan;
520
+ }
521
+
522
+ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
523
+ try {
524
+ const argv = process.argv.slice(2);
525
+ const plan = buildPlanFromFiles({ coverageFile: arg(argv, '--coverage'), baselineFile: arg(argv, '--baseline'),
526
+ candidateFile: arg(argv, '--candidate'), oracleFile: arg(argv, '--oracle'), assetsDir: arg(argv, '--assets'),
527
+ outFile: arg(argv, '--out'), cwd: arg(argv, '--repo') || process.cwd() });
528
+ console.log(JSON.stringify({ ok: true, planSha256: plan.planSha256, cases: plan.cases.length }));
529
+ } catch (error) {
530
+ console.error(`[retrieval-canary] ${error.message}`);
531
+ process.exitCode = 1;
532
+ }
533
+ }
@@ -53,6 +53,7 @@ import {
53
53
  canon,
54
54
  } from '@metaharness/flywheel';
55
55
  import { effectivePrices, loadLabelledRows, OUTCOMES } from './metaharness-router.mjs';
56
+ import { assertEvolutionAllowed } from './metaharness-gate.mjs';
56
57
  import { loadCatalog, applyProfile, loadProfile } from './model-router-engine.mjs';
57
58
 
58
59
  // ─── HARD CAPS — the self-DDoS fence. Clamped, never raised by flags. ─────────────────────────────
@@ -398,6 +399,10 @@ export async function runRoutingFlywheel(opts = {}) {
398
399
  let proposer;
399
400
  let dataSource;
400
401
  if (mode === 'live') {
402
+ // A live proposer is the only path that can spend metered credits. Require an external,
403
+ // measured score/OIA/drift receipt and its explicit spend opt-in before constructing it.
404
+ // Synthetic replay remains available without this gate and never makes a paid call.
405
+ assertEvolutionAllowed(opts.gateReceipt);
401
406
  if (!opts.apiKey) throw new Error('--live requires OPENROUTER_API_KEY');
402
407
  const model = opts.proposerModel ?? LIVE_PROPOSER_MODEL;
403
408
  const entry = catalog.find((c) => c.id === model);
@@ -440,9 +445,14 @@ export async function runRoutingFlywheel(opts = {}) {
440
445
  const fp = gateFingerprint(meetsPromotionRule);
441
446
  const verdict = verifyReplayBundle(result.replayBundle, { pinnedGateFingerprint: fp, promotionRule: meetsPromotionRule });
442
447
  const promoted = result.promotions.filter((c) => c.verdict === 'PROMOTED');
448
+ // If a caller supplied the measured MetaHarness preflight, its promotion decision is an
449
+ // additional fail-closed wall. Live evolution may run only after explicit spend consent, but
450
+ // it never self-promotes; synthetic replay can be promoted only when the caller's receipt says
451
+ // so. The historical no-receipt path remains compatible for the $0 unit harness.
452
+ const promotionGate = opts.gateReceipt ? opts.gateReceipt.promotion?.allowed === true : true;
443
453
 
444
454
  let candidatePath = null;
445
- if (promoted.length) {
455
+ if (promoted.length && promotionGate) {
446
456
  const provenance = {
447
457
  ts: runIso,
448
458
  mode,
@@ -483,6 +493,7 @@ export async function runRoutingFlywheel(opts = {}) {
483
493
  lift_curve: result.replayBundle.lift_curve,
484
494
  milestone_reached: result.milestoneReached,
485
495
  candidate_path: candidatePath,
496
+ promotion_gate: opts.gateReceipt ? { allowed: promotionGate, reason: opts.gateReceipt.promotion?.reason || null } : null,
486
497
  signer_public_key: signer.publicKey(),
487
498
  };
488
499
  appendReceipt(opts.receiptsFile ?? RECEIPTS_PATH_DEFAULT, receipt);
@@ -119,3 +119,53 @@ export function verifyRvfGenerations(dir, {
119
119
  }
120
120
  return { manifest, failures };
121
121
  }
122
+
123
+ // Release-time closure check for the exact public roots selected for a bundle. Unlike the
124
+ // source-only verifier above, this rejects every ambiguity in the byte-bearing asset directory.
125
+ export function validateSelectedRvfGenerations(dir, {
126
+ selectedStores = canonicalRvfStores(dir),
127
+ privateStores = [],
128
+ excludedStores = [],
129
+ version,
130
+ releaseTag,
131
+ } = {}) {
132
+ const manifest = readRvfGenerations(dir);
133
+ const failures = [];
134
+ const selected = [...new Set(selectedStores)].sort();
135
+ const selectedLower = new Map(selected.map((name) => [name.toLowerCase(), name]));
136
+ const privateLower = new Set(privateStores.map((name) => String(name).toLowerCase()));
137
+ const excludedLower = new Set(excludedStores.map((name) => String(name).toLowerCase()));
138
+ if (version !== undefined && manifest.brainVersion !== version) failures.push(`brainVersion=${manifest.brainVersion}, expected ${version}`);
139
+ if (releaseTag !== undefined && manifest.releaseTag !== releaseTag) failures.push(`releaseTag=${manifest.releaseTag}, expected ${releaseTag}`);
140
+ for (const name of selected) if (privateLower.has(name.toLowerCase())) failures.push(`${name}: private store selected`);
141
+ for (const name of selected) if (excludedLower.has(name.toLowerCase())) failures.push(`${name}: excluded store selected`);
142
+ for (const [store, row] of Object.entries(manifest.stores || {})) {
143
+ const canonical = selectedLower.get(store.toLowerCase());
144
+ if (!canonical) {
145
+ if (!privateLower.has(store.toLowerCase()) && !excludedLower.has(store.toLowerCase())) {
146
+ failures.push(`${store}: extra generation record`);
147
+ }
148
+ continue;
149
+ }
150
+ if (canonical !== store) failures.push(`${store}: alias differs from selected store ${canonical}`);
151
+ const expectedFile = `${canonical}.big.rvf`;
152
+ if (row?.file !== expectedFile) failures.push(`${store}: file=${row?.file}, expected ${expectedFile}`);
153
+ const file = path.join(dir, expectedFile);
154
+ if (!fs.existsSync(file)) failures.push(`${store}: missing ${expectedFile}`);
155
+ else {
156
+ const stat = fs.statSync(file);
157
+ if (row?.bytes !== stat.size) failures.push(`${store}: bytes=${row?.bytes}, actual ${stat.size}`);
158
+ const digest = sha256File(file);
159
+ if (row?.sha256 !== digest) failures.push(`${store}: sha256=${digest}, recorded ${row?.sha256}`);
160
+ }
161
+ if (typeof row?.model !== 'string' || !row.model.trim()) failures.push(`${store}: invalid model`);
162
+ if (!Number.isInteger(row?.dimensions) || row.dimensions <= 0) failures.push(`${store}: invalid dimensions`);
163
+ if (row?.sourceCommit !== null && (typeof row?.sourceCommit !== 'string' || !/^[a-f0-9]{7,64}$/i.test(row.sourceCommit))) failures.push(`${store}: invalid sourceCommit`);
164
+ if (typeof row?.builtUtc !== 'string' || !Number.isFinite(Date.parse(row.builtUtc))) failures.push(`${store}: invalid builtUtc`);
165
+ }
166
+ for (const name of selected) if (!manifest.stores?.[name]) failures.push(`${name}: no exact generation record`);
167
+ const publicRows = Object.keys(manifest.stores || {}).filter((name) => !privateLower.has(name.toLowerCase())
168
+ && !excludedLower.has(name.toLowerCase()));
169
+ if (publicRows.length !== selected.length) failures.push(`store count=${publicRows.length}, selected ${selected.length}`);
170
+ return { manifest, selectedStores: selected, excludedStores: [...excludedLower].sort(), failures };
171
+ }