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
package/scripts/release.mjs
CHANGED
|
@@ -32,11 +32,12 @@ import crypto from 'node:crypto';
|
|
|
32
32
|
import { validateProtectedPublishInvocation } from './protected-release-invocation.mjs';
|
|
33
33
|
import { runReleaseTransaction } from './release-transaction.mjs';
|
|
34
34
|
import { liveReleaseProvider } from './release-transaction-provider.mjs';
|
|
35
|
-
import { stagedHostVerifier } from './staged-host-verifier.mjs';
|
|
36
35
|
import { verifyPayload } from './release-payload.mjs';
|
|
37
36
|
|
|
38
37
|
const ROOT = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
38
|
+
const IS_MAIN = path.resolve(process.argv[1] || '') === fileURLToPath(import.meta.url);
|
|
39
39
|
const PUBLISH = process.argv.includes('--publish');
|
|
40
|
+
const CORPUS_SEED = process.argv.includes('--corpus-seed');
|
|
40
41
|
let protectedCandidate = null;
|
|
41
42
|
let sealedPackageArtifact = null;
|
|
42
43
|
let publicationReceiptPath = null;
|
|
@@ -56,6 +57,149 @@ function runOrDie(label, cmd, args, opts = {}) {
|
|
|
56
57
|
}
|
|
57
58
|
}
|
|
58
59
|
|
|
60
|
+
const cliArg = (argv, name) => {
|
|
61
|
+
const index = argv.indexOf(name);
|
|
62
|
+
return index >= 0 ? argv[index + 1] : undefined;
|
|
63
|
+
};
|
|
64
|
+
const isHex = (value, length) => new RegExp(`^[a-f0-9]{${length}}$`).test(String(value || ''));
|
|
65
|
+
const sha256File = (file) => {
|
|
66
|
+
const hash = crypto.createHash('sha256');
|
|
67
|
+
const fd = fs.openSync(file, 'r');
|
|
68
|
+
const buffer = Buffer.allocUnsafe(1024 * 1024);
|
|
69
|
+
try {
|
|
70
|
+
let bytes;
|
|
71
|
+
while ((bytes = fs.readSync(fd, buffer, 0, buffer.length, null)) > 0) hash.update(buffer.subarray(0, bytes));
|
|
72
|
+
} finally {
|
|
73
|
+
fs.closeSync(fd);
|
|
74
|
+
}
|
|
75
|
+
return hash.digest('hex');
|
|
76
|
+
};
|
|
77
|
+
const exactFileIdentity = (value) => value && typeof value.file === 'string'
|
|
78
|
+
&& isHex(value.sha256, 64) && Number.isSafeInteger(value.bytes) && value.bytes >= 0;
|
|
79
|
+
|
|
80
|
+
function corpusFailure(message) {
|
|
81
|
+
throw new Error(`[corpus-seed] ${message}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function runProtectedCorpusSeed({
|
|
85
|
+
argv = process.argv.slice(2),
|
|
86
|
+
env = process.env,
|
|
87
|
+
root = ROOT,
|
|
88
|
+
run = (command, args, options) => spawnSync(command, args, { encoding: 'utf8', ...options }),
|
|
89
|
+
} = {}) {
|
|
90
|
+
if (env.GITHUB_ACTIONS !== 'true' || env.GITHUB_WORKFLOW !== 'protected-release'
|
|
91
|
+
|| env.GITHUB_REPOSITORY !== 'stuinfla/ruvnet-brain') {
|
|
92
|
+
corpusFailure('publication is allowed only inside the protected-release GitHub workflow for stuinfla/ruvnet-brain');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const tag = cliArg(argv, '--corpus-tag');
|
|
96
|
+
const bundleFile = cliArg(argv, '--corpus-bundle');
|
|
97
|
+
const receiptFile = cliArg(argv, '--corpus-receipt');
|
|
98
|
+
const target = cliArg(argv, '--target');
|
|
99
|
+
const repo = cliArg(argv, '--repo') || env.GITHUB_REPOSITORY;
|
|
100
|
+
const digestMatch = String(tag || '').match(/^corpus-sha256-([a-f0-9]{64})$/);
|
|
101
|
+
if (!digestMatch) corpusFailure('corpus tag must be corpus-sha256- followed by 64 lowercase hex characters');
|
|
102
|
+
if (repo !== env.GITHUB_REPOSITORY || repo !== 'stuinfla/ruvnet-brain') corpusFailure('repository does not match the protected workflow');
|
|
103
|
+
|
|
104
|
+
for (const [label, file] of [['bundle', bundleFile], ['receipt', receiptFile]]) {
|
|
105
|
+
if (!file || !path.isAbsolute(file)) corpusFailure(`${label} must be an absolute regular file`);
|
|
106
|
+
try {
|
|
107
|
+
const stat = fs.lstatSync(file);
|
|
108
|
+
if (!stat.isFile() || stat.isSymbolicLink()) corpusFailure(`${label} must be an absolute regular file`);
|
|
109
|
+
} catch (error) {
|
|
110
|
+
if (String(error.message).startsWith('[corpus-seed]')) throw error;
|
|
111
|
+
corpusFailure(`${label} must be an absolute regular file (${error.message})`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const headResult = run('git', ['rev-parse', 'HEAD'], {
|
|
116
|
+
cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'],
|
|
117
|
+
});
|
|
118
|
+
if (headResult.error || headResult.status !== 0) corpusFailure('cannot resolve release HEAD');
|
|
119
|
+
const head = String(headResult.stdout || '').trim();
|
|
120
|
+
|
|
121
|
+
let receipt;
|
|
122
|
+
try {
|
|
123
|
+
receipt = JSON.parse(fs.readFileSync(receiptFile, 'utf8'));
|
|
124
|
+
} catch (error) {
|
|
125
|
+
corpusFailure(`corpus receipt is unreadable/corrupt (${error.message})`);
|
|
126
|
+
}
|
|
127
|
+
if (!isHex(target, 40) || target !== head || target !== env.GITHUB_SHA || target !== receipt.builderSourceSha) {
|
|
128
|
+
corpusFailure('target must exactly equal HEAD, GITHUB_SHA, and the corpus receipt builderSourceSha');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const boundaryIdentities = [receipt.privateFence, receipt.eligibilityPolicy, receipt.generationLedger];
|
|
132
|
+
const storeBindingsValid = Number.isSafeInteger(receipt.storeCount) && receipt.storeCount > 0
|
|
133
|
+
&& Array.isArray(receipt.stores) && receipt.stores.length === receipt.storeCount
|
|
134
|
+
&& receipt.stores.every((store) => typeof store?.name === 'string' && store.name.length > 0
|
|
135
|
+
&& /^[a-f0-9]{7,64}$/.test(String(store.sourceCommit || ''))
|
|
136
|
+
&& typeof store.builtUtc === 'string' && Number.isFinite(Date.parse(store.builtUtc))
|
|
137
|
+
&& typeof store.model === 'string' && store.model.length > 0
|
|
138
|
+
&& Number.isSafeInteger(store.dimensions) && store.dimensions > 0
|
|
139
|
+
&& Array.isArray(store.files) && store.files.length > 0 && store.files.every(exactFileIdentity));
|
|
140
|
+
const emptyFailureArrays = ['duplicateRvfDigests', 'unreceiptedRvfFiles', 'missingSidecars']
|
|
141
|
+
.every((key) => Array.isArray(receipt[key]) && receipt[key].length === 0);
|
|
142
|
+
const privateExclusionsValid = Array.isArray(receipt.excludedPrivateStores)
|
|
143
|
+
&& receipt.excludedPrivateStores.every((name) => typeof name === 'string' && name.length > 0);
|
|
144
|
+
const generatorFile = path.join(root, 'scripts/corpus-candidate.mjs');
|
|
145
|
+
const generatorValid = fs.existsSync(generatorFile)
|
|
146
|
+
&& receipt.generator?.corpusCandidateSha256 === sha256File(generatorFile);
|
|
147
|
+
if (receipt.schemaVersion !== 1 || receipt.kind !== 'ruvnet-brain-corpus-candidate'
|
|
148
|
+
|| !receipt.createdAt || !receipt.coverageGeneration || !storeBindingsValid || !emptyFailureArrays
|
|
149
|
+
|| !privateExclusionsValid || !boundaryIdentities.every(exactFileIdentity) || !exactFileIdentity(receipt.archive)
|
|
150
|
+
|| !generatorValid
|
|
151
|
+
|| receipt.archive.file !== path.basename(bundleFile)) {
|
|
152
|
+
corpusFailure('corpus receipt bindings are incomplete or invalid');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const archiveSha256 = sha256File(bundleFile);
|
|
156
|
+
if (archiveSha256 !== receipt.archive.sha256 || fs.statSync(bundleFile).size !== receipt.archive.bytes) {
|
|
157
|
+
corpusFailure('archive bytes do not match the corpus receipt');
|
|
158
|
+
}
|
|
159
|
+
if (digestMatch[1] !== archiveSha256) corpusFailure('corpus tag digest does not match the receipt and archive');
|
|
160
|
+
|
|
161
|
+
const viewArgs = ['release', 'view', tag, '--json', 'tagName', '--repo', repo];
|
|
162
|
+
const ghCommand = env.RUVNET_GH_COMMAND || 'gh';
|
|
163
|
+
const ghPrefix = env.RUVNET_GH_SCRIPT ? [env.RUVNET_GH_SCRIPT] : [];
|
|
164
|
+
const view = run(ghCommand, [...ghPrefix, ...viewArgs], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
165
|
+
if (!view.error && view.status === 0) corpusFailure(`release ${tag} already exists; refusing to overwrite immutable corpus seed`);
|
|
166
|
+
const viewError = String(view.error?.message || view.stderr || view.stdout || '');
|
|
167
|
+
if (!/(release not found|no release found)/i.test(viewError)) corpusFailure(`cannot prove ${tag} is absent (${viewError.trim() || `gh exited ${view.status}`})`);
|
|
168
|
+
|
|
169
|
+
const notes = [
|
|
170
|
+
'Content-addressed RuvNet Brain corpus seed.',
|
|
171
|
+
`Archive SHA-256: ${archiveSha256}`,
|
|
172
|
+
`Receipt SHA-256: ${sha256File(receiptFile)}`,
|
|
173
|
+
`Stores: ${receipt.storeCount}`,
|
|
174
|
+
`Coverage generation: ${receipt.coverageGeneration}`,
|
|
175
|
+
'This published prerelease is immutable and must never be replaced.',
|
|
176
|
+
].join('\n');
|
|
177
|
+
const createArgs = [
|
|
178
|
+
'release', 'create', tag,
|
|
179
|
+
'--prerelease', '--latest=false',
|
|
180
|
+
'--target', target,
|
|
181
|
+
'--repo', repo,
|
|
182
|
+
'--title', `Immutable corpus seed ${archiveSha256.slice(0, 16)}`,
|
|
183
|
+
'--notes', notes,
|
|
184
|
+
bundleFile, receiptFile,
|
|
185
|
+
];
|
|
186
|
+
const create = run(ghCommand, [...ghPrefix, ...createArgs], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
187
|
+
if (create.error || create.status !== 0) {
|
|
188
|
+
corpusFailure(`protected corpus publication failed (${String(create.error?.message || create.stderr || create.stdout || '').trim()})`);
|
|
189
|
+
}
|
|
190
|
+
return { tag, target, repository: repo, archiveSha256, receiptSha256: sha256File(receiptFile) };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (IS_MAIN && CORPUS_SEED) {
|
|
194
|
+
try {
|
|
195
|
+
const result = runProtectedCorpusSeed();
|
|
196
|
+
console.log(JSON.stringify({ ok: true, mode: 'corpus-seed', ...result }, null, 2));
|
|
197
|
+
} catch (error) {
|
|
198
|
+
console.error(error.message);
|
|
199
|
+
process.exitCode = 1;
|
|
200
|
+
}
|
|
201
|
+
} else if (IS_MAIN) {
|
|
202
|
+
|
|
59
203
|
console.log(`\n${c.b('RuvNet Brain — release / definition-of-done')} ${c.dim('· ' + (PUBLISH ? 'PUBLISH' : 'check-only') + ' · shipping ' + V())}\n`);
|
|
60
204
|
|
|
61
205
|
// The local CLI remains useful as a read-only preflight, but publication authority lives only in
|
|
@@ -195,6 +339,8 @@ if (PUBLISH) {
|
|
|
195
339
|
bundleSignaturePath: path.join(payloadRoot, 'ruvnet-brain.zip.sig'),
|
|
196
340
|
bundleDigestPath: path.join(payloadRoot, 'ruvnet-brain.zip.sha256'),
|
|
197
341
|
packagePath: byRole.get('npm'),
|
|
342
|
+
corpusSeedPath: byRole.get('corpus-seed'),
|
|
343
|
+
generationLedgerPath: byRole.get('generation-ledger'),
|
|
198
344
|
};
|
|
199
345
|
for (const asset of Object.values(assets)) {
|
|
200
346
|
if (!fs.existsSync(asset)) {
|
|
@@ -224,7 +370,12 @@ if (PUBLISH) {
|
|
|
224
370
|
bundleSha256,
|
|
225
371
|
bundleSignatureSha256: crypto.createHash('sha256').update(fs.readFileSync(assets.bundleSignaturePath)).digest('hex'),
|
|
226
372
|
bundleDigestSha256: crypto.createHash('sha256').update(fs.readFileSync(assets.bundleDigestPath)).digest('hex'),
|
|
373
|
+
corpusSeedSha256: crypto.createHash('sha256').update(fs.readFileSync(assets.corpusSeedPath)).digest('hex'),
|
|
374
|
+
generationLedgerSha256: crypto.createHash('sha256').update(fs.readFileSync(assets.generationLedgerPath)).digest('hex'),
|
|
227
375
|
};
|
|
376
|
+
const identityPath = path.resolve(ROOT, process.env.RUVNET_RELEASE_IDENTITY || 'release-evidence/release-identity.json');
|
|
377
|
+
if (fs.existsSync(identityPath)) throw new Error(`refusing to overwrite release identity: ${identityPath}`);
|
|
378
|
+
fs.writeFileSync(identityPath, `${JSON.stringify(identity, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
|
|
228
379
|
const privatePem = process.env.RUVNET_SIGNING_KEY;
|
|
229
380
|
if (!privatePem) throw new Error('RUVNET_SIGNING_KEY is required for signed transaction receipts');
|
|
230
381
|
const finalReceipt = await runReleaseTransaction({
|
|
@@ -235,9 +386,12 @@ if (PUBLISH) {
|
|
|
235
386
|
}),
|
|
236
387
|
privateKey: crypto.createPrivateKey(privatePem),
|
|
237
388
|
publicKey: crypto.createPublicKey(fs.readFileSync(path.join(ROOT, 'keys/ruvnet-brain-signing.pub.pem'), 'utf8')),
|
|
238
|
-
hostVerifier: stagedHostVerifier({ assets, identity }),
|
|
239
389
|
});
|
|
240
390
|
if (finalReceipt.state !== 'channels-converged') throw new Error(`release transaction stopped at ${finalReceipt.state}`);
|
|
391
|
+
const channelReceiptPath = path.resolve(ROOT,
|
|
392
|
+
process.env.RUVNET_CHANNEL_RECEIPT || 'release-evidence/channels-converged-receipt.json');
|
|
393
|
+
if (fs.existsSync(channelReceiptPath)) throw new Error(`refusing to overwrite channel receipt: ${channelReceiptPath}`);
|
|
394
|
+
fs.writeFileSync(channelReceiptPath, `${JSON.stringify(finalReceipt, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
|
|
241
395
|
} else {
|
|
242
396
|
step('D', 'remote staged release transaction — SKIPPED (check-only; pass --publish to publish)');
|
|
243
397
|
}
|
|
@@ -250,7 +404,8 @@ if (!PUBLISH) {
|
|
|
250
404
|
}
|
|
251
405
|
|
|
252
406
|
if (PUBLISH) {
|
|
253
|
-
console.log(`\n${c.
|
|
407
|
+
console.log(`\n${c.y(c.b('PUBLISHED, NOT VERIFIED'))} — npm and GitHub expose the sealed candidate; public install verification is still required.\n`);
|
|
254
408
|
} else {
|
|
255
409
|
console.log(`\n${c.g(c.b('✓✓✓ PREFLIGHT PASS — NOT PUBLISHED'))} — the committed candidate passed every check-only gate.\n`);
|
|
256
410
|
}
|
|
411
|
+
}
|
|
@@ -42,7 +42,7 @@ import fs from 'node:fs';
|
|
|
42
42
|
import path from 'node:path';
|
|
43
43
|
import { execFileSync } from 'node:child_process';
|
|
44
44
|
import { fileURLToPath } from 'node:url';
|
|
45
|
-
import { storeRoot, storesAt } from '../kb/store-root.mjs';
|
|
45
|
+
import { storeRoot, storesAt, rootNeverMaterialized } from '../kb/store-root.mjs';
|
|
46
46
|
|
|
47
47
|
const ROOT = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
48
48
|
const LEDGER = path.join(ROOT, 'kb', 'local-ingests.json');
|
|
@@ -71,7 +71,7 @@ export function missingIngests({ ledgerFile = LEDGER, root = storeRoot() } = {})
|
|
|
71
71
|
export function classify({ ledgerFile = LEDGER, root = storeRoot() } = {}) {
|
|
72
72
|
const missing = missingIngests({ ledgerFile, root });
|
|
73
73
|
if (!missing.length) return { state: OK, missing };
|
|
74
|
-
if (
|
|
74
|
+
if (rootNeverMaterialized(root)) return { state: NEVER_MATERIALIZED, missing };
|
|
75
75
|
return { state: WIPED, missing };
|
|
76
76
|
}
|
|
77
77
|
|