ruvnet-brain 4.3.3 → 4.3.9
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 +1 -1
- package/bin/install.mjs +8 -7
- package/data/model-catalog.json +3 -3
- package/package.json +2 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/scripts/learn-flush.mjs +1 -1
- package/plugin/skills/release-proof/SKILL.md +19 -10
- package/plugin/skills/release-proof/references/receipt-contract.md +18 -8
- package/scripts/candidate-host-evidence.mjs +9 -2
- package/scripts/execution-policy.mjs +49 -4
- package/scripts/execution-preflight.mjs +18 -0
- package/scripts/host-install-matrix.mjs +247 -40
- package/scripts/integration-evidence.mjs +73 -0
- package/scripts/nightly-wrapper.sh +4 -3
- package/scripts/onboarding-console.mjs +3 -3
- package/scripts/prepublication-evidence.mjs +174 -0
- package/scripts/protected-release-invocation.mjs +32 -4
- package/scripts/public-verification-aggregate.mjs +24 -53
- package/scripts/public-verification-lane.mjs +49 -4
- package/scripts/publication-receipt.mjs +66 -23
- package/scripts/qa-runner.mjs +1 -0
- package/scripts/refresh-model-catalog.mjs +32 -3
- package/scripts/release-authority.mjs +19 -7
- package/scripts/release-convergence-watchdog.mjs +10 -22
- package/scripts/release-publication-handoff.mjs +101 -0
- package/scripts/release-transaction.mjs +1 -1
- package/scripts/release.mjs +14 -6
- package/scripts/source-coverage.mjs +11 -2
- package/scripts/staged-host-verifier.mjs +2 -2
- package/scripts/wired-check.mjs +1 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { canonicalJson, transactionIdFor, verifyReceipt } from './release-transaction.mjs';
|
|
5
|
+
|
|
6
|
+
const jsonBytes = (value) => Buffer.from(`${JSON.stringify(value, null, 2)}\n`);
|
|
7
|
+
|
|
8
|
+
function trustedEvidenceDirectory(root) {
|
|
9
|
+
const evidenceRoot = path.join(path.resolve(root), 'release-evidence');
|
|
10
|
+
let stat;
|
|
11
|
+
try { stat = fs.lstatSync(evidenceRoot); } catch { throw new Error('release-evidence directory is missing'); }
|
|
12
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
13
|
+
throw new Error('release-evidence must be a trusted directory');
|
|
14
|
+
}
|
|
15
|
+
return evidenceRoot;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function resolvePublicationHandoffPaths({ root, identityPath, receiptPath } = {}) {
|
|
19
|
+
const evidenceRoot = trustedEvidenceDirectory(root);
|
|
20
|
+
const resolveTarget = (configured, label) => {
|
|
21
|
+
if (typeof configured !== 'string' || !configured.trim()) throw new Error(`${label} output is required`);
|
|
22
|
+
const target = path.resolve(root, configured);
|
|
23
|
+
if (path.dirname(target) !== evidenceRoot || path.extname(target) !== '.json') {
|
|
24
|
+
throw new Error(`${label} output must be a direct JSON file inside release-evidence`);
|
|
25
|
+
}
|
|
26
|
+
if (fs.existsSync(target)) throw new Error(`${label} output must be a new file`);
|
|
27
|
+
return target;
|
|
28
|
+
};
|
|
29
|
+
const identity = resolveTarget(identityPath, 'release identity');
|
|
30
|
+
const receipt = resolveTarget(receiptPath, 'channel receipt');
|
|
31
|
+
if (identity === receipt) throw new Error('release identity and channel receipt outputs must differ');
|
|
32
|
+
return { evidenceRoot, identity, receipt };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function validatePublicationHandoff({ identity, receipt, publicKey } = {}) {
|
|
36
|
+
verifyReceipt(receipt, publicKey);
|
|
37
|
+
if (receipt.schemaVersion !== 3 || receipt.state !== 'channels-converged'
|
|
38
|
+
|| receipt.observation?.verdict !== 'PUBLISHED_NOT_VERIFIED') {
|
|
39
|
+
throw new Error('channel receipt is not signed PUBLISHED_NOT_VERIFIED convergence evidence');
|
|
40
|
+
}
|
|
41
|
+
if (receipt.transactionId !== transactionIdFor(identity)
|
|
42
|
+
|| canonicalJson(receipt.identity) !== canonicalJson(identity)) {
|
|
43
|
+
throw new Error('channel receipt identity differs from the release identity');
|
|
44
|
+
}
|
|
45
|
+
return receipt;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function writePrivateTemp(directory, label, bytes) {
|
|
49
|
+
const temp = path.join(directory, `.${label}.${process.pid}.${crypto.randomUUID()}.tmp`);
|
|
50
|
+
const fd = fs.openSync(temp, 'wx', 0o600);
|
|
51
|
+
let closed = false;
|
|
52
|
+
try {
|
|
53
|
+
fs.writeFileSync(fd, bytes);
|
|
54
|
+
fs.fsyncSync(fd);
|
|
55
|
+
fs.closeSync(fd);
|
|
56
|
+
closed = true;
|
|
57
|
+
return temp;
|
|
58
|
+
} catch (error) {
|
|
59
|
+
if (!closed) {
|
|
60
|
+
try { fs.closeSync(fd); } catch { /* preserve the write failure */ }
|
|
61
|
+
}
|
|
62
|
+
fs.rmSync(temp, { force: true });
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function removeOwnedLink(destination, temporary) {
|
|
68
|
+
try {
|
|
69
|
+
const linked = fs.lstatSync(destination);
|
|
70
|
+
const source = fs.lstatSync(temporary);
|
|
71
|
+
if (linked.dev === source.dev && linked.ino === source.ino) fs.unlinkSync(destination);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
if (error.code !== 'ENOENT') throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function materializePublicationHandoff({ paths, identity, receipt, publicKey } = {}) {
|
|
78
|
+
if (!paths?.evidenceRoot || path.dirname(paths.identity || '') !== paths.evidenceRoot
|
|
79
|
+
|| path.dirname(paths.receipt || '') !== paths.evidenceRoot || paths.identity === paths.receipt) {
|
|
80
|
+
throw new Error('publication handoff paths were not validated');
|
|
81
|
+
}
|
|
82
|
+
validatePublicationHandoff({ identity, receipt, publicKey });
|
|
83
|
+
const temporary = [];
|
|
84
|
+
let identityLinked = false;
|
|
85
|
+
let receiptLinked = false;
|
|
86
|
+
try {
|
|
87
|
+
temporary.push(writePrivateTemp(paths.evidenceRoot, 'release-identity', jsonBytes(identity)));
|
|
88
|
+
temporary.push(writePrivateTemp(paths.evidenceRoot, 'channels-converged', jsonBytes(receipt)));
|
|
89
|
+
fs.linkSync(temporary[0], paths.identity);
|
|
90
|
+
identityLinked = true;
|
|
91
|
+
fs.linkSync(temporary[1], paths.receipt);
|
|
92
|
+
receiptLinked = true;
|
|
93
|
+
} catch (error) {
|
|
94
|
+
if (receiptLinked) removeOwnedLink(paths.receipt, temporary[1]);
|
|
95
|
+
if (identityLinked) removeOwnedLink(paths.identity, temporary[0]);
|
|
96
|
+
throw new Error(`publication handoff materialization failed: ${error.message}`);
|
|
97
|
+
} finally {
|
|
98
|
+
for (const temp of temporary) fs.rmSync(temp, { force: true });
|
|
99
|
+
}
|
|
100
|
+
return { identityPath: paths.identity, receiptPath: paths.receipt };
|
|
101
|
+
}
|
|
@@ -565,7 +565,7 @@ export async function finalizeReleaseTransaction({
|
|
|
565
565
|
lanes: aggregate.metrics.leaves,
|
|
566
566
|
recallAt10: aggregate.metrics.recallAt10,
|
|
567
567
|
deltaCitationRate: aggregate.metrics.deltaCitationRate,
|
|
568
|
-
reviewReceipts: aggregate.reviews,
|
|
568
|
+
reviewReceipts: aggregate.reviews || [],
|
|
569
569
|
},
|
|
570
570
|
npm: snapshot.npm,
|
|
571
571
|
github: snapshot.github,
|
package/scripts/release.mjs
CHANGED
|
@@ -29,8 +29,9 @@ import { fileURLToPath } from 'node:url';
|
|
|
29
29
|
import { spawnSync, execFileSync } from 'node:child_process';
|
|
30
30
|
import fs from 'node:fs';
|
|
31
31
|
import crypto from 'node:crypto';
|
|
32
|
-
import { validateProtectedPublishInvocation } from './protected-release-invocation.mjs';
|
|
32
|
+
import { validateProtectedPublishEnvironment, validateProtectedPublishInvocation } from './protected-release-invocation.mjs';
|
|
33
33
|
import { runReleaseTransaction } from './release-transaction.mjs';
|
|
34
|
+
import { materializePublicationHandoff, resolvePublicationHandoffPaths } from './release-publication-handoff.mjs';
|
|
34
35
|
import { liveReleaseProvider } from './release-transaction-provider.mjs';
|
|
35
36
|
import { stagedHostVerifier } from './staged-host-verifier.mjs';
|
|
36
37
|
import { verifyPayload } from './release-payload.mjs';
|
|
@@ -44,6 +45,7 @@ let publicationReceiptPath = null;
|
|
|
44
45
|
let protectedReleaseMode = 'strict';
|
|
45
46
|
let verifiedPayload = null;
|
|
46
47
|
let aggregateEnvelope = null;
|
|
48
|
+
let publicationHandoffPaths = null;
|
|
47
49
|
const c = { g: (s) => `\x1b[32m${s}\x1b[0m`, r: (s) => `\x1b[31m${s}\x1b[0m`, y: (s) => `\x1b[33m${s}\x1b[0m`, b: (s) => `\x1b[1m${s}\x1b[0m`, dim: (s) => `\x1b[2m${s}\x1b[0m` };
|
|
48
50
|
const V = () => JSON.parse(fs.readFileSync(path.join(ROOT, 'plugin/.claude-plugin/plugin.json'), 'utf8')).version;
|
|
49
51
|
|
|
@@ -87,9 +89,9 @@ export function runProtectedCorpusSeed({
|
|
|
87
89
|
root = ROOT,
|
|
88
90
|
run = (command, args, options) => spawnSync(command, args, { encoding: 'utf8', ...options }),
|
|
89
91
|
} = {}) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
corpusFailure('
|
|
92
|
+
const environmentFailures = validateProtectedPublishEnvironment(env);
|
|
93
|
+
if (environmentFailures.length) {
|
|
94
|
+
corpusFailure(environmentFailures.join('; '));
|
|
93
95
|
}
|
|
94
96
|
|
|
95
97
|
const tag = cliArg(argv, '--corpus-tag');
|
|
@@ -222,6 +224,11 @@ if (PUBLISH) {
|
|
|
222
224
|
console.error(`\n${c.r('✗ PROTECTED RELEASE GATE FAILED')}\n publication receipt output must be a new file inside release-evidence\n`);
|
|
223
225
|
process.exit(1);
|
|
224
226
|
}
|
|
227
|
+
publicationHandoffPaths = resolvePublicationHandoffPaths({
|
|
228
|
+
root: ROOT,
|
|
229
|
+
identityPath: process.env.RUVNET_RELEASE_IDENTITY,
|
|
230
|
+
receiptPath: process.env.RUVNET_CHANNEL_RECEIPT,
|
|
231
|
+
});
|
|
225
232
|
const payloadManifestPath = path.resolve(ROOT, process.env.RUVNET_CANDIDATE_PAYLOAD || '');
|
|
226
233
|
const payloadSignaturePath = path.resolve(ROOT, process.env.RUVNET_CANDIDATE_PAYLOAD_SIGNATURE || '');
|
|
227
234
|
const aggregatePath = path.resolve(ROOT, process.env.RUVNET_AGGREGATE_ENVELOPE || '');
|
|
@@ -375,6 +382,7 @@ if (PUBLISH) {
|
|
|
375
382
|
};
|
|
376
383
|
const privatePem = process.env.RUVNET_SIGNING_KEY;
|
|
377
384
|
if (!privatePem) throw new Error('RUVNET_SIGNING_KEY is required for signed transaction receipts');
|
|
385
|
+
const publicKey = crypto.createPublicKey(fs.readFileSync(path.join(ROOT, 'keys/ruvnet-brain-signing.pub.pem'), 'utf8'));
|
|
378
386
|
const finalReceipt = await runReleaseTransaction({
|
|
379
387
|
identity, assets, adapter: liveReleaseProvider({
|
|
380
388
|
root: ROOT,
|
|
@@ -382,10 +390,11 @@ if (PUBLISH) {
|
|
|
382
390
|
publicationReceipt: process.env.RUVNET_PUBLICATION_RECEIPT,
|
|
383
391
|
}),
|
|
384
392
|
privateKey: crypto.createPrivateKey(privatePem),
|
|
385
|
-
publicKey
|
|
393
|
+
publicKey,
|
|
386
394
|
hostVerifier: stagedHostVerifier({ assets, identity }),
|
|
387
395
|
});
|
|
388
396
|
if (finalReceipt.state !== 'channels-converged') throw new Error(`release transaction stopped at ${finalReceipt.state}`);
|
|
397
|
+
materializePublicationHandoff({ paths: publicationHandoffPaths, identity, receipt: finalReceipt, publicKey });
|
|
389
398
|
} else {
|
|
390
399
|
step('D', 'remote staged release transaction — SKIPPED (check-only; pass --publish to publish)');
|
|
391
400
|
}
|
|
@@ -399,7 +408,6 @@ if (!PUBLISH) {
|
|
|
399
408
|
|
|
400
409
|
if (PUBLISH) {
|
|
401
410
|
console.log(`\n${c.y(c.b('PUBLISHED, NOT VERIFIED'))}`);
|
|
402
|
-
console.log(`\n${c.g(c.b('✓✓✓ SHIPPED'))} — every gate passed and every live channel is current. ${c.dim('A user on any path (npm, npx, explainer, --update) gets the working, current build.')}\n`);
|
|
403
411
|
} else {
|
|
404
412
|
console.log(`\n${c.g(c.b('✓✓✓ PREFLIGHT PASS — NOT PUBLISHED'))} — the committed candidate passed every check-only gate.\n`);
|
|
405
413
|
}
|
|
@@ -6,6 +6,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
7
|
import { readRvfGenerations, sha256File } from './rvf-generation.mjs';
|
|
8
8
|
import { canonicalJson, coverageGenerationFor, digest, validateGistAggregateReceipt } from './coverage-integrity.mjs';
|
|
9
|
+
import { repositoryNames } from '../kb/card-lane.mjs';
|
|
9
10
|
|
|
10
11
|
export { canonicalJson, digest } from './coverage-integrity.mjs';
|
|
11
12
|
|
|
@@ -272,7 +273,15 @@ export function classifyGist(gist, evidence) {
|
|
|
272
273
|
};
|
|
273
274
|
}
|
|
274
275
|
|
|
275
|
-
|
|
276
|
+
// ALIAS-AWARE, BECAUSE THE ROUTER IS. `cardStores` holds card HEADINGS; a store reachable only
|
|
277
|
+
// under an alias (e.g. `metaharness` via its `## agent-harness-generator` card) has no heading of
|
|
278
|
+
// its own, so a direct `cardStores.has(store)` reports it card-absent even though the router finds
|
|
279
|
+
// it. `kb/store-root.mjs`'s `darkStores()` and `scripts/brain-score.mjs`'s `readCoverage()` were
|
|
280
|
+
// already fixed for this exact conflation (ADR-058; metaharness is the standing example in both);
|
|
281
|
+
// this sibling computation, which feeds the committed `data/source-coverage.json` and
|
|
282
|
+
// `docs/RUVNET-COVERAGE.md` (ADR-069), never received it. `repositoryNames` is the router's own
|
|
283
|
+
// resolver (kb/card-lane.mjs), imported rather than reimplemented.
|
|
284
|
+
export function artifactEvidence(kbDir, ledger, cardStores, name) {
|
|
276
285
|
const store = storeName(name);
|
|
277
286
|
const receipt = Object.entries(ledger.stores).find(([key]) => key.toLowerCase() === store)?.[1] || null;
|
|
278
287
|
const rvfFile = receipt?.file || `${store}.big.rvf`;
|
|
@@ -284,7 +293,7 @@ function artifactEvidence(kbDir, ledger, cardStores, name) {
|
|
|
284
293
|
bytesVerified: Boolean(rvfPresent && receipt?.sha256 && sha256File(rvfPath) === receipt.sha256),
|
|
285
294
|
passagesPresent: fs.existsSync(path.join(kbDir, `${store}.passages.jsonl`))
|
|
286
295
|
&& fs.statSync(path.join(kbDir, `${store}.passages.jsonl`)).size > 0,
|
|
287
|
-
cardPresent: cardStores.has(
|
|
296
|
+
cardPresent: repositoryNames(store, kbDir).some((alias) => cardStores.has(storeName(alias))),
|
|
288
297
|
};
|
|
289
298
|
}
|
|
290
299
|
|
|
@@ -3,7 +3,7 @@ import crypto from 'node:crypto';
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import os from 'node:os';
|
|
5
5
|
import path from 'node:path';
|
|
6
|
-
import {
|
|
6
|
+
import { runHostMatrixAsync } from './host-install-matrix.mjs';
|
|
7
7
|
|
|
8
8
|
const sha256 = (file) => crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
|
|
9
9
|
const locate = (name) => {
|
|
@@ -50,7 +50,7 @@ export function stagedHostVerifier({ assets, identity }) {
|
|
|
50
50
|
// install shape than the one that runs after publication — so the two halves of a release
|
|
51
51
|
// were judging different things and could not be compared. Only the STAGED-vs-PUBLISHED
|
|
52
52
|
// difference is real, and it is now a named variant rather than a second implementation.
|
|
53
|
-
const matrix =
|
|
53
|
+
const matrix = await runHostMatrixAsync({
|
|
54
54
|
packageRoot: prepared.packageRoot,
|
|
55
55
|
version: identity.version,
|
|
56
56
|
variant: 'staged',
|
package/scripts/wired-check.mjs
CHANGED
|
@@ -95,6 +95,7 @@ const STANDALONE = [
|
|
|
95
95
|
+ 'RVFs to RVF-GENERATIONS.json and optionally prunes legacy sidecars; build-bundle.mjs consumes '
|
|
96
96
|
+ 'and validates the resulting manifest, so scheduling this destructive migration would be wrong'],
|
|
97
97
|
['release', 'the ship path, run by a human'],
|
|
98
|
+
['execution-preflight', 'external orchestration boundary — invoked by the host before consequential Ruflo/Codex execution; no in-repo caller exists because the host supplies the live Brain and AgentDB receipts'],
|
|
98
99
|
['fix-workstream', 'session-supervised coordination CLI run explicitly by the integration owner or an '
|
|
99
100
|
+ 'isolated writing agent to start and hand off a fix lane. Scheduling it would violate its safety '
|
|
100
101
|
+ 'boundary: it prepares evidence but never merges, pushes, publishes, deletes, or cleans worktrees'],
|