ruvnet-brain 4.3.1 → 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.
Files changed (56) hide show
  1. package/README.md +12 -13
  2. package/bin/install.mjs +18 -8
  3. package/console/app.js +4 -4
  4. package/data/model-catalog.json +3 -3
  5. package/kb/verify-citation.mjs +32 -5
  6. package/package.json +13 -9
  7. package/plugin/.claude-plugin/plugin.json +1 -1
  8. package/plugin/.codex-plugin/plugin.json +1 -1
  9. package/plugin/mcp/managed-cli-interface.mjs +4 -2
  10. package/plugin/scripts/codex-hook-adapter.mjs +12 -7
  11. package/plugin/scripts/codex-hook-events.mjs +39 -0
  12. package/plugin/scripts/coverage-integrity.mjs +4 -4
  13. package/plugin/scripts/hook-shim.mjs +9 -7
  14. package/plugin/scripts/learn-capture.sh +20 -1
  15. package/plugin/scripts/learn-flush.mjs +7 -1
  16. package/plugin/scripts/lesson-gate.mjs +17 -2
  17. package/plugin/scripts/lesson-hooks.sh +17 -2
  18. package/plugin/scripts/session-start-core.mjs +47 -11
  19. package/plugin/skills/release-proof/SKILL.md +19 -10
  20. package/plugin/skills/release-proof/references/receipt-contract.md +18 -8
  21. package/scripts/brain-stamp-resolve.mjs +22 -0
  22. package/scripts/brain-stamp.mjs +5 -3
  23. package/scripts/build-bundle.mjs +41 -4
  24. package/scripts/candidate-host-evidence.mjs +9 -2
  25. package/scripts/ci/step-watchdog.mjs +86 -0
  26. package/scripts/convergence-manifest.mjs +66 -0
  27. package/scripts/distill-project.mjs +22 -4
  28. package/scripts/eval-brain.mjs +13 -3
  29. package/scripts/execution-policy.mjs +111 -0
  30. package/scripts/execution-preflight.mjs +18 -0
  31. package/scripts/git-hooks/pre-push +16 -0
  32. package/scripts/health-repair.mjs +5 -1
  33. package/scripts/host-install-matrix.mjs +247 -40
  34. package/scripts/integration-evidence.mjs +73 -0
  35. package/scripts/nightly-wrapper.sh +4 -3
  36. package/scripts/onboarding-console.mjs +3 -3
  37. package/scripts/prepublication-evidence.mjs +174 -0
  38. package/scripts/protected-release-invocation.mjs +32 -4
  39. package/scripts/public-verification-aggregate.mjs +27 -54
  40. package/scripts/public-verification-inputs.mjs +5 -3
  41. package/scripts/qa-runner.mjs +72 -0
  42. package/scripts/qe/agentic-qe-4.3.mjs +195 -0
  43. package/scripts/qe/aggregate-4.3.mjs +42 -0
  44. package/scripts/record-lesson.mjs +28 -6
  45. package/scripts/refresh-model-catalog.mjs +32 -3
  46. package/scripts/release-authority.mjs +19 -7
  47. package/scripts/release-convergence-watchdog.mjs +10 -22
  48. package/scripts/release-projection.mjs +76 -32
  49. package/scripts/release-transaction.mjs +1 -1
  50. package/scripts/release.mjs +9 -15
  51. package/scripts/retrieval-canary.mjs +14 -6
  52. package/scripts/set-version.mjs +31 -0
  53. package/scripts/source-coverage.mjs +11 -2
  54. package/scripts/staged-host-verifier.mjs +2 -2
  55. package/scripts/verify-channels.mjs +1 -1
  56. package/scripts/wired-check.mjs +1 -0
@@ -0,0 +1,174 @@
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 { aggregateEvidence } from './release-evidence-aggregate.mjs';
7
+ import { canonicalJson } from './release-transaction.mjs';
8
+ import { EXCLUSION_POLICY } from './integration-evidence.mjs';
9
+
10
+ const sha256 = (bytes) => crypto.createHash('sha256').update(bytes).digest('hex');
11
+
12
+ function readJson(file) {
13
+ const bytes = fs.readFileSync(file);
14
+ return { value: JSON.parse(bytes), digest: sha256(bytes) };
15
+ }
16
+
17
+ function requireExactSet(actual, expected, label) {
18
+ const got = [...actual].sort();
19
+ const want = [...expected].sort();
20
+ if (canonicalJson(got) !== canonicalJson(want)) {
21
+ throw new Error(`${label} differs: expected ${want.join(', ')}, got ${got.join(', ')}`);
22
+ }
23
+ }
24
+
25
+ function passLeaf({ name, sha, payloadId, source, receiptSha256, ...details }) {
26
+ const validated = /^[a-f0-9]{64}$/.test(receiptSha256 || '');
27
+ return {
28
+ name,
29
+ sha,
30
+ payloadId,
31
+ status: validated ? 'completed' : 'failed',
32
+ conclusion: validated ? 'success' : 'failure',
33
+ verdict: validated ? 'PASS' : 'FAIL',
34
+ source,
35
+ receiptSha256,
36
+ ...details,
37
+ };
38
+ }
39
+
40
+ export function buildPrepublicationEvidence({
41
+ sha,
42
+ version,
43
+ runId,
44
+ manifestFile,
45
+ payloadProofFile,
46
+ hostFile,
47
+ ciFile,
48
+ integrationFile,
49
+ uxFiles,
50
+ strangerFile,
51
+ }) {
52
+ if (!/^[a-f0-9]{40}$/.test(sha || '') || !/^\d+\.\d+\.\d+$/.test(version || '') || !Number.isSafeInteger(runId) || runId <= 0) {
53
+ throw new Error('prepublication identity is malformed');
54
+ }
55
+
56
+ const manifestBytes = fs.readFileSync(manifestFile);
57
+ const manifest = JSON.parse(manifestBytes);
58
+ const payload = JSON.parse(fs.readFileSync(payloadProofFile));
59
+ if (manifest.candidateSha !== sha || manifest.version !== version || manifest.tag !== `v${version}`) {
60
+ throw new Error('candidate manifest identity mismatch');
61
+ }
62
+ if (!/^[a-f0-9]{64}$/.test(payload.payloadId || '')) throw new Error('payload identity is malformed');
63
+
64
+ const host = readJson(hostFile);
65
+ if (host.value.schemaVersion !== 1 || host.value.sha !== sha || host.value.payloadId !== payload.payloadId
66
+ || !/^[a-f0-9]{64}$/.test(host.value.artifactSha256 || '') || host.value.leaves?.length !== 3) {
67
+ throw new Error('candidate host evidence identity or shape mismatch');
68
+ }
69
+ requireExactSet(host.value.leaves.map(({ name }) => name), ['claude-only', 'codex-only', 'dual-host'], 'candidate host leaves');
70
+ for (const leaf of host.value.leaves) {
71
+ const grounding = leaf.grounding;
72
+ const grounded = grounding && ['repo', 'path', 'file', 'storedPath']
73
+ .every((field) => typeof grounding[field] === 'string' && grounding[field].trim());
74
+ if (leaf.sha !== sha || leaf.payloadId !== payload.payloadId || leaf.status !== 'completed'
75
+ || leaf.conclusion !== 'success' || leaf.verdict !== 'PASS'
76
+ || leaf.functionalSearch !== true || leaf.searchExit !== 0 || !grounded
77
+ || leaf.artifactSha256 !== host.value.artifactSha256) {
78
+ throw new Error(`candidate host leaf is not an exact PASS: ${leaf.name || '(missing)'}`);
79
+ }
80
+ }
81
+
82
+ const ci = readJson(ciFile);
83
+ const requiredCiJobs = ['candidate-preflight', 'check', 'windows-unit', 'warm-brain', 'release-qe'];
84
+ if (ci.value.schemaVersion !== 1 || ci.value.kind !== 'ruvnet-brain-candidate-ci-evidence'
85
+ || ci.value.sourceSha !== sha || ci.value.version !== version || ci.value.payloadId !== payload.payloadId
86
+ || ci.value.payloadManifestSha256 !== sha256(manifestBytes) || ci.value.workflow !== 'ci'
87
+ || ci.value.runId !== runId || ci.value.verdict !== 'PASS' || ci.value.skipped !== 0 || ci.value.unknown !== 0) {
88
+ throw new Error('candidate CI receipt identity or verdict mismatch');
89
+ }
90
+ requireExactSet(ci.value.jobs?.map(({ name }) => name) || [], requiredCiJobs, 'candidate CI jobs');
91
+ if (ci.value.jobs.some(({ conclusion }) => conclusion !== 'success')) throw new Error('candidate CI receipt contains a non-success job');
92
+
93
+ const integration = readJson(integrationFile);
94
+ if (integration.value.schemaVersion !== 1 || integration.value.kind !== 'ruvnet-brain-integration-evidence'
95
+ || integration.value.sourceSha !== sha || integration.value.workflow !== 'integration-linux'
96
+ || integration.value.runId !== runId || integration.value.verdict !== 'PASS'
97
+ || integration.value.total <= 0
98
+ || integration.value.passed + Number(integration.value.todo || 0) + Number(integration.value.skipped || 0) !== integration.value.total
99
+ || integration.value.failed !== 0
100
+ || !Array.isArray(integration.value.skippedTests)
101
+ || integration.value.skippedTests.length !== integration.value.skipped
102
+ || integration.value.skippedTests.some((name) => typeof name !== 'string' || !name.trim())) {
103
+ throw new Error('integration receipt is not an exact, fully accounted PASS');
104
+ }
105
+ if (integration.value.exclusionPolicy !== EXCLUSION_POLICY
106
+ || !Array.isArray(integration.value.todoTests)
107
+ || integration.value.todoTests.length !== Number(integration.value.todo || 0)
108
+ || !/^[a-f0-9]{64}$/.test(integration.value.exclusionsSha256 || '')) {
109
+ throw new Error('integration receipt exclusions are not governed and fully enumerated');
110
+ }
111
+
112
+ if (!Array.isArray(uxFiles) || uxFiles.length !== 3) throw new Error('exactly three UX receipts are required');
113
+ const ux = uxFiles.map(readJson);
114
+ requireExactSet(ux.map(({ value }) => value.platform), ['darwin', 'linux', 'win32'], 'UX platforms');
115
+ for (const receipt of ux) {
116
+ const value = receipt.value;
117
+ if (value.schemaVersion !== 1 || value.suite !== 'ruvnet-brain-ux-qe' || value.gitSha !== sha
118
+ || value.pass !== true || !Array.isArray(value.hardFailures) || value.hardFailures.length !== 0) {
119
+ throw new Error(`UX receipt is not an exact PASS: ${value.platform || '(missing)'}`);
120
+ }
121
+ }
122
+ const uxDigest = sha256(Buffer.from(canonicalJson(ux.map(({ value, digest }) => ({ platform: value.platform, digest }))
123
+ .sort((a, b) => a.platform.localeCompare(b.platform)))));
124
+
125
+ const stranger = readJson(strangerFile);
126
+ const strangerJobs = ['ubuntu', 'macos', 'windows-gitbash', 'windows-powershell', 'hostile'];
127
+ if (stranger.value.schemaVersion !== 1 || stranger.value.sha !== sha || stranger.value.payloadId !== payload.payloadId
128
+ || Number(stranger.value.sourceCiRunId) !== runId || Number(stranger.value.strangerRunId) !== runId
129
+ || stranger.value.verdict !== 'PASS') {
130
+ throw new Error('stranger receipt identity or verdict mismatch');
131
+ }
132
+ requireExactSet(stranger.value.jobs || [], strangerJobs, 'stranger jobs');
133
+
134
+ const common = { sha, payloadId: payload.payloadId, runId };
135
+ const leaves = [
136
+ passLeaf({ ...common, name: 'source-quality', source: 'candidate-ci-receipt:check', receiptSha256: ci.digest }),
137
+ passLeaf({ ...common, name: 'ux-qe', source: 'ux-qe-receipts:darwin,linux,win32', receiptSha256: uxDigest }),
138
+ passLeaf({ ...common, name: 'release-qe', source: 'candidate-ci-receipt:release-qe', receiptSha256: ci.digest }),
139
+ passLeaf({ ...common, name: 'integration-linux', source: 'integration-receipt', receiptSha256: integration.digest,
140
+ skipped: integration.value.skipped, todo: integration.value.todo }),
141
+ passLeaf({ ...common, name: 'stranger-linux', source: 'stranger-receipt:ubuntu', receiptSha256: stranger.digest }),
142
+ passLeaf({ ...common, name: 'stranger-macos', source: 'stranger-receipt:macos', receiptSha256: stranger.digest }),
143
+ passLeaf({ ...common, name: 'stranger-windows', source: 'stranger-receipt:windows', receiptSha256: stranger.digest }),
144
+ ...host.value.leaves.map((leaf) => ({ ...leaf, runId, receiptSha256: host.digest })),
145
+ ];
146
+ return { leaves, envelope: aggregateEvidence({ sha, payloadId: payload.payloadId, leaves }) };
147
+ }
148
+
149
+ const isCli = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
150
+ if (isCli) {
151
+ const arg = (name) => {
152
+ const index = process.argv.indexOf(name);
153
+ return index >= 0 ? process.argv[index + 1] : null;
154
+ };
155
+ const uxDir = path.resolve(arg('--ux-dir'));
156
+ const uxFiles = fs.readdirSync(uxDir).filter((name) => name.endsWith('.json')).sort().map((name) => path.join(uxDir, name));
157
+ const result = buildPrepublicationEvidence({
158
+ sha: arg('--sha'),
159
+ version: arg('--version'),
160
+ runId: Number(arg('--run-id')),
161
+ manifestFile: path.resolve(arg('--manifest')),
162
+ payloadProofFile: path.resolve(arg('--payload-proof')),
163
+ hostFile: path.resolve(arg('--hosts')),
164
+ ciFile: path.resolve(arg('--ci')),
165
+ integrationFile: path.resolve(arg('--integration')),
166
+ uxFiles,
167
+ strangerFile: path.resolve(arg('--stranger')),
168
+ });
169
+ const leavesOut = path.resolve(arg('--leaves-out'));
170
+ const envelopeOut = path.resolve(arg('--envelope-out'));
171
+ fs.writeFileSync(leavesOut, `${JSON.stringify(result.leaves, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
172
+ fs.writeFileSync(envelopeOut, `${canonicalJson(result.envelope)}\n`, { flag: 'wx', mode: 0o600 });
173
+ process.stdout.write(`${JSON.stringify({ verdict: result.envelope.verdict, evidenceDigest: result.envelope.evidenceDigest })}\n`);
174
+ }
@@ -4,6 +4,7 @@
4
4
  import crypto from 'node:crypto';
5
5
  import fs from 'node:fs';
6
6
  import path from 'node:path';
7
+ import { pathToFileURL } from 'node:url';
7
8
  import { evaluateCandidateReceipt } from './release-proof.mjs';
8
9
  import { getVersion } from './version.mjs';
9
10
 
@@ -13,12 +14,27 @@ export const PROTECTED_VERSION = getVersion();
13
14
  const hex = (value, length) => new RegExp(`^[0-9a-f]{${length}}$`).test(String(value || ''));
14
15
  const digestOf = (receipt) => String(receipt?.artifact?.sha256 || '').replace(/^sha256:/, '');
15
16
 
16
- export function validateProtectedPublishInvocation({ root = process.cwd(), env = process.env } = {}) {
17
+ export function validatePublishableVersion(version) {
18
+ return /^\d+\.\d+\.\d+$/.test(String(version || ''))
19
+ ? []
20
+ : ['development versions cannot be published'];
21
+ }
22
+
23
+ export function validateProtectedPublishEnvironment(env = process.env) {
17
24
  const failures = [];
18
- let releaseMode = 'unknown';
19
- if (env.GITHUB_ACTIONS !== 'true' || env.GITHUB_WORKFLOW !== PROTECTED_WORKFLOW) {
20
- failures.push('publication is allowed only inside the protected-release GitHub workflow');
25
+ if (env.GITHUB_ACTIONS !== 'true'
26
+ || env.GITHUB_WORKFLOW !== PROTECTED_WORKFLOW
27
+ || env.GITHUB_REPOSITORY !== 'stuinfla/ruvnet-brain'
28
+ || env.GITHUB_EVENT_NAME !== 'workflow_dispatch'
29
+ || env.GITHUB_REF_PROTECTED !== 'true') {
30
+ failures.push('publication is allowed only inside the protected-release GitHub workflow on a protected workflow_dispatch ref');
21
31
  }
32
+ return failures;
33
+ }
34
+
35
+ export function validateProtectedPublishInvocation({ root = process.cwd(), env = process.env } = {}) {
36
+ const failures = [...validateProtectedPublishEnvironment(env)];
37
+ let releaseMode = 'unknown';
22
38
 
23
39
  const expectedSha = String(env.RUVNET_EXPECTED_SHA || '');
24
40
  const expectedDigest = String(env.RUVNET_EXPECTED_ARTIFACT_SHA256 || '').replace(/^sha256:/, '');
@@ -26,6 +42,7 @@ export function validateProtectedPublishInvocation({ root = process.cwd(), env =
26
42
  if (!hex(expectedSha, 40)) failures.push('expected candidate SHA is missing or malformed');
27
43
  if (!hex(expectedDigest, 64)) failures.push('expected artifact digest is missing or malformed');
28
44
  if (expectedVersion !== PROTECTED_VERSION) failures.push(`release generation must be ${PROTECTED_VERSION}`);
45
+ failures.push(...validatePublishableVersion(expectedVersion));
29
46
 
30
47
  const receiptRelative = String(env.RUVNET_CANDIDATE_RECEIPT || '');
31
48
  const evidenceRoot = path.resolve(root, 'release-evidence');
@@ -74,3 +91,14 @@ export function validateProtectedPublishInvocation({ root = process.cwd(), env =
74
91
  failures,
75
92
  };
76
93
  }
94
+
95
+ if (process.argv[1] && pathToFileURL(path.resolve(process.argv[1])).href === import.meta.url) {
96
+ const result = validateProtectedPublishInvocation();
97
+ if (result.verdict !== 'PASS') {
98
+ console.error('[protected-release] publication refused:');
99
+ for (const failure of result.failures) console.error(` - ${failure}`);
100
+ process.exitCode = 1;
101
+ } else {
102
+ console.log('[protected-release] exact candidate publication authority verified');
103
+ }
104
+ }
@@ -133,7 +133,7 @@ const identityOf = (leaf) => ({
133
133
  releaseTransactionId: leaf.releaseTransactionId,
134
134
  });
135
135
 
136
- export function buildPublicVerificationAggregate(leaves, reviews) {
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
  }
@@ -159,32 +159,20 @@ export function buildPublicVerificationAggregate(leaves, reviews) {
159
159
  deltaCases: delta.length,
160
160
  deltaHits: delta.filter(({ retrievalHit }) => retrievalHit === true).length,
161
161
  recallAt10: retrievalCases.filter(({ retrievalHit }) => retrievalHit === true).length / retrievalCases.length,
162
- deltaCitationRate: delta.filter(({ retrievalHit, citationResolved }) => retrievalHit === true && citationResolved === true).length / delta.length,
162
+ // A release with no corpus delta has no delta citations to score; the sealed
163
+ // retrieval plan carries noDelta=true and still requires the legacy cohort.
164
+ deltaCitationRate: delta.length === 0 ? 1 : delta.filter(({ retrievalHit, citationResolved }) => retrievalHit === true && citationResolved === true).length / delta.length,
163
165
  skipped: leaves.reduce((sum, leaf) => sum + leaf.skipped + leaf.retrieval.metrics.skipped, 0),
164
166
  unknown: leaves.reduce((sum, leaf) => sum + leaf.unknown + leaf.retrieval.metrics.unknown, 0),
165
167
  };
166
168
  if (metrics.recallAt10 < 0.98 || metrics.deltaCitationRate !== 1 || metrics.skipped || metrics.unknown) {
167
169
  throw new Error('public verification aggregate retrieval acceptance failed');
168
170
  }
169
- if (!Array.isArray(reviews) || reviews.length !== REQUIRED_REVIEW_MODELS.length) {
170
- throw new Error('public verification aggregate requires exactly two independent reviews');
171
- }
172
- reviews.forEach(validateIndependentReviewReceipt);
173
- const byModel = new Map(reviews.map((review) => [review.model, review]));
174
- if (byModel.size !== REQUIRED_REVIEW_MODELS.length || REQUIRED_REVIEW_MODELS.some((model) => !byModel.has(model))) {
175
- throw new Error('required independent review models are missing or duplicated');
176
- }
177
- const firstReview = byModel.get(REQUIRED_REVIEW_MODELS[0]);
178
- const semanticIdentity = validateReviewOracleAgainstPlan(firstReview, first.retrievalPlan);
179
- for (const model of REQUIRED_REVIEW_MODELS) {
180
- const review = byModel.get(model);
181
- const semantic = validateReviewOracleAgainstPlan(review, first.retrievalPlan);
182
- if (review.sourceSha !== identity.sourceSha || review.artifactSha256 !== identity.artifactSha256
183
- || review.payloadId !== identity.payloadId || review.productContractSha256 !== firstReview.productContractSha256
184
- || review.rubricSha256 !== firstReview.rubricSha256
185
- || semantic.recordSetSha256 !== semanticIdentity.recordSetSha256) {
186
- throw new Error(`${model} review identity, rubric, or oracle identity differs`);
187
- }
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');
188
176
  }
189
177
  return {
190
178
  schemaVersion: 1,
@@ -192,17 +180,11 @@ export function buildPublicVerificationAggregate(leaves, reviews) {
192
180
  identity,
193
181
  coverage: first.coverage,
194
182
  lanes: required.map((lane) => ({ lane, leafSha256: byLane.get(lane).leafSha256 })),
195
- reviews: REQUIRED_REVIEW_MODELS.map((model) => ({ model, receiptSha256: byModel.get(model).receiptSha256,
196
- score: byModel.get(model).score })),
197
- evidence: { leaves: required.map((lane) => structuredClone(byLane.get(lane))),
198
- reviews: REQUIRED_REVIEW_MODELS.map((model) => structuredClone(byModel.get(model))) },
199
- productContractSha256: firstReview.productContractSha256,
200
- rubricSha256: firstReview.rubricSha256,
183
+ evidence: { leaves: required.map((lane) => structuredClone(byLane.get(lane))) },
201
184
  retrievalOracle: {
202
- oracleReceiptSha256: semanticIdentity.oracleReceiptSha256,
203
- queryStoreSetSha256: semanticIdentity.queryStoreSetSha256,
204
- recordCount: semanticIdentity.recordCount,
205
- recordSetSha256: semanticIdentity.recordSetSha256,
185
+ oracleReceiptSha256: oracle.receiptSha256,
186
+ queryStoreSetSha256: oracle.queryStoreSetSha256,
187
+ recordCount: first.retrievalPlan.denominator.eligibleStores.length,
206
188
  },
207
189
  metrics,
208
190
  verdict: 'PASS',
@@ -210,8 +192,8 @@ export function buildPublicVerificationAggregate(leaves, reviews) {
210
192
  };
211
193
  }
212
194
 
213
- export function signPublicVerificationAggregate({ leaves, reviews }, privateKey) {
214
- const payload = buildPublicVerificationAggregate(leaves, reviews);
195
+ export function signPublicVerificationAggregate({ leaves }, privateKey) {
196
+ const payload = buildPublicVerificationAggregate(leaves);
215
197
  const aggregateSha256 = digest(payload);
216
198
  const signed = { ...payload, aggregateSha256 };
217
199
  return { ...signed, signature: crypto.sign(null, Buffer.from(canonicalJson(signed)), privateKey).toString('base64') };
@@ -225,10 +207,10 @@ export function verifyPublicVerificationAggregate(aggregate, publicKey, expected
225
207
  }
226
208
  const payload = aggregatePayload(aggregate);
227
209
  if (digest(payload) !== aggregate.aggregateSha256) throw new Error('public verification aggregate digest mismatch');
228
- if (!Array.isArray(aggregate.evidence?.leaves) || !Array.isArray(aggregate.evidence?.reviews)) {
229
- throw new Error('public verification aggregate lacks raw leaf or review evidence');
210
+ if (!Array.isArray(aggregate.evidence?.leaves)) {
211
+ throw new Error('public verification aggregate lacks raw leaf evidence');
230
212
  }
231
- const rebuilt = buildPublicVerificationAggregate(aggregate.evidence.leaves, aggregate.evidence.reviews);
213
+ const rebuilt = buildPublicVerificationAggregate(aggregate.evidence.leaves);
232
214
  if (canonicalJson(rebuilt) !== canonicalJson(payload)) {
233
215
  throw new Error('public verification aggregate differs from rebuilt raw evidence');
234
216
  }
@@ -247,17 +229,10 @@ export function verifyPublicVerificationAggregate(aggregate, publicKey, expected
247
229
  || aggregate.coverage.gistCurrent !== aggregate.coverage.gistTotal) {
248
230
  throw new Error('public verification aggregate evidence is incomplete');
249
231
  }
250
- if (aggregate.reviews?.length !== REQUIRED_REVIEW_MODELS.length
251
- || REQUIRED_REVIEW_MODELS.some((model) => !aggregate.reviews.some((row) => row.model === model
252
- && Number.isInteger(row.score) && row.score >= 95 && HEX64.test(String(row.receiptSha256 || ''))))
253
- || !HEX64.test(String(aggregate.productContractSha256 || '')) || !HEX64.test(String(aggregate.rubricSha256 || ''))) {
254
- throw new Error('public verification aggregate reviews are incomplete');
255
- }
256
232
  if (aggregate.retrievalOracle?.oracleReceiptSha256 !== aggregate.evidence.leaves[0].retrievalPlan.oracle.receiptSha256
257
233
  || aggregate.retrievalOracle?.queryStoreSetSha256 !== aggregate.evidence.leaves[0].retrievalPlan.oracle.queryStoreSetSha256
258
- || aggregate.retrievalOracle?.recordCount !== aggregate.evidence.leaves[0].retrievalPlan.denominator.eligibleStores.length
259
- || !HEX64.test(String(aggregate.retrievalOracle?.recordSetSha256 || ''))) {
260
- 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');
261
236
  }
262
237
  if (expectedIdentity && canonicalJson(aggregate.identity) !== canonicalJson(expectedIdentity)) {
263
238
  throw new Error('public verification aggregate identity differs from the release transaction');
@@ -270,13 +245,13 @@ function parseCliArgs(argv) {
270
245
  for (let index = 0; index < argv.length; index += 2) {
271
246
  const flag = argv[index];
272
247
  const value = argv[index + 1];
273
- if (!['--lanes', '--reviews', '--out'].includes(flag) || !value) {
274
- throw new Error('usage: public-verification-aggregate.mjs --lanes <dir> --reviews <dir> --out <file>');
248
+ if (!['--lanes', '--out'].includes(flag) || !value) {
249
+ throw new Error('usage: public-verification-aggregate.mjs --lanes <dir> --out <file>');
275
250
  }
276
251
  parsed[flag.slice(2)] = value;
277
252
  }
278
- if (!parsed.lanes || !parsed.reviews || !parsed.out || Object.keys(parsed).length !== 3) {
279
- throw new Error('usage: public-verification-aggregate.mjs --lanes <dir> --reviews <dir> --out <file>');
253
+ if (!parsed.lanes || !parsed.out || Object.keys(parsed).length !== 2) {
254
+ throw new Error('usage: public-verification-aggregate.mjs --lanes <dir> --out <file>');
280
255
  }
281
256
  return parsed;
282
257
  }
@@ -312,12 +287,11 @@ function readLaneLeaves(directory) {
312
287
  return PUBLIC_VERIFICATION_OS.flatMap((osName) => byOs.get(osName));
313
288
  }
314
289
 
315
- export function generatePublicVerificationAggregate({ lanesDirectory, reviewsDirectory, outputFile, privateKey }) {
290
+ export function generatePublicVerificationAggregate({ lanesDirectory, outputFile, privateKey }) {
316
291
  if (!privateKey) throw new Error('RUVNET_SIGNING_KEY is required');
317
292
  if (fs.existsSync(outputFile)) throw new Error(`refusing to overwrite existing aggregate: ${outputFile}`);
318
293
  const leaves = readLaneLeaves(lanesDirectory);
319
- const reviews = readExactJsonFiles(reviewsDirectory, REQUIRED_REVIEW_MODELS.length, 'review');
320
- const aggregate = signPublicVerificationAggregate({ leaves, reviews }, privateKey);
294
+ const aggregate = signPublicVerificationAggregate({ leaves }, privateKey);
321
295
  fs.writeFileSync(outputFile, `${JSON.stringify(aggregate, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
322
296
  return aggregate;
323
297
  }
@@ -326,12 +300,11 @@ async function main() {
326
300
  const options = parseCliArgs(process.argv.slice(2));
327
301
  const aggregate = generatePublicVerificationAggregate({
328
302
  lanesDirectory: options.lanes,
329
- reviewsDirectory: options.reviews,
330
303
  outputFile: options.out,
331
304
  privateKey: process.env.RUVNET_SIGNING_KEY,
332
305
  });
333
306
  process.stdout.write(`${JSON.stringify({ ok: true, aggregateSha256: aggregate.aggregateSha256,
334
- leaves: aggregate.metrics.leaves, reviews: aggregate.reviews.length })}\n`);
307
+ leaves: aggregate.metrics.leaves })}\n`);
335
308
  }
336
309
 
337
310
  if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
@@ -148,7 +148,7 @@ function validateCandidate({ root, bundleFile, packageFile }) {
148
148
  const inventory = validatePublicInventory({ assetsDir: root, coverage, ledger });
149
149
  if (inventory.publicStores.length !== storeCount
150
150
  || inventory.partitionSha256 !== coverage.publicInventoryPartitionSha256) {
151
- fail('candidate public inventory partition differs from release coverage');
151
+ fail(`candidate public inventory partition differs from release coverage (inventory=${inventory.partitionSha256}, coverage=${coverage.publicInventoryPartitionSha256}, stores=${inventory.publicStores.length}/${storeCount})`);
152
152
  }
153
153
  const coverageIdentity = { sha256: crypto.createHash('sha256').update(coverageBytes).digest('hex'), bytes: coverageBytes.length };
154
154
  const candidate = {
@@ -412,10 +412,12 @@ export async function createPublicVerificationInputs({ baselineBundle, candidate
412
412
  stores: baselineStores, storeCount: baselineStores.length };
413
413
  const { value: queryEvidence } = readJson(oraclePath, 'independent strict-ancestor query oracle');
414
414
  validateRetrievalQueryEvidence(queryEvidence);
415
- verifyQueryOracleSource(queryEvidence, candidateResult.candidate.sourceSha, { cwd: path.resolve(repo) });
415
+ verifyQueryOracleSource(queryEvidence, candidateResult.candidate.sourceSha, {
416
+ cwd: path.resolve(repo), allowSquashedSource: true,
417
+ });
416
418
  const plan = buildRetrievalCanaryPlan({ coverage: candidateResult.coverage, baseline,
417
419
  candidate: candidateResult.candidate, coverageIdentity: candidateResult.coverageIdentity,
418
- queryEvidence, assetsDir: candidateTree.root });
420
+ queryEvidence, assetsDir: candidateTree.root, allowNoDelta: true });
419
421
  writeExactOutputs(outDir, {
420
422
  [baselineMode === 'observed' ? 'baseline-observation-receipt.json' : 'baseline-verification-receipt.json']: baselineProof.bytes,
421
423
  'COVERAGE.json': candidateResult.coverageBytes,
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env node
2
+ // Canonical bounded QA runner. Local and CI use the same lanes, command order, timeout policy,
3
+ // and machine-readable receipt; a human log is never the release verdict.
4
+ import fs from 'node:fs';
5
+ import path from 'node:path';
6
+ import { execFileSync, spawn } from 'node:child_process';
7
+ import { fileURLToPath } from 'node:url';
8
+
9
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
10
+ const release = process.argv.includes('--release');
11
+ const timeoutMs = Number(process.env.QA_TIMEOUT_MS || (release ? 15 * 60_000 : 8 * 60_000));
12
+ const lanes = [
13
+ ['version', 'npm', ['run', 'version:check']],
14
+ ['convergence', 'npm', ['run', 'convergence:check']],
15
+ ['execution-policy', 'npm', ['run', 'execution-policy:check']],
16
+ ['docs', 'npm', ['run', 'doc:currency']],
17
+ ['wiring', 'npm', ['run', 'wired:check']],
18
+ ['substitution', 'npm', ['run', 'substitution:check']],
19
+ ['catalog', 'npm', ['run', 'catalog:verify']],
20
+ ['contract', 'npx', ['vitest', 'run',
21
+ 'tests/unit/brain-off.test.mjs',
22
+ 'tests/unit/hook-hardening.test.mjs',
23
+ 'tests/unit/hook-battery.test.mjs',
24
+ 'tests/unit/session-start-core-parity.test.mjs',
25
+ 'tests/unit/lesson-gate.test.mjs',
26
+ 'tests/unit/wired-check.test.mjs',
27
+ 'tests/unit/no-restated-truth.test.mjs',
28
+ 'tests/unit/verify-citation.test.mjs',
29
+ 'tests/unit/eval-brain-gate.test.mjs',
30
+ 'tests/unit/distill-project.test.mjs',
31
+ 'tests/unit/record-lesson.test.mjs',
32
+ 'tests/unit/learn-capture-project-root.test.mjs',
33
+ 'tests/unit/codex-claude-hook-parity.test.mjs',
34
+ 'tests/unit/brain-stamp-resolve-built-from-sha.test.mjs',
35
+ 'tests/unit/qa-runner-concurrency.test.mjs',
36
+ 'tests/unit/execution-preflight.test.mjs',
37
+ '--reporter=dot']],
38
+ ['mesh', 'npm', ['run', 'test:mesh']],
39
+ ['plugin', 'npm', ['test']],
40
+ ...(release ? [['mutation', 'npm', ['run', 'test:mutation']], ['claims', 'npm', ['run', 'claims:verify']], ['integration', 'npm', ['run', 'test:integration']]] : []),
41
+ ];
42
+ const receiptDir = path.join(root, '.qa');
43
+ fs.mkdirSync(receiptDir, { recursive: true });
44
+ const started = new Date().toISOString();
45
+ const sha = (() => {
46
+ try { return execFileSync('git', ['rev-parse', 'HEAD'], { cwd: root, encoding: 'utf8' }).trim(); }
47
+ catch { return null; }
48
+ })();
49
+ const run = ([name, command, args]) => new Promise((resolve) => {
50
+ const begin = Date.now();
51
+ const child = spawn(command, args, { cwd: root, env: process.env, stdio: ['ignore', 'pipe', 'pipe'], shell: false });
52
+ let stdout = '', stderr = '', timedOut = false;
53
+ child.stdout.on('data', (chunk) => { stdout += chunk; process.stdout.write(chunk); });
54
+ child.stderr.on('data', (chunk) => { stderr += chunk; process.stderr.write(chunk); });
55
+ const timer = setTimeout(() => { timedOut = true; child.kill('SIGTERM'); setTimeout(() => child.kill('SIGKILL'), 2000).unref(); }, timeoutMs);
56
+ child.on('close', (code, signal) => {
57
+ clearTimeout(timer);
58
+ resolve({ name, command: [command, ...args].join(' '), status: timedOut ? 'TIMEOUT' : code === 0 ? 'PASS' : 'FAIL', exitCode: code, signal, elapsedMs: Date.now() - begin, stdoutTail: stdout.slice(-4000), stderrTail: stderr.slice(-4000) });
59
+ });
60
+ });
61
+ // Every lane is independent. Run the complete set concurrently so one failure does not hide
62
+ // later failures and force a serial fix-rerun cycle. The aggregate receipt remains the single
63
+ // verdict, while each lane keeps its own evidence for targeted repair.
64
+ const results = await Promise.all(lanes.map(async (lane) => {
65
+ const result = await run(lane);
66
+ fs.writeFileSync(path.join(receiptDir, `${result.name}.json`), `${JSON.stringify({ schema: 'ruvnet-brain.qa.lane', sha, ...result }, null, 2)}\n`);
67
+ return result;
68
+ }));
69
+ const receipt = { schema: 'ruvnet-brain.qa.aggregate', contract: release ? 'release' : 'pr', sha, started, ended: new Date().toISOString(), status: results.every((r) => r.status === 'PASS') && results.length === lanes.length ? 'PASS' : 'FAIL', requiredLanes: lanes.map(([name]) => name), results };
70
+ fs.writeFileSync(path.join(receiptDir, 'aggregate.json'), `${JSON.stringify(receipt, null, 2)}\n`);
71
+ console.log(JSON.stringify({ status: receipt.status, sha, lanes: results.map(({ name, status, elapsedMs }) => ({ name, status, elapsedMs })) }));
72
+ process.exit(receipt.status === 'PASS' ? 0 : 1);