ruvnet-brain 4.2.2-dev → 4.3.1

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.
Files changed (73) hide show
  1. package/README.md +15 -14
  2. package/bin/install.mjs +11 -2
  3. package/console/architecture.html +12 -14
  4. package/console/install-mockup.html +3 -3
  5. package/kb/zip-extract.mjs +8 -2
  6. package/package.json +7 -1
  7. package/plugin/.claude-plugin/plugin.json +1 -1
  8. package/plugin/.codex-plugin/plugin.json +1 -1
  9. package/plugin/hooks/codex-hooks.json +1 -1
  10. package/plugin/hooks/hooks.json +10 -0
  11. package/plugin/host-adapters/claude.json +11 -0
  12. package/plugin/host-adapters/codex.json +11 -0
  13. package/plugin/mcp/managed-cli-interface.mjs +77 -10
  14. package/plugin/mcp/server.mjs +2 -1
  15. package/plugin/scripts/anticipate.sh +30 -1
  16. package/plugin/scripts/capability-claim-evidence.mjs +431 -0
  17. package/plugin/scripts/capability-inventory-receipt.mjs +225 -0
  18. package/plugin/scripts/capability-registry.mjs +6 -0
  19. package/plugin/scripts/capability-routing.mjs +70 -0
  20. package/plugin/scripts/continuation-gate.mjs +68 -2
  21. package/plugin/scripts/coverage-integrity.mjs +575 -0
  22. package/plugin/scripts/ground-ruvnet.sh +2 -2
  23. package/plugin/scripts/nightly-controller.mjs +19 -2
  24. package/plugin/scripts/project-progression-contract.mjs +409 -0
  25. package/plugin/scripts/project-progression-hook.mjs +196 -0
  26. package/plugin/scripts/project-progression-outbox.mjs +95 -0
  27. package/plugin/scripts/project-progression-session-start.mjs +183 -0
  28. package/plugin/scripts/project-progression-store.mjs +248 -0
  29. package/plugin/scripts/project-store-resolver.mjs +106 -0
  30. package/plugin/scripts/session-snapshot-hook.mjs +29 -1
  31. package/plugin/scripts/session-start-core.mjs +22 -18
  32. package/plugin/scripts/spend-guard.mjs +6 -1
  33. package/scripts/adr-072-completion.mjs +98 -0
  34. package/scripts/behavioral-l1-l4.mjs +11 -8
  35. package/scripts/brain-score.mjs +7 -2
  36. package/scripts/build-bundle.mjs +82 -6
  37. package/scripts/card-from-source.mjs +32 -6
  38. package/scripts/corpus-aggregates.mjs +194 -0
  39. package/scripts/corpus-candidate.mjs +56 -36
  40. package/scripts/corpus-reconcile.mjs +295 -28
  41. package/scripts/corpus-seed-publish.mjs +3 -3
  42. package/scripts/coverage-integrity.mjs +3 -0
  43. package/scripts/gist-receipts.mjs +183 -0
  44. package/scripts/git-hooks/pre-push +21 -0
  45. package/scripts/host-registry.mjs +118 -0
  46. package/scripts/independent-review-receipt.mjs +499 -0
  47. package/scripts/ingest-gists.mjs +34 -5
  48. package/scripts/ingest-new-repos.mjs +5 -0
  49. package/scripts/metaharness-gate.mjs +58 -0
  50. package/scripts/nightly-gists.sh +3 -3
  51. package/scripts/nightly-wrapper.sh +75 -69
  52. package/scripts/product-integrity-contract.mjs +156 -0
  53. package/scripts/public-inventory.mjs +4 -0
  54. package/scripts/public-verification-aggregate.mjs +342 -0
  55. package/scripts/public-verification-finalizer.mjs +59 -0
  56. package/scripts/public-verification-inputs.mjs +473 -0
  57. package/scripts/public-verification-lane.mjs +244 -0
  58. package/scripts/publication-receipt.mjs +50 -6
  59. package/scripts/rebuild-gists-from-receipts.mjs +50 -5
  60. package/scripts/release-projection.mjs +86 -0
  61. package/scripts/release-transaction-provider.mjs +43 -102
  62. package/scripts/release-transaction.mjs +139 -51
  63. package/scripts/release.mjs +15 -7
  64. package/scripts/restore-local-ingests.mjs +2 -2
  65. package/scripts/retrieval-canary.mjs +533 -0
  66. package/scripts/routing-flywheel.mjs +12 -1
  67. package/scripts/rvf-generation.mjs +9 -3
  68. package/scripts/self-update.mjs +19 -44
  69. package/scripts/source-coverage.mjs +415 -0
  70. package/scripts/source-scope-receipt.mjs +84 -0
  71. package/scripts/sync-census.mjs +0 -0
  72. package/scripts/wired-check.mjs +114 -24
  73. package/scripts/worktree-integrity.mjs +123 -0
@@ -0,0 +1,118 @@
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 { HOST_MODES, MODE_HOSTS } from './host-install-matrix.mjs';
8
+
9
+ export const HOST_ADAPTER_FILES = Object.freeze([
10
+ 'plugin/host-adapters/claude.json',
11
+ 'plugin/host-adapters/codex.json',
12
+ ]);
13
+ export const HOST_OPERATING_SYSTEMS = Object.freeze(['linux', 'macos', 'windows']);
14
+
15
+ const ROOT = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
16
+ const ordered = (values) => [...values].sort((a, b) => a.localeCompare(b));
17
+ const sha256 = (bytes) => crypto.createHash('sha256').update(bytes).digest('hex');
18
+
19
+ function trackedFile(root, relative, label) {
20
+ if (typeof relative !== 'string' || !relative || path.isAbsolute(relative)
21
+ || relative.split(/[\\/]/).includes('..')) throw new Error(`${label} is not a safe tracked file`);
22
+ const absolute = path.resolve(root, relative);
23
+ let stat;
24
+ try { stat = fs.lstatSync(absolute); } catch { throw new Error(`${label} is not a tracked file: ${relative}`); }
25
+ if (!stat.isFile() || stat.isSymbolicLink()) throw new Error(`${label} is not a tracked file: ${relative}`);
26
+ // Windows may expose the same temp path through 8.3 and long-name aliases. Bind receipts to
27
+ // the filesystem's canonical spelling so cross-host verification compares identity, not alias.
28
+ return fs.realpathSync(absolute);
29
+ }
30
+
31
+ function expectedModes(id) {
32
+ return HOST_MODES.filter((mode) => MODE_HOSTS[mode].includes(id));
33
+ }
34
+
35
+ function readAdapter(root, descriptorFile) {
36
+ const absolute = trackedFile(root, descriptorFile, 'host adapter descriptor');
37
+ const bytes = fs.readFileSync(absolute);
38
+ let value;
39
+ try { value = JSON.parse(bytes); } catch (error) { throw new Error(`host adapter descriptor is invalid JSON: ${error.message}`); }
40
+ const expectedKeys = ['displayName', 'hooks', 'id', 'kind', 'loader', 'manifest', 'modes', 'os', 'schemaVersion'];
41
+ if (canonicalJson(Object.keys(value).sort()) !== canonicalJson(expectedKeys)
42
+ || value.schemaVersion !== 1 || value.kind !== 'ruvnet-brain-host-adapter'
43
+ || !['claude', 'codex'].includes(value.id) || typeof value.displayName !== 'string' || !value.displayName
44
+ || canonicalJson(value.os) !== canonicalJson(HOST_OPERATING_SYSTEMS)
45
+ || canonicalJson(value.modes) !== canonicalJson(expectedModes(value.id))) {
46
+ throw new Error(`${descriptorFile} host adapter identity, OS, or mode ownership is invalid`);
47
+ }
48
+ for (const field of ['manifest', 'hooks', 'loader']) trackedFile(root, value[field], `${value.id} ${field}`);
49
+ return { ...value, descriptorFile, descriptorSha256: sha256(bytes) };
50
+ }
51
+
52
+ function assemble(root) {
53
+ const adapters = HOST_ADAPTER_FILES.map((file) => readAdapter(root, file))
54
+ .sort((a, b) => a.id.localeCompare(b.id));
55
+ const payload = {
56
+ schemaVersion: 1,
57
+ kind: 'ruvnet-brain-host-registry',
58
+ os: [...HOST_OPERATING_SYSTEMS],
59
+ modes: [...HOST_MODES],
60
+ adapters,
61
+ };
62
+ return { ...payload, registrySha256: digest(payload) };
63
+ }
64
+
65
+ export function validateHostRegistry(registry, { root = ROOT } = {}) {
66
+ if (registry?.schemaVersion !== 1 || registry?.kind !== 'ruvnet-brain-host-registry') {
67
+ throw new Error('host registry identity is invalid');
68
+ }
69
+ if (canonicalJson(registry.os) !== canonicalJson(HOST_OPERATING_SYSTEMS)) {
70
+ throw new Error('host registry operating systems are incomplete');
71
+ }
72
+ if (canonicalJson(registry.modes) !== canonicalJson(HOST_MODES)) {
73
+ throw new Error('host registry modes are incomplete');
74
+ }
75
+ if (!Array.isArray(registry.adapters)
76
+ || canonicalJson(registry.adapters.map(({ id }) => id)) !== canonicalJson(['claude', 'codex'])) {
77
+ throw new Error('host registry adapter set is incomplete');
78
+ }
79
+ for (const adapter of registry.adapters) {
80
+ if (canonicalJson(adapter.modes) !== canonicalJson(expectedModes(adapter.id))) {
81
+ throw new Error(`${adapter.id} host mode ownership is invalid`);
82
+ }
83
+ for (const field of ['manifest', 'hooks', 'loader']) trackedFile(root, adapter[field], `${adapter.id} ${field}`);
84
+ }
85
+ const expected = assemble(root);
86
+ const { registrySha256: _registrySha256, ...payload } = registry;
87
+ if (registry.registrySha256 !== digest(payload)) throw new Error('host registry digest mismatch');
88
+ if (canonicalJson(registry) !== canonicalJson(expected)) throw new Error('host registry differs from tracked descriptors');
89
+ return registry;
90
+ }
91
+
92
+ export function buildHostRegistry({ root = ROOT } = {}) {
93
+ return validateHostRegistry(assemble(path.resolve(root)), { root: path.resolve(root) });
94
+ }
95
+
96
+ function argument(args, name) {
97
+ const index = args.indexOf(name);
98
+ return index >= 0 ? args[index + 1] : null;
99
+ }
100
+
101
+ export function main(args = process.argv.slice(2)) {
102
+ try {
103
+ const out = argument(args, '--out');
104
+ if (!out) throw new Error('--out is required');
105
+ const output = path.resolve(out);
106
+ if (fs.existsSync(output)) throw new Error(`refusing to overwrite existing host registry: ${output}`);
107
+ const registry = buildHostRegistry({ root: process.cwd() });
108
+ fs.mkdirSync(path.dirname(output), { recursive: true });
109
+ fs.writeFileSync(output, `${JSON.stringify(registry, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
110
+ process.stdout.write(`${JSON.stringify({ ok: true, registrySha256: registry.registrySha256 })}\n`);
111
+ return 0;
112
+ } catch (error) {
113
+ console.error(`host-registry: ${error.message}`);
114
+ return 1;
115
+ }
116
+ }
117
+
118
+ if (path.resolve(process.argv[1] || '') === fileURLToPath(import.meta.url)) process.exitCode = main();
@@ -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();