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,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
|
+
}
|
|
@@ -7,6 +7,8 @@ import os from 'node:os';
|
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
import { spawnSync } from 'node:child_process';
|
|
9
9
|
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import { extractZip } from '../kb/zip-extract.mjs';
|
|
11
|
+
import { validatePublicInventory } from './public-inventory.mjs';
|
|
10
12
|
|
|
11
13
|
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
12
14
|
const REQUIRED_SUFFIXES = [
|
|
@@ -68,55 +70,62 @@ function policyRows(policy) {
|
|
|
68
70
|
|
|
69
71
|
function assertPolicyCurrent(policy) {
|
|
70
72
|
const rows = policyRows(policy);
|
|
71
|
-
const
|
|
73
|
+
const eligible = (row) => row?.eligible !== false && (!row?.disposition || row.disposition === 'eligible');
|
|
74
|
+
const stale = rows.filter((row) => eligible(row) && row?.status !== 'CURRENT');
|
|
72
75
|
if (stale.length) fail(`eligibility policy has ${stale.length} eligible row(s) that are not CURRENT`);
|
|
73
76
|
return {
|
|
74
|
-
eligibleRepoCount: rows.filter((row) => row?.
|
|
75
|
-
gistCount: rows.filter((row) => /gist/i.test(String(row?.kind || row?.type || ''))).length,
|
|
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,
|
|
76
79
|
};
|
|
77
80
|
}
|
|
78
81
|
|
|
79
|
-
function
|
|
80
|
-
const result = spawnSync('unzip', ['-Z1', bundleFile], { encoding: 'utf8' });
|
|
81
|
-
if (result.status !== 0) fail(`cannot list archive (${result.stderr.trim() || `exit ${result.status}`})`);
|
|
82
|
-
const entries = result.stdout.split(/\r?\n/).filter(Boolean);
|
|
83
|
-
for (const entry of entries) {
|
|
84
|
-
const normalized = entry.replaceAll('\\', '/');
|
|
85
|
-
if (normalized.startsWith('/') || normalized.split('/').includes('..')) {
|
|
86
|
-
fail(`archive contains unsafe path ${JSON.stringify(entry)}`);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
return entries;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function verifyArchive({ bundleFile, stores, privateStores }) {
|
|
82
|
+
async function verifyArchive({ bundleFile, stores, privateStores }) {
|
|
93
83
|
if (!bundleFile || !fs.existsSync(bundleFile)) fail(`bundle missing (${bundleFile || 'no path supplied'})`);
|
|
94
|
-
const entries = archiveEntries(bundleFile);
|
|
95
|
-
for (const privateStore of privateStores) {
|
|
96
|
-
const escaped = privateStore.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
97
|
-
const privateFile = new RegExp(`(?:^|/)${escaped}(?:\\.|$)`, 'i');
|
|
98
|
-
if (entries.some((entry) => privateFile.test(entry))) fail(`private store ${privateStore} is present in archive`);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
84
|
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'corpus-candidate-'));
|
|
102
85
|
try {
|
|
103
|
-
|
|
104
|
-
|
|
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
|
+
}
|
|
105
100
|
for (const store of stores) {
|
|
106
101
|
for (const expected of store.files) {
|
|
107
|
-
const matches = entries.filter((entry) => path.posix.basename(entry) === expected.file);
|
|
102
|
+
const matches = entries.filter((entry) => path.posix.basename(entry.replaceAll('\\', '/')) === expected.file);
|
|
108
103
|
if (matches.length !== 1) fail(`archive must contain exactly one ${expected.file}; found ${matches.length}`);
|
|
109
104
|
const extracted = path.join(tmp, ...matches[0].replaceAll('\\', '/').split('/'));
|
|
110
105
|
if (sha256File(extracted) !== expected.sha256) fail(`archive ${expected.file} differs from canonical assets`);
|
|
111
106
|
}
|
|
112
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(', ')}`);
|
|
113
122
|
} finally {
|
|
114
123
|
fs.rmSync(tmp, { recursive: true, force: true });
|
|
115
124
|
}
|
|
116
125
|
return fileIdentity(bundleFile);
|
|
117
126
|
}
|
|
118
127
|
|
|
119
|
-
function buildReceipt({
|
|
128
|
+
async function buildReceipt({
|
|
120
129
|
assetsDir,
|
|
121
130
|
bundleFile,
|
|
122
131
|
policyFile,
|
|
@@ -138,7 +147,6 @@ function buildReceipt({
|
|
|
138
147
|
fail('RVF generation ledger has no stores object');
|
|
139
148
|
}
|
|
140
149
|
const policy = readJson(path.resolve(policyFile || ''), 'eligibility policy');
|
|
141
|
-
const counts = assertPolicyCurrent(policy);
|
|
142
150
|
|
|
143
151
|
const rvfFiles = fs.readdirSync(assets).filter((name) => /^.+\.big\.rvf$/.test(name)).sort();
|
|
144
152
|
const rvfByStore = new Map(rvfFiles.map((file) => [file.slice(0, -'.big.rvf'.length), file]));
|
|
@@ -160,6 +168,9 @@ function buildReceipt({
|
|
|
160
168
|
.filter(([, owners]) => owners.length > 1)
|
|
161
169
|
.map(([sha256, owners]) => ({ sha256, stores: owners.sort() }));
|
|
162
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);
|
|
163
174
|
|
|
164
175
|
const missingSidecars = [];
|
|
165
176
|
const stores = [];
|
|
@@ -172,7 +183,7 @@ function buildReceipt({
|
|
|
172
183
|
if (!generation.sourceCommit || !generation.builtUtc || !generation.model || !Number.isInteger(generation.dimensions)) {
|
|
173
184
|
fail(`${store}: generation receipt lacks sourceCommit, builtUtc, model, or dimensions provenance`);
|
|
174
185
|
}
|
|
175
|
-
if (privateSet.has(store.toLowerCase())) continue;
|
|
186
|
+
if (privateSet.has(store.toLowerCase()) || !publicSet.has(store.toLowerCase())) continue;
|
|
176
187
|
const files = [];
|
|
177
188
|
for (const suffix of REQUIRED_SUFFIXES) {
|
|
178
189
|
const file = path.join(assets, `${store}${suffix}`);
|
|
@@ -194,7 +205,14 @@ function buildReceipt({
|
|
|
194
205
|
}
|
|
195
206
|
if (missingSidecars.length) fail(`missing sidecars: ${missingSidecars.join(', ')}`);
|
|
196
207
|
if (!stores.length) fail('zero public corpus stores remain after the private fence');
|
|
197
|
-
const
|
|
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 });
|
|
198
216
|
const policyIdentity = fileIdentity(path.resolve(policyFile));
|
|
199
217
|
const fenceIdentity = fileIdentity(fenceFile);
|
|
200
218
|
const ledgerIdentity = fileIdentity(ledgerFile);
|
|
@@ -213,6 +231,8 @@ function buildReceipt({
|
|
|
213
231
|
privateFence: fenceIdentity,
|
|
214
232
|
eligibilityPolicy: policyIdentity,
|
|
215
233
|
generationLedger: ledgerIdentity,
|
|
234
|
+
publicInventory,
|
|
235
|
+
finalBytePartitionSha256,
|
|
216
236
|
excludedPrivateStores: privateStores.filter((privateStore) =>
|
|
217
237
|
[...rvfByStore.keys()].some((store) => store.toLowerCase() === privateStore)),
|
|
218
238
|
duplicateRvfDigests,
|
|
@@ -229,9 +249,9 @@ function currentGitSha() {
|
|
|
229
249
|
return result.stdout.trim();
|
|
230
250
|
}
|
|
231
251
|
|
|
232
|
-
export function createCorpusReceipt(options) {
|
|
252
|
+
export async function createCorpusReceipt(options) {
|
|
233
253
|
const receiptFile = path.resolve(options.receiptFile || 'dist/corpus-receipt.json');
|
|
234
|
-
const receipt = buildReceipt({
|
|
254
|
+
const receipt = await buildReceipt({
|
|
235
255
|
...options,
|
|
236
256
|
builderSourceSha: options.builderSourceSha || currentGitSha(),
|
|
237
257
|
createdAt: options.createdAt || new Date().toISOString(),
|
|
@@ -241,7 +261,7 @@ export function createCorpusReceipt(options) {
|
|
|
241
261
|
return receipt;
|
|
242
262
|
}
|
|
243
263
|
|
|
244
|
-
export function verifyCorpusReceipt(options) {
|
|
264
|
+
export async function verifyCorpusReceipt(options) {
|
|
245
265
|
const receipt = readJson(path.resolve(options.receiptFile || ''), 'corpus receipt');
|
|
246
266
|
if (receipt.kind !== 'ruvnet-brain-corpus-candidate' || receipt.schemaVersion !== 1) fail('unsupported corpus receipt');
|
|
247
267
|
const exactFiles = [
|
|
@@ -262,7 +282,7 @@ export function verifyCorpusReceipt(options) {
|
|
|
262
282
|
if (sha256File(file) !== expected?.sha256) fail(`${label} sha256 differs from receipt`);
|
|
263
283
|
if (fs.statSync(file).size !== expected?.bytes) fail(`${label} byte length differs from receipt`);
|
|
264
284
|
}
|
|
265
|
-
const actual = buildReceipt({
|
|
285
|
+
const actual = await buildReceipt({
|
|
266
286
|
...options,
|
|
267
287
|
builderSourceSha: receipt.builderSourceSha,
|
|
268
288
|
createdAt: receipt.createdAt,
|
|
@@ -285,7 +305,7 @@ async function main() {
|
|
|
285
305
|
receiptFile: arg('--receipt', arg('--out', 'dist/corpus-receipt.json')),
|
|
286
306
|
builderSourceSha: arg('--builder-source-sha'),
|
|
287
307
|
};
|
|
288
|
-
const receipt = mode === 'verify' ? verifyCorpusReceipt(options) : createCorpusReceipt(options);
|
|
308
|
+
const receipt = mode === 'verify' ? await verifyCorpusReceipt(options) : await createCorpusReceipt(options);
|
|
289
309
|
console.log(JSON.stringify({ ok: true, mode, archive: receipt.archive, stores: receipt.storeCount }, null, 2));
|
|
290
310
|
}
|
|
291
311
|
|