ruvnet-brain 4.3.9 → 4.3.11

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 (87) hide show
  1. package/README.md +26 -9
  2. package/bin/install.mjs +646 -366
  3. package/bin/nightly-refresh.mjs +115 -0
  4. package/kb/brain-profile.mjs +118 -32
  5. package/kb/lifecycle-evidence-retention.mjs +237 -0
  6. package/kb/refresh-run.mjs +367 -0
  7. package/kb/retrieval-result.mjs +39 -0
  8. package/kb/update-storage-transaction.mjs +439 -0
  9. package/package.json +12 -3
  10. package/plugin/.claude-plugin/plugin.json +1 -1
  11. package/plugin/.codex-plugin/plugin.json +1 -1
  12. package/plugin/hooks/codex-hooks.json +2 -165
  13. package/plugin/hooks/hook-contracts.json +4 -65
  14. package/plugin/hooks/hooks.json +2 -208
  15. package/plugin/scripts/adr-currency-gate.mjs +29 -19
  16. package/plugin/scripts/capability-registry.mjs +10 -83
  17. package/plugin/scripts/codex-hook-adapter.mjs +62 -9
  18. package/plugin/scripts/codex-hook-wrapper.mjs +19 -1
  19. package/plugin/scripts/continuation-gate.mjs +30 -28
  20. package/plugin/scripts/continuation-objective.mjs +34 -0
  21. package/plugin/scripts/development-maintenance.mjs +49 -0
  22. package/plugin/scripts/hook-shim.mjs +3 -0
  23. package/plugin/scripts/learn-flush.mjs +6 -1
  24. package/plugin/scripts/lesson-gate.mjs +5 -2
  25. package/plugin/scripts/lesson-presentation.mjs +5 -0
  26. package/plugin/scripts/md-stamp.mjs +109 -6
  27. package/plugin/scripts/memory-doctor.mjs +3 -7
  28. package/plugin/scripts/nightly-controller.mjs +9 -27
  29. package/plugin/scripts/nightly-scheduler.mjs +494 -0
  30. package/plugin/scripts/project-progression-contract.mjs +4 -4
  31. package/plugin/scripts/project-progression-store.mjs +35 -6
  32. package/plugin/scripts/ruflo-bin.mjs +20 -0
  33. package/plugin/scripts/session-snapshot-contract.mjs +6 -1
  34. package/plugin/scripts/version-bump-gate.sh +14 -2
  35. package/plugin/skills/release-proof/SKILL.md +6 -2
  36. package/plugin/skills/release-proof/references/receipt-contract.md +31 -2
  37. package/plugin/skills/release-proof/scripts/release-proof.mjs +11 -2
  38. package/plugin/skills/ruvnet-brain/PLAYBOOK.md +15 -23
  39. package/scripts/build-bundle.mjs +8 -0
  40. package/scripts/build-primer.mjs +2 -3
  41. package/scripts/candidate-host-evidence.mjs +59 -41
  42. package/scripts/ci/build-fixture-kb.mjs +59 -2
  43. package/scripts/ci/mutate-hook-timeout.mjs +24 -47
  44. package/scripts/ci/stranger-scenario.mjs +13 -28
  45. package/scripts/claims-verify.mjs +219 -21
  46. package/scripts/console-engine.mjs +4 -18
  47. package/scripts/console-runtime-identity.mjs +6 -0
  48. package/scripts/development-maintenance.mjs +47 -0
  49. package/scripts/development-push-check.mjs +35 -0
  50. package/scripts/distill-project.mjs +24 -47
  51. package/scripts/doc-currency.mjs +91 -19
  52. package/scripts/git-hooks/pre-push +4 -162
  53. package/scripts/health-repair.mjs +22 -4
  54. package/scripts/hook-retirement-check.mjs +22 -0
  55. package/scripts/host-install-matrix.mjs +125 -61
  56. package/scripts/integration-evidence.mjs +11 -6
  57. package/scripts/learning-replay-fixture.mjs +23 -2
  58. package/scripts/nightly-package-observation.mjs +67 -0
  59. package/scripts/nightly-two-run-proof.mjs +489 -0
  60. package/scripts/nightly-watchdog.mjs +17 -2
  61. package/scripts/npm-invocation.mjs +20 -0
  62. package/scripts/prepublication-evidence.mjs +42 -6
  63. package/scripts/primer-grounding.mjs +20 -0
  64. package/scripts/product-integrity-contract.mjs +22 -2
  65. package/scripts/public-verification-abandon.mjs +101 -0
  66. package/scripts/public-verification-aggregate.mjs +61 -19
  67. package/scripts/public-verification-finalizer.mjs +8 -2
  68. package/scripts/public-verification-lane.mjs +64 -8
  69. package/scripts/publication-receipt.mjs +156 -50
  70. package/scripts/published-surface-probe.mjs +10 -2
  71. package/scripts/qa-contract.mjs +57 -0
  72. package/scripts/qa-lanes.mjs +40 -0
  73. package/scripts/qa-runner.mjs +54 -64
  74. package/scripts/qe/ux-suite.mjs +10 -34
  75. package/scripts/qualified-candidate-check.mjs +124 -0
  76. package/scripts/release-abort-stale.mjs +2 -2
  77. package/scripts/release-projection.mjs +20 -1
  78. package/scripts/release-qualification-contract.mjs +87 -0
  79. package/scripts/release-qualification.mjs +135 -0
  80. package/scripts/release-transaction.mjs +81 -1
  81. package/scripts/remedy-registry.mjs +6 -13
  82. package/scripts/retrieval-canary.mjs +91 -18
  83. package/scripts/selfcheck.mjs +12 -4
  84. package/scripts/snapshot-freshness.mjs +58 -0
  85. package/scripts/stack-sync.mjs +41 -43
  86. package/scripts/staged-host-verifier.mjs +63 -14
  87. package/scripts/wired-check.mjs +31 -2
@@ -0,0 +1,489 @@
1
+ #!/usr/bin/env node
2
+
3
+ import crypto from 'node:crypto';
4
+ import fs from 'node:fs';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import { spawnSync } from 'node:child_process';
8
+ import { fileURLToPath, pathToFileURL } from 'node:url';
9
+ import { assessLifecycleEvidence } from '../kb/lifecycle-evidence-retention.mjs';
10
+ import { managedStorageInventory } from '../kb/update-storage-transaction.mjs';
11
+ import { REQUIRED_REFRESH_PHASES, validateCurrentRunPhaseExecution, inspectRefreshOwner, refreshLockPath } from '../kb/refresh-run.mjs';
12
+ import { npmInvocation } from './npm-invocation.mjs';
13
+ import { packageTreeObservation, observeNpxPackage, validateNpxObservations } from './nightly-package-observation.mjs';
14
+ import { validateRefreshReceiptEnvelope } from '../plugin/scripts/nightly-scheduler.mjs';
15
+ import { validateCoverageDirectory, validateCoverageLedger } from '../plugin/scripts/coverage-integrity.mjs';
16
+
17
+ const PUBLIC_KEY = fs.readFileSync(new URL('../keys/ruvnet-brain-signing.pub.pem', import.meta.url), 'utf8');
18
+
19
+ const sha256 = (bytes) => crypto.createHash('sha256').update(bytes).digest('hex');
20
+ const sleep = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
21
+
22
+ function parseArgs(argv) {
23
+ const result = {};
24
+ for (let i = 0; i < argv.length; i += 1) {
25
+ const token = argv[i];
26
+ if (!token.startsWith('--') || !argv[i + 1] || argv[i + 1].startsWith('--')) {
27
+ throw new Error(`invalid argument: ${token}`);
28
+ }
29
+ result[token.slice(2)] = argv[++i];
30
+ }
31
+ return result;
32
+ }
33
+
34
+ function run(command, args, options = {}) {
35
+ const result = spawnSync(command, args, { encoding: 'utf8', ...options });
36
+ if (result.error || result.status !== 0) {
37
+ throw new Error(`${command} ${args.join(' ')} failed: ${result.error?.message
38
+ || String(result.stderr || result.stdout || `exit ${result.status}`).trim()}`);
39
+ }
40
+ return result;
41
+ }
42
+
43
+ function canonical(value) {
44
+ if (Array.isArray(value)) return value.map(canonical);
45
+ if (value && typeof value === 'object') return Object.fromEntries(Object.keys(value).sort()
46
+ .map((key) => [key, canonical(value[key])]));
47
+ return value;
48
+ }
49
+
50
+ function receiptDigest(receipt) {
51
+ return sha256(Buffer.from(JSON.stringify(canonical(receipt))));
52
+ }
53
+
54
+ export function stageExactBundle({ bundlePath, packageRoot } = {}) {
55
+ const sourcePath = path.resolve(bundlePath || '');
56
+ let sourceStat;
57
+ try { sourceStat = fs.lstatSync(sourcePath); } catch { /* handled below */ }
58
+ if (!sourcePath.endsWith('.zip') || !sourceStat?.isFile() || sourceStat.isSymbolicLink()) {
59
+ throw new Error('bundle must be a regular candidate bundle .zip file');
60
+ }
61
+ const packageDir = path.resolve(packageRoot || '');
62
+ if (!fs.lstatSync(packageDir).isDirectory()) throw new Error('installed package root is not a directory');
63
+ const bytes = fs.readFileSync(sourcePath);
64
+ const dist = path.join(packageDir, 'dist');
65
+ const stagedPath = path.join(dist, 'ruvnet-brain.zip');
66
+ fs.mkdirSync(dist, { recursive: false });
67
+ fs.copyFileSync(sourcePath, stagedPath, fs.constants.COPYFILE_EXCL);
68
+ return { sourcePath, stagedPath, bytes: bytes.length, sha256: sha256(bytes) };
69
+ }
70
+
71
+ export function removeEmptyInstallerScaffolds(kbDir) {
72
+ const parent = path.dirname(kbDir);
73
+ const prefix = `${path.basename(kbDir)}.install-preserved-`;
74
+ const removed = [];
75
+ for (const name of fs.readdirSync(parent)) {
76
+ if (!name.startsWith(prefix)) continue;
77
+ const file = path.join(parent, name);
78
+ const stat = fs.lstatSync(file);
79
+ if (stat.isDirectory() && !stat.isSymbolicLink() && fs.readdirSync(file).length === 0) {
80
+ fs.rmdirSync(file); removed.push(file);
81
+ }
82
+ }
83
+ return removed;
84
+ }
85
+
86
+ function observeUpdateLog(file) {
87
+ if (!fs.existsSync(file)) return { bytes: 0, sha256: sha256(Buffer.alloc(0)) };
88
+ const stat = fs.lstatSync(file);
89
+ if (!stat.isFile() || stat.isSymbolicLink()) throw new Error('native update log is not a regular file');
90
+ const bytes = fs.readFileSync(file);
91
+ return { bytes: bytes.length, sha256: sha256(bytes) };
92
+ }
93
+
94
+ function nightlyReceipts(brainHome, identity) {
95
+ const dir = path.join(brainHome, 'refresh-runs');
96
+ if (!fs.existsSync(dir)) return [];
97
+ return fs.readdirSync(dir).filter((name) => name.endsWith('.json')).sort().map((name) => {
98
+ const file = path.join(dir, name);
99
+ const stat = fs.lstatSync(file);
100
+ if (!stat.isFile() || stat.isSymbolicLink()) throw new Error(`untrusted refresh evidence: ${file}`);
101
+ const receipt = JSON.parse(fs.readFileSync(file, 'utf8'));
102
+ return { file, receipt };
103
+ }).filter(({ receipt }) => receipt.kind === 'ruvnet-brain-refresh-run'
104
+ && receipt.action === 'nightly' && receipt.schedulerIdentity === identity);
105
+ }
106
+
107
+ function refreshExecutionStopped(receipt, kbDir) {
108
+ if (inspectRefreshOwner(receipt.ownerToken) !== 'dead') return false;
109
+ const lock = refreshLockPath(kbDir);
110
+ const parent = path.dirname(lock);
111
+ return !fs.existsSync(lock) && (!fs.existsSync(parent) || !fs.readdirSync(parent)
112
+ .some((name) => name.startsWith(`${path.basename(lock)}.release-`)));
113
+ }
114
+
115
+ // Dependency seams here exercise orchestration without touching a developer's native scheduler.
116
+ // Only runNightlyTwoRunProof writes proof, and it supplies the real installed adapter and commands.
117
+ export async function triggerNativeRun({ scheduler, registration, platform, env, brainHome, kbDir,
118
+ timeoutMs, command = run, receipts = nightlyReceipts, pause = sleep, now = Date.now, stopped = refreshExecutionStopped }) {
119
+ const identity = registration.identity;
120
+ if (!/^com\.ruvnet\.brain-update\.proof-[A-Za-z0-9._-]+$/.test(identity || '')) {
121
+ throw new Error('native proof requires a unique proof scheduler identity');
122
+ }
123
+ if (!['darwin', 'linux', 'win32'].includes(platform)) throw new Error(`unsupported native scheduler platform: ${platform}`);
124
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) throw new Error('proof timeout must be a positive integer');
125
+ const options = { platform, env, brainHome, kbDir, identity, proofTick: platform === 'linux',
126
+ ...(platform === 'linux' ? { proofAt: now() + 60_000 } : {}) };
127
+ const before = scheduler.schedulerStatus(options);
128
+ if (before.state !== 'off') throw new Error(`proof scheduler identity is not absent: ${before.evidence}`);
129
+ const beforeIds = new Set(receipts(brainHome, identity).map(({ receipt }) => receipt.runId));
130
+ let attempted = false;
131
+ let removed = false;
132
+ const remove = () => {
133
+ const result = scheduler.removeScheduler(options);
134
+ if (!result.ok) throw new Error(`proof scheduler cleanup failed: ${result.why}`);
135
+ const status = scheduler.schedulerStatus(options);
136
+ if (status.state !== 'off') throw new Error(`proof scheduler absence unverified: ${status.evidence}`);
137
+ removed = true;
138
+ };
139
+ try {
140
+ attempted = true;
141
+ const installed = scheduler.installScheduler(registration, { ...options, pathValue: env.PATH });
142
+ if (!installed.ok) throw new Error(`native scheduler unavailable: ${installed.why}`);
143
+ const status = scheduler.schedulerStatus(options);
144
+ if (status.state !== 'on') throw new Error(`proof scheduler is not on: ${status.evidence}`);
145
+ if (platform === 'darwin') {
146
+ // Darwin has getuid, but the qualification suite also exercises the Darwin
147
+ // branch from Windows. Keep that simulation portable without changing the
148
+ // real launchctl identity on macOS.
149
+ const uid = typeof process.getuid === 'function' ? process.getuid() : 0;
150
+ command('launchctl', ['kickstart', '-k', `gui/${uid}/${identity}`]);
151
+ }
152
+ if (platform === 'win32') command('schtasks', ['/Run', '/TN', identity]);
153
+ // Cron itself must deliver the Linux trigger. Never execute its command directly as proof.
154
+ const startedAt = now();
155
+ let observedStart = false;
156
+ while (now() - startedAt < timeoutMs) {
157
+ const created = receipts(brainHome, identity)
158
+ .filter(({ receipt }) => !beforeIds.has(receipt.runId));
159
+ if (created.length > 1) throw new Error(`scheduler produced ${created.length} refresh runs; expected exactly one`);
160
+ if (created.length === 1) {
161
+ observedStart = true;
162
+ // Stop the minute cadence at the first receipt, including RUNNING, so long updates do
163
+ // not launch again. Removing a cron row does not kill its already running child.
164
+ if (platform === 'linux' && !removed) remove();
165
+ if (['SUCCEEDED', 'FAILED', 'ABANDONED'].includes(created[0].receipt.status)
166
+ && stopped(created[0].receipt, kbDir)) {
167
+ await pause(250); // Let the exited child's npm parent finish flushing its log.
168
+ return { ...created[0],
169
+ trigger: { kind: { darwin: 'launchctl-kickstart', linux: 'cron-tick', win32: 'schtasks-run' }[platform], identity } };
170
+ }
171
+ }
172
+ if (platform === 'linux' && !observedStart && now() - startedAt >= 120_000) {
173
+ throw new Error('cron did not start the owned proof job within two minutes; check that the cron service is running');
174
+ }
175
+ await pause(1000);
176
+ }
177
+ throw new Error(`timed out waiting for ${identity} terminal refresh receipt`);
178
+ } finally {
179
+ if (attempted && !removed) remove();
180
+ }
181
+ }
182
+
183
+ export function validateTwoRunEvidence({ first, second, inventoryBefore, inventoryAfterFirst,
184
+ inventoryAfterSecond, retention, validateEnvelope, identity }) {
185
+ const failures = [];
186
+ for (const [label, receipt] of [['first', first], ['second', second]]) {
187
+ const envelope = validateEnvelope(receipt);
188
+ if (!envelope.ok) failures.push(`${label} run: ${envelope.why}`);
189
+ const execution = validateCurrentRunPhaseExecution(receipt);
190
+ failures.push(...execution.failures.map((failure) => `${label} run: ${failure}`));
191
+ if (receipt.schedulerIdentity !== identity) failures.push(`${label} run scheduler identity differs`);
192
+ }
193
+ failures.push(...validateStorageEvidence({ first, second, inventoryBefore, inventoryAfterFirst, inventoryAfterSecond, retention }));
194
+ return { ok: failures.length === 0, failures };
195
+ }
196
+
197
+ function validateStorageEvidence({ first, second, inventoryBefore, inventoryAfterFirst, inventoryAfterSecond, retention }) {
198
+ const failures = [];
199
+ if (first.runId === second.runId) failures.push('two scheduler invocations reused one run ID');
200
+ if (!Number.isFinite(Date.parse(first.finishedAt)) || !Number.isFinite(Date.parse(second.startedAt))
201
+ || Date.parse(second.startedAt) < Date.parse(first.finishedAt)) failures.push('second run did not start after first completion');
202
+ if (!['applied', 'noop'].includes(first.terminalVerdict)) failures.push('first run did not converge');
203
+ if (second.terminalVerdict !== 'noop') failures.push('second run was not a no-op');
204
+ const secondDelta = second.detail?.storageDelta;
205
+ if (!secondDelta || secondDelta.redundantCopyCount !== 0
206
+ || !Number.isSafeInteger(secondDelta.additionalFullCorpusCopyDelta)
207
+ || secondDelta.additionalFullCorpusCopyDelta > 0) {
208
+ failures.push('second run did not prove a zero-redundancy storage delta');
209
+ }
210
+ for (const [label, inventory] of [['before', inventoryBefore], ['after first', inventoryAfterFirst],
211
+ ['after second', inventoryAfterSecond]]) {
212
+ if (inventory.additionalFullCorpusCopyCount !== 0) failures.push(`${label} inventory has redundant corpus copies`);
213
+ if (!Number.isSafeInteger(inventory.totalManagedBytes) || inventory.totalManagedBytes < 0) failures.push(`${label} inventory has no measured byte count`);
214
+ }
215
+ let logGrowth = 0;
216
+ if (retention.updateLog !== undefined) {
217
+ const { before, after } = retention.updateLog;
218
+ if (![before, after].every((row) => Number.isSafeInteger(row?.bytes) && row.bytes >= 0
219
+ && /^[a-f0-9]{64}$/.test(row.sha256 || '')) || !Number.isSafeInteger(retention.policy?.maxEvidenceBytes)
220
+ || retention.policy.maxEvidenceBytes <= 0 || after.bytes > retention.policy.maxEvidenceBytes) {
221
+ failures.push('native log byte measurements are invalid or exceed the evidence budget');
222
+ } else logGrowth = Math.max(0, after.bytes - before.bytes);
223
+ }
224
+ if (inventoryAfterSecond.totalManagedBytes > inventoryAfterFirst.totalManagedBytes
225
+ + Math.max(0, retention.after?.bytes - retention.before?.bytes) + logGrowth) {
226
+ failures.push('second no-op increased managed storage outside retained lifecycle evidence');
227
+ }
228
+ if (retention.withinBudget !== true || retention.unsafe?.length) failures.push('lifecycle evidence is not within policy');
229
+ if (![retention.before?.bytes, retention.after?.bytes].every((bytes) => Number.isSafeInteger(bytes) && bytes >= 0)) failures.push('lifecycle evidence byte measurements are missing');
230
+ return failures;
231
+ }
232
+
233
+ function validateInstalledRun(run, proof) {
234
+ const receipt = run.receipt;
235
+ const failures = [];
236
+ const envelope = validateRefreshReceiptEnvelope(receipt);
237
+ if (!envelope.ok) failures.push(envelope.why);
238
+ const projection = run.installedCoverage;
239
+ const coverage = JSON.parse(projection?.raw || 'null');
240
+ if (!coverage || projection.sha256 !== sha256(Buffer.from(projection.raw))) throw new Error('installed coverage bytes differ');
241
+ const ledger = validateCoverageLedger(coverage);
242
+ failures.push(...ledger.failures);
243
+ if (coverage.kind !== 'ruvnet-brain-release-coverage' || coverage.releaseIdentity?.sourceSnapshot !== proof.sourceSha
244
+ || coverage.releaseIdentity?.version !== proof.candidate.version) failures.push('installed ReleaseCoverage is not the exact candidate');
245
+ const start = Date.parse(receipt.startedAt), end = Date.parse(receipt.finishedAt);
246
+ if (!Number.isFinite(start) || !Number.isFinite(end) || end < start) failures.push('installed update time window is invalid');
247
+ if (!Number.isFinite(Date.parse(projection.observedAt)) || Date.parse(projection.observedAt) < end
248
+ || Date.parse(projection.observedAt) > Date.parse(proof.observedAt)) failures.push('installed coverage observation is outside run completion');
249
+ if (JSON.stringify(receipt.phases?.map(({ phase }) => phase)) !== JSON.stringify(REQUIRED_REFRESH_PHASES)) failures.push('installed update phases are incomplete');
250
+ const performed = new Set(['update', 'host-convergence', 'cleanup']);
251
+ for (const phase of receipt.phases || []) {
252
+ const execution = phase.evidence?.execution;
253
+ const at = Date.parse(phase.at);
254
+ if (phase.status !== 'PASS' || !Number.isFinite(at) || at < start || at > end) failures.push(`${phase.phase} has invalid run evidence`);
255
+ if (performed.has(phase.phase) || (phase.phase === 'local-overlay-restoration' && execution?.kind === 'executed')) {
256
+ if (execution?.kind !== 'executed' || execution.runId !== receipt.runId) failures.push(`${phase.phase} was not executed by this run`);
257
+ } else if (phase.phase === 'local-overlay-restoration') {
258
+ if (execution?.kind !== 'not-executed' || phase.evidence?.restoredStores !== 0) failures.push('unexecuted private overlay restoration is not empty');
259
+ } else if (execution?.kind !== 'imported-release' || execution.sourceSnapshot !== proof.sourceSha
260
+ || execution.upstreamFreshness !== 'UNKNOWN') failures.push(`${phase.phase} imported corpus provenance is invalid`);
261
+ }
262
+ const evidence = Object.fromEntries((receipt.phases || []).map(({ phase, evidence }) => [phase, evidence]));
263
+ if (evidence['coverage-generation']?.coverageSha256 !== projection.sha256
264
+ || evidence['coverage-generation']?.releaseCoverageGeneration !== coverage.releaseCoverageGeneration
265
+ || evidence['source-enumeration']?.sourceObservationSha256 !== coverage.sourceObservationSha256
266
+ || evidence['source-enumeration']?.rows !== coverage.totals.rows
267
+ || evidence['source-enumeration']?.terminal !== true
268
+ || evidence.ingestion?.eligibleCurrent !== coverage.rows.filter((row) => row.disposition === 'eligible' && row.status === 'CURRENT').length
269
+ || evidence.ingestion?.storeCount !== coverage.generationLedger.storeCount
270
+ || evidence['bundle-assembly']?.version !== coverage.releaseIdentity.version
271
+ || evidence['bundle-assembly']?.sourceSnapshot !== proof.sourceSha
272
+ || evidence['generation-ledger-reconciliation']?.sha256 !== coverage.generationLedger.sha256) failures.push('imported corpus evidence differs from installed projection');
273
+ const r = proof.registration;
274
+ const expectedIdentity = { schedulerIdentity: proof.identity, registrationPath: r.recordPath,
275
+ nodePath: r.nodePath, runnerPath: r.runnerPath, runnerSha256: r.runnerSha256, argv: [] };
276
+ const absolute = (value) => typeof value === 'string' && (proof.platform === 'win32' ? path.win32.isAbsolute(value) && !value.startsWith('/') : path.posix.isAbsolute(value));
277
+ if (!/^[a-f0-9]{64}$/.test(r.runnerSha256 || '') || !absolute(r.recordPath)
278
+ || !absolute(r.nodePath) || !absolute(r.runnerPath)
279
+ || !receipt.executableIdentity || receiptDigest(receipt.executableIdentity) !== receiptDigest(expectedIdentity)
280
+ || receipt.schedulerIdentity !== proof.identity) failures.push('native executable identity differs');
281
+ if (run.trigger?.identity !== proof.identity || run.trigger?.kind !== {
282
+ darwin: 'launchctl-kickstart', linux: 'cron-tick', win32: 'schtasks-run',
283
+ }[proof.platform]) failures.push('native scheduler trigger differs');
284
+ return failures;
285
+ }
286
+
287
+ export function validateNightlyProofReceipt(receipt, { platform, version, packageSha256, bundleSha256,
288
+ sourceSha, workflowRunId, publicKey = PUBLIC_KEY } = {}) {
289
+ const failures = [];
290
+ if (!receipt || receipt.schemaVersion !== 1 || receipt.kind !== 'ruvnet-brain-native-two-run-nightly-proof') {
291
+ return { ok: false, failures: ['invalid native nightly proof envelope'] };
292
+ }
293
+ const { receiptSha256: declaredDigest, ...body } = receipt;
294
+ if (receipt.scope !== 'installed-update' || receipt.upstreamFreshness !== 'UNKNOWN') failures.push('native proof scope is not installed-update with unknown upstream freshness');
295
+ try {
296
+ if (!crypto.verify(null, Buffer.from(bundleSha256 || '', 'hex'), publicKey,
297
+ Buffer.from(receipt.candidate?.bundle?.signatureBase64 || '', 'base64'))) failures.push('public bundle signature is invalid');
298
+ } catch { failures.push('public bundle signature is invalid'); }
299
+ if (declaredDigest !== receiptDigest(body)) failures.push('native proof receipt digest differs');
300
+ if (!['darwin', 'linux', 'win32'].includes(platform) || receipt.platform !== platform) failures.push('native proof platform differs');
301
+ if (!version || receipt.candidate?.version !== version) failures.push('native proof version differs');
302
+ if (!/^[a-f0-9]{64}$/.test(packageSha256 || '') || receipt.candidate?.sha256 !== packageSha256) failures.push('native proof package digest differs');
303
+ if (!/^[a-f0-9]{64}$/.test(bundleSha256 || '') || receipt.candidate?.bundle?.sha256 !== bundleSha256) failures.push('native proof bundle digest differs');
304
+ if (!/^[a-f0-9]{40}$/.test(sourceSha || '') || receipt.sourceSha !== sourceSha) failures.push('native proof source SHA differs');
305
+ if (!/^\d+$/.test(String(workflowRunId || '')) || String(receipt.workflowRunId || '') !== String(workflowRunId)) failures.push('native proof workflow run differs');
306
+ if (!/^com\.ruvnet\.brain-update\.proof-[A-Za-z0-9._-]+$/.test(receipt.identity || '')) failures.push('native proof identity is not isolated');
307
+ if (receipt.packageExecution !== undefined || receipt.registration?.packageTarget?.spec === 'ruvnet-brain@latest') {
308
+ failures.push(...validateNpxObservations(receipt));
309
+ }
310
+ const observedAt = Date.parse(receipt.observedAt);
311
+ if (!Number.isFinite(observedAt) || observedAt > Date.now() + 60_000) failures.push('native proof observation time is invalid');
312
+ if (!Array.isArray(receipt.runs) || receipt.runs.length !== 2) failures.push('native proof requires exactly two runs');
313
+ else {
314
+ for (const run of receipt.runs) {
315
+ if (!run.receipt || run.receiptSha256 !== receiptDigest(run.receipt)
316
+ || run.runId !== run.receipt.runId || run.terminalVerdict !== run.receipt.terminalVerdict) {
317
+ failures.push('embedded run identity or digest differs');
318
+ }
319
+ const finishedAt = Date.parse(run.receipt?.finishedAt);
320
+ if (!Number.isFinite(finishedAt) || finishedAt > observedAt) failures.push('native run completion is not bounded by observation');
321
+ }
322
+ try {
323
+ failures.push(...receipt.runs.flatMap((run) => validateInstalledRun(run, receipt)));
324
+ if (Date.parse(receipt.runs[0].installedCoverage.observedAt) > Date.parse(receipt.runs[1].receipt.startedAt)) {
325
+ failures.push('first installed coverage was not observed before the second run');
326
+ }
327
+ failures.push(...validateStorageEvidence({ first: receipt.runs[0].receipt, second: receipt.runs[1].receipt,
328
+ inventoryBefore: receipt.inventory.before, inventoryAfterFirst: receipt.inventory.afterFirst,
329
+ inventoryAfterSecond: receipt.inventory.afterSecond, retention: receipt.retention }));
330
+ } catch (error) { failures.push(`incomplete native proof evidence: ${error.message}`); }
331
+ }
332
+ return { ok: failures.length === 0, failures };
333
+ }
334
+
335
+ export async function runNightlyTwoRunProof({ packagePath, bundlePath, out, timeoutMs = 30 * 60 * 1000,
336
+ keepRoot = false, sourceSha = process.env.GITHUB_SHA || null,
337
+ workflowRunId = process.env.GITHUB_RUN_ID || null, signaturePath = `${bundlePath}.sig` } = {}) {
338
+ if (!['darwin', 'linux', 'win32'].includes(process.platform)) throw new Error(`unsupported native scheduler platform: ${process.platform}`);
339
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) throw new Error('proof timeout must be a positive integer');
340
+ const candidate = path.resolve(packagePath || '');
341
+ const output = path.resolve(out || '');
342
+ if (!candidate.endsWith('.tgz') || !fs.lstatSync(candidate).isFile()) throw new Error('candidate must be a regular .tgz file');
343
+ const bundle = path.resolve(bundlePath || '');
344
+ let bundleStat;
345
+ try { bundleStat = fs.lstatSync(bundle); } catch { /* handled below */ }
346
+ if (!bundle.endsWith('.zip') || !bundleStat?.isFile() || bundleStat.isSymbolicLink()) {
347
+ throw new Error('bundle must be a regular candidate bundle .zip file');
348
+ }
349
+ const bundleSha256 = sha256(fs.readFileSync(bundle));
350
+ const signatureBase64 = fs.readFileSync(signaturePath).toString('base64');
351
+ if (!crypto.verify(null, Buffer.from(bundleSha256, 'hex'), PUBLIC_KEY, Buffer.from(signatureBase64, 'base64'))) {
352
+ throw new Error('public candidate bundle signature is invalid');
353
+ }
354
+ const packageSha256 = sha256(fs.readFileSync(candidate));
355
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'ruvnet-nightly-proof-'));
356
+ const home = path.join(root, 'home');
357
+ const brainHome = path.join(root, 'brain');
358
+ const kbDir = path.join(brainHome, 'kb');
359
+ const prefix = path.join(root, 'prefix');
360
+ const npmCache = path.join(root, 'npm-cache');
361
+ const logPath = path.join(kbDir, 'update.log');
362
+ const identity = `com.ruvnet.brain-update.proof-${process.pid}-${crypto.randomBytes(6).toString('hex')}`;
363
+ const tempDir = path.join(root, 'tmp');
364
+ const env = {
365
+ ...Object.fromEntries(['SystemRoot', 'SYSTEMROOT', 'ComSpec', 'COMSPEC', 'PATHEXT']
366
+ .filter((key) => process.env[key] !== undefined).map((key) => [key, process.env[key]])),
367
+ PATH: [path.dirname(process.execPath), process.env.PATH || ''].filter(Boolean).join(path.delimiter),
368
+ HOME: home,
369
+ USERPROFILE: home,
370
+ RUVNET_BRAIN_HOME: brainHome,
371
+ RUVNET_BRAIN_KB: kbDir,
372
+ npm_config_cache: npmCache,
373
+ TEMP: tempDir,
374
+ TMP: tempDir,
375
+ NO_COLOR: '1',
376
+ };
377
+ fs.mkdirSync(home, { recursive: true });
378
+ fs.mkdirSync(tempDir);
379
+ let scheduler;
380
+ let registration;
381
+ let completed = false;
382
+ try {
383
+ const npm = npmInvocation(['install', '--prefix', prefix, '--ignore-scripts', '--no-audit', '--no-fund', candidate]);
384
+ run(npm.executable, npm.args, { env });
385
+ const packageRoot = path.join(prefix, 'node_modules', 'ruvnet-brain');
386
+ const packageJson = JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'));
387
+ const expectedPackage = packageTreeObservation(packageRoot);
388
+ // Exercise the production target, not npx's ambiguous positional local-tarball path.
389
+ const prepare = npmInvocation(['exec', '--yes', '--package=ruvnet-brain@latest', '--', 'ruvnet-brain', '--help']);
390
+ run(prepare.executable, prepare.args, { env, cwd: home });
391
+ const packageObservations = [observeNpxPackage(npmCache, expectedPackage)];
392
+ // Register the exact public package's plugin in this fresh home. GitHub shorthand can
393
+ // select SSH on Windows and leave no staged plugin for the first scheduled activation.
394
+ env.RUVNET_CLAUDE_MARKETPLACE_SOURCE = packageRoot;
395
+ const stagedBundle = stageExactBundle({ bundlePath: bundle, packageRoot });
396
+ run(process.execPath, [path.join(packageRoot, 'bin', 'install.mjs'), '--yes', '--no-nightly-prompt'], {
397
+ env: { ...env, npm_config_bin_links: 'false' }, stdio: 'inherit', encoding: undefined,
398
+ });
399
+ const removedEmptyScaffolds = removeEmptyInstallerScaffolds(kbDir);
400
+ scheduler = await import(`${pathToFileURL(path.join(packageRoot, 'plugin', 'scripts', 'nightly-scheduler.mjs')).href}?proof=${Date.now()}`);
401
+ registration = scheduler.installNightlyRunner({ brainHome,
402
+ source: path.join(packageRoot, 'bin', 'nightly-refresh.mjs'), nodePath: process.execPath, identity, env,
403
+ packageTarget: { spec: 'ruvnet-brain@latest', sha256: null },
404
+ bundleTarget: { spec: stagedBundle.sourcePath, sha256: stagedBundle.sha256 } });
405
+ const inventoryBefore = managedStorageInventory(kbDir);
406
+ const kick = () => triggerNativeRun({ scheduler, registration, platform: process.platform,
407
+ env, brainHome, kbDir, timeoutMs });
408
+ const observeCoverage = () => {
409
+ const checked = validateCoverageDirectory(kbDir, { expectedVersion: packageJson.version, expectedSourceSnapshot: sourceSha });
410
+ if (!checked.valid) throw new Error(`installed candidate ReleaseCoverage failed: ${checked.failures.join('; ')}`);
411
+ const raw = fs.readFileSync(path.join(kbDir, 'COVERAGE.json'), 'utf8');
412
+ return { raw, sha256: sha256(Buffer.from(raw)), observedAt: new Date().toISOString() };
413
+ };
414
+ const firstEntry = await kick();
415
+ firstEntry.installedCoverage = observeCoverage();
416
+ const inventoryAfterFirst = managedStorageInventory(kbDir);
417
+ packageObservations.push(observeNpxPackage(npmCache, expectedPackage));
418
+ const retentionAfterFirst = assessLifecycleEvidence({ brainHome, kbDir });
419
+ const logAfterFirst = observeUpdateLog(logPath);
420
+ const secondEntry = await kick();
421
+ secondEntry.installedCoverage = observeCoverage();
422
+ const inventoryAfterSecond = managedStorageInventory(kbDir);
423
+ packageObservations.push(observeNpxPackage(npmCache, expectedPackage));
424
+ const retentionAfterSecond = assessLifecycleEvidence({ brainHome, kbDir });
425
+ const retention = { ...retentionAfterSecond, before: retentionAfterFirst.after,
426
+ withinBudget: retentionAfterFirst.withinBudget && retentionAfterSecond.withinBudget,
427
+ unsafe: [...retentionAfterFirst.unsafe, ...retentionAfterSecond.unsafe],
428
+ updateLog: { before: logAfterFirst, after: observeUpdateLog(logPath) } };
429
+ const receipt = {
430
+ schemaVersion: 1,
431
+ kind: 'ruvnet-brain-native-two-run-nightly-proof',
432
+ scope: 'installed-update',
433
+ installationConfiguration: { npmBinLinks: false, source: 'initial-reader-install-only', removedEmptyScaffolds },
434
+ packageExecution: { policy: 'production-latest-exact-cache-v1',
435
+ expected: { ...expectedPackage, packageSha256 }, observations: packageObservations },
436
+ upstreamFreshness: 'UNKNOWN',
437
+ observedAt: new Date().toISOString(),
438
+ platform: process.platform,
439
+ sourceSha,
440
+ workflowRunId: workflowRunId == null ? null : String(workflowRunId),
441
+ identity,
442
+ candidate: { path: candidate, sha256: packageSha256, version: packageJson.version,
443
+ bundle: { path: stagedBundle.sourcePath, sha256: stagedBundle.sha256, bytes: stagedBundle.bytes, signatureBase64 } },
444
+ registration: { recordPath: registration.recordPath, runnerSha256: registration.runnerSha256,
445
+ nodePath: registration.nodePath, runnerPath: registration.runnerPath,
446
+ packageTarget: registration.packageTarget, bundleTarget: registration.bundleTarget },
447
+ runs: [firstEntry, secondEntry].map(({ receipt: row, installedCoverage, trigger }) => ({ runId: row.runId,
448
+ terminalVerdict: row.terminalVerdict, receiptSha256: receiptDigest(row), finishedAt: row.finishedAt,
449
+ receipt: row, installedCoverage, trigger })),
450
+ inventory: { before: inventoryBefore, afterFirst: inventoryAfterFirst, afterSecond: inventoryAfterSecond },
451
+ retention,
452
+ log: { path: logPath, exists: fs.existsSync(logPath),
453
+ sha256: fs.existsSync(logPath) ? sha256(fs.readFileSync(logPath)) : null },
454
+ };
455
+ receipt.receiptSha256 = receiptDigest(receipt);
456
+ const validation = validateNightlyProofReceipt(receipt, { platform: process.platform, version: packageJson.version,
457
+ packageSha256, bundleSha256, sourceSha, workflowRunId });
458
+ delete receipt.receiptSha256;
459
+ receipt.validation = validation;
460
+ receipt.receiptSha256 = receiptDigest(receipt);
461
+ fs.mkdirSync(path.dirname(output), { recursive: true });
462
+ fs.writeFileSync(output, `${JSON.stringify(receipt, null, 2)}\n`, { flag: 'wx' });
463
+ if (!validation.ok) throw new Error(`native two-run proof failed: ${validation.failures.join('; ')}`);
464
+ completed = true;
465
+ return receipt;
466
+ } finally {
467
+ // Each trigger owns cleanup and verifies absence before returning. Failed runs retain their
468
+ // root so an active child or failed cleanup never loses the evidence/runner it still needs.
469
+ if (completed && !keepRoot) fs.rmSync(root, { recursive: true, force: true });
470
+ if (!completed) console.error(`Native proof diagnostics retained at ${root}`);
471
+ }
472
+ }
473
+
474
+ if (path.resolve(process.argv[1] || '') === fileURLToPath(import.meta.url)) {
475
+ try {
476
+ const args = parseArgs(process.argv.slice(2));
477
+ const receipt = await runNightlyTwoRunProof({ packagePath: args.package, bundlePath: args.bundle, out: args.out,
478
+ timeoutMs: args['timeout-ms'] ? Number(args['timeout-ms']) : undefined,
479
+ sourceSha: args['source-sha'] || process.env.GITHUB_SHA,
480
+ signaturePath: args.signature,
481
+ workflowRunId: args['workflow-run-id'] || process.env.GITHUB_RUN_ID,
482
+ keepRoot: args['keep-root'] === 'true' });
483
+ console.log(JSON.stringify({ ok: true, receipt: path.resolve(args.out),
484
+ receiptSha256: receipt.receiptSha256, runs: receipt.runs }, null, 2));
485
+ } catch (error) {
486
+ console.error(error.message);
487
+ process.exitCode = 1;
488
+ }
489
+ }
@@ -38,6 +38,7 @@ import path from 'node:path';
38
38
  import os from 'node:os';
39
39
  import { spawnSync, execFileSync } from 'node:child_process';
40
40
  import { fileURLToPath, pathToFileURL } from 'node:url';
41
+ import { NIGHTLY_LABEL, schedulerStatus } from '../plugin/scripts/nightly-scheduler.mjs';
41
42
 
42
43
  const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
43
44
  const REGISTRY = process.env.WATCHDOG_REGISTRY || path.join(ROOT, 'config', 'scheduled-jobs.json');
@@ -158,6 +159,17 @@ export function checkAll(now, { registry = REGISTRY, loaded = loadedLabels(), hb
158
159
  });
159
160
  }
160
161
 
162
+ export function productSchedulerVerdict(status) {
163
+ const base = { label: NIGHTLY_LABEL, what: 'Refreshes the installed Brain corpus and converges every detected host',
164
+ schedule: 'daily 03:47', maxAgeHours: 30, required: true };
165
+ if (status?.state !== 'on') return { ...base, state: MISSING, detail: status?.evidence || 'scheduler is not registered' };
166
+ const run = status.runHealth || { state: 'never-ran', evidence: 'No nightly refresh receipt exists yet.' };
167
+ const mapped = run.state === 'ok' || run.state === 'running' ? OK
168
+ : run.state === 'stale' ? STALE
169
+ : run.state === 'never-ran' ? NEVER_RAN : FAILING;
170
+ return { ...base, state: mapped, detail: run.evidence };
171
+ }
172
+
161
173
  const loadState = () => { try { return JSON.parse(fs.readFileSync(STATE, 'utf8')); } catch { return {}; } };
162
174
  const saveState = (s) => { fs.mkdirSync(path.dirname(STATE), { recursive: true }); fs.writeFileSync(STATE, JSON.stringify(s, null, 2)); };
163
175
 
@@ -183,7 +195,10 @@ async function push(title, body, priority) {
183
195
  async function main() {
184
196
  const json = process.argv.includes('--json');
185
197
  const quiet = process.argv.includes('--quiet');
186
- const results = checkAll(new Date());
198
+ const brainHome = process.env.RUVNET_BRAIN_HOME || path.join(os.homedir(), '.cache', 'ruvnet-brain');
199
+ const results = [...checkAll(new Date()), productSchedulerVerdict(schedulerStatus({
200
+ brainHome, kbDir: process.env.RUVNET_BRAIN_KB || path.join(brainHome, 'kb'),
201
+ }))];
187
202
  const bad = results.filter((r) => r.state !== OK);
188
203
 
189
204
  const prev = loadState();
@@ -219,7 +234,7 @@ async function main() {
219
234
  ? `${bad.length} of ${results.length} job(s) are NOT confirmed working. NEVER-RAN means the schedule has never fired — it does not mean "probably fine".`
220
235
  : `All ${results.length} jobs produced a fresh, successful receipt.`);
221
236
  }
222
- process.exit(bad.length ? 1 : 0);
237
+ process.exit(bad.length || undelivered.size ? 1 : 0);
223
238
  }
224
239
 
225
240
  if (process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) await main();
@@ -0,0 +1,20 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ // Run the already installed npm CLI with Node, including on Windows where npm.cmd
5
+ // cannot be exec'd directly. Never install another npm or concatenate a shell command.
6
+ export function npmInvocation(args, { env = process.env, nodePath = process.execPath } = {}) {
7
+ const candidates = [env.npm_execpath, path.join(path.dirname(nodePath), 'node_modules/npm/bin/npm-cli.js'),
8
+ path.join(path.dirname(nodePath), 'npm')].filter(Boolean);
9
+ for (const candidate of candidates) {
10
+ try {
11
+ const entry = fs.realpathSync(candidate);
12
+ const root = path.dirname(path.dirname(entry));
13
+ const manifest = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
14
+ if (manifest.name !== 'npm' || typeof manifest.bin?.npm !== 'string'
15
+ || fs.realpathSync(path.resolve(root, manifest.bin.npm)) !== entry || !fs.statSync(entry).isFile()) continue;
16
+ return { executable: nodePath, args: [entry, ...args] };
17
+ } catch { /* only a positively identified installed npm entry is accepted */ }
18
+ }
19
+ throw new Error('acceptance requires installed npm beside Node or an explicit npm_execpath pointing to its CLI');
20
+ }