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.
- package/README.md +15 -14
- package/bin/install.mjs +11 -2
- package/console/architecture.html +12 -14
- package/console/install-mockup.html +3 -3
- package/kb/zip-extract.mjs +8 -2
- package/package.json +7 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/hooks/codex-hooks.json +1 -1
- package/plugin/hooks/hooks.json +10 -0
- package/plugin/host-adapters/claude.json +11 -0
- package/plugin/host-adapters/codex.json +11 -0
- package/plugin/mcp/managed-cli-interface.mjs +77 -10
- package/plugin/mcp/server.mjs +2 -1
- package/plugin/scripts/anticipate.sh +30 -1
- package/plugin/scripts/capability-claim-evidence.mjs +431 -0
- package/plugin/scripts/capability-inventory-receipt.mjs +225 -0
- package/plugin/scripts/capability-registry.mjs +6 -0
- package/plugin/scripts/capability-routing.mjs +70 -0
- package/plugin/scripts/continuation-gate.mjs +68 -2
- package/plugin/scripts/coverage-integrity.mjs +575 -0
- package/plugin/scripts/ground-ruvnet.sh +2 -2
- package/plugin/scripts/nightly-controller.mjs +19 -2
- package/plugin/scripts/project-progression-contract.mjs +409 -0
- package/plugin/scripts/project-progression-hook.mjs +196 -0
- package/plugin/scripts/project-progression-outbox.mjs +95 -0
- package/plugin/scripts/project-progression-session-start.mjs +183 -0
- package/plugin/scripts/project-progression-store.mjs +248 -0
- package/plugin/scripts/project-store-resolver.mjs +106 -0
- package/plugin/scripts/session-snapshot-hook.mjs +29 -1
- package/plugin/scripts/session-start-core.mjs +22 -18
- package/plugin/scripts/spend-guard.mjs +6 -1
- package/scripts/adr-072-completion.mjs +98 -0
- package/scripts/behavioral-l1-l4.mjs +11 -8
- package/scripts/brain-score.mjs +7 -2
- package/scripts/build-bundle.mjs +82 -6
- package/scripts/card-from-source.mjs +32 -6
- package/scripts/corpus-aggregates.mjs +194 -0
- package/scripts/corpus-candidate.mjs +56 -36
- package/scripts/corpus-reconcile.mjs +295 -28
- package/scripts/corpus-seed-publish.mjs +3 -3
- package/scripts/coverage-integrity.mjs +3 -0
- package/scripts/gist-receipts.mjs +183 -0
- package/scripts/git-hooks/pre-push +21 -0
- package/scripts/host-registry.mjs +118 -0
- package/scripts/independent-review-receipt.mjs +499 -0
- package/scripts/ingest-gists.mjs +34 -5
- package/scripts/ingest-new-repos.mjs +5 -0
- package/scripts/metaharness-gate.mjs +58 -0
- package/scripts/nightly-gists.sh +3 -3
- package/scripts/nightly-wrapper.sh +75 -69
- package/scripts/product-integrity-contract.mjs +156 -0
- package/scripts/public-inventory.mjs +4 -0
- package/scripts/public-verification-aggregate.mjs +342 -0
- package/scripts/public-verification-finalizer.mjs +59 -0
- package/scripts/public-verification-inputs.mjs +473 -0
- package/scripts/public-verification-lane.mjs +244 -0
- package/scripts/publication-receipt.mjs +50 -6
- package/scripts/rebuild-gists-from-receipts.mjs +50 -5
- package/scripts/release-projection.mjs +86 -0
- package/scripts/release-transaction-provider.mjs +43 -102
- package/scripts/release-transaction.mjs +139 -51
- package/scripts/release.mjs +15 -7
- package/scripts/restore-local-ingests.mjs +2 -2
- package/scripts/retrieval-canary.mjs +533 -0
- package/scripts/routing-flywheel.mjs +12 -1
- package/scripts/rvf-generation.mjs +9 -3
- package/scripts/self-update.mjs +19 -44
- package/scripts/source-coverage.mjs +415 -0
- package/scripts/source-scope-receipt.mjs +84 -0
- package/scripts/sync-census.mjs +0 -0
- package/scripts/wired-check.mjs +114 -24
- package/scripts/worktree-integrity.mjs +123 -0
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Compile the exact public-verification inputs from immutable artifact bytes.
|
|
3
|
+
// Queries are an independent review input: this producer validates them but never invents them.
|
|
4
|
+
|
|
5
|
+
import crypto from 'node:crypto';
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import os from 'node:os';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import { extractZip } from '../kb/zip-extract.mjs';
|
|
11
|
+
import { canonicalJson, digest, validateCoverageLedger, validateCoverageLink } from './coverage-integrity.mjs';
|
|
12
|
+
import { validatePublicInventory } from './public-inventory.mjs';
|
|
13
|
+
import {
|
|
14
|
+
buildRetrievalCanaryPlan,
|
|
15
|
+
validateRetrievalQueryEvidence,
|
|
16
|
+
verifyQueryOracleSource,
|
|
17
|
+
} from './retrieval-canary.mjs';
|
|
18
|
+
|
|
19
|
+
const HEX40 = /^[a-f0-9]{40}$/;
|
|
20
|
+
const HEX64 = /^[a-f0-9]{64}$/;
|
|
21
|
+
const MANIFEST = 'ARCHIVE-MANIFEST.json';
|
|
22
|
+
|
|
23
|
+
function fail(message) {
|
|
24
|
+
throw new Error(`[public-verification-inputs] ${message}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function trustedFile(file, label) {
|
|
28
|
+
if (!file) fail(`${label} path is required`);
|
|
29
|
+
const resolved = path.resolve(file);
|
|
30
|
+
let stat;
|
|
31
|
+
try { stat = fs.lstatSync(resolved); } catch { fail(`${label} is missing (${resolved})`); }
|
|
32
|
+
if (!stat.isFile() || stat.isSymbolicLink()) fail(`${label} is not a trusted regular file`);
|
|
33
|
+
return resolved;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function readJson(file, label) {
|
|
37
|
+
const resolved = trustedFile(file, label);
|
|
38
|
+
try { return { value: JSON.parse(fs.readFileSync(resolved, 'utf8')), file: resolved }; }
|
|
39
|
+
catch (error) { fail(`${label} is unreadable: ${error.message}`); }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function sha256File(file) {
|
|
43
|
+
const hash = crypto.createHash('sha256');
|
|
44
|
+
const fd = fs.openSync(file, 'r');
|
|
45
|
+
const buffer = Buffer.allocUnsafe(1024 * 1024);
|
|
46
|
+
try {
|
|
47
|
+
let bytes;
|
|
48
|
+
while ((bytes = fs.readSync(fd, buffer, 0, buffer.length, null)) > 0) hash.update(buffer.subarray(0, bytes));
|
|
49
|
+
} finally { fs.closeSync(fd); }
|
|
50
|
+
return hash.digest('hex');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function identity(file) {
|
|
54
|
+
return { sha256: sha256File(file), bytes: fs.statSync(file).size };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function namedIdentity(file) {
|
|
58
|
+
return { file: path.basename(file), ...identity(file) };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function filesUnder(root, prefix = '', { includeManifest = false } = {}) {
|
|
62
|
+
const rows = [];
|
|
63
|
+
for (const entry of fs.readdirSync(path.join(root, prefix), { withFileTypes: true })
|
|
64
|
+
.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
65
|
+
const relative = prefix ? path.join(prefix, entry.name) : entry.name;
|
|
66
|
+
if (!includeManifest && relative === MANIFEST) continue;
|
|
67
|
+
const file = path.join(root, relative);
|
|
68
|
+
if (entry.isSymbolicLink()) fail(`archive tree contains a symbolic link: ${relative}`);
|
|
69
|
+
if (entry.isDirectory()) rows.push(...filesUnder(root, relative, { includeManifest }));
|
|
70
|
+
else if (entry.isFile()) rows.push({ path: relative.split(path.sep).join('/'), ...identity(file) });
|
|
71
|
+
else fail(`archive tree contains an unsupported entry: ${relative}`);
|
|
72
|
+
}
|
|
73
|
+
return rows;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function findNamed(root, name) {
|
|
77
|
+
const matches = [];
|
|
78
|
+
const visit = (dir) => {
|
|
79
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
80
|
+
const file = path.join(dir, entry.name);
|
|
81
|
+
if (entry.isSymbolicLink()) fail(`extracted archive contains a symbolic link: ${file}`);
|
|
82
|
+
if (entry.isDirectory()) visit(file);
|
|
83
|
+
else if (entry.isFile() && entry.name === name) matches.push(file);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
visit(root);
|
|
87
|
+
if (matches.length !== 1) fail(`archive must contain exactly one ${name}; found ${matches.length}`);
|
|
88
|
+
return matches[0];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function validateArchive(root) {
|
|
92
|
+
const manifestFile = findNamed(root, MANIFEST);
|
|
93
|
+
const archiveRoot = path.dirname(manifestFile);
|
|
94
|
+
const { value: manifest } = readJson(manifestFile, 'archive manifest');
|
|
95
|
+
if (manifest.schemaVersion !== 1 || manifest.kind !== 'ruvnet-brain-archive-manifest'
|
|
96
|
+
|| typeof manifest.version !== 'string' || !manifest.version || typeof manifest.releaseTag !== 'string'
|
|
97
|
+
|| !manifest.releaseTag || !Array.isArray(manifest.files)) fail('archive manifest schema is invalid');
|
|
98
|
+
const actual = filesUnder(archiveRoot);
|
|
99
|
+
const byPath = (left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0;
|
|
100
|
+
if (canonicalJson([...actual].sort(byPath)) !== canonicalJson([...manifest.files].sort(byPath))) {
|
|
101
|
+
fail('archive file set or bytes differ from ARCHIVE-MANIFEST.json');
|
|
102
|
+
}
|
|
103
|
+
if (manifest.fileCount !== actual.length
|
|
104
|
+
|| manifest.totalBytes !== actual.reduce((total, row) => total + row.bytes, 0)) {
|
|
105
|
+
fail('archive manifest totals differ from its file rows');
|
|
106
|
+
}
|
|
107
|
+
return { root: archiveRoot, manifest };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function exactNamedFile(root, name, label) {
|
|
111
|
+
if (typeof name !== 'string' || !name || path.basename(name) !== name) fail(`${label} file name is invalid`);
|
|
112
|
+
return trustedFile(path.join(root, name), label);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function validateCandidate({ root, bundleFile, packageFile }) {
|
|
116
|
+
const coverageFile = trustedFile(path.join(root, 'COVERAGE.json'), 'candidate release coverage');
|
|
117
|
+
const coverageBytes = fs.readFileSync(coverageFile);
|
|
118
|
+
let coverage;
|
|
119
|
+
try { coverage = JSON.parse(coverageBytes); } catch (error) { fail(`candidate release coverage is unreadable: ${error.message}`); }
|
|
120
|
+
const checked = validateCoverageLedger(coverage);
|
|
121
|
+
const eligible = Array.isArray(coverage.rows) ? coverage.rows.filter((row) => row.disposition === 'eligible') : [];
|
|
122
|
+
if (!checked.valid || coverage.kind !== 'ruvnet-brain-release-coverage'
|
|
123
|
+
|| !eligible.length || eligible.some((row) => row.status !== 'CURRENT')) {
|
|
124
|
+
fail(`candidate release coverage is invalid or incomplete: ${checked.failures.join('; ')}`);
|
|
125
|
+
}
|
|
126
|
+
const corpusCoverageFile = trustedFile(path.join(root, 'CORPUS-COVERAGE.json'), 'candidate corpus coverage');
|
|
127
|
+
const corpusCoverageBytes = fs.readFileSync(corpusCoverageFile);
|
|
128
|
+
let corpusCoverage;
|
|
129
|
+
try { corpusCoverage = JSON.parse(corpusCoverageBytes); }
|
|
130
|
+
catch (error) { fail(`candidate corpus coverage is unreadable: ${error.message}`); }
|
|
131
|
+
const linked = validateCoverageLink({ releaseCoverage: coverage, corpusCoverage,
|
|
132
|
+
corpusCoverageSha256: crypto.createHash('sha256').update(corpusCoverageBytes).digest('hex') });
|
|
133
|
+
if (!linked.valid) fail(`candidate corpus coverage link is invalid: ${linked.failures.join('; ')}`);
|
|
134
|
+
const ledgerFile = trustedFile(path.join(root, 'PUBLIC-RVF-GENERATIONS.json'), 'candidate public generation ledger');
|
|
135
|
+
const ledgerBytes = fs.readFileSync(ledgerFile);
|
|
136
|
+
let ledger;
|
|
137
|
+
try { ledger = JSON.parse(ledgerBytes); } catch (error) { fail(`candidate public generation ledger is unreadable: ${error.message}`); }
|
|
138
|
+
const ledgerIdentity = identity(ledgerFile);
|
|
139
|
+
const storeCount = Object.keys(ledger?.stores || {}).length;
|
|
140
|
+
if (ledger.schemaVersion !== 2 || ledger.kind !== 'ruvnet-brain-public-generation-ledger'
|
|
141
|
+
|| ledger.brainVersion !== coverage.releaseIdentity.version || ledger.releaseTag !== coverage.releaseIdentity.tag
|
|
142
|
+
|| ledger.sourceSnapshot !== coverage.releaseIdentity.sourceSnapshot
|
|
143
|
+
|| coverage.generationLedger.file !== 'PUBLIC-RVF-GENERATIONS.json'
|
|
144
|
+
|| coverage.generationLedger.sha256 !== ledgerIdentity.sha256
|
|
145
|
+
|| coverage.generationLedger.bytes !== ledgerIdentity.bytes || coverage.generationLedger.storeCount !== storeCount) {
|
|
146
|
+
fail('candidate public generation ledger differs from release coverage');
|
|
147
|
+
}
|
|
148
|
+
const inventory = validatePublicInventory({ assetsDir: root, coverage, ledger });
|
|
149
|
+
if (inventory.publicStores.length !== storeCount
|
|
150
|
+
|| inventory.partitionSha256 !== coverage.publicInventoryPartitionSha256) {
|
|
151
|
+
fail('candidate public inventory partition differs from release coverage');
|
|
152
|
+
}
|
|
153
|
+
const coverageIdentity = { sha256: crypto.createHash('sha256').update(coverageBytes).digest('hex'), bytes: coverageBytes.length };
|
|
154
|
+
const candidate = {
|
|
155
|
+
sourceSha: coverage.releaseIdentity.sourceSnapshot,
|
|
156
|
+
packageSha256: sha256File(packageFile),
|
|
157
|
+
archiveSha256: sha256File(bundleFile),
|
|
158
|
+
coverageSha256: coverageIdentity.sha256,
|
|
159
|
+
publicLedgerSha256: ledgerIdentity.sha256,
|
|
160
|
+
publicLedgerBytes: ledgerIdentity.bytes,
|
|
161
|
+
publicStoreCount: storeCount,
|
|
162
|
+
publicInventoryPartitionSha256: inventory.partitionSha256,
|
|
163
|
+
};
|
|
164
|
+
if (!HEX40.test(candidate.sourceSha) || !Object.values(candidate).filter((value) => typeof value === 'string')
|
|
165
|
+
.filter((value) => value.length === 64).every((value) => HEX64.test(value))) fail('candidate identity is malformed');
|
|
166
|
+
return { coverage, coverageBytes, coverageIdentity, candidate };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function encoded(value) {
|
|
170
|
+
return Buffer.from(`${JSON.stringify(value, null, 2)}\n`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function retrospectiveBaselineFromTree({ extractedRoot, bundleFile, expectedTag, expectedSha256, expectedBytes }) {
|
|
174
|
+
const ledgerFile = findNamed(extractedRoot, 'RVF-GENERATIONS.json');
|
|
175
|
+
const root = path.dirname(ledgerFile);
|
|
176
|
+
const { value: ledger } = readJson(ledgerFile, 'historical baseline generation ledger');
|
|
177
|
+
const names = Object.keys(ledger?.stores || {}).sort();
|
|
178
|
+
if (ledger.schemaVersion !== 1 || typeof ledger.brainVersion !== 'string' || !ledger.brainVersion
|
|
179
|
+
|| typeof ledger.releaseTag !== 'string' || !ledger.releaseTag || !names.length
|
|
180
|
+
|| new Set(names.map((name) => name.toLowerCase())).size !== names.length) {
|
|
181
|
+
fail('historical baseline generation ledger is malformed');
|
|
182
|
+
}
|
|
183
|
+
const archive = namedIdentity(bundleFile);
|
|
184
|
+
if (typeof expectedTag !== 'string' || !expectedTag || ledger.releaseTag !== expectedTag) {
|
|
185
|
+
fail('historical baseline differs from the expected public release tag');
|
|
186
|
+
}
|
|
187
|
+
if (!HEX64.test(String(expectedSha256 || '')) || archive.sha256 !== expectedSha256) {
|
|
188
|
+
fail('historical baseline differs from the expected public archive SHA-256');
|
|
189
|
+
}
|
|
190
|
+
if (!Number.isSafeInteger(expectedBytes) || expectedBytes < 1 || archive.bytes !== expectedBytes) {
|
|
191
|
+
fail('historical baseline differs from the expected public archive byte length');
|
|
192
|
+
}
|
|
193
|
+
const archiveFiles = filesUnder(root, '', { includeManifest: true });
|
|
194
|
+
const archiveManifest = { schemaVersion: 1, kind: 'ruvnet-brain-retrospective-archive-manifest',
|
|
195
|
+
fileCount: archiveFiles.length, totalBytes: archiveFiles.reduce((total, row) => total + row.bytes, 0),
|
|
196
|
+
files: archiveFiles };
|
|
197
|
+
const actualRvfFiles = archiveFiles.filter(({ path: relative }) => relative.endsWith('.big.rvf'))
|
|
198
|
+
.map(({ path: relative }) => path.posix.basename(relative)).sort();
|
|
199
|
+
const expectedRvfFiles = names.map((name) => `${name}.big.rvf`).sort();
|
|
200
|
+
if (canonicalJson(actualRvfFiles) !== canonicalJson(expectedRvfFiles)) {
|
|
201
|
+
fail('historical baseline RVF set differs from its generation ledger');
|
|
202
|
+
}
|
|
203
|
+
const stores = names.map((name) => {
|
|
204
|
+
const row = ledger.stores[name];
|
|
205
|
+
const file = exactNamedFile(root, row?.file, `historical ${name} RVF`);
|
|
206
|
+
const actual = namedIdentity(file);
|
|
207
|
+
if (row.file !== `${name}.big.rvf` || row.sha256 !== actual.sha256 || row.bytes !== actual.bytes
|
|
208
|
+
|| typeof row.model !== 'string' || !row.model.trim() || !Number.isInteger(row.dimensions) || row.dimensions < 1
|
|
209
|
+
|| !(row.sourceCommit === null || /^[a-f0-9]{7,64}$/i.test(String(row.sourceCommit || '')))
|
|
210
|
+
|| typeof row.builtUtc !== 'string' || !Number.isFinite(Date.parse(row.builtUtc))) {
|
|
211
|
+
fail(`historical ${name} generation record does not bind its RVF bytes and provenance`);
|
|
212
|
+
}
|
|
213
|
+
return { name: name.toLowerCase(), ...actual, model: row.model, dimensions: row.dimensions,
|
|
214
|
+
sourceCommit: row.sourceCommit === null ? null : row.sourceCommit.toLowerCase(), builtUtc: row.builtUtc };
|
|
215
|
+
});
|
|
216
|
+
const provenanceGaps = stores.filter(({ sourceCommit }) => sourceCommit === null).map(({ name }) => name);
|
|
217
|
+
const payload = {
|
|
218
|
+
schemaVersion: 1,
|
|
219
|
+
kind: 'ruvnet-brain-retrospective-baseline-verification',
|
|
220
|
+
verificationMode: 'retrospective-byte-verification',
|
|
221
|
+
historicalCorpusReceipt: false,
|
|
222
|
+
releaseTag: ledger.releaseTag,
|
|
223
|
+
brainVersion: ledger.brainVersion,
|
|
224
|
+
archive,
|
|
225
|
+
archiveManifestSha256: digest(archiveManifest),
|
|
226
|
+
generationLedger: namedIdentity(ledgerFile),
|
|
227
|
+
storeCount: stores.length,
|
|
228
|
+
storeSetSha256: digest(stores.map(({ name }) => name)),
|
|
229
|
+
stores,
|
|
230
|
+
provenanceGaps,
|
|
231
|
+
limitations: [
|
|
232
|
+
'no historical corpus receipt was published with these bytes',
|
|
233
|
+
'verification proves immutable archive, ledger, RVF bytes, and recorded provenance; it does not invent historical process evidence',
|
|
234
|
+
...(provenanceGaps.length ? [`${provenanceGaps.length} store(s) have null sourceCommit in the historical ledger`] : []),
|
|
235
|
+
],
|
|
236
|
+
};
|
|
237
|
+
const receipt = { ...payload, receiptSha256: digest(payload) };
|
|
238
|
+
const bytes = encoded(receipt);
|
|
239
|
+
return { receipt, bytes, fileSha256: crypto.createHash('sha256').update(bytes).digest('hex'), root, archiveManifest };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function observedBaselineFromTree({ extractedRoot, bundleFile, expectedTag, expectedSha256, expectedBytes }) {
|
|
243
|
+
const ledgerFile = findNamed(extractedRoot, 'RVF-GENERATIONS.json');
|
|
244
|
+
const root = path.dirname(ledgerFile);
|
|
245
|
+
const { value: ledger } = readJson(ledgerFile, 'historical baseline generation ledger');
|
|
246
|
+
const ledgerNames = Object.keys(ledger?.stores || {}).sort();
|
|
247
|
+
if (ledger.schemaVersion !== 1 || typeof ledger.brainVersion !== 'string' || !ledger.brainVersion
|
|
248
|
+
|| typeof ledger.releaseTag !== 'string' || !ledger.releaseTag || !ledgerNames.length
|
|
249
|
+
|| new Set(ledgerNames.map((name) => name.toLowerCase())).size !== ledgerNames.length) {
|
|
250
|
+
fail('historical baseline generation ledger is malformed');
|
|
251
|
+
}
|
|
252
|
+
const archive = namedIdentity(bundleFile);
|
|
253
|
+
if (ledger.releaseTag !== expectedTag) fail('historical baseline differs from the expected public release tag');
|
|
254
|
+
if (!HEX64.test(String(expectedSha256 || '')) || archive.sha256 !== expectedSha256) {
|
|
255
|
+
fail('historical baseline differs from the expected public archive SHA-256');
|
|
256
|
+
}
|
|
257
|
+
if (!Number.isSafeInteger(expectedBytes) || expectedBytes < 1 || archive.bytes !== expectedBytes) {
|
|
258
|
+
fail('historical baseline differs from the expected public archive byte length');
|
|
259
|
+
}
|
|
260
|
+
const archiveFiles = filesUnder(root, '', { includeManifest: true });
|
|
261
|
+
const archiveManifest = { schemaVersion: 1, kind: 'ruvnet-brain-retrospective-archive-manifest',
|
|
262
|
+
fileCount: archiveFiles.length, totalBytes: archiveFiles.reduce((total, row) => total + row.bytes, 0),
|
|
263
|
+
files: archiveFiles };
|
|
264
|
+
const actualRvfs = new Map(archiveFiles.filter(({ path: relative }) => relative.endsWith('.big.rvf')).map((row) => {
|
|
265
|
+
const name = path.posix.basename(row.path).slice(0, -'.big.rvf'.length).toLowerCase();
|
|
266
|
+
return [name, { file: path.posix.basename(row.path), sha256: row.sha256, bytes: row.bytes }];
|
|
267
|
+
}));
|
|
268
|
+
if (actualRvfs.size !== archiveFiles.filter(({ path: relative }) => relative.endsWith('.big.rvf')).length) {
|
|
269
|
+
fail('historical baseline RVF names have case-fold aliases');
|
|
270
|
+
}
|
|
271
|
+
const ledgerByName = new Map(ledgerNames.map((name) => [name.toLowerCase(), ledger.stores[name]]));
|
|
272
|
+
const allNames = [...new Set([...actualRvfs.keys(), ...ledgerByName.keys()])].sort();
|
|
273
|
+
const ledgerDiscrepancies = [];
|
|
274
|
+
for (const name of allNames) {
|
|
275
|
+
const actual = actualRvfs.get(name);
|
|
276
|
+
const expected = ledgerByName.get(name);
|
|
277
|
+
if (!expected) ledgerDiscrepancies.push({ store: name, type: 'unreceipted-rvf', archive: actual });
|
|
278
|
+
else if (!actual) ledgerDiscrepancies.push({ store: name, type: 'missing-rvf', ledger: {
|
|
279
|
+
file: expected.file, sha256: expected.sha256, bytes: expected.bytes } });
|
|
280
|
+
else if (expected.file !== actual.file || expected.sha256 !== actual.sha256 || expected.bytes !== actual.bytes) {
|
|
281
|
+
ledgerDiscrepancies.push({ store: name, type: 'byte-identity-mismatch',
|
|
282
|
+
ledger: { file: expected.file, sha256: expected.sha256, bytes: expected.bytes }, archive: actual });
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
const stores = [...actualRvfs].map(([name, actual]) => {
|
|
286
|
+
const row = ledgerByName.get(name) || {};
|
|
287
|
+
return { name, ...actual, model: typeof row.model === 'string' ? row.model : null,
|
|
288
|
+
dimensions: Number.isInteger(row.dimensions) ? row.dimensions : null,
|
|
289
|
+
sourceCommit: row.sourceCommit === null ? null : String(row.sourceCommit || '').toLowerCase() || null,
|
|
290
|
+
builtUtc: typeof row.builtUtc === 'string' ? row.builtUtc : null };
|
|
291
|
+
});
|
|
292
|
+
const provenanceGaps = stores.filter(({ sourceCommit }) => sourceCommit === null).map(({ name }) => name);
|
|
293
|
+
const discrepancyDigest = digest({ ledgerDiscrepancies, provenanceGaps });
|
|
294
|
+
const payload = { schemaVersion: 1, kind: 'ruvnet-brain-observed-failed-public-baseline', integrity: 'DEGRADED',
|
|
295
|
+
historicalCorpusReceipt: false, candidateVerificationEligible: false, releaseTag: ledger.releaseTag,
|
|
296
|
+
brainVersion: ledger.brainVersion, archive, archiveManifestSha256: digest(archiveManifest),
|
|
297
|
+
generationLedger: namedIdentity(ledgerFile), storeCount: stores.length,
|
|
298
|
+
storeSetSha256: digest(stores.map(({ name }) => name)), stores, ledgerDiscrepancies, provenanceGaps,
|
|
299
|
+
discrepancyDigest, limitations: [
|
|
300
|
+
'no historical corpus receipt was published with these bytes',
|
|
301
|
+
'observation binds actual public archive members but is not corpus, candidate, or release integrity proof',
|
|
302
|
+
] };
|
|
303
|
+
const receipt = { ...payload, receiptSha256: digest(payload) };
|
|
304
|
+
const bytes = encoded(receipt);
|
|
305
|
+
return { receipt, bytes, fileSha256: crypto.createHash('sha256').update(bytes).digest('hex'), root, archiveManifest };
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function writeExactFile(file, bytes, label) {
|
|
309
|
+
const output = path.resolve(file);
|
|
310
|
+
if (fs.existsSync(output)) {
|
|
311
|
+
const existing = trustedFile(output, `existing ${label}`);
|
|
312
|
+
if (!fs.readFileSync(existing).equals(bytes)) fail(`existing ${label} differs from the derived evidence`);
|
|
313
|
+
return output;
|
|
314
|
+
}
|
|
315
|
+
fs.mkdirSync(path.dirname(output), { recursive: true });
|
|
316
|
+
fs.writeFileSync(output, bytes, { flag: 'wx', mode: 0o600 });
|
|
317
|
+
return output;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export async function createRetrospectiveBaselineVerification({ baselineBundle,
|
|
321
|
+
expectedTag, expectedSha256, expectedBytes,
|
|
322
|
+
outFile = 'release-evidence/baseline-verification-receipt.json' } = {}) {
|
|
323
|
+
const archive = trustedFile(baselineBundle, 'baseline archive');
|
|
324
|
+
const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'retrospective-baseline-'));
|
|
325
|
+
try {
|
|
326
|
+
try { await extractZip(archive, temp); }
|
|
327
|
+
catch (error) { fail(`baseline archive extraction failed: ${error.message}`); }
|
|
328
|
+
const result = retrospectiveBaselineFromTree({ extractedRoot: temp, bundleFile: archive,
|
|
329
|
+
expectedTag, expectedSha256, expectedBytes });
|
|
330
|
+
writeExactFile(outFile, result.bytes, 'baseline-verification-receipt.json');
|
|
331
|
+
return result;
|
|
332
|
+
} finally {
|
|
333
|
+
fs.rmSync(temp, { recursive: true, force: true });
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export async function createObservedBaselineReceipt({ baselineBundle, expectedTag, expectedSha256, expectedBytes,
|
|
338
|
+
outFile = 'release-evidence/baseline-observation-receipt.json' } = {}) {
|
|
339
|
+
const archive = trustedFile(baselineBundle, 'baseline archive');
|
|
340
|
+
const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'observed-baseline-'));
|
|
341
|
+
try {
|
|
342
|
+
try { await extractZip(archive, temp); }
|
|
343
|
+
catch (error) { fail(`baseline archive extraction failed: ${error.message}`); }
|
|
344
|
+
const result = observedBaselineFromTree({ extractedRoot: temp, bundleFile: archive,
|
|
345
|
+
expectedTag, expectedSha256, expectedBytes });
|
|
346
|
+
writeExactFile(outFile, result.bytes, 'baseline-observation-receipt.json');
|
|
347
|
+
return result;
|
|
348
|
+
} finally { fs.rmSync(temp, { recursive: true, force: true }); }
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function writeExactOutputs(outDir, outputs) {
|
|
352
|
+
const root = path.resolve(outDir || 'release-evidence');
|
|
353
|
+
for (const [name, bytes] of Object.entries(outputs)) {
|
|
354
|
+
const file = path.join(root, name);
|
|
355
|
+
if (!fs.existsSync(file)) continue;
|
|
356
|
+
const existing = trustedFile(file, `existing ${name}`);
|
|
357
|
+
if (!fs.readFileSync(existing).equals(bytes)) fail(`existing ${name} differs from the derived evidence`);
|
|
358
|
+
}
|
|
359
|
+
fs.mkdirSync(root, { recursive: true });
|
|
360
|
+
for (const [name, bytes] of Object.entries(outputs)) {
|
|
361
|
+
const file = path.join(root, name);
|
|
362
|
+
if (!fs.existsSync(file)) fs.writeFileSync(file, bytes, { flag: 'wx', mode: 0o600 });
|
|
363
|
+
}
|
|
364
|
+
return root;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export async function createPublicVerificationInputs({ baselineBundle, candidateBundle,
|
|
368
|
+
candidatePackage, oracleFile, repo = process.cwd(), outDir = 'release-evidence', baselineMode = 'verified' } = {}) {
|
|
369
|
+
const baselineArchive = trustedFile(baselineBundle, 'baseline archive');
|
|
370
|
+
const candidateArchive = trustedFile(candidateBundle, 'candidate archive');
|
|
371
|
+
const packageFile = trustedFile(candidatePackage, 'candidate package');
|
|
372
|
+
if (!oracleFile || !fs.existsSync(path.resolve(oracleFile))) {
|
|
373
|
+
fail('independent strict-ancestor query oracle is required; refusing to synthesize queries');
|
|
374
|
+
}
|
|
375
|
+
const oraclePath = trustedFile(oracleFile, 'independent strict-ancestor query oracle');
|
|
376
|
+
const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'public-verification-inputs-'));
|
|
377
|
+
try {
|
|
378
|
+
const baselineExtracted = path.join(temp, 'baseline');
|
|
379
|
+
const candidateExtracted = path.join(temp, 'candidate');
|
|
380
|
+
try { await extractZip(baselineArchive, baselineExtracted); }
|
|
381
|
+
catch (error) { fail(`baseline archive extraction failed: ${error.message}`); }
|
|
382
|
+
try { await extractZip(candidateArchive, candidateExtracted); }
|
|
383
|
+
catch (error) { fail(`candidate archive extraction failed: ${error.message}`); }
|
|
384
|
+
const candidateTree = validateArchive(candidateExtracted);
|
|
385
|
+
const candidateResult = validateCandidate({ root: candidateTree.root, bundleFile: candidateArchive, packageFile });
|
|
386
|
+
const seed = candidateResult.coverage.corpusSeed;
|
|
387
|
+
if (!['verified', 'observed'].includes(baselineMode)) fail('baseline mode must be verified or observed');
|
|
388
|
+
const baselineProof = baselineMode === 'observed'
|
|
389
|
+
? observedBaselineFromTree({ extractedRoot: baselineExtracted, bundleFile: baselineArchive,
|
|
390
|
+
expectedTag: seed.tag, expectedSha256: seed.archiveSha256, expectedBytes: seed.archiveBytes })
|
|
391
|
+
: retrospectiveBaselineFromTree({ extractedRoot: baselineExtracted, bundleFile: baselineArchive,
|
|
392
|
+
expectedTag: seed.tag, expectedSha256: seed.archiveSha256, expectedBytes: seed.archiveBytes });
|
|
393
|
+
if (seed.archiveSha256 !== baselineProof.receipt.archive.sha256
|
|
394
|
+
|| seed.archiveBytes !== baselineProof.receipt.archive.bytes) {
|
|
395
|
+
fail('baseline archive bytes differ from release coverage');
|
|
396
|
+
}
|
|
397
|
+
if (seed.receiptSha256 !== baselineProof.fileSha256) fail('baseline receipt differs from release coverage');
|
|
398
|
+
if (seed.tag !== baselineProof.receipt.releaseTag) {
|
|
399
|
+
fail('baseline release tag differs from release coverage');
|
|
400
|
+
}
|
|
401
|
+
const baselineStores = baselineProof.receipt.stores.map(({ name }) => name);
|
|
402
|
+
const baseline = { schemaVersion: 1,
|
|
403
|
+
kind: baselineMode === 'observed' ? 'ruvnet-brain-observed-failed-public-baseline' : 'ruvnet-brain-verified-public-baseline',
|
|
404
|
+
...(baselineMode === 'observed' ? { integrity: 'DEGRADED', historicalCorpusReceipt: false,
|
|
405
|
+
candidateVerificationEligible: false, observationReceiptSha256: baselineProof.fileSha256,
|
|
406
|
+
discrepancyDigest: baselineProof.receipt.discrepancyDigest,
|
|
407
|
+
ledgerDiscrepancies: baselineProof.receipt.ledgerDiscrepancies,
|
|
408
|
+
provenanceGaps: baselineProof.receipt.provenanceGaps } : {}), tag: seed.tag,
|
|
409
|
+
archiveSha256: seed.archiveSha256, archiveBytes: seed.archiveBytes,
|
|
410
|
+
archiveManifestSha256: baselineProof.receipt.archiveManifestSha256,
|
|
411
|
+
...(baselineMode === 'verified' ? { verificationReceiptSha256: baselineProof.fileSha256 } : {}),
|
|
412
|
+
stores: baselineStores, storeCount: baselineStores.length };
|
|
413
|
+
const { value: queryEvidence } = readJson(oraclePath, 'independent strict-ancestor query oracle');
|
|
414
|
+
validateRetrievalQueryEvidence(queryEvidence);
|
|
415
|
+
verifyQueryOracleSource(queryEvidence, candidateResult.candidate.sourceSha, { cwd: path.resolve(repo) });
|
|
416
|
+
const plan = buildRetrievalCanaryPlan({ coverage: candidateResult.coverage, baseline,
|
|
417
|
+
candidate: candidateResult.candidate, coverageIdentity: candidateResult.coverageIdentity,
|
|
418
|
+
queryEvidence, assetsDir: candidateTree.root });
|
|
419
|
+
writeExactOutputs(outDir, {
|
|
420
|
+
[baselineMode === 'observed' ? 'baseline-observation-receipt.json' : 'baseline-verification-receipt.json']: baselineProof.bytes,
|
|
421
|
+
'COVERAGE.json': candidateResult.coverageBytes,
|
|
422
|
+
'retrieval-baseline.json': encoded(baseline),
|
|
423
|
+
'retrieval-candidate.json': encoded(candidateResult.candidate),
|
|
424
|
+
'retrieval-canary-plan.json': encoded(plan),
|
|
425
|
+
});
|
|
426
|
+
return { coverage: candidateResult.coverage, baseline, candidate: candidateResult.candidate, plan };
|
|
427
|
+
} finally {
|
|
428
|
+
fs.rmSync(temp, { recursive: true, force: true });
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function arg(argv, name) {
|
|
433
|
+
const index = argv.indexOf(name);
|
|
434
|
+
return index >= 0 ? argv[index + 1] : null;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
export async function main(argv = process.argv.slice(2)) {
|
|
438
|
+
if (argv[0] === 'baseline') {
|
|
439
|
+
const result = await createRetrospectiveBaselineVerification({ baselineBundle: arg(argv, '--baseline-bundle'),
|
|
440
|
+
expectedTag: arg(argv, '--expected-tag'), expectedSha256: arg(argv, '--expected-sha256'),
|
|
441
|
+
expectedBytes: Number(arg(argv, '--expected-bytes')),
|
|
442
|
+
outFile: arg(argv, '--out') || 'release-evidence/baseline-verification-receipt.json' });
|
|
443
|
+
console.log(JSON.stringify({ ok: true, mode: 'baseline', releaseTag: result.receipt.releaseTag,
|
|
444
|
+
stores: result.receipt.storeCount, receiptFileSha256: result.fileSha256 }));
|
|
445
|
+
return result;
|
|
446
|
+
}
|
|
447
|
+
if (argv[0] === 'observe-baseline') {
|
|
448
|
+
const result = await createObservedBaselineReceipt({ baselineBundle: arg(argv, '--baseline-bundle'),
|
|
449
|
+
expectedTag: arg(argv, '--expected-tag'), expectedSha256: arg(argv, '--expected-sha256'),
|
|
450
|
+
expectedBytes: Number(arg(argv, '--expected-bytes')),
|
|
451
|
+
outFile: arg(argv, '--out') || 'release-evidence/baseline-observation-receipt.json' });
|
|
452
|
+
console.log(JSON.stringify({ ok: true, mode: 'observe-baseline', releaseTag: result.receipt.releaseTag,
|
|
453
|
+
stores: result.receipt.storeCount, discrepancies: result.receipt.ledgerDiscrepancies.length,
|
|
454
|
+
provenanceGaps: result.receipt.provenanceGaps.length, receiptFileSha256: result.fileSha256 }));
|
|
455
|
+
return result;
|
|
456
|
+
}
|
|
457
|
+
const result = await createPublicVerificationInputs({
|
|
458
|
+
baselineBundle: arg(argv, '--baseline-bundle'),
|
|
459
|
+
candidateBundle: arg(argv, '--candidate-bundle'),
|
|
460
|
+
candidatePackage: arg(argv, '--candidate-package'),
|
|
461
|
+
oracleFile: arg(argv, '--oracle'),
|
|
462
|
+
repo: arg(argv, '--repo') || process.cwd(),
|
|
463
|
+
outDir: arg(argv, '--out-dir') || 'release-evidence',
|
|
464
|
+
baselineMode: argv.includes('--observed-baseline') ? 'observed' : 'verified',
|
|
465
|
+
});
|
|
466
|
+
console.log(JSON.stringify({ ok: true, sourceSha: result.candidate.sourceSha,
|
|
467
|
+
coverageGeneration: result.coverage.releaseCoverageGeneration, cases: result.plan.cases.length }));
|
|
468
|
+
return result;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
472
|
+
main().catch((error) => { console.error(error.message); process.exitCode = 1; });
|
|
473
|
+
}
|