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,499 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import crypto from 'node:crypto';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { canonicalJson, digest } from './coverage-integrity.mjs';
|
|
7
|
+
import { transactionIdFor } from './release-transaction.mjs';
|
|
8
|
+
const HEX40 = /^[a-f0-9]{40}$/;
|
|
9
|
+
const HEX64 = /^[a-f0-9]{64}$/;
|
|
10
|
+
const BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
|
|
11
|
+
export const ALLOWED_INDEPENDENT_REVIEWERS = Object.freeze([
|
|
12
|
+
Object.freeze({ identity: 'claude-fable-5', model: 'claude-fable-5', provider: 'firstParty' }),
|
|
13
|
+
Object.freeze({ identity: 'gpt-5.6-sol', model: 'gpt-5.6-sol', provider: 'openai' })]);
|
|
14
|
+
|
|
15
|
+
const INPUT_KEYS = Object.freeze([
|
|
16
|
+
'artifactSha256', 'deductions', 'execution', 'findings', 'id', 'independent', 'model', 'payloadId',
|
|
17
|
+
'payloadSha256', 'productContractSha256', 'provider', 'releaseIdentity', 'reviewedAt', 'rubricSha256',
|
|
18
|
+
'retrievalOracleReview', 'score', 'sourceSha', 'sourceTree', 'subjectProducerIdentity', 'untested', 'verdict',
|
|
19
|
+
]);
|
|
20
|
+
const CORE_KEYS = Object.freeze([
|
|
21
|
+
...INPUT_KEYS, 'kind', 'releaseIdentitySha256', 'schemaVersion', 'signatureAlgorithm', 'signingKeyId',
|
|
22
|
+
]);
|
|
23
|
+
const RECEIPT_KEYS = Object.freeze([...CORE_KEYS, 'receiptSha256', 'signature']);
|
|
24
|
+
const RELEASE_KEYS = Object.freeze([
|
|
25
|
+
'bundleDigestSha256', 'bundleSha256', 'bundleSignatureSha256', 'candidateSha', 'corpusSeedSha256',
|
|
26
|
+
'evidenceDigest', 'generationLedgerSha256', 'package', 'packageAssetName', 'packageIntegrity',
|
|
27
|
+
'packageSha256', 'payloadId', 'repository', 'tag', 'transactionId', 'version',
|
|
28
|
+
]);
|
|
29
|
+
const REQUIRED_RELEASE_KEYS = Object.freeze([
|
|
30
|
+
'bundleSha256', 'candidateSha', 'evidenceDigest', 'package', 'packageIntegrity', 'packageSha256',
|
|
31
|
+
'payloadId', 'repository', 'tag', 'transactionId', 'version',
|
|
32
|
+
]);
|
|
33
|
+
const FINDING_KEYS = Object.freeze(['code', 'evidence', 'severity', 'summary']);
|
|
34
|
+
const DEDUCTION_KEYS = Object.freeze(['code', 'evidence', 'points', 'reason']);
|
|
35
|
+
const SEVERITIES = Object.freeze(['info', 'minor', 'major', 'critical']);
|
|
36
|
+
const ORACLE_REVIEW_KEYS = Object.freeze(['kind', 'oracleReceiptSha256', 'queryStoreSetSha256', 'recordCount',
|
|
37
|
+
'recordSetSha256', 'records', 'schemaVersion', 'untested', 'verdict']);
|
|
38
|
+
const ORACLE_RECORD_KEYS = Object.freeze(['evidence', 'oracleRecordSha256', 'relevant', 'store', 'untested', 'verdict']);
|
|
39
|
+
const EXPECTED_ORACLE_KEYS = Object.freeze(['oracleReceiptSha256', 'queryStoreSetSha256', 'records', 'stores']);
|
|
40
|
+
const EXPECTED_ORACLE_REQUIRED = Object.freeze(['oracleReceiptSha256', 'queryStoreSetSha256', 'stores']);
|
|
41
|
+
|
|
42
|
+
function plainObject(value, label) {
|
|
43
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)
|
|
44
|
+
|| Object.getPrototypeOf(value) !== Object.prototype) throw new Error(`${label} must be a plain object`);
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function exactKeys(value, allowed, label, required = allowed) {
|
|
49
|
+
plainObject(value, label);
|
|
50
|
+
const unknown = Object.keys(value).filter((key) => !allowed.includes(key));
|
|
51
|
+
if (unknown.length) throw new Error(`${label} has unknown field: ${unknown.sort()[0]}`);
|
|
52
|
+
const missing = required.filter((key) => !Object.hasOwn(value, key));
|
|
53
|
+
if (missing.length) throw new Error(`${label} is missing field: ${missing[0]}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function text(value, label) {
|
|
57
|
+
if (typeof value !== 'string' || !value.trim() || value !== value.trim()) throw new Error(`${label} is invalid`); return value;
|
|
58
|
+
}
|
|
59
|
+
function hex(value, pattern, label) {
|
|
60
|
+
if (typeof value !== 'string' || !pattern.test(value)) throw new Error(`${label} is invalid`); return value;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function sortedUniqueStrings(values, label, { allowEmpty = true } = {}) {
|
|
64
|
+
if (!Array.isArray(values) || (!allowEmpty && values.length === 0)) throw new Error(`${label} must be an array`);
|
|
65
|
+
const sorted = values.map((value, index) => text(value, `${label}[${index}]`)).sort();
|
|
66
|
+
if (new Set(sorted).size !== sorted.length) throw new Error(`${label} contains duplicates`);
|
|
67
|
+
return sorted;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function normalizeFindings(findings) {
|
|
71
|
+
if (!Array.isArray(findings)) throw new Error('findings must be an array');
|
|
72
|
+
const rows = findings.map((finding, index) => {
|
|
73
|
+
exactKeys(finding, FINDING_KEYS, `findings[${index}]`);
|
|
74
|
+
if (!SEVERITIES.includes(finding.severity)) throw new Error(`findings[${index}] severity is invalid`);
|
|
75
|
+
return {
|
|
76
|
+
code: text(finding.code, `findings[${index}].code`),
|
|
77
|
+
severity: finding.severity,
|
|
78
|
+
summary: text(finding.summary, `findings[${index}].summary`),
|
|
79
|
+
evidence: sortedUniqueStrings(finding.evidence, `findings[${index}].evidence`, { allowEmpty: false }),
|
|
80
|
+
};
|
|
81
|
+
}).sort((left, right) => canonicalJson(left).localeCompare(canonicalJson(right)));
|
|
82
|
+
if (new Set(rows.map(({ code }) => code)).size !== rows.length) throw new Error('findings contain duplicate codes');
|
|
83
|
+
return rows;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function normalizeDeductions(deductions) {
|
|
87
|
+
if (!Array.isArray(deductions)) throw new Error('deductions must be an array');
|
|
88
|
+
const rows = deductions.map((deduction, index) => {
|
|
89
|
+
exactKeys(deduction, DEDUCTION_KEYS, `deductions[${index}]`);
|
|
90
|
+
if (!Number.isInteger(deduction.points) || deduction.points < 1 || deduction.points > 100) {
|
|
91
|
+
throw new Error(`deductions[${index}].points is invalid`);
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
code: text(deduction.code, `deductions[${index}].code`),
|
|
95
|
+
points: deduction.points,
|
|
96
|
+
reason: text(deduction.reason, `deductions[${index}].reason`),
|
|
97
|
+
evidence: sortedUniqueStrings(deduction.evidence, `deductions[${index}].evidence`, { allowEmpty: false }),
|
|
98
|
+
};
|
|
99
|
+
}).sort((left, right) => canonicalJson(left).localeCompare(canonicalJson(right)));
|
|
100
|
+
if (new Set(rows.map(({ code }) => code)).size !== rows.length) throw new Error('deductions contain duplicate codes');
|
|
101
|
+
return rows;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function validateRetrievalOracleReview(review, { requireAcceptance = false } = {}) {
|
|
105
|
+
exactKeys(review, ORACLE_REVIEW_KEYS, 'retrieval oracle semantic review');
|
|
106
|
+
if (review.schemaVersion !== 1 || review.kind !== 'ruvnet-brain-retrieval-oracle-semantic-review') {
|
|
107
|
+
throw new Error('retrieval oracle semantic review schema is invalid');
|
|
108
|
+
}
|
|
109
|
+
hex(review.oracleReceiptSha256, HEX64, 'retrieval oracle receipt SHA-256');
|
|
110
|
+
hex(review.queryStoreSetSha256, HEX64, 'retrieval oracle store-set SHA-256');
|
|
111
|
+
hex(review.recordSetSha256, HEX64, 'retrieval oracle record-set SHA-256');
|
|
112
|
+
if (!Array.isArray(review.records) || !review.records.length) throw new Error('retrieval oracle semantic records are missing');
|
|
113
|
+
const records = review.records.map((record, index) => {
|
|
114
|
+
exactKeys(record, ORACLE_RECORD_KEYS, `retrieval oracle semantic records[${index}]`);
|
|
115
|
+
const store = text(record.store, `retrieval oracle semantic records[${index}].store`);
|
|
116
|
+
if (store !== store.toLowerCase()) throw new Error('retrieval oracle semantic record store is not canonical');
|
|
117
|
+
hex(record.oracleRecordSha256, HEX64, `retrieval oracle semantic records[${index}].oracleRecordSha256`);
|
|
118
|
+
if (typeof record.relevant !== 'boolean' || !['PASS', 'FAIL'].includes(record.verdict)) {
|
|
119
|
+
throw new Error('retrieval oracle semantic record verdict is invalid');
|
|
120
|
+
}
|
|
121
|
+
const evidence = sortedUniqueStrings(record.evidence, `retrieval oracle semantic records[${index}].evidence`,
|
|
122
|
+
{ allowEmpty: false });
|
|
123
|
+
const untested = sortedUniqueStrings(record.untested, `retrieval oracle semantic records[${index}].untested`);
|
|
124
|
+
const blocked = record.relevant !== true || untested.length > 0;
|
|
125
|
+
if ((record.verdict === 'PASS') === blocked) throw new Error('retrieval oracle semantic record verdict is not evidence-derived');
|
|
126
|
+
return { store, oracleRecordSha256: record.oracleRecordSha256, relevant: record.relevant,
|
|
127
|
+
verdict: record.verdict, evidence, untested };
|
|
128
|
+
}).sort((left, right) => left.store.localeCompare(right.store));
|
|
129
|
+
if (new Set(records.map(({ store }) => store)).size !== records.length) {
|
|
130
|
+
throw new Error('retrieval oracle semantic records contain duplicate stores');
|
|
131
|
+
}
|
|
132
|
+
if (!Number.isSafeInteger(review.recordCount) || review.recordCount !== records.length) {
|
|
133
|
+
throw new Error('retrieval oracle semantic record count is incomplete');
|
|
134
|
+
}
|
|
135
|
+
const stores = records.map(({ store }) => store);
|
|
136
|
+
const recordSet = records.map(({ store, oracleRecordSha256 }) => ({ store, oracleRecordSha256 }));
|
|
137
|
+
if (review.queryStoreSetSha256 !== digest(stores) || review.recordSetSha256 !== digest(recordSet)) {
|
|
138
|
+
throw new Error('retrieval oracle semantic store or record set digest differs');
|
|
139
|
+
}
|
|
140
|
+
const untested = sortedUniqueStrings(review.untested, 'retrieval oracle semantic untested scope');
|
|
141
|
+
if (!['PASS', 'FAIL'].includes(review.verdict)) throw new Error('retrieval oracle semantic review verdict is invalid');
|
|
142
|
+
const blocked = untested.length > 0 || records.some(({ verdict }) => verdict !== 'PASS');
|
|
143
|
+
if ((review.verdict === 'PASS') === blocked) throw new Error('retrieval oracle semantic review verdict is not evidence-derived');
|
|
144
|
+
if (requireAcceptance && blocked) throw new Error('retrieval oracle semantic review is failed or untested');
|
|
145
|
+
return { schemaVersion: 1, kind: review.kind, oracleReceiptSha256: review.oracleReceiptSha256,
|
|
146
|
+
queryStoreSetSha256: review.queryStoreSetSha256, recordCount: records.length, recordSetSha256: review.recordSetSha256,
|
|
147
|
+
records, verdict: review.verdict, untested };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function normalizeExpectedOracle(expected) {
|
|
151
|
+
exactKeys(expected, EXPECTED_ORACLE_KEYS, 'expected retrieval oracle', EXPECTED_ORACLE_REQUIRED);
|
|
152
|
+
hex(expected.oracleReceiptSha256, HEX64, 'expected retrieval oracle receipt SHA-256');
|
|
153
|
+
hex(expected.queryStoreSetSha256, HEX64, 'expected retrieval oracle store-set SHA-256');
|
|
154
|
+
const stores = sortedUniqueStrings(expected.stores, 'expected retrieval oracle stores', { allowEmpty: false });
|
|
155
|
+
if (canonicalJson(stores) !== canonicalJson(expected.stores)
|
|
156
|
+
|| digest(stores) !== expected.queryStoreSetSha256) throw new Error('expected retrieval oracle store set is invalid');
|
|
157
|
+
const records = expected.records === undefined ? null : expected.records.map((record, index) => {
|
|
158
|
+
exactKeys(record, ['oracleRecordSha256', 'store'], `expected retrieval oracle records[${index}]`);
|
|
159
|
+
text(record.store, `expected retrieval oracle records[${index}].store`);
|
|
160
|
+
hex(record.oracleRecordSha256, HEX64, `expected retrieval oracle records[${index}].oracleRecordSha256`);
|
|
161
|
+
return { store: record.store, oracleRecordSha256: record.oracleRecordSha256 };
|
|
162
|
+
}).sort((left, right) => left.store.localeCompare(right.store));
|
|
163
|
+
if (records && canonicalJson(records.map(({ store }) => store)) !== canonicalJson(stores)) {
|
|
164
|
+
throw new Error('expected retrieval oracle records differ from its store set');
|
|
165
|
+
}
|
|
166
|
+
return { oracleReceiptSha256: expected.oracleReceiptSha256, queryStoreSetSha256: expected.queryStoreSetSha256,
|
|
167
|
+
stores, ...(records ? { records } : {}) };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function retrievalOracleExpectationFromPlan(plan) {
|
|
171
|
+
if (plan?.schemaVersion !== 2 || plan?.kind !== 'ruvnet-brain-retrieval-canary-plan') {
|
|
172
|
+
throw new Error('retrieval canary plan is invalid for semantic review');
|
|
173
|
+
}
|
|
174
|
+
const stores = plan.denominator?.eligibleStores;
|
|
175
|
+
const queries = plan.oracle?.evidence?.queries;
|
|
176
|
+
if (!queries || typeof queries !== 'object' || Array.isArray(queries)
|
|
177
|
+
|| canonicalJson(Object.keys(queries).sort()) !== canonicalJson(stores)) {
|
|
178
|
+
throw new Error('retrieval canary plan lacks complete sealed oracle query evidence');
|
|
179
|
+
}
|
|
180
|
+
return normalizeExpectedOracle({ oracleReceiptSha256: plan.oracle?.receiptSha256,
|
|
181
|
+
queryStoreSetSha256: plan.oracle?.queryStoreSetSha256,
|
|
182
|
+
stores, records: stores.map((store) => ({ store, oracleRecordSha256: queries[store]?.recordSha256 })) });
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function assertOracleReviewMatches(review, expected) {
|
|
186
|
+
const normalized = validateRetrievalOracleReview(review, { requireAcceptance: true });
|
|
187
|
+
if (normalized.oracleReceiptSha256 !== expected.oracleReceiptSha256
|
|
188
|
+
|| normalized.queryStoreSetSha256 !== expected.queryStoreSetSha256
|
|
189
|
+
|| canonicalJson(normalized.records.map(({ store }) => store)) !== canonicalJson(expected.stores)) {
|
|
190
|
+
throw new Error('review does not cover the complete oracle store set or exact oracle identity');
|
|
191
|
+
}
|
|
192
|
+
if (expected.records && normalized.recordSetSha256 !== digest(expected.records)) {
|
|
193
|
+
throw new Error('retrieval oracle record identity differs from the sealed oracle');
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function normalizeReleaseIdentity(identity) {
|
|
198
|
+
exactKeys(identity, RELEASE_KEYS, 'release identity', REQUIRED_RELEASE_KEYS);
|
|
199
|
+
const normalized = Object.fromEntries(RELEASE_KEYS.filter((key) => Object.hasOwn(identity, key))
|
|
200
|
+
.map((key) => [key, identity[key]]));
|
|
201
|
+
text(normalized.repository, 'release repository');
|
|
202
|
+
text(normalized.package, 'release package');
|
|
203
|
+
text(normalized.version, 'release version');
|
|
204
|
+
if (normalized.tag !== `v${normalized.version}`) throw new Error('release tag does not match version');
|
|
205
|
+
hex(normalized.candidateSha, HEX40, 'release candidate SHA');
|
|
206
|
+
for (const key of ['payloadId', 'evidenceDigest', 'packageSha256', 'bundleSha256', 'transactionId',
|
|
207
|
+
'bundleDigestSha256', 'bundleSignatureSha256', 'corpusSeedSha256', 'generationLedgerSha256']) {
|
|
208
|
+
if (normalized[key] !== undefined) hex(normalized[key], HEX64, `release ${key}`);
|
|
209
|
+
}
|
|
210
|
+
text(normalized.packageIntegrity, 'release package integrity');
|
|
211
|
+
if (normalized.packageAssetName !== undefined) text(normalized.packageAssetName, 'release package asset name');
|
|
212
|
+
if (normalized.transactionId !== transactionIdFor(normalized)) throw new Error('release transaction identity differs');
|
|
213
|
+
return normalized;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function reviewerFor(input) {
|
|
217
|
+
const reviewer = ALLOWED_INDEPENDENT_REVIEWERS.find(({ identity }) => identity === input.id);
|
|
218
|
+
if (!reviewer || input.model !== reviewer.model || input.provider !== reviewer.provider || input.id !== input.model) {
|
|
219
|
+
throw new Error('reviewer identity, model, and provider are not an allowed exact tuple');
|
|
220
|
+
}
|
|
221
|
+
return reviewer;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function normalizeExecution(execution, reviewer) {
|
|
225
|
+
const required = reviewer.identity === 'gpt-5.6-sol'
|
|
226
|
+
? ['catalogRowSha256', 'invocationDigest', 'subscriptionAuthenticated', 'threadId']
|
|
227
|
+
: ['invocationDigest', 'subscriptionAuthenticated'];
|
|
228
|
+
if (reviewer.identity === 'gpt-5.6-sol'
|
|
229
|
+
&& (!Object.hasOwn(execution || {}, 'threadId') || !Object.hasOwn(execution || {}, 'catalogRowSha256'))) {
|
|
230
|
+
throw new Error('GPT review thread and catalog evidence are required');
|
|
231
|
+
}
|
|
232
|
+
exactKeys(execution, required, 'review execution');
|
|
233
|
+
if (execution.subscriptionAuthenticated !== true) throw new Error('review execution is not subscription authenticated');
|
|
234
|
+
hex(execution.invocationDigest, HEX64, 'review invocation digest');
|
|
235
|
+
if (reviewer.identity === 'gpt-5.6-sol') {
|
|
236
|
+
text(execution.threadId, 'GPT review thread');
|
|
237
|
+
hex(execution.catalogRowSha256, HEX64, 'GPT review catalog row');
|
|
238
|
+
}
|
|
239
|
+
return Object.fromEntries(required.map((key) => [key, execution[key]]));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function normalizeInput(input) {
|
|
243
|
+
exactKeys(input, INPUT_KEYS, 'independent review input');
|
|
244
|
+
const reviewer = reviewerFor(input);
|
|
245
|
+
const releaseIdentity = normalizeReleaseIdentity(input.releaseIdentity);
|
|
246
|
+
const findings = normalizeFindings(input.findings);
|
|
247
|
+
const deductions = normalizeDeductions(input.deductions);
|
|
248
|
+
const untested = sortedUniqueStrings(input.untested, 'untested scope');
|
|
249
|
+
const retrievalOracleReview = validateRetrievalOracleReview(input.retrievalOracleReview);
|
|
250
|
+
text(input.subjectProducerIdentity, 'subject producer identity');
|
|
251
|
+
if (input.subjectProducerIdentity === input.id) throw new Error('self-review is forbidden');
|
|
252
|
+
hex(input.sourceSha, HEX40, 'source SHA');
|
|
253
|
+
hex(input.sourceTree, HEX40, 'source tree');
|
|
254
|
+
for (const [label, value] of [['artifact SHA-256', input.artifactSha256], ['payload id', input.payloadId],
|
|
255
|
+
['payload SHA-256', input.payloadSha256], ['product contract SHA-256', input.productContractSha256],
|
|
256
|
+
['rubric SHA-256', input.rubricSha256]]) hex(value, HEX64, label);
|
|
257
|
+
if (input.sourceSha !== releaseIdentity.candidateSha || input.artifactSha256 !== releaseIdentity.packageSha256
|
|
258
|
+
|| input.payloadId !== releaseIdentity.payloadId) throw new Error('reviewed source, artifact, or payload differs from release identity');
|
|
259
|
+
if (input.independent !== true) throw new Error('review must declare independent execution');
|
|
260
|
+
if (!Number.isInteger(input.score) || input.score < 0 || input.score > 100
|
|
261
|
+
|| input.score !== 100 - deductions.reduce((sum, { points }) => sum + points, 0)) {
|
|
262
|
+
throw new Error('review score does not equal 100 minus deductions');
|
|
263
|
+
}
|
|
264
|
+
if (!['PASS', 'FAIL'].includes(input.verdict)) throw new Error('review verdict is invalid');
|
|
265
|
+
const blocked = input.score < 95 || untested.length > 0 || retrievalOracleReview.verdict !== 'PASS'
|
|
266
|
+
|| findings.some(({ severity }) => severity === 'critical');
|
|
267
|
+
if (input.verdict === 'PASS' && retrievalOracleReview.verdict !== 'PASS') {
|
|
268
|
+
throw new Error('PASS review has a failed or untested retrieval oracle semantic review');
|
|
269
|
+
}
|
|
270
|
+
if (input.verdict === 'PASS' && blocked) throw new Error('PASS review has a score below 95, critical finding, or untested scope');
|
|
271
|
+
if (input.verdict === 'FAIL' && !blocked) throw new Error('FAIL review has no acceptance blocker');
|
|
272
|
+
if (typeof input.reviewedAt !== 'string' || Number.isNaN(Date.parse(input.reviewedAt))
|
|
273
|
+
|| new Date(input.reviewedAt).toISOString() !== input.reviewedAt) throw new Error('review timestamp must be exact UTC ISO-8601');
|
|
274
|
+
return {
|
|
275
|
+
...input,
|
|
276
|
+
releaseIdentity,
|
|
277
|
+
findings,
|
|
278
|
+
deductions,
|
|
279
|
+
untested,
|
|
280
|
+
retrievalOracleReview,
|
|
281
|
+
execution: normalizeExecution(input.execution, reviewer),
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function asKey(key, kind) {
|
|
286
|
+
let parsed;
|
|
287
|
+
try {
|
|
288
|
+
parsed = key?.type === kind && key?.asymmetricKeyType ? key
|
|
289
|
+
: kind === 'private' ? crypto.createPrivateKey(key) : crypto.createPublicKey(key);
|
|
290
|
+
}
|
|
291
|
+
catch { throw new Error(`review signing ${kind} key is invalid`); }
|
|
292
|
+
if (parsed.asymmetricKeyType !== 'ed25519') throw new Error(`review signing ${kind} key must be Ed25519`);
|
|
293
|
+
return parsed;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function keyId(publicKey) {
|
|
297
|
+
return crypto.createHash('sha256').update(publicKey.export({ type: 'spki', format: 'der' })).digest('hex');
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function signingPayload(receipt) {
|
|
301
|
+
const { receiptSha256: _receiptSha256, signature: _signature, ...payload } = receipt;
|
|
302
|
+
return payload;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function assertNormalizedCore(core) {
|
|
306
|
+
exactKeys(core, CORE_KEYS, 'independent review receipt core');
|
|
307
|
+
if (core.schemaVersion !== 2 || core.kind !== 'ruvnet-brain-independent-review'
|
|
308
|
+
|| core.signatureAlgorithm !== 'Ed25519') throw new Error('independent review receipt schema is invalid');
|
|
309
|
+
hex(core.releaseIdentitySha256, HEX64, 'release identity SHA-256');
|
|
310
|
+
hex(core.signingKeyId, HEX64, 'review signing key id');
|
|
311
|
+
const normalized = normalizeInput(Object.fromEntries(INPUT_KEYS.map((key) => [key, core[key]])));
|
|
312
|
+
if (canonicalJson(normalized) !== canonicalJson(Object.fromEntries(INPUT_KEYS.map((key) => [key, core[key]])))) {
|
|
313
|
+
throw new Error('independent review receipt arrays are not canonical');
|
|
314
|
+
}
|
|
315
|
+
if (core.releaseIdentitySha256 !== digest(core.releaseIdentity)) throw new Error('release identity digest mismatch');
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export function createIndependentReviewReceipt(input, privateKey) {
|
|
319
|
+
const normalized = normalizeInput(input);
|
|
320
|
+
const privateObject = asKey(privateKey, 'private');
|
|
321
|
+
const publicObject = crypto.createPublicKey(privateObject);
|
|
322
|
+
const payload = {
|
|
323
|
+
schemaVersion: 2,
|
|
324
|
+
kind: 'ruvnet-brain-independent-review',
|
|
325
|
+
...normalized,
|
|
326
|
+
releaseIdentitySha256: digest(normalized.releaseIdentity),
|
|
327
|
+
signatureAlgorithm: 'Ed25519',
|
|
328
|
+
signingKeyId: keyId(publicObject),
|
|
329
|
+
};
|
|
330
|
+
assertNormalizedCore(payload);
|
|
331
|
+
const signature = crypto.sign(null, Buffer.from(canonicalJson(payload)), privateObject).toString('base64');
|
|
332
|
+
const signed = { ...payload, signature };
|
|
333
|
+
return { ...signed, receiptSha256: digest(signed) };
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export function verifyIndependentReviewReceipt(receipt, publicKey) {
|
|
337
|
+
exactKeys(receipt, RECEIPT_KEYS, 'independent review receipt');
|
|
338
|
+
const payload = signingPayload(receipt);
|
|
339
|
+
assertNormalizedCore(payload);
|
|
340
|
+
const publicObject = asKey(publicKey, 'public');
|
|
341
|
+
if (receipt.signingKeyId !== keyId(publicObject)) throw new Error('review signing key identity differs');
|
|
342
|
+
if (typeof receipt.signature !== 'string' || !BASE64.test(receipt.signature)
|
|
343
|
+
|| Buffer.from(receipt.signature, 'base64').length !== 64) throw new Error('review signature is malformed');
|
|
344
|
+
const signed = { ...payload, signature: receipt.signature };
|
|
345
|
+
if (receipt.receiptSha256 !== digest(signed)) throw new Error('independent review receipt digest mismatch');
|
|
346
|
+
if (!crypto.verify(null, Buffer.from(canonicalJson(payload)), publicObject, Buffer.from(receipt.signature, 'base64'))) {
|
|
347
|
+
throw new Error('independent review receipt signature mismatch');
|
|
348
|
+
}
|
|
349
|
+
return receipt;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function publicKeyFor(source, identity) {
|
|
353
|
+
return source instanceof Map ? source.get(identity) : source && typeof source === 'object' ? source[identity] : undefined;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const reviewedIdentity = (receipt) => ({
|
|
357
|
+
subjectProducerIdentity: receipt.subjectProducerIdentity,
|
|
358
|
+
sourceSha: receipt.sourceSha,
|
|
359
|
+
sourceTree: receipt.sourceTree,
|
|
360
|
+
artifactSha256: receipt.artifactSha256,
|
|
361
|
+
payloadId: receipt.payloadId,
|
|
362
|
+
payloadSha256: receipt.payloadSha256,
|
|
363
|
+
releaseIdentitySha256: receipt.releaseIdentitySha256,
|
|
364
|
+
productContractSha256: receipt.productContractSha256,
|
|
365
|
+
rubricSha256: receipt.rubricSha256,
|
|
366
|
+
retrievalOracleReview: {
|
|
367
|
+
oracleReceiptSha256: receipt.retrievalOracleReview.oracleReceiptSha256,
|
|
368
|
+
queryStoreSetSha256: receipt.retrievalOracleReview.queryStoreSetSha256,
|
|
369
|
+
recordCount: receipt.retrievalOracleReview.recordCount,
|
|
370
|
+
recordSetSha256: receipt.retrievalOracleReview.recordSetSha256,
|
|
371
|
+
},
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
export function validateIndependentReviewPair(receipts, { publicKeysByReviewer, expectedIdentity = null,
|
|
375
|
+
expectedOracle = null } = {}) {
|
|
376
|
+
if (!Array.isArray(receipts) || receipts.length !== ALLOWED_INDEPENDENT_REVIEWERS.length) {
|
|
377
|
+
throw new Error('independent review pair requires exactly two receipts');
|
|
378
|
+
}
|
|
379
|
+
const identities = receipts.map(({ id }) => id);
|
|
380
|
+
const allowed = ALLOWED_INDEPENDENT_REVIEWERS.map(({ identity }) => identity);
|
|
381
|
+
if (new Set(identities).size !== allowed.length || allowed.some((identity) => !identities.includes(identity))) {
|
|
382
|
+
throw new Error('independent review pair requires two distinct allowed reviewers');
|
|
383
|
+
}
|
|
384
|
+
const ordered = allowed.map((identity) => receipts.find((receipt) => receipt.id === identity));
|
|
385
|
+
for (const receipt of ordered) {
|
|
386
|
+
const publicKey = publicKeyFor(publicKeysByReviewer, receipt.id);
|
|
387
|
+
if (!publicKey) throw new Error(`public key is missing for reviewer ${receipt.id}`);
|
|
388
|
+
verifyIndependentReviewReceipt(receipt, publicKey);
|
|
389
|
+
if (receipt.verdict !== 'PASS' || receipt.score < 95 || receipt.untested.length) {
|
|
390
|
+
throw new Error('public verification requires two passing reviews with no untested scope');
|
|
391
|
+
}
|
|
392
|
+
validateRetrievalOracleReview(receipt.retrievalOracleReview, { requireAcceptance: true });
|
|
393
|
+
}
|
|
394
|
+
const identity = reviewedIdentity(ordered[0]);
|
|
395
|
+
if (ordered.slice(1).some((receipt) => canonicalJson(reviewedIdentity(receipt)) !== canonicalJson(identity))) {
|
|
396
|
+
throw new Error('independent reviewers reviewed identity differs');
|
|
397
|
+
}
|
|
398
|
+
if (expectedIdentity && canonicalJson(identity) !== canonicalJson(expectedIdentity)) {
|
|
399
|
+
throw new Error('independent review pair differs from expected reviewed identity');
|
|
400
|
+
}
|
|
401
|
+
if (expectedOracle) {
|
|
402
|
+
const expected = normalizeExpectedOracle(expectedOracle);
|
|
403
|
+
ordered.forEach((receipt) => assertOracleReviewMatches(receipt.retrievalOracleReview, expected));
|
|
404
|
+
}
|
|
405
|
+
return ordered;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const CLI_OPTIONS = Object.freeze({
|
|
409
|
+
produce: Object.freeze(['--input', '--out']),
|
|
410
|
+
verify: Object.freeze(['--public-key', '--receipt']),
|
|
411
|
+
'verify-pair': Object.freeze([
|
|
412
|
+
'--expected-identity', '--fable', '--fable-public-key', '--retrieval-plan', '--sol', '--sol-public-key',
|
|
413
|
+
]),
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
function parseCli(args) {
|
|
417
|
+
const command = args[0];
|
|
418
|
+
const allowed = CLI_OPTIONS[command];
|
|
419
|
+
if (!allowed) throw new Error('command must be produce, verify, or verify-pair');
|
|
420
|
+
const options = {};
|
|
421
|
+
for (let index = 1; index < args.length; index += 2) {
|
|
422
|
+
const name = args[index];
|
|
423
|
+
const value = args[index + 1];
|
|
424
|
+
if (!allowed.includes(name)) throw new Error(`unknown ${command} option: ${String(name)}`);
|
|
425
|
+
if (!value || value.startsWith('--')) throw new Error(`${name} requires a value`);
|
|
426
|
+
if (Object.hasOwn(options, name)) throw new Error(`${name} may be supplied only once`);
|
|
427
|
+
options[name] = value;
|
|
428
|
+
}
|
|
429
|
+
const required = allowed.filter((name) => name !== '--expected-identity');
|
|
430
|
+
const missing = required.filter((name) => !options[name]);
|
|
431
|
+
if (missing.length) throw new Error(`${command} requires ${missing.join(', ')}`);
|
|
432
|
+
return { command, options };
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function readRegular(file, label) {
|
|
436
|
+
const resolved = path.resolve(file);
|
|
437
|
+
let stat;
|
|
438
|
+
try { stat = fs.lstatSync(resolved); } catch { throw new Error(`${label} is missing`); }
|
|
439
|
+
if (!stat.isFile() || stat.isSymbolicLink()) throw new Error(`${label} must be a regular non-symlink file`);
|
|
440
|
+
return fs.readFileSync(resolved, 'utf8');
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function readJson(file, label) {
|
|
444
|
+
try { return JSON.parse(readRegular(file, label)); }
|
|
445
|
+
catch (error) {
|
|
446
|
+
if (error.message.startsWith(label)) throw error;
|
|
447
|
+
throw new Error(`${label} is not valid JSON`);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function writeReceipt(file, receipt) {
|
|
452
|
+
fs.writeFileSync(path.resolve(file), `${JSON.stringify(receipt, null, 2)}\n`, { flag: 'wx', mode: 0o600 }); }
|
|
453
|
+
|
|
454
|
+
export function main(args = process.argv.slice(2), runtime = {}) {
|
|
455
|
+
const env = runtime.env || process.env;
|
|
456
|
+
const stdout = runtime.stdout || process.stdout;
|
|
457
|
+
const stderr = runtime.stderr || process.stderr;
|
|
458
|
+
try {
|
|
459
|
+
const { command, options } = parseCli(args);
|
|
460
|
+
if (command === 'produce') {
|
|
461
|
+
if (!env.RUVNET_REVIEW_SIGNING_KEY) throw new Error('RUVNET_REVIEW_SIGNING_KEY is required');
|
|
462
|
+
const receipt = createIndependentReviewReceipt(
|
|
463
|
+
readJson(options['--input'], 'review input'),
|
|
464
|
+
env.RUVNET_REVIEW_SIGNING_KEY,
|
|
465
|
+
);
|
|
466
|
+
writeReceipt(options['--out'], receipt);
|
|
467
|
+
stdout.write(`${JSON.stringify({ verdict: receipt.verdict, reviewer: receipt.id,
|
|
468
|
+
receiptSha256: receipt.receiptSha256 })}\n`);
|
|
469
|
+
return 0;
|
|
470
|
+
}
|
|
471
|
+
if (command === 'verify') {
|
|
472
|
+
const receipt = readJson(options['--receipt'], 'review receipt');
|
|
473
|
+
verifyIndependentReviewReceipt(receipt, readRegular(options['--public-key'], 'review public key'));
|
|
474
|
+
stdout.write(`${JSON.stringify({ verdict: receipt.verdict, reviewer: receipt.id,
|
|
475
|
+
receiptSha256: receipt.receiptSha256 })}\n`);
|
|
476
|
+
return 0;
|
|
477
|
+
}
|
|
478
|
+
const receipts = [readJson(options['--fable'], 'Fable review receipt'),
|
|
479
|
+
readJson(options['--sol'], 'Sol review receipt')];
|
|
480
|
+
const ordered = validateIndependentReviewPair(receipts, {
|
|
481
|
+
publicKeysByReviewer: {
|
|
482
|
+
'claude-fable-5': readRegular(options['--fable-public-key'], 'Fable review public key'),
|
|
483
|
+
'gpt-5.6-sol': readRegular(options['--sol-public-key'], 'Sol review public key'),
|
|
484
|
+
},
|
|
485
|
+
expectedIdentity: options['--expected-identity']
|
|
486
|
+
? readJson(options['--expected-identity'], 'expected review identity') : null,
|
|
487
|
+
expectedOracle: retrievalOracleExpectationFromPlan(
|
|
488
|
+
readJson(options['--retrieval-plan'], 'retrieval canary plan')),
|
|
489
|
+
});
|
|
490
|
+
const reviews = ordered.map(({ id, receiptSha256 }) => ({ id, receiptSha256 }));
|
|
491
|
+
stdout.write(`${JSON.stringify({ verdict: 'PASS', reviews, pairSha256: digest(reviews) })}\n`);
|
|
492
|
+
return 0;
|
|
493
|
+
} catch (error) {
|
|
494
|
+
stderr.write(`independent-review-receipt: ${error.message}\n`);
|
|
495
|
+
return 1;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) process.exitCode = main();
|
package/scripts/ingest-gists.mjs
CHANGED
|
@@ -42,6 +42,7 @@ const DRY = argv.includes('--dry-run');
|
|
|
42
42
|
// and ship via Release, so nightly CI can keep the INDEX fresh even though it cannot commit vectors.
|
|
43
43
|
const INDEX_ONLY = argv.includes('--index-only');
|
|
44
44
|
const INDEX_PATH = path.join(ROOT, 'docs', 'RUV-GISTS.md');
|
|
45
|
+
const FETCH_TIMEOUT_MS = Number(process.env.RUVNET_GISTS_FETCH_TIMEOUT_MS || 30_000);
|
|
45
46
|
|
|
46
47
|
// Markdown/text only. A gist's code files are better read from the repo they land in; prose is the
|
|
47
48
|
// thing repos don't carry.
|
|
@@ -49,8 +50,17 @@ const TEXT_EXT = new Set(['.md', '.markdown', '.txt', '.rst']);
|
|
|
49
50
|
|
|
50
51
|
/** Authenticated GitHub calls via `gh` — 5000 req/hr instead of 60, and no token handling here. */
|
|
51
52
|
function gh(endpointArgs) {
|
|
52
|
-
const r = spawnSync('gh', endpointArgs, {
|
|
53
|
-
|
|
53
|
+
const r = spawnSync('gh', endpointArgs, {
|
|
54
|
+
encoding: 'utf8',
|
|
55
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
56
|
+
timeout: FETCH_TIMEOUT_MS,
|
|
57
|
+
});
|
|
58
|
+
if (r.status !== 0) {
|
|
59
|
+
const detail = r.error?.code === 'ETIMEDOUT' || r.signal
|
|
60
|
+
? `timed out after ${FETCH_TIMEOUT_MS}ms`
|
|
61
|
+
: (r.stderr || '').trim().slice(0, 200);
|
|
62
|
+
throw new Error(`gh ${endpointArgs.join(' ')} failed: ${detail}`);
|
|
63
|
+
}
|
|
54
64
|
return r.stdout;
|
|
55
65
|
}
|
|
56
66
|
|
|
@@ -187,11 +197,23 @@ async function main() {
|
|
|
187
197
|
for (const [i, stub] of gists.entries()) {
|
|
188
198
|
if (i % 25 === 0) process.stdout.write(`\r fetching ${i}/${gists.length}…`);
|
|
189
199
|
let g;
|
|
190
|
-
try {
|
|
200
|
+
try {
|
|
201
|
+
g = fetchGist(stub.id);
|
|
202
|
+
} catch (err) {
|
|
203
|
+
skipped++;
|
|
204
|
+
console.error(`\n fetch failed for gist ${stub.id}: ${err.message}`);
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
191
207
|
for (const [fname, f] of Object.entries(g.files || {})) {
|
|
192
208
|
if (!TEXT_EXT.has(path.extname(fname).toLowerCase())) continue;
|
|
193
|
-
|
|
194
|
-
|
|
209
|
+
// JSONL readers treat U+2028/U+2029 as physical line separators on some runtimes. Normalize
|
|
210
|
+
// them before serialization so one JSON object always remains one physical passage line.
|
|
211
|
+
const body = (f.truncated && f.raw_url ? '' : (f.content || '')).replace(/[\u2028\u2029]/g, '\n');
|
|
212
|
+
if (!body.trim()) {
|
|
213
|
+
skipped++;
|
|
214
|
+
console.error(`\n empty or truncated text file: ${stub.id}/${fname}`);
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
195
217
|
const title = (g.description || fname).replace(/\s+/g, ' ').trim().slice(0, 180) || fname;
|
|
196
218
|
const head = banner(g, fname);
|
|
197
219
|
// Banner on EVERY chunk, not just the first. Retrieval returns ONE chunk — if the provenance
|
|
@@ -212,6 +234,13 @@ async function main() {
|
|
|
212
234
|
}
|
|
213
235
|
process.stdout.write('\r');
|
|
214
236
|
|
|
237
|
+
// A receipt-only or partial corpus is more dangerous than a failed run: its RVF idmap can look
|
|
238
|
+
// healthy while silently omitting live source. Preserve the previous complete corpus and make
|
|
239
|
+
// the nightly job retry instead of publishing incomplete search data.
|
|
240
|
+
if (skipped > 0) {
|
|
241
|
+
throw new Error(`ingest-gists: refusing to write partial corpus (${skipped} fetch/content failures)`);
|
|
242
|
+
}
|
|
243
|
+
|
|
215
244
|
fs.writeFileSync(path.join(KB, `${NAME}.passages.jsonl`), passages.map((p) => JSON.stringify(p)).join('\n') + '\n');
|
|
216
245
|
fs.writeFileSync(path.join(KB, `${NAME}.meta.json`), JSON.stringify({
|
|
217
246
|
model: NAME, dimensions: 0, metric: 'cosine', name: NAME,
|