ruvnet-brain 4.3.3 → 4.3.8
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/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-transaction.mjs +1 -1
- package/scripts/release.mjs +4 -5
- package/scripts/source-coverage.mjs +11 -2
- package/scripts/staged-host-verifier.mjs +2 -2
- package/scripts/wired-check.mjs +1 -0
|
@@ -133,7 +133,7 @@ const identityOf = (leaf) => ({
|
|
|
133
133
|
releaseTransactionId: leaf.releaseTransactionId,
|
|
134
134
|
});
|
|
135
135
|
|
|
136
|
-
export function buildPublicVerificationAggregate(leaves
|
|
136
|
+
export function buildPublicVerificationAggregate(leaves) {
|
|
137
137
|
if (!Array.isArray(leaves) || leaves.length !== PUBLIC_VERIFICATION_OS.length * PUBLIC_VERIFICATION_MODES.length) {
|
|
138
138
|
throw new Error('public verification requires exactly nine leaves');
|
|
139
139
|
}
|
|
@@ -168,25 +168,11 @@ export function buildPublicVerificationAggregate(leaves, reviews) {
|
|
|
168
168
|
if (metrics.recallAt10 < 0.98 || metrics.deltaCitationRate !== 1 || metrics.skipped || metrics.unknown) {
|
|
169
169
|
throw new Error('public verification aggregate retrieval acceptance failed');
|
|
170
170
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if (byModel.size !== REQUIRED_REVIEW_MODELS.length || REQUIRED_REVIEW_MODELS.some((model) => !byModel.has(model))) {
|
|
177
|
-
throw new Error('required independent review models are missing or duplicated');
|
|
178
|
-
}
|
|
179
|
-
const firstReview = byModel.get(REQUIRED_REVIEW_MODELS[0]);
|
|
180
|
-
const semanticIdentity = validateReviewOracleAgainstPlan(firstReview, first.retrievalPlan);
|
|
181
|
-
for (const model of REQUIRED_REVIEW_MODELS) {
|
|
182
|
-
const review = byModel.get(model);
|
|
183
|
-
const semantic = validateReviewOracleAgainstPlan(review, first.retrievalPlan);
|
|
184
|
-
if (review.sourceSha !== identity.sourceSha || review.artifactSha256 !== identity.artifactSha256
|
|
185
|
-
|| review.payloadId !== identity.payloadId || review.productContractSha256 !== firstReview.productContractSha256
|
|
186
|
-
|| review.rubricSha256 !== firstReview.rubricSha256
|
|
187
|
-
|| semantic.recordSetSha256 !== semanticIdentity.recordSetSha256) {
|
|
188
|
-
throw new Error(`${model} review identity, rubric, or oracle identity differs`);
|
|
189
|
-
}
|
|
171
|
+
const oracle = first.retrievalPlan.oracle;
|
|
172
|
+
if (!oracle || !HEX64.test(String(oracle.receiptSha256 || ''))
|
|
173
|
+
|| !HEX64.test(String(oracle.queryStoreSetSha256 || ''))
|
|
174
|
+
|| oracle.queryStoreSetSha256 !== digest(first.retrievalPlan.denominator.eligibleStores)) {
|
|
175
|
+
throw new Error('sealed retrieval oracle identity is incomplete');
|
|
190
176
|
}
|
|
191
177
|
return {
|
|
192
178
|
schemaVersion: 1,
|
|
@@ -194,17 +180,11 @@ export function buildPublicVerificationAggregate(leaves, reviews) {
|
|
|
194
180
|
identity,
|
|
195
181
|
coverage: first.coverage,
|
|
196
182
|
lanes: required.map((lane) => ({ lane, leafSha256: byLane.get(lane).leafSha256 })),
|
|
197
|
-
|
|
198
|
-
score: byModel.get(model).score })),
|
|
199
|
-
evidence: { leaves: required.map((lane) => structuredClone(byLane.get(lane))),
|
|
200
|
-
reviews: REQUIRED_REVIEW_MODELS.map((model) => structuredClone(byModel.get(model))) },
|
|
201
|
-
productContractSha256: firstReview.productContractSha256,
|
|
202
|
-
rubricSha256: firstReview.rubricSha256,
|
|
183
|
+
evidence: { leaves: required.map((lane) => structuredClone(byLane.get(lane))) },
|
|
203
184
|
retrievalOracle: {
|
|
204
|
-
oracleReceiptSha256:
|
|
205
|
-
queryStoreSetSha256:
|
|
206
|
-
recordCount:
|
|
207
|
-
recordSetSha256: semanticIdentity.recordSetSha256,
|
|
185
|
+
oracleReceiptSha256: oracle.receiptSha256,
|
|
186
|
+
queryStoreSetSha256: oracle.queryStoreSetSha256,
|
|
187
|
+
recordCount: first.retrievalPlan.denominator.eligibleStores.length,
|
|
208
188
|
},
|
|
209
189
|
metrics,
|
|
210
190
|
verdict: 'PASS',
|
|
@@ -212,8 +192,8 @@ export function buildPublicVerificationAggregate(leaves, reviews) {
|
|
|
212
192
|
};
|
|
213
193
|
}
|
|
214
194
|
|
|
215
|
-
export function signPublicVerificationAggregate({ leaves
|
|
216
|
-
const payload = buildPublicVerificationAggregate(leaves
|
|
195
|
+
export function signPublicVerificationAggregate({ leaves }, privateKey) {
|
|
196
|
+
const payload = buildPublicVerificationAggregate(leaves);
|
|
217
197
|
const aggregateSha256 = digest(payload);
|
|
218
198
|
const signed = { ...payload, aggregateSha256 };
|
|
219
199
|
return { ...signed, signature: crypto.sign(null, Buffer.from(canonicalJson(signed)), privateKey).toString('base64') };
|
|
@@ -227,10 +207,10 @@ export function verifyPublicVerificationAggregate(aggregate, publicKey, expected
|
|
|
227
207
|
}
|
|
228
208
|
const payload = aggregatePayload(aggregate);
|
|
229
209
|
if (digest(payload) !== aggregate.aggregateSha256) throw new Error('public verification aggregate digest mismatch');
|
|
230
|
-
if (!Array.isArray(aggregate.evidence?.leaves)
|
|
231
|
-
throw new Error('public verification aggregate lacks raw leaf
|
|
210
|
+
if (!Array.isArray(aggregate.evidence?.leaves)) {
|
|
211
|
+
throw new Error('public verification aggregate lacks raw leaf evidence');
|
|
232
212
|
}
|
|
233
|
-
const rebuilt = buildPublicVerificationAggregate(aggregate.evidence.leaves
|
|
213
|
+
const rebuilt = buildPublicVerificationAggregate(aggregate.evidence.leaves);
|
|
234
214
|
if (canonicalJson(rebuilt) !== canonicalJson(payload)) {
|
|
235
215
|
throw new Error('public verification aggregate differs from rebuilt raw evidence');
|
|
236
216
|
}
|
|
@@ -249,17 +229,10 @@ export function verifyPublicVerificationAggregate(aggregate, publicKey, expected
|
|
|
249
229
|
|| aggregate.coverage.gistCurrent !== aggregate.coverage.gistTotal) {
|
|
250
230
|
throw new Error('public verification aggregate evidence is incomplete');
|
|
251
231
|
}
|
|
252
|
-
if (aggregate.reviews?.length !== REQUIRED_REVIEW_MODELS.length
|
|
253
|
-
|| REQUIRED_REVIEW_MODELS.some((model) => !aggregate.reviews.some((row) => row.model === model
|
|
254
|
-
&& Number.isInteger(row.score) && row.score >= 95 && HEX64.test(String(row.receiptSha256 || ''))))
|
|
255
|
-
|| !HEX64.test(String(aggregate.productContractSha256 || '')) || !HEX64.test(String(aggregate.rubricSha256 || ''))) {
|
|
256
|
-
throw new Error('public verification aggregate reviews are incomplete');
|
|
257
|
-
}
|
|
258
232
|
if (aggregate.retrievalOracle?.oracleReceiptSha256 !== aggregate.evidence.leaves[0].retrievalPlan.oracle.receiptSha256
|
|
259
233
|
|| aggregate.retrievalOracle?.queryStoreSetSha256 !== aggregate.evidence.leaves[0].retrievalPlan.oracle.queryStoreSetSha256
|
|
260
|
-
|| aggregate.retrievalOracle?.recordCount !== aggregate.evidence.leaves[0].retrievalPlan.denominator.eligibleStores.length
|
|
261
|
-
|
|
262
|
-
throw new Error('public verification aggregate retrieval oracle review is incomplete');
|
|
234
|
+
|| aggregate.retrievalOracle?.recordCount !== aggregate.evidence.leaves[0].retrievalPlan.denominator.eligibleStores.length) {
|
|
235
|
+
throw new Error('public verification aggregate retrieval oracle identity is incomplete');
|
|
263
236
|
}
|
|
264
237
|
if (expectedIdentity && canonicalJson(aggregate.identity) !== canonicalJson(expectedIdentity)) {
|
|
265
238
|
throw new Error('public verification aggregate identity differs from the release transaction');
|
|
@@ -272,13 +245,13 @@ function parseCliArgs(argv) {
|
|
|
272
245
|
for (let index = 0; index < argv.length; index += 2) {
|
|
273
246
|
const flag = argv[index];
|
|
274
247
|
const value = argv[index + 1];
|
|
275
|
-
if (!['--lanes', '--
|
|
276
|
-
throw new Error('usage: public-verification-aggregate.mjs --lanes <dir> --
|
|
248
|
+
if (!['--lanes', '--out'].includes(flag) || !value) {
|
|
249
|
+
throw new Error('usage: public-verification-aggregate.mjs --lanes <dir> --out <file>');
|
|
277
250
|
}
|
|
278
251
|
parsed[flag.slice(2)] = value;
|
|
279
252
|
}
|
|
280
|
-
if (!parsed.lanes || !parsed.
|
|
281
|
-
throw new Error('usage: public-verification-aggregate.mjs --lanes <dir> --
|
|
253
|
+
if (!parsed.lanes || !parsed.out || Object.keys(parsed).length !== 2) {
|
|
254
|
+
throw new Error('usage: public-verification-aggregate.mjs --lanes <dir> --out <file>');
|
|
282
255
|
}
|
|
283
256
|
return parsed;
|
|
284
257
|
}
|
|
@@ -314,12 +287,11 @@ function readLaneLeaves(directory) {
|
|
|
314
287
|
return PUBLIC_VERIFICATION_OS.flatMap((osName) => byOs.get(osName));
|
|
315
288
|
}
|
|
316
289
|
|
|
317
|
-
export function generatePublicVerificationAggregate({ lanesDirectory,
|
|
290
|
+
export function generatePublicVerificationAggregate({ lanesDirectory, outputFile, privateKey }) {
|
|
318
291
|
if (!privateKey) throw new Error('RUVNET_SIGNING_KEY is required');
|
|
319
292
|
if (fs.existsSync(outputFile)) throw new Error(`refusing to overwrite existing aggregate: ${outputFile}`);
|
|
320
293
|
const leaves = readLaneLeaves(lanesDirectory);
|
|
321
|
-
const
|
|
322
|
-
const aggregate = signPublicVerificationAggregate({ leaves, reviews }, privateKey);
|
|
294
|
+
const aggregate = signPublicVerificationAggregate({ leaves }, privateKey);
|
|
323
295
|
fs.writeFileSync(outputFile, `${JSON.stringify(aggregate, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
|
|
324
296
|
return aggregate;
|
|
325
297
|
}
|
|
@@ -328,12 +300,11 @@ async function main() {
|
|
|
328
300
|
const options = parseCliArgs(process.argv.slice(2));
|
|
329
301
|
const aggregate = generatePublicVerificationAggregate({
|
|
330
302
|
lanesDirectory: options.lanes,
|
|
331
|
-
reviewsDirectory: options.reviews,
|
|
332
303
|
outputFile: options.out,
|
|
333
304
|
privateKey: process.env.RUVNET_SIGNING_KEY,
|
|
334
305
|
});
|
|
335
306
|
process.stdout.write(`${JSON.stringify({ ok: true, aggregateSha256: aggregate.aggregateSha256,
|
|
336
|
-
leaves: aggregate.metrics.leaves
|
|
307
|
+
leaves: aggregate.metrics.leaves })}\n`);
|
|
337
308
|
}
|
|
338
309
|
|
|
339
310
|
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
package/scripts/qa-runner.mjs
CHANGED
|
@@ -33,6 +33,7 @@ const lanes = [
|
|
|
33
33
|
'tests/unit/codex-claude-hook-parity.test.mjs',
|
|
34
34
|
'tests/unit/brain-stamp-resolve-built-from-sha.test.mjs',
|
|
35
35
|
'tests/unit/qa-runner-concurrency.test.mjs',
|
|
36
|
+
'tests/unit/execution-preflight.test.mjs',
|
|
36
37
|
'--reporter=dot']],
|
|
37
38
|
['mesh', 'npm', ['run', 'test:mesh']],
|
|
38
39
|
['plugin', 'npm', ['test']],
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// scripts/refresh-model-catalog.mjs — pulls the LIVE OpenRouter model catalog and writes the committed
|
|
3
|
-
// snapshot data/openrouter-catalog-snapshot.json (with a pulledAt stamp)
|
|
3
|
+
// snapshot data/openrouter-catalog-snapshot.json (with a pulledAt stamp) and synchronizes the catalog's
|
|
4
|
+
// factual prices. verify-model-catalog.mjs then
|
|
4
5
|
// enforces, offline in CI, that every model in data/model-catalog.json exists and is priced correctly
|
|
5
|
-
// against this snapshot — and that the snapshot is fresh. Run
|
|
6
|
+
// against this snapshot — and that the snapshot is fresh. Run weekly (the anti-rot mechanism) + on demand.
|
|
6
7
|
// ADR-0016.
|
|
7
8
|
//
|
|
8
9
|
// It ALSO flags drift so a new flagship (e.g. a GPT-5.6-class release) surfaces instead of rotting:
|
|
@@ -45,6 +46,30 @@ export function detectDrift(catalog, models) {
|
|
|
45
46
|
return missing;
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
/** Synchronize factual catalog prices from the same live map that backs the snapshot. */
|
|
50
|
+
export function syncCatalogPrices(catalog, models, pulledAt) {
|
|
51
|
+
const changes = [];
|
|
52
|
+
for (const [provider, entry] of Object.entries(catalog.providers || {})) {
|
|
53
|
+
if (entry.aliasOf) continue;
|
|
54
|
+
for (const tier of ['frontier', 'mid', 'cheap']) {
|
|
55
|
+
const candidate = entry[tier];
|
|
56
|
+
if (!candidate?.model) continue;
|
|
57
|
+
const live = models[candidate.model]
|
|
58
|
+
|| (!candidate.model.includes('/') ? models[`anthropic/${candidate.model}`] : null);
|
|
59
|
+
if (!live) continue;
|
|
60
|
+
if (candidate.in !== live.in || candidate.out !== live.out) {
|
|
61
|
+
changes.push({ provider, tier, model: candidate.model, from: { in: candidate.in, out: candidate.out }, to: live });
|
|
62
|
+
candidate.in = live.in;
|
|
63
|
+
candidate.out = live.out;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (catalog._meta?.sources) {
|
|
68
|
+
catalog._meta.sources.prices = `OpenRouter /api/v1/models live catalog, pulled ${pulledAt.slice(0, 10)} (in/out USD per Mtok).`;
|
|
69
|
+
}
|
|
70
|
+
return changes;
|
|
71
|
+
}
|
|
72
|
+
|
|
48
73
|
export function shapeSnapshot(models, pulledAt) {
|
|
49
74
|
return { _meta: { source: 'OpenRouter /api/v1/models (live metadata)', pulledAt, modelCount: Object.keys(models).length }, models };
|
|
50
75
|
}
|
|
@@ -73,7 +98,8 @@ async function main() {
|
|
|
73
98
|
|
|
74
99
|
const catalog = JSON.parse(fs.readFileSync(CATALOG, 'utf8'));
|
|
75
100
|
const missing = detectDrift(catalog, models);
|
|
76
|
-
const
|
|
101
|
+
const pulledAt = new Date().toISOString();
|
|
102
|
+
const snapshot = shapeSnapshot(models, pulledAt);
|
|
77
103
|
const next = JSON.stringify(snapshot, null, 2) + '\n';
|
|
78
104
|
|
|
79
105
|
if (check) {
|
|
@@ -86,8 +112,11 @@ async function main() {
|
|
|
86
112
|
return;
|
|
87
113
|
}
|
|
88
114
|
|
|
115
|
+
const priceChanges = syncCatalogPrices(catalog, models, pulledAt);
|
|
89
116
|
fs.writeFileSync(SNAPSHOT, next);
|
|
117
|
+
fs.writeFileSync(CATALOG, JSON.stringify(catalog, null, 2) + '\n');
|
|
90
118
|
console.log(`✓ wrote ${SNAPSHOT.replace(ROOT + '/', '')} — ${Object.keys(models).length} live models, pulledAt ${snapshot._meta.pulledAt}`);
|
|
119
|
+
console.log(`✓ synchronized ${priceChanges.length} catalog price field(s) from the same live response`);
|
|
91
120
|
if (missing.length) {
|
|
92
121
|
console.log(`\n⚠ DRIFT: ${missing.length} catalog model(s) no longer in the live catalog — update data/model-catalog.json:`);
|
|
93
122
|
for (const m of missing) console.log(' - ' + m);
|
|
@@ -11,7 +11,7 @@ const CANONICAL_PUBLISHERS = new Set([
|
|
|
11
11
|
'scripts/release.mjs',
|
|
12
12
|
'scripts/release-transaction-provider.mjs',
|
|
13
13
|
]);
|
|
14
|
-
const SOURCE_EXTENSIONS = new Set(['.mjs', '.js', '.cjs', '.sh']);
|
|
14
|
+
const SOURCE_EXTENSIONS = new Set(['.mjs', '.js', '.cjs', '.sh', '.yml', '.yaml']);
|
|
15
15
|
|
|
16
16
|
function executableSource(source) {
|
|
17
17
|
const withoutBlocks = source.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
@@ -28,7 +28,7 @@ const ACTIONS = [
|
|
|
28
28
|
jsPatterns: [
|
|
29
29
|
/\[\s*['"]release['"]\s*,\s*['"]create['"]/,
|
|
30
30
|
],
|
|
31
|
-
shellPatterns: [/^\s*gh\s+release\s+create\b/m],
|
|
31
|
+
shellPatterns: [/^\s*(?:-\s*run:\s*)?gh\s+release\s+create\b/m],
|
|
32
32
|
},
|
|
33
33
|
{
|
|
34
34
|
action: 'github-release-update',
|
|
@@ -36,7 +36,7 @@ const ACTIONS = [
|
|
|
36
36
|
/['"]PATCH['"][\s\S]{0,120}releases\//,
|
|
37
37
|
/\[\s*['"]release['"]\s*,\s*['"]edit['"]/,
|
|
38
38
|
],
|
|
39
|
-
shellPatterns: [/^\s*gh\s+(?:release\s+edit|api\s+.*releases\/.*PATCH)\b/m],
|
|
39
|
+
shellPatterns: [/^\s*(?:-\s*run:\s*)?gh\s+(?:release\s+edit|api\s+.*releases\/.*PATCH)\b/m],
|
|
40
40
|
},
|
|
41
41
|
{
|
|
42
42
|
action: 'npm-publish',
|
|
@@ -44,7 +44,7 @@ const ACTIONS = [
|
|
|
44
44
|
/(?:execFileSync|spawnSync)\(\s*['"]npm['"]\s*,\s*\[\s*['"]publish['"]/,
|
|
45
45
|
/runOrDie\(\s*['"]npm publish['"]/,
|
|
46
46
|
],
|
|
47
|
-
shellPatterns: [/^\s*npm\s+publish\b/m],
|
|
47
|
+
shellPatterns: [/^\s*(?:-\s*run:\s*)?npm\s+publish\b/m],
|
|
48
48
|
},
|
|
49
49
|
{
|
|
50
50
|
action: 'npm-dist-tag',
|
|
@@ -52,7 +52,7 @@ const ACTIONS = [
|
|
|
52
52
|
/(?:execFileSync|spawnSync)\(\s*['"]npm['"]\s*,\s*\[\s*['"]dist-tag['"]\s*,\s*['"]add['"]/,
|
|
53
53
|
/runOrDie\(\s*['"]npm dist-tag[^'"]*['"]/,
|
|
54
54
|
],
|
|
55
|
-
shellPatterns: [/^\s*npm\s+dist-tag\s+add\b/m],
|
|
55
|
+
shellPatterns: [/^\s*(?:-\s*run:\s*)?npm\s+dist-tag\s+add\b/m],
|
|
56
56
|
},
|
|
57
57
|
];
|
|
58
58
|
|
|
@@ -60,7 +60,10 @@ export function detectPublisherActions(file, source) {
|
|
|
60
60
|
const relative = file.split(path.sep).join('/');
|
|
61
61
|
if (CANONICAL_PUBLISHERS.has(relative)) return [];
|
|
62
62
|
const executable = executableSource(source);
|
|
63
|
-
const
|
|
63
|
+
const extension = path.extname(relative);
|
|
64
|
+
const kind = ['.sh', '.yml', '.yaml'].includes(extension) || relative.startsWith('package.json#scripts.')
|
|
65
|
+
? 'shellPatterns'
|
|
66
|
+
: 'jsPatterns';
|
|
64
67
|
return ACTIONS
|
|
65
68
|
.filter((action) => action[kind].some((pattern) => pattern.test(executable)))
|
|
66
69
|
.map(({ action }) => ({ file: relative, action }));
|
|
@@ -79,14 +82,23 @@ function sourceFiles(root) {
|
|
|
79
82
|
visit(path.join(root, 'scripts'));
|
|
80
83
|
visit(path.join(root, 'deploy'));
|
|
81
84
|
visit(path.join(root, 'bin'));
|
|
85
|
+
visit(path.join(root, '.github', 'workflows'));
|
|
82
86
|
return files;
|
|
83
87
|
}
|
|
84
88
|
|
|
85
89
|
export function findUnauthorizedPublishers(root = ROOT) {
|
|
86
|
-
|
|
90
|
+
const findings = sourceFiles(root).flatMap((absolute) => {
|
|
87
91
|
const relative = path.relative(root, absolute).split(path.sep).join('/');
|
|
88
92
|
return detectPublisherActions(relative, fs.readFileSync(absolute, 'utf8'));
|
|
89
93
|
});
|
|
94
|
+
const packageFile = path.join(root, 'package.json');
|
|
95
|
+
if (fs.existsSync(packageFile)) {
|
|
96
|
+
const pkg = JSON.parse(fs.readFileSync(packageFile, 'utf8'));
|
|
97
|
+
for (const [name, command] of Object.entries(pkg.scripts || {})) {
|
|
98
|
+
findings.push(...detectPublisherActions(`package.json#scripts.${name}`, String(command)));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return findings;
|
|
90
102
|
}
|
|
91
103
|
|
|
92
104
|
export function main(root = ROOT) {
|
|
@@ -11,18 +11,16 @@
|
|
|
11
11
|
* for a month.
|
|
12
12
|
*
|
|
13
13
|
* WHAT IT WILL NOT DO. It does not publish. `scripts/self-update.mjs:56` refuses `--publish` and
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* still runs inside the workflow exactly as designed. Bypassing them to "get it done while nobody
|
|
18
|
-
* is looking" would be the worst possible reading of an instruction to finish the job.
|
|
14
|
+
* `release-candidate-preflight.yml` owns the one-time long lanes and `protected-release.yml` is the
|
|
15
|
+
* sole publication controller. This watchdog is report-only: it never dispatches either phase and
|
|
16
|
+
* cannot manufacture typed candidate evidence.
|
|
19
17
|
*
|
|
20
18
|
* IT IS A NO-OP UNLESS EVERY PRECONDITION HOLDS. It runs from the nightly, unattended, for weeks.
|
|
21
19
|
* A watchdog that acts on a partial picture is worse than no watchdog, so it refuses on anything
|
|
22
20
|
* unexpected and says why. The default outcome is "nothing happened, here is the reason".
|
|
23
21
|
*
|
|
24
22
|
* node scripts/release-convergence-watchdog.mjs # report only, never acts
|
|
25
|
-
* node scripts/release-convergence-watchdog.mjs --dispatch #
|
|
23
|
+
* node scripts/release-convergence-watchdog.mjs --dispatch # compatibility flag; still reports only
|
|
26
24
|
*/
|
|
27
25
|
import { execFileSync } from 'node:child_process';
|
|
28
26
|
import fs from 'node:fs';
|
|
@@ -99,21 +97,11 @@ const runsFor = (wf) => JSON.parse(tryShy(() => sh('gh', ['run', 'list', '--repo
|
|
|
99
97
|
const greenAt = (wf) => runsFor(wf).find((r) => r.headSha === originMain && r.conclusion === 'success');
|
|
100
98
|
|
|
101
99
|
const ci = greenAt('ci');
|
|
102
|
-
const aggregate = greenAt('release-aggregate');
|
|
103
100
|
if (!ci) stand_down(`no successful exact-SHA \`ci\` run at ${originMain.slice(0, 7)} yet`);
|
|
104
|
-
if (!aggregate) stand_down(`no successful exact-SHA \`release-aggregate\` run at ${originMain.slice(0, 7)} yet`);
|
|
105
101
|
|
|
106
|
-
log(`
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// ── 6. Dispatch THE SANCTIONED PUBLISHER. Nothing here publishes; the workflow does. ─────────────
|
|
113
|
-
sh('gh', ['workflow', 'run', 'protected-release.yml', '--repo', REPO,
|
|
114
|
-
'-f', `candidate_sha=${originMain}`,
|
|
115
|
-
'-f', `version=${version}`,
|
|
116
|
-
'-f', `release_qe_run_id=${ci.databaseId}`,
|
|
117
|
-
'-f', `aggregate_run_id=${aggregate.databaseId}`]);
|
|
118
|
-
log(`DISPATCHED protected-release for ${version}. The workflow owns every safety gate from here.`);
|
|
119
|
-
log('Verify afterwards with: node scripts/published-surface-probe.mjs (D-version-coherence must PASS).');
|
|
102
|
+
log(`PRECONDITIONS VISIBLE — candidate=${originMain.slice(0, 7)} version=${version} ci=${ci.databaseId}`);
|
|
103
|
+
// ── 6. Never dispatch a publisher from the watchdog. ───────────────────────────────────────────
|
|
104
|
+
// Only protected-release may derive the complete typed evidence chain and cross Production.
|
|
105
|
+
// This unattended observer cannot turn visible preconditions into release authority.
|
|
106
|
+
log(`PRECONDITIONS ONLY — no candidate artifact inferred and no action taken for ${version} at ${originMain}`);
|
|
107
|
+
log('Run release-candidate-preflight on release/** once; fast-forward that exact SHA to main; then protected-release imports release-candidate-<SHA> without a run ID.');
|
|
@@ -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,7 +29,7 @@ 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
34
|
import { liveReleaseProvider } from './release-transaction-provider.mjs';
|
|
35
35
|
import { stagedHostVerifier } from './staged-host-verifier.mjs';
|
|
@@ -87,9 +87,9 @@ export function runProtectedCorpusSeed({
|
|
|
87
87
|
root = ROOT,
|
|
88
88
|
run = (command, args, options) => spawnSync(command, args, { encoding: 'utf8', ...options }),
|
|
89
89
|
} = {}) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
corpusFailure('
|
|
90
|
+
const environmentFailures = validateProtectedPublishEnvironment(env);
|
|
91
|
+
if (environmentFailures.length) {
|
|
92
|
+
corpusFailure(environmentFailures.join('; '));
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
const tag = cliArg(argv, '--corpus-tag');
|
|
@@ -399,7 +399,6 @@ if (!PUBLISH) {
|
|
|
399
399
|
|
|
400
400
|
if (PUBLISH) {
|
|
401
401
|
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
402
|
} else {
|
|
404
403
|
console.log(`\n${c.g(c.b('✓✓✓ PREFLIGHT PASS — NOT PUBLISHED'))} — the committed candidate passed every check-only gate.\n`);
|
|
405
404
|
}
|
|
@@ -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'],
|