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.
- package/README.md +31 -7
- package/bin/install.mjs +153 -22
- 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/decision-outcomes.mjs +0 -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 +23 -9
- package/scripts/build-bundle.mjs +94 -8
- package/scripts/card-from-source.mjs +140 -0
- package/scripts/corpus-aggregates.mjs +194 -0
- package/scripts/corpus-candidate.mjs +314 -0
- package/scripts/corpus-reconcile.mjs +540 -0
- package/scripts/corpus-seed-publish.mjs +110 -0
- 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 +127 -0
- package/scripts/metaharness-gate.mjs +58 -0
- package/scripts/nightly-gists.sh +3 -3
- package/scripts/nightly-wrapper.sh +105 -64
- 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 +291 -0
- package/scripts/release-projection.mjs +86 -0
- package/scripts/release-transaction-provider.mjs +43 -102
- package/scripts/release-transaction.mjs +165 -49
- package/scripts/release.mjs +158 -3
- 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 +50 -0
- 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,194 @@
|
|
|
1
|
+
// Rebuild the two source-derived public aggregate stores as one atomic artifact set.
|
|
2
|
+
|
|
3
|
+
import crypto from 'node:crypto';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { spawnSync } from 'node:child_process';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import { promoteArtifactSet } from '../kb/incremental-refresh.mjs';
|
|
9
|
+
import { reconstructGists, writeReconstruction } from './rebuild-gists-from-receipts.mjs';
|
|
10
|
+
import { writeRvfGeneration } from './rvf-generation.mjs';
|
|
11
|
+
|
|
12
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
13
|
+
const HEX64 = /^[a-f0-9]{64}$/;
|
|
14
|
+
const MODEL = 'Xenova/bge-base-en-v1.5';
|
|
15
|
+
const DIMENSIONS = 768;
|
|
16
|
+
const AGGREGATES = ['ruv-gists', 'concepts'];
|
|
17
|
+
|
|
18
|
+
function fail(message) {
|
|
19
|
+
throw new Error(`[corpus-aggregates] ${message}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function readJson(file, label) {
|
|
23
|
+
try { return JSON.parse(fs.readFileSync(file, 'utf8')); }
|
|
24
|
+
catch (error) { fail(`${label} is missing or unreadable (${error.message})`); }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function sha256File(file) {
|
|
28
|
+
return crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function chunks(text, size = 3200) {
|
|
32
|
+
const result = [];
|
|
33
|
+
let buffer = '';
|
|
34
|
+
for (const paragraph of String(text).split(/\n\n+/)) {
|
|
35
|
+
if (buffer && buffer.length + paragraph.length + 2 > size) {
|
|
36
|
+
result.push(buffer);
|
|
37
|
+
buffer = '';
|
|
38
|
+
}
|
|
39
|
+
buffer = buffer ? `${buffer}\n\n${paragraph}` : paragraph;
|
|
40
|
+
}
|
|
41
|
+
if (buffer.trim()) result.push(buffer);
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function buildConceptAggregate({ inputDir, outDir, sourceObservationSha256,
|
|
46
|
+
now = () => new Date().toISOString() } = {}) {
|
|
47
|
+
const input = path.resolve(inputDir || '');
|
|
48
|
+
const output = path.resolve(outDir || '');
|
|
49
|
+
if (!HEX64.test(String(sourceObservationSha256 || ''))) fail('concepts require an exact source observation');
|
|
50
|
+
const fenceFile = path.join(input, 'PRIVATE-STORES.json');
|
|
51
|
+
const fence = readJson(fenceFile, 'private fence');
|
|
52
|
+
if (!Array.isArray(fence.privateStores)) fail('private fence has no privateStores array');
|
|
53
|
+
const privateStores = new Set(fence.privateStores.map((store) => String(store).toLowerCase()));
|
|
54
|
+
const privateSlugs = new Set();
|
|
55
|
+
for (const store of privateStores) {
|
|
56
|
+
const file = path.join(input, `l2-topics.${store}.json`);
|
|
57
|
+
if (!fs.existsSync(file)) continue;
|
|
58
|
+
const topics = readJson(file, `${store} private topic fence`);
|
|
59
|
+
if (!Array.isArray(topics)) fail(`${store} private topic fence is malformed`);
|
|
60
|
+
for (const topic of topics) if (topic?.slug) privateSlugs.add(String(topic.slug));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const repositories = fs.readdirSync(input)
|
|
64
|
+
.filter((file) => file.endsWith('-primer.md'))
|
|
65
|
+
.map((file) => file.slice(0, -'-primer.md'.length))
|
|
66
|
+
.filter((store) => !privateStores.has(store.toLowerCase()))
|
|
67
|
+
.sort();
|
|
68
|
+
const slugRepository = new Map([
|
|
69
|
+
['guidance-mechanism', 'ruflo'], ['memory-end-to-end', 'ruflo'], ['adr-coverage', 'ruflo'],
|
|
70
|
+
]);
|
|
71
|
+
const inputFiles = new Set(['PRIVATE-STORES.json']);
|
|
72
|
+
for (const repository of repositories) {
|
|
73
|
+
const relative = `l2-topics.${repository}.json`;
|
|
74
|
+
const file = path.join(input, relative);
|
|
75
|
+
if (!fs.existsSync(file)) continue;
|
|
76
|
+
const topics = readJson(file, `${repository} topics`);
|
|
77
|
+
if (!Array.isArray(topics)) fail(`${repository} topics are malformed`);
|
|
78
|
+
inputFiles.add(relative);
|
|
79
|
+
for (const topic of topics) if (topic?.slug) slugRepository.set(String(topic.slug), repository);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const passages = [];
|
|
83
|
+
const entries = {};
|
|
84
|
+
let nextId = 0;
|
|
85
|
+
const add = (repository, kind, slug, title, body) => {
|
|
86
|
+
const parts = chunks(body);
|
|
87
|
+
for (const [index, text] of parts.entries()) {
|
|
88
|
+
const id = String(nextId++);
|
|
89
|
+
const passagePath = `${repository}/${kind}/${slug}${parts.length > 1 ? `#${index}` : ''}`;
|
|
90
|
+
passages.push({ id, text, path: passagePath, title });
|
|
91
|
+
entries[id] = { path: passagePath, kind: 'doc', title, chunk: index, preview: text.slice(0, 200) };
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const l2Dir = path.join(input, 'l2');
|
|
96
|
+
if (!fs.existsSync(l2Dir) || !fs.statSync(l2Dir).isDirectory()) fail('concept L2 input directory is missing');
|
|
97
|
+
for (const name of fs.readdirSync(l2Dir).filter((file) => file.endsWith('.md')).sort()) {
|
|
98
|
+
const slug = name.slice(0, -3);
|
|
99
|
+
const repository = slugRepository.get(slug) || 'ruvnet';
|
|
100
|
+
if (privateStores.has(repository.toLowerCase()) || privateSlugs.has(slug)) continue;
|
|
101
|
+
const relative = `l2/${name}`;
|
|
102
|
+
const body = fs.readFileSync(path.join(input, relative), 'utf8');
|
|
103
|
+
inputFiles.add(relative);
|
|
104
|
+
add(repository, 'L2', slug, body.match(/^#\s+(.+)/m)?.[1] || slug, body);
|
|
105
|
+
}
|
|
106
|
+
for (const repository of repositories) {
|
|
107
|
+
const relative = `${repository}-primer.md`;
|
|
108
|
+
const body = fs.readFileSync(path.join(input, relative), 'utf8');
|
|
109
|
+
inputFiles.add(relative);
|
|
110
|
+
add(repository, 'PRIMER', `${repository}-primer`, `${repository} — Primer`, body);
|
|
111
|
+
}
|
|
112
|
+
const cardsRelative = 'capability-cards.md';
|
|
113
|
+
const cardsFile = path.join(input, cardsRelative);
|
|
114
|
+
if (fs.existsSync(cardsFile)) {
|
|
115
|
+
inputFiles.add(cardsRelative);
|
|
116
|
+
const sections = fs.readFileSync(cardsFile, 'utf8').split(/^##\s+/m).slice(1);
|
|
117
|
+
for (const section of sections) {
|
|
118
|
+
const newline = section.indexOf('\n');
|
|
119
|
+
if (newline < 0) continue;
|
|
120
|
+
const repository = section.slice(0, newline).trim();
|
|
121
|
+
const body = section.slice(newline + 1).trim();
|
|
122
|
+
if (!repository || !body || privateStores.has(repository.toLowerCase())) continue;
|
|
123
|
+
add(repository, 'CARD', `${repository}-card`, `${repository} — Capability`, `${repository} — ${body}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (!passages.length) fail('concept aggregate produced zero public passages');
|
|
127
|
+
|
|
128
|
+
fs.mkdirSync(output, { recursive: true });
|
|
129
|
+
const passageBody = `${passages.map((row) => JSON.stringify(row)).join('\n')}\n`;
|
|
130
|
+
fs.writeFileSync(path.join(output, 'concepts.passages.jsonl'), passageBody);
|
|
131
|
+
fs.writeFileSync(path.join(output, 'concepts.meta.json'), `${JSON.stringify({
|
|
132
|
+
model: 'concepts', dimensions: 0, metric: 'cosine', name: 'concepts', generated: now(),
|
|
133
|
+
repo: 'ruvnet-concepts', note: 'Public L2 synthesis, primers, and capability cards.', entries,
|
|
134
|
+
}, null, 2)}\n`);
|
|
135
|
+
const receipt = {
|
|
136
|
+
schemaVersion: 1,
|
|
137
|
+
kind: 'ruvnet-brain-derived-store-receipt',
|
|
138
|
+
store: 'concepts',
|
|
139
|
+
sourceObservationSha256,
|
|
140
|
+
inputs: [...inputFiles].sort().map((relative) => ({ path: relative, sha256: sha256File(path.join(input, relative)) })),
|
|
141
|
+
passagesSha256: crypto.createHash('sha256').update(passageBody).digest('hex'),
|
|
142
|
+
};
|
|
143
|
+
fs.writeFileSync(path.join(output, 'concepts.sources.json'), `${JSON.stringify(receipt, null, 2)}\n`);
|
|
144
|
+
const classesFile = path.join(input, 'public-store-classes.json');
|
|
145
|
+
const classes = fs.existsSync(classesFile) ? readJson(classesFile, 'public store classes') : { schemaVersion: 1, derived: [] };
|
|
146
|
+
if (classes.schemaVersion !== 1 || !Array.isArray(classes.derived)) fail('public store classes are malformed');
|
|
147
|
+
classes.derived = [...classes.derived.filter((entry) => String(entry?.store || '').toLowerCase() !== 'concepts'),
|
|
148
|
+
{ store: 'concepts', receipt: 'concepts.sources.json' }]
|
|
149
|
+
.sort((a, b) => String(a.store).localeCompare(String(b.store)));
|
|
150
|
+
fs.writeFileSync(path.join(output, 'public-store-classes.json'), `${JSON.stringify(classes, null, 2)}\n`);
|
|
151
|
+
return { passages: passages.length, receipt };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function defaultBuildVector({ root, assetsDir, store }) {
|
|
155
|
+
const script = path.join(path.resolve(root || ROOT), 'kb', 'forge-big.mjs');
|
|
156
|
+
const result = spawnSync(process.execPath, [script, 'both', '--dir', assetsDir, '--name', store], {
|
|
157
|
+
encoding: 'utf8', stdio: 'inherit', env: { ...process.env },
|
|
158
|
+
});
|
|
159
|
+
if (result.error || result.status !== 0) fail(`${store} vector build failed (${result.error?.message || `exit ${result.status}`})`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export async function rebuildCorpusAggregates({ assetsDir, observation, root = ROOT,
|
|
163
|
+
fetchFn = globalThis.fetch, buildVector = defaultBuildVector, now = () => new Date().toISOString() } = {}) {
|
|
164
|
+
const assets = path.resolve(assetsDir || '');
|
|
165
|
+
const observationSha256 = String(observation?.observationSha256 || '');
|
|
166
|
+
if (!HEX64.test(observationSha256)) fail('aggregate rebuild requires an exact source observation');
|
|
167
|
+
const sourceFile = path.join(assets, 'ruv-gists.sources.json');
|
|
168
|
+
const source = readJson(sourceFile, 'gist source receipt');
|
|
169
|
+
if (source.sourceObservationSha256 !== observationSha256) fail('gist source receipt observation differs from the stable observation');
|
|
170
|
+
if (typeof buildVector !== 'function') fail('aggregate vector builder is unavailable');
|
|
171
|
+
|
|
172
|
+
const stage = fs.mkdtempSync(path.join(path.dirname(assets), '.corpus-aggregates-'));
|
|
173
|
+
try {
|
|
174
|
+
const gists = await reconstructGists(source, { fetchFn });
|
|
175
|
+
writeReconstruction(gists, { outDir: stage });
|
|
176
|
+
buildConceptAggregate({ inputDir: assets, outDir: stage, sourceObservationSha256: observationSha256, now });
|
|
177
|
+
for (const store of AGGREGATES) await buildVector({ root, assetsDir: stage, store });
|
|
178
|
+
writeRvfGeneration({ dir: stage, previousDir: assets, store: 'ruv-gists', model: MODEL,
|
|
179
|
+
dimensions: DIMENSIONS, sourceCommit: observationSha256, builtUtc: now() });
|
|
180
|
+
writeRvfGeneration({ dir: stage, previousDir: stage, store: 'concepts', model: MODEL,
|
|
181
|
+
dimensions: DIMENSIONS, sourceCommit: observationSha256, builtUtc: now() });
|
|
182
|
+
const files = [
|
|
183
|
+
...AGGREGATES.flatMap((store) => [
|
|
184
|
+
`${store}.passages.jsonl`, `${store}.meta.json`, `${store}.big.rvf`,
|
|
185
|
+
`${store}.big.rvf.idmap.json`, `${store}.big.rvf.embed.json`,
|
|
186
|
+
]),
|
|
187
|
+
'ruv-gists.sources.json', 'concepts.sources.json', 'public-store-classes.json', 'RVF-GENERATIONS.json',
|
|
188
|
+
];
|
|
189
|
+
promoteArtifactSet({ liveDir: assets, candidateDir: stage, files });
|
|
190
|
+
return { rebuilt: [...AGGREGATES].sort(), sourceObservationSha256: observationSha256 };
|
|
191
|
+
} finally {
|
|
192
|
+
fs.rmSync(stage, { recursive: true, force: true });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Seal and independently verify the exact public corpus bytes used by a release.
|
|
3
|
+
|
|
4
|
+
import crypto from 'node:crypto';
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import os from 'node:os';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { spawnSync } from 'node:child_process';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import { extractZip } from '../kb/zip-extract.mjs';
|
|
11
|
+
import { validatePublicInventory } from './public-inventory.mjs';
|
|
12
|
+
|
|
13
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const REQUIRED_SUFFIXES = [
|
|
15
|
+
'.big.rvf',
|
|
16
|
+
'.big.rvf.idmap.json',
|
|
17
|
+
'.big.rvf.embed.json',
|
|
18
|
+
'.passages.jsonl',
|
|
19
|
+
'.meta.json',
|
|
20
|
+
];
|
|
21
|
+
const OPTIONAL_SHIPPED_SUFFIXES = ['.big.passages.jsonl', '.big.meta.json'];
|
|
22
|
+
|
|
23
|
+
function fail(message) {
|
|
24
|
+
throw new Error(`[corpus-candidate] ${message}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function readJson(file, label) {
|
|
28
|
+
if (!file || !fs.existsSync(file)) fail(`${label} missing (${file || 'no path supplied'})`);
|
|
29
|
+
try {
|
|
30
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
31
|
+
} catch (error) {
|
|
32
|
+
fail(`${label} unreadable/corrupt (${error.message})`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function sha256File(file) {
|
|
37
|
+
const hash = crypto.createHash('sha256');
|
|
38
|
+
const fd = fs.openSync(file, 'r');
|
|
39
|
+
const buffer = Buffer.allocUnsafe(1024 * 1024);
|
|
40
|
+
try {
|
|
41
|
+
let bytes;
|
|
42
|
+
while ((bytes = fs.readSync(fd, buffer, 0, buffer.length, null)) > 0) {
|
|
43
|
+
hash.update(buffer.subarray(0, bytes));
|
|
44
|
+
}
|
|
45
|
+
} finally {
|
|
46
|
+
fs.closeSync(fd);
|
|
47
|
+
}
|
|
48
|
+
return hash.digest('hex');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function fileIdentity(file) {
|
|
52
|
+
return { file: path.basename(file), sha256: sha256File(file), bytes: fs.statSync(file).size };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function canonicalJson(value) {
|
|
56
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(',')}]`;
|
|
57
|
+
if (value && typeof value === 'object') {
|
|
58
|
+
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(',')}}`;
|
|
59
|
+
}
|
|
60
|
+
return JSON.stringify(value);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function policyRows(policy) {
|
|
64
|
+
if (Array.isArray(policy)) return policy;
|
|
65
|
+
for (const key of ['sources', 'rows', 'repositories', 'repos']) {
|
|
66
|
+
if (Array.isArray(policy?.[key])) return policy[key];
|
|
67
|
+
}
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function assertPolicyCurrent(policy) {
|
|
72
|
+
const rows = policyRows(policy);
|
|
73
|
+
const eligible = (row) => row?.eligible !== false && (!row?.disposition || row.disposition === 'eligible');
|
|
74
|
+
const stale = rows.filter((row) => eligible(row) && row?.status !== 'CURRENT');
|
|
75
|
+
if (stale.length) fail(`eligibility policy has ${stale.length} eligible row(s) that are not CURRENT`);
|
|
76
|
+
return {
|
|
77
|
+
eligibleRepoCount: rows.filter((row) => eligible(row) && row?.kind === 'repository' && row?.status === 'CURRENT').length,
|
|
78
|
+
gistCount: rows.filter((row) => eligible(row) && /gist/i.test(String(row?.kind || row?.type || ''))).length,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function verifyArchive({ bundleFile, stores, privateStores }) {
|
|
83
|
+
if (!bundleFile || !fs.existsSync(bundleFile)) fail(`bundle missing (${bundleFile || 'no path supplied'})`);
|
|
84
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'corpus-candidate-'));
|
|
85
|
+
try {
|
|
86
|
+
let extraction;
|
|
87
|
+
try {
|
|
88
|
+
extraction = await extractZip(bundleFile, tmp);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
fail(`cannot extract archive (${error.message})`);
|
|
91
|
+
}
|
|
92
|
+
const entries = extraction.entryNames;
|
|
93
|
+
for (const privateStore of privateStores) {
|
|
94
|
+
const escaped = privateStore.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
95
|
+
const privateFile = new RegExp(`(?:^|/)${escaped}(?:\\.|$)`, 'i');
|
|
96
|
+
if (entries.some((entry) => privateFile.test(entry.replaceAll('\\', '/')))) {
|
|
97
|
+
fail(`private store ${privateStore} is present in archive`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
for (const store of stores) {
|
|
101
|
+
for (const expected of store.files) {
|
|
102
|
+
const matches = entries.filter((entry) => path.posix.basename(entry.replaceAll('\\', '/')) === expected.file);
|
|
103
|
+
if (matches.length !== 1) fail(`archive must contain exactly one ${expected.file}; found ${matches.length}`);
|
|
104
|
+
const extracted = path.join(tmp, ...matches[0].replaceAll('\\', '/').split('/'));
|
|
105
|
+
if (sha256File(extracted) !== expected.sha256) fail(`archive ${expected.file} differs from canonical assets`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const selectedStores = new Map(stores.map(({ name }) => [name.toLowerCase(), name]));
|
|
109
|
+
const storeSuffixes = [...REQUIRED_SUFFIXES, ...OPTIONAL_SHIPPED_SUFFIXES]
|
|
110
|
+
.sort((left, right) => right.length - left.length);
|
|
111
|
+
const archiveFamilies = entries.map((entry) => path.posix.basename(entry.replaceAll('\\', '/')))
|
|
112
|
+
.map((name) => {
|
|
113
|
+
const suffix = storeSuffixes.find((candidate) => name.toLowerCase().endsWith(candidate));
|
|
114
|
+
return suffix ? { name, store: name.slice(0, -suffix.length) } : null;
|
|
115
|
+
}).filter(Boolean);
|
|
116
|
+
const unclassified = [...new Set(archiveFamilies.filter(({ store }) => !selectedStores.has(store.toLowerCase()))
|
|
117
|
+
.map(({ store }) => store))].sort();
|
|
118
|
+
if (unclassified.length) fail(`unclassified archive store(s): ${unclassified.join(', ')}`);
|
|
119
|
+
const aliases = archiveFamilies.filter(({ store }) => selectedStores.get(store.toLowerCase()) !== store)
|
|
120
|
+
.map(({ name }) => name).sort();
|
|
121
|
+
if (aliases.length) fail(`archive store-family alias differs by case: ${aliases.join(', ')}`);
|
|
122
|
+
} finally {
|
|
123
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
124
|
+
}
|
|
125
|
+
return fileIdentity(bundleFile);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function buildReceipt({
|
|
129
|
+
assetsDir,
|
|
130
|
+
bundleFile,
|
|
131
|
+
policyFile,
|
|
132
|
+
builderSourceSha,
|
|
133
|
+
createdAt,
|
|
134
|
+
}) {
|
|
135
|
+
const assets = path.resolve(assetsDir || '');
|
|
136
|
+
if (!fs.existsSync(assets) || !fs.statSync(assets).isDirectory()) fail(`assets directory missing (${assets})`);
|
|
137
|
+
if (!/^[a-f0-9]{40,64}$/i.test(builderSourceSha || '')) fail('builderSourceSha must be a 40-64 hex source identity');
|
|
138
|
+
|
|
139
|
+
const fenceFile = path.join(assets, 'PRIVATE-STORES.json');
|
|
140
|
+
const fence = readJson(fenceFile, 'private fence');
|
|
141
|
+
if (!Array.isArray(fence.privateStores)) fail('private fence has no privateStores array');
|
|
142
|
+
const privateStores = [...new Set(fence.privateStores.map((name) => String(name).toLowerCase()))].sort();
|
|
143
|
+
const privateSet = new Set(privateStores);
|
|
144
|
+
const ledgerFile = path.join(assets, 'RVF-GENERATIONS.json');
|
|
145
|
+
const ledger = readJson(ledgerFile, 'RVF generation ledger');
|
|
146
|
+
if (!ledger.stores || typeof ledger.stores !== 'object' || Array.isArray(ledger.stores)) {
|
|
147
|
+
fail('RVF generation ledger has no stores object');
|
|
148
|
+
}
|
|
149
|
+
const policy = readJson(path.resolve(policyFile || ''), 'eligibility policy');
|
|
150
|
+
|
|
151
|
+
const rvfFiles = fs.readdirSync(assets).filter((name) => /^.+\.big\.rvf$/.test(name)).sort();
|
|
152
|
+
const rvfByStore = new Map(rvfFiles.map((file) => [file.slice(0, -'.big.rvf'.length), file]));
|
|
153
|
+
const unreceipted = [...rvfByStore.keys()].filter((store) => !ledger.stores[store]);
|
|
154
|
+
if (unreceipted.length) fail(`unreceipted RVF files: ${unreceipted.join(', ')}`);
|
|
155
|
+
const orphanLedger = Object.entries(ledger.stores)
|
|
156
|
+
.filter(([store, generation]) => generation.file !== `${store}.big.rvf` || !rvfByStore.has(store))
|
|
157
|
+
.map(([store]) => store);
|
|
158
|
+
if (orphanLedger.length) fail(`ledger rows without RVFs: ${orphanLedger.join(', ')}`);
|
|
159
|
+
|
|
160
|
+
const digestOwners = new Map();
|
|
161
|
+
for (const [store, file] of rvfByStore) {
|
|
162
|
+
const digest = sha256File(path.join(assets, file));
|
|
163
|
+
const owners = digestOwners.get(digest) || [];
|
|
164
|
+
owners.push(store);
|
|
165
|
+
digestOwners.set(digest, owners);
|
|
166
|
+
}
|
|
167
|
+
const duplicateRvfDigests = [...digestOwners.entries()]
|
|
168
|
+
.filter(([, owners]) => owners.length > 1)
|
|
169
|
+
.map(([sha256, owners]) => ({ sha256, stores: owners.sort() }));
|
|
170
|
+
if (duplicateRvfDigests.length) fail(`duplicate RVF bytes: ${duplicateRvfDigests.map((row) => row.stores.join('/')).join(', ')}`);
|
|
171
|
+
const publicInventory = validatePublicInventory({ assetsDir: assets, coverage: policy, ledger });
|
|
172
|
+
const publicSet = new Set(publicInventory.publicStores);
|
|
173
|
+
const counts = assertPolicyCurrent(policy);
|
|
174
|
+
|
|
175
|
+
const missingSidecars = [];
|
|
176
|
+
const stores = [];
|
|
177
|
+
for (const [store, rvfFile] of [...rvfByStore].sort(([a], [b]) => a.localeCompare(b))) {
|
|
178
|
+
const generation = ledger.stores[store];
|
|
179
|
+
const rvfPath = path.join(assets, rvfFile);
|
|
180
|
+
if (generation.file !== rvfFile || generation.sha256 !== sha256File(rvfPath) || generation.bytes !== fs.statSync(rvfPath).size) {
|
|
181
|
+
fail(`${store}: generation receipt does not match RVF bytes`);
|
|
182
|
+
}
|
|
183
|
+
if (!generation.sourceCommit || !generation.builtUtc || !generation.model || !Number.isInteger(generation.dimensions)) {
|
|
184
|
+
fail(`${store}: generation receipt lacks sourceCommit, builtUtc, model, or dimensions provenance`);
|
|
185
|
+
}
|
|
186
|
+
if (privateSet.has(store.toLowerCase()) || !publicSet.has(store.toLowerCase())) continue;
|
|
187
|
+
const files = [];
|
|
188
|
+
for (const suffix of REQUIRED_SUFFIXES) {
|
|
189
|
+
const file = path.join(assets, `${store}${suffix}`);
|
|
190
|
+
if (!fs.existsSync(file)) missingSidecars.push(path.basename(file));
|
|
191
|
+
else files.push(fileIdentity(file));
|
|
192
|
+
}
|
|
193
|
+
for (const suffix of OPTIONAL_SHIPPED_SUFFIXES) {
|
|
194
|
+
const file = path.join(assets, `${store}${suffix}`);
|
|
195
|
+
if (fs.existsSync(file)) files.push(fileIdentity(file));
|
|
196
|
+
}
|
|
197
|
+
stores.push({
|
|
198
|
+
name: store,
|
|
199
|
+
sourceCommit: generation.sourceCommit,
|
|
200
|
+
builtUtc: generation.builtUtc,
|
|
201
|
+
model: generation.model,
|
|
202
|
+
dimensions: generation.dimensions,
|
|
203
|
+
files,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
if (missingSidecars.length) fail(`missing sidecars: ${missingSidecars.join(', ')}`);
|
|
207
|
+
if (!stores.length) fail('zero public corpus stores remain after the private fence');
|
|
208
|
+
const finalByteFiles = stores.flatMap((store) => store.files.map((file) => ({ store: store.name, ...file })))
|
|
209
|
+
.sort((a, b) => a.file.localeCompare(b.file) || a.store.localeCompare(b.store));
|
|
210
|
+
const finalBytePartitionSha256 = crypto.createHash('sha256').update(canonicalJson({
|
|
211
|
+
publicInventoryPartitionSha256: publicInventory.partitionSha256,
|
|
212
|
+
publicStores: publicInventory.publicStores,
|
|
213
|
+
files: finalByteFiles,
|
|
214
|
+
})).digest('hex');
|
|
215
|
+
const archive = await verifyArchive({ bundleFile: path.resolve(bundleFile || ''), stores, privateStores });
|
|
216
|
+
const policyIdentity = fileIdentity(path.resolve(policyFile));
|
|
217
|
+
const fenceIdentity = fileIdentity(fenceFile);
|
|
218
|
+
const ledgerIdentity = fileIdentity(ledgerFile);
|
|
219
|
+
const coverageGeneration = String(policy.coverageGeneration || policy.generation || policy.generated || policyIdentity.sha256);
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
schemaVersion: 1,
|
|
223
|
+
kind: 'ruvnet-brain-corpus-candidate',
|
|
224
|
+
builderSourceSha: builderSourceSha.toLowerCase(),
|
|
225
|
+
createdAt,
|
|
226
|
+
coverageGeneration,
|
|
227
|
+
eligibleRepoCount: counts.eligibleRepoCount,
|
|
228
|
+
gistCount: counts.gistCount,
|
|
229
|
+
storeCount: stores.length,
|
|
230
|
+
stores,
|
|
231
|
+
privateFence: fenceIdentity,
|
|
232
|
+
eligibilityPolicy: policyIdentity,
|
|
233
|
+
generationLedger: ledgerIdentity,
|
|
234
|
+
publicInventory,
|
|
235
|
+
finalBytePartitionSha256,
|
|
236
|
+
excludedPrivateStores: privateStores.filter((privateStore) =>
|
|
237
|
+
[...rvfByStore.keys()].some((store) => store.toLowerCase() === privateStore)),
|
|
238
|
+
duplicateRvfDigests,
|
|
239
|
+
unreceiptedRvfFiles: [],
|
|
240
|
+
missingSidecars: [],
|
|
241
|
+
archive,
|
|
242
|
+
generator: { corpusCandidateSha256: sha256File(fileURLToPath(import.meta.url)) },
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function currentGitSha() {
|
|
247
|
+
const result = spawnSync('git', ['rev-parse', 'HEAD'], { cwd: path.resolve(HERE, '..'), encoding: 'utf8' });
|
|
248
|
+
if (result.status !== 0) fail('cannot determine builder source SHA');
|
|
249
|
+
return result.stdout.trim();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export async function createCorpusReceipt(options) {
|
|
253
|
+
const receiptFile = path.resolve(options.receiptFile || 'dist/corpus-receipt.json');
|
|
254
|
+
const receipt = await buildReceipt({
|
|
255
|
+
...options,
|
|
256
|
+
builderSourceSha: options.builderSourceSha || currentGitSha(),
|
|
257
|
+
createdAt: options.createdAt || new Date().toISOString(),
|
|
258
|
+
});
|
|
259
|
+
fs.mkdirSync(path.dirname(receiptFile), { recursive: true });
|
|
260
|
+
fs.writeFileSync(receiptFile, `${JSON.stringify(receipt, null, 2)}\n`, { flag: options.overwrite === false ? 'wx' : 'w' });
|
|
261
|
+
return receipt;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export async function verifyCorpusReceipt(options) {
|
|
265
|
+
const receipt = readJson(path.resolve(options.receiptFile || ''), 'corpus receipt');
|
|
266
|
+
if (receipt.kind !== 'ruvnet-brain-corpus-candidate' || receipt.schemaVersion !== 1) fail('unsupported corpus receipt');
|
|
267
|
+
const exactFiles = [
|
|
268
|
+
['archive', path.resolve(options.bundleFile || '')],
|
|
269
|
+
['eligibility policy', path.resolve(options.policyFile || '')],
|
|
270
|
+
['private fence', path.join(path.resolve(options.assetsDir || ''), 'PRIVATE-STORES.json')],
|
|
271
|
+
['generation ledger', path.join(path.resolve(options.assetsDir || ''), 'RVF-GENERATIONS.json')],
|
|
272
|
+
];
|
|
273
|
+
for (const [label, file] of exactFiles) {
|
|
274
|
+
if (!fs.existsSync(file)) fail(`${label} missing (${file})`);
|
|
275
|
+
const expected = label === 'archive'
|
|
276
|
+
? receipt.archive
|
|
277
|
+
: label === 'eligibility policy'
|
|
278
|
+
? receipt.eligibilityPolicy
|
|
279
|
+
: label === 'private fence'
|
|
280
|
+
? receipt.privateFence
|
|
281
|
+
: receipt.generationLedger;
|
|
282
|
+
if (sha256File(file) !== expected?.sha256) fail(`${label} sha256 differs from receipt`);
|
|
283
|
+
if (fs.statSync(file).size !== expected?.bytes) fail(`${label} byte length differs from receipt`);
|
|
284
|
+
}
|
|
285
|
+
const actual = await buildReceipt({
|
|
286
|
+
...options,
|
|
287
|
+
builderSourceSha: receipt.builderSourceSha,
|
|
288
|
+
createdAt: receipt.createdAt,
|
|
289
|
+
});
|
|
290
|
+
if (canonicalJson(actual) !== canonicalJson(receipt)) fail('receipt does not match the exact corpus inputs');
|
|
291
|
+
return receipt;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function arg(name, fallback) {
|
|
295
|
+
const index = process.argv.indexOf(name);
|
|
296
|
+
return index >= 0 ? process.argv[index + 1] : fallback;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async function main() {
|
|
300
|
+
const mode = process.argv.includes('--verify') ? 'verify' : 'create';
|
|
301
|
+
const options = {
|
|
302
|
+
assetsDir: arg('--assets', 'kb'),
|
|
303
|
+
bundleFile: arg('--bundle', 'dist/ruvnet-brain.zip'),
|
|
304
|
+
policyFile: arg('--policy'),
|
|
305
|
+
receiptFile: arg('--receipt', arg('--out', 'dist/corpus-receipt.json')),
|
|
306
|
+
builderSourceSha: arg('--builder-source-sha'),
|
|
307
|
+
};
|
|
308
|
+
const receipt = mode === 'verify' ? await verifyCorpusReceipt(options) : await createCorpusReceipt(options);
|
|
309
|
+
console.log(JSON.stringify({ ok: true, mode, archive: receipt.archive, stores: receipt.storeCount }, null, 2));
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (path.resolve(process.argv[1] || '') === fileURLToPath(import.meta.url)) {
|
|
313
|
+
main().catch((error) => { console.error(error.message); process.exitCode = 1; });
|
|
314
|
+
}
|