ruvnet-brain 4.3.28 → 4.3.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/install.mjs +90 -8
- package/console/app.js +7 -2
- package/kb/corpus-release-identity.mjs +73 -0
- package/package.json +4 -3
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/mcp/managed-cli-interface.mjs +83 -9
- package/plugin/scripts/advocacy-route.mjs +4 -1
- package/plugin/scripts/capacity-aware-parallel-work.mjs +4 -0
- package/plugin/scripts/continuation-gate.mjs +71 -0
- package/plugin/scripts/decision-gate.mjs +29 -57
- package/plugin/scripts/ground-ruvnet.sh +77 -4
- package/plugin/scripts/grounding-stamp.sh +34 -5
- package/plugin/scripts/grounding-turn-gate.mjs +8 -2
- package/plugin/scripts/grounding-turn-mark.mjs +6 -1
- package/plugin/scripts/hook-input.mjs +54 -0
- package/plugin/scripts/hook-shim.mjs +10 -0
- package/plugin/scripts/memory-doctor.mjs +43 -0
- package/plugin/scripts/nightly-controller.mjs +6 -1
- package/plugin/scripts/project-progression-contract.mjs +17 -0
- package/plugin/scripts/project-progression-hook.mjs +18 -5
- package/plugin/scripts/project-progression-producer.mjs +16 -12
- package/plugin/scripts/ruvnet-gate1-pattern.mjs +17 -0
- package/plugin/scripts/session-start-core.mjs +11 -5
- package/plugin/scripts/session-start-update-plane.mjs +5 -1
- package/plugin/scripts/unprompted-runtime.mjs +29 -8
- package/plugin/skills/ruvnet-brain/PLAYBOOK.md +1 -1
- package/scripts/adr-072-completion.mjs +2 -1
- package/scripts/calibrate-router.mjs +6 -6
- package/scripts/corpus-reconcile.mjs +32 -3
- package/scripts/correction-detect.mjs +10 -11
- package/scripts/dispatch-receipt.mjs +2 -2
- package/scripts/dual-host-deliberation.mjs +3 -2
- package/scripts/execution-policy.mjs +10 -2
- package/scripts/gen-console-images.mjs +0 -1
- package/scripts/gen-images.mjs +0 -4
- package/scripts/host-install-matrix.mjs +7 -4
- package/scripts/independent-review-receipt.mjs +7 -8
- package/scripts/ingest-repo.mjs +45 -1
- package/scripts/learning-replay-cli.mjs +2 -1
- package/scripts/learning-replay-fixture.mjs +2 -1
- package/scripts/learnings.mjs +19 -5
- package/scripts/lesson-migrate-agentdb.mjs +635 -0
- package/scripts/loop-checkpoint.mjs +37 -1
- package/scripts/metaharness-receipts.mjs +3 -2
- package/scripts/nightly-two-run-proof.mjs +6 -6
- package/scripts/nightly-watchdog.mjs +10 -2
- package/scripts/onboarding-console.mjs +98 -49
- package/scripts/oracle/producer-hosts.mjs +2 -1
- package/scripts/private-overlay.mjs +31 -9
- package/scripts/public-verification-aggregate.mjs +4 -3
- package/scripts/publication-receipt.mjs +9 -0
- package/scripts/qe/agentic-qe-4.3.mjs +0 -1
- package/scripts/rebuild-gists-from-receipts.mjs +1 -1
- package/scripts/reconcile-project.mjs +0 -0
- package/scripts/release.mjs +34 -82
- package/scripts/retrieval-canary.mjs +3 -2
- package/scripts/review-model-defaults.mjs +26 -0
- package/scripts/route-cheap.mjs +20 -7
- package/scripts/router-utilization.mjs +5 -5
- package/scripts/rvf-generation.mjs +68 -2
- package/scripts/single-source-check.mjs +270 -0
- package/scripts/subscription-hosts.mjs +4 -0
- package/scripts/sync-version.mjs +22 -0
- package/scripts/trismart.mjs +3 -3
- package/scripts/wired-check.mjs +40 -4
- package/console/assets/memory.webp +0 -0
- package/plugin/scripts/version-bump-gate.sh +0 -124
- package/scripts/qe/aggregate-4.3.mjs +0 -42
- package/scripts/release-convergence-watchdog.mjs +0 -112
- package/scripts/stamp-existing-rvf-generations.mjs +0 -53
- package/scripts/verify-channels.mjs +0 -196
|
@@ -15,6 +15,9 @@
|
|
|
15
15
|
// check → evaluate stop conditions. Exit codes are the protocol:
|
|
16
16
|
// 0 = continue 3 = DONE (doneCriteria command exited 0)
|
|
17
17
|
// 4 = NO-PROGRESS stop (two strikes) 2 = usage/state error
|
|
18
|
+
// stale → is `updatedAt` too old to trust (H3, ADR-0011 Phase 1's stale-resume gap)? Exit codes:
|
|
19
|
+
// 0 = fresh (safe to resume) 1 = stale (stdout: age in whole days)
|
|
20
|
+
// 2 = unknown (no checkpoint, or no parseable updatedAt — never treated as fresh OR stale)
|
|
18
21
|
//
|
|
19
22
|
// doneCriteria is a SHELL COMMAND, not prose — "machine-checkable done" means exit code 0, not a
|
|
20
23
|
// model's opinion that it's finished.
|
|
@@ -52,6 +55,28 @@ export function writeCheckpoint({ iteration, doneCriteria, next, blockers }, fil
|
|
|
52
55
|
return cp;
|
|
53
56
|
}
|
|
54
57
|
|
|
58
|
+
// H3 (GitHub issue: AUTONOMOUS MODE injected into attended sessions + a stale checkpoint resumed as
|
|
59
|
+
// if it were live). ground-ruvnet.sh used to inject `.ruvnet-brain/checkpoint.json`'s raw content on
|
|
60
|
+
// every autonomous-flagged turn with no age check at all — a checkpoint from a loop that ended days
|
|
61
|
+
// or weeks ago would be resumed as though it were the current session's own state. This is the ONE
|
|
62
|
+
// place "how old is too old" is decided; ground-ruvnet.sh (via the `stale` CLI verb below) and
|
|
63
|
+
// scripts/single-source-check.mjs's E1 audit both judge staleness against this exact threshold and
|
|
64
|
+
// this exact field, so the two can never quietly disagree about what "stale" means.
|
|
65
|
+
export const CHECKPOINT_STALE_MS = 24 * 60 * 60 * 1000; // 24h
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Age verdict for a checkpoint object, judged by its OWN `updatedAt` claim — never by the file's
|
|
69
|
+
* mtime, which a copy/rsync/restore can reset without the checkpoint's content actually changing.
|
|
70
|
+
* `known: false` means there is nothing to judge (no checkpoint, or no parseable `updatedAt`); a
|
|
71
|
+
* caller must treat "unknown" as neither proven-fresh nor proven-stale.
|
|
72
|
+
*/
|
|
73
|
+
export function checkpointStaleness(cp, now = Date.now()) {
|
|
74
|
+
const updatedAtMs = cp && typeof cp.updatedAt === 'string' ? Date.parse(cp.updatedAt) : NaN;
|
|
75
|
+
if (!Number.isFinite(updatedAtMs)) return { known: false, stale: false, ageMs: null, ageDays: null };
|
|
76
|
+
const ageMs = now - updatedAtMs;
|
|
77
|
+
return { known: true, stale: ageMs >= CHECKPOINT_STALE_MS, ageMs, ageDays: ageMs / 86_400_000 };
|
|
78
|
+
}
|
|
79
|
+
|
|
55
80
|
/** Stop-condition verdict. Pure decision from state + one real doneCriteria execution. */
|
|
56
81
|
export function checkCheckpoint(file = FILE, runner = (c) => spawnSync('sh', ['-c', c], { stdio: 'ignore' }).status) {
|
|
57
82
|
const cp = readCheckpoint(file);
|
|
@@ -79,8 +104,19 @@ if (invokedDirectly) {
|
|
|
79
104
|
const r = checkCheckpoint();
|
|
80
105
|
console.log(r.verdict);
|
|
81
106
|
process.exit(r.code);
|
|
107
|
+
} else if (cmd === 'stale') {
|
|
108
|
+
// H3: the ONE place a caller (ground-ruvnet.sh, via `node loop-checkpoint.mjs stale`) asks "is
|
|
109
|
+
// this checkpoint too old to resume from?" without re-deriving the 24h rule itself. Exit codes
|
|
110
|
+
// are the protocol, same style as `check`: 0 = fresh (safe to inject/resume), 1 = stale (stdout
|
|
111
|
+
// carries the whole age in days, floored, for a human-readable message), 2 = unknown — no
|
|
112
|
+
// checkpoint, unparseable JSON, or no usable `updatedAt` to judge by. A caller must not treat 2
|
|
113
|
+
// as either fresh or stale.
|
|
114
|
+
const info = checkpointStaleness(readCheckpoint());
|
|
115
|
+
if (!info.known) process.exit(2);
|
|
116
|
+
if (info.stale) { console.log(String(Math.floor(info.ageDays))); process.exit(1); }
|
|
117
|
+
process.exit(0);
|
|
82
118
|
} else {
|
|
83
|
-
console.error('usage: loop-checkpoint.mjs <read|write|check> [--dir D] [--iteration N --done-criteria CMD --next "…" --blockers "…"]');
|
|
119
|
+
console.error('usage: loop-checkpoint.mjs <read|write|check|stale> [--dir D] [--iteration N --done-criteria CMD --next "…" --blockers "…"]');
|
|
84
120
|
process.exit(2);
|
|
85
121
|
}
|
|
86
122
|
}
|
|
@@ -11,6 +11,7 @@ import fs from 'node:fs';
|
|
|
11
11
|
import path from 'node:path';
|
|
12
12
|
import os from 'node:os';
|
|
13
13
|
import { pathToFileURL } from 'node:url';
|
|
14
|
+
import { CLAUDE_MODEL_IDS } from './route-cheap.mjs';
|
|
14
15
|
|
|
15
16
|
export function receiptsPath() {
|
|
16
17
|
return (
|
|
@@ -61,7 +62,7 @@ export function formatTable(rows) {
|
|
|
61
62
|
r.source === 'claude-subagent' ? 'subagent' : r.source === 'calibration' ? 'calibrate' : 'openrouter',
|
|
62
63
|
r.task_class || '?',
|
|
63
64
|
r.model,
|
|
64
|
-
r.frontier_ref ||
|
|
65
|
+
r.frontier_ref || CLAUDE_MODEL_IDS.opus,
|
|
65
66
|
fmt$(r.est_cost ?? 0),
|
|
66
67
|
fmt$(r.est_frontier_cost ?? 0),
|
|
67
68
|
fmt$(r.saved),
|
|
@@ -79,7 +80,7 @@ export function formatTable(rows) {
|
|
|
79
80
|
const pct = totalFrontier > 0 ? Math.round((totalSaved / totalFrontier) * 100) : 0;
|
|
80
81
|
|
|
81
82
|
// Baselines now vary per row; name them all rather than picking one and implying it covers everything.
|
|
82
|
-
const baselines = [...new Set(rows.map((r) => r.frontier_ref ||
|
|
83
|
+
const baselines = [...new Set(rows.map((r) => r.frontier_ref || CLAUDE_MODEL_IDS.opus))].join(', ');
|
|
83
84
|
const timedTotal = rows.reduce((s, r) => s + (typeof r.duration_ms === 'number' && r.duration_ms > 0 ? r.duration_ms : 0), 0);
|
|
84
85
|
const timedRows = rows.filter((r) => typeof r.duration_ms === 'number' && r.duration_ms > 0).length;
|
|
85
86
|
// TIME as a percentage, like cost — "20 seconds" is trivia; "~40% faster" is the message
|
|
@@ -11,7 +11,7 @@ import { managedStorageInventory } from '../kb/update-storage-transaction.mjs';
|
|
|
11
11
|
import { REQUIRED_REFRESH_PHASES, validateCurrentRunPhaseExecution, inspectRefreshOwner, refreshLockPath } from '../kb/refresh-run.mjs';
|
|
12
12
|
import { npmInvocation } from './npm-invocation.mjs';
|
|
13
13
|
import { packageTreeObservation, observeNpxPackage, validateNpxObservations } from './nightly-package-observation.mjs';
|
|
14
|
-
import { validateRefreshReceiptEnvelope } from '../plugin/scripts/nightly-scheduler.mjs';
|
|
14
|
+
import { NIGHTLY_LABEL, NIGHTLY_PROOF_LABEL, validateRefreshReceiptEnvelope } from '../plugin/scripts/nightly-scheduler.mjs';
|
|
15
15
|
import { validateCoverageDirectory, validateCoverageLedger } from '../plugin/scripts/coverage-integrity.mjs';
|
|
16
16
|
|
|
17
17
|
const PUBLIC_KEY = fs.readFileSync(new URL('../keys/ruvnet-brain-signing.pub.pem', import.meta.url), 'utf8');
|
|
@@ -117,7 +117,7 @@ function refreshExecutionStopped(receipt, kbDir) {
|
|
|
117
117
|
export async function triggerNativeRun({ scheduler, registration, platform, env, brainHome, kbDir,
|
|
118
118
|
timeoutMs, command = run, receipts = nightlyReceipts, pause = sleep, now = Date.now, stopped = refreshExecutionStopped }) {
|
|
119
119
|
const identity = registration.identity;
|
|
120
|
-
if (
|
|
120
|
+
if (!NIGHTLY_PROOF_LABEL.test(identity || '')) {
|
|
121
121
|
throw new Error('native proof requires a unique proof scheduler identity');
|
|
122
122
|
}
|
|
123
123
|
if (!['darwin', 'linux', 'win32'].includes(platform)) throw new Error(`unsupported native scheduler platform: ${platform}`);
|
|
@@ -209,7 +209,7 @@ export async function runNativeSchedulerSmoke({ packageRoot, packagePath, bundle
|
|
|
209
209
|
|| typeof scheduler.installScheduler !== 'function' || typeof scheduler.schedulerStatus !== 'function'
|
|
210
210
|
|| typeof scheduler.removeScheduler !== 'function') throw new Error('scheduler smoke adapter is incomplete');
|
|
211
211
|
|
|
212
|
-
const identity =
|
|
212
|
+
const identity = `${NIGHTLY_LABEL}.proof-smoke-${process.pid}-${crypto.randomBytes(6).toString('hex')}`;
|
|
213
213
|
const packageSha256 = sha256(fs.readFileSync(packageArchive));
|
|
214
214
|
const bundleSha256 = sha256(fs.readFileSync(bundle));
|
|
215
215
|
const registration = scheduler.installNightlyRunner({ brainHome,
|
|
@@ -281,7 +281,7 @@ export function validateNativeSchedulerSmoke(smoke, { platform, sourceSha, workf
|
|
|
281
281
|
|| smoke.scope !== 'public-release-scheduler-boundary') failures.push('scheduler smoke envelope is invalid');
|
|
282
282
|
if (smoke.platform !== platform || smoke.sourceSha !== sourceSha
|
|
283
283
|
|| String(smoke.workflowRunId || '') !== String(workflowRunId || '')) failures.push('scheduler smoke identity differs');
|
|
284
|
-
if (
|
|
284
|
+
if (!NIGHTLY_PROOF_LABEL.test(smoke.identity || '') || !String(smoke.identity || '').includes('.proof-smoke-')) failures.push('scheduler smoke identity is not isolated');
|
|
285
285
|
if (smoke.loaded !== true || smoke.cleaned !== true || smoke.trigger?.identity !== smoke.identity) failures.push('scheduler smoke did not prove load, trigger, and cleanup');
|
|
286
286
|
const expectedTrigger = { darwin: 'launchctl-kickstart', linux: 'cron-registration', win32: 'schtasks-run' }[platform];
|
|
287
287
|
if (smoke.trigger?.kind !== expectedTrigger) failures.push('scheduler smoke trigger differs');
|
|
@@ -424,7 +424,7 @@ export function validateNightlyProofReceipt(receipt, { platform, version, packag
|
|
|
424
424
|
if (!/^[a-f0-9]{64}$/.test(bundleSha256 || '') || receipt.candidate?.bundle?.sha256 !== bundleSha256) failures.push('native proof bundle digest differs');
|
|
425
425
|
if (!/^[a-f0-9]{40}$/.test(sourceSha || '') || receipt.sourceSha !== sourceSha) failures.push('native proof source SHA differs');
|
|
426
426
|
if (!/^\d+$/.test(String(workflowRunId || '')) || String(receipt.workflowRunId || '') !== String(workflowRunId)) failures.push('native proof workflow run differs');
|
|
427
|
-
if (
|
|
427
|
+
if (!NIGHTLY_PROOF_LABEL.test(receipt.identity || '')) failures.push('native proof identity is not isolated');
|
|
428
428
|
if (receipt.packageExecution !== undefined || receipt.registration?.packageTarget?.spec === 'ruvnet-brain@latest') {
|
|
429
429
|
failures.push(...validateNpxObservations(receipt));
|
|
430
430
|
}
|
|
@@ -480,7 +480,7 @@ export async function runNightlyTwoRunProof({ packagePath, bundlePath, out, time
|
|
|
480
480
|
const prefix = path.join(root, 'prefix');
|
|
481
481
|
const npmCache = path.join(root, 'npm-cache');
|
|
482
482
|
const logPath = path.join(kbDir, 'update.log');
|
|
483
|
-
const identity =
|
|
483
|
+
const identity = `${NIGHTLY_LABEL}.proof-${process.pid}-${crypto.randomBytes(6).toString('hex')}`;
|
|
484
484
|
const tempDir = path.join(root, 'tmp');
|
|
485
485
|
const env = {
|
|
486
486
|
...Object.fromEntries(['SystemRoot', 'SYSTEMROOT', 'ComSpec', 'COMSPEC', 'PATHEXT']
|
|
@@ -227,6 +227,14 @@ export function productSchedulerVerdict(status) {
|
|
|
227
227
|
return { ...base, state: mapped, detail: run.evidence };
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
+
/** ONE update owner per machine (CONTRIBUTING.md § The knowledge corpus). agentic-kit removes the
|
|
231
|
+
* Brain's own scheduler by design and updates through `ak sync`, which the registry watches as
|
|
232
|
+
* com.stuartkerr.ak-sync — so on such a machine the Brain scheduler is not expected to exist. */
|
|
233
|
+
export function brainUpdateOwnedByAgenticKit(home = os.homedir()) {
|
|
234
|
+
try { return JSON.parse(fs.readFileSync(path.join(home, '.config', 'agentic-kit', 'kit.json'), 'utf8')).ruvnetBrain === true; }
|
|
235
|
+
catch { return false; }
|
|
236
|
+
}
|
|
237
|
+
|
|
230
238
|
const loadState = () => { try { return JSON.parse(fs.readFileSync(STATE, 'utf8')); } catch { return {}; } };
|
|
231
239
|
const saveState = (s) => { fs.mkdirSync(path.dirname(STATE), { recursive: true }); fs.writeFileSync(STATE, JSON.stringify(s, null, 2)); };
|
|
232
240
|
|
|
@@ -253,9 +261,9 @@ async function main() {
|
|
|
253
261
|
const json = process.argv.includes('--json');
|
|
254
262
|
const quiet = process.argv.includes('--quiet');
|
|
255
263
|
const brainHome = process.env.RUVNET_BRAIN_HOME || path.join(os.homedir(), '.cache', 'ruvnet-brain');
|
|
256
|
-
const results = [...checkAll(new Date()), productSchedulerVerdict(schedulerStatus({
|
|
264
|
+
const results = [...checkAll(new Date()), ...(brainUpdateOwnedByAgenticKit() ? [] : [productSchedulerVerdict(schedulerStatus({
|
|
257
265
|
brainHome, kbDir: process.env.RUVNET_BRAIN_KB || path.join(brainHome, 'kb'),
|
|
258
|
-
}))];
|
|
266
|
+
}))])];
|
|
259
267
|
const bad = results.filter((r) => r.state !== OK);
|
|
260
268
|
|
|
261
269
|
const prev = loadState();
|
|
@@ -27,7 +27,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
27
27
|
import { spawnSync, execFileSync, spawn } from 'node:child_process';
|
|
28
28
|
|
|
29
29
|
import { auditModel, installedVersion } from './stack-sync.mjs';
|
|
30
|
-
import { candidateRoots, findStores, diagnose } from './memory-doctor.mjs';
|
|
30
|
+
import { candidateRoots, findStores, findProjects, diagnose } from './memory-doctor.mjs';
|
|
31
31
|
import { buildStackRecommendations, buildWiringRecommendations, summarizeWiring, scoreMemoryHealth, buildHealthRecommendations, buildCapabilityRecommendations } from './console-engine.mjs';
|
|
32
32
|
import { planFor } from './remedy-registry.mjs';
|
|
33
33
|
import { auditAll as capabilityAuditAll } from './capability-registry.mjs';
|
|
@@ -73,6 +73,7 @@ import { applyNightlyChoice, nightlyStatus } from './nightly-controller.mjs';
|
|
|
73
73
|
// One canonical answer to "which directory is this, and have I counted it already?" — shared with
|
|
74
74
|
// the PreCompact snapshot producer (#85) and with memory-doctor's root scan (#107).
|
|
75
75
|
import { canonicalPath, pathIdentity, projectDirectory } from '../plugin/scripts/project-identity.mjs';
|
|
76
|
+
import { resolveProjectStore } from '../plugin/scripts/project-store-resolver.mjs';
|
|
76
77
|
|
|
77
78
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
78
79
|
const REPO = path.dirname(__dirname);
|
|
@@ -350,27 +351,6 @@ function robustReadJSON(db, sql) {
|
|
|
350
351
|
const VENDOR = ['/clones/', '/node_modules/', '/vendor/', '/upstream/', '.claude-backup', '_snapshots',
|
|
351
352
|
'/ruvnet-repos/', '/ruvnet_repos/'];
|
|
352
353
|
|
|
353
|
-
// memory-doctor.mjs owns candidate-root policy for the standalone CLI, Console, and other callers.
|
|
354
|
-
// Keeping one exported implementation prevents a new project-root convention from fixing one
|
|
355
|
-
// surface while another continues to print a confident but incomplete machine-wide count (#81).
|
|
356
|
-
function findProjects(root) {
|
|
357
|
-
const out = new Set();
|
|
358
|
-
const walk = (dir, depth) => {
|
|
359
|
-
if (depth > 4) return;
|
|
360
|
-
let ents; try { ents = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
|
361
|
-
for (const e of ents) {
|
|
362
|
-
const p = path.join(dir, e.name);
|
|
363
|
-
if (VENDOR.some((m) => (p + '/').includes(m))) continue;
|
|
364
|
-
if (e.isDirectory()) {
|
|
365
|
-
if (e.name === '.claude') { out.add(dir); continue; }
|
|
366
|
-
if (e.name.startsWith('.') || e.name === 'node_modules') continue;
|
|
367
|
-
walk(p, depth + 1);
|
|
368
|
-
} else if (e.name === '.mcp.json') out.add(dir);
|
|
369
|
-
}
|
|
370
|
-
};
|
|
371
|
-
walk(root, 0);
|
|
372
|
-
return [...out].sort();
|
|
373
|
-
}
|
|
374
354
|
// Text that PRINTS the word npx is not an npx call site. Two of the sites this card warned about were
|
|
375
355
|
// `echo "Session ended. Run: npx aqe learn status"` — advice being displayed to the user, matched as
|
|
376
356
|
// though the machine were executing it. Strip quoted echo/printf payloads before classifying.
|
|
@@ -483,15 +463,25 @@ function memoryRows(file) {
|
|
|
483
463
|
return r.ok ? (r.value === null ? 0 : parseInt(r.value, 10)) : null;
|
|
484
464
|
}
|
|
485
465
|
export function memoryStores(projectDir) {
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
466
|
+
let canonicalPath;
|
|
467
|
+
let resolutionError = null;
|
|
468
|
+
try { canonicalPath = resolveProjectStore({ projectDir }).canonicalAgentDbPath; }
|
|
469
|
+
catch (error) {
|
|
470
|
+
// A resolver refusal (foreign store, broken symlink, or ambiguous project) is an
|
|
471
|
+
// authoritative UNKNOWN. Never probe a guessed path after the resolver declines it.
|
|
472
|
+
canonicalPath = path.join(projectDir, '.swarm', MEMORY_DB_NAMES[0]);
|
|
473
|
+
resolutionError = String(error?.message || error);
|
|
474
|
+
}
|
|
475
|
+
const coordinationPath = path.join(path.dirname(canonicalPath), MEMORY_DB_NAMES[1]);
|
|
476
|
+
const canonical = resolutionError
|
|
477
|
+
? { path: canonicalPath, exists: null, rows: null, status: 'unknown', error: resolutionError }
|
|
478
|
+
: { path: canonicalPath, exists: fs.existsSync(canonicalPath), rows: memoryRows(canonicalPath) };
|
|
479
|
+
const coordination = resolutionError
|
|
480
|
+
? { path: coordinationPath, exists: null, rows: null, status: 'unknown', error: 'canonical project store could not be resolved' }
|
|
481
|
+
: { path: coordinationPath, exists: fs.existsSync(coordinationPath), rows: memoryRows(coordinationPath) };
|
|
482
|
+
// Coordination storage is a separate diagnostic container. It can never become project memory
|
|
483
|
+
// merely because the canonical file is absent, empty, busy, or larger.
|
|
484
|
+
return { canonical, coordination, scored: canonical.path, resolutionError };
|
|
495
485
|
}
|
|
496
486
|
export function resolveMemoryDb(projectDir) {
|
|
497
487
|
return memoryStores(projectDir).scored;
|
|
@@ -519,6 +509,12 @@ function probeMemory(projectDir, { now = Date.now() } = {}) {
|
|
|
519
509
|
// recall quality: honestly NOT probed at render (a true probe needs an embedding query; left for an explicit deep test)
|
|
520
510
|
probes.recallQuality = { status: 'notTested', detail: 'not checked this session — a real recall probe needs an embedding round-trip, which render deliberately avoids' };
|
|
521
511
|
|
|
512
|
+
if (stores.resolutionError) {
|
|
513
|
+
probes.liveness = { status: 'unknown', detail: `canonical project store could not be resolved: ${stores.resolutionError}` };
|
|
514
|
+
probes.coverage = { status: 'unknown', detail: 'project checkpoint cannot be assessed until the canonical store resolves' };
|
|
515
|
+
return probes;
|
|
516
|
+
}
|
|
517
|
+
|
|
522
518
|
if (!fs.existsSync(db)) {
|
|
523
519
|
probes.liveness = { status: 'fail', detail: 'this project has no memory store (.swarm/memory.db) yet' };
|
|
524
520
|
probes.coverage = { status: 'warn', detail: 'no checkpoint — no store has been created here' };
|
|
@@ -580,7 +576,8 @@ function gatherMemory(cwd, { fleet = true } = {}) {
|
|
|
580
576
|
// launched from a subdirectory probes the project root the hook actually wrote to, instead of
|
|
581
577
|
// warning that a snapshot it can see on disk does not exist (#85).
|
|
582
578
|
const scope = projectDirectory({ cwd });
|
|
583
|
-
|
|
579
|
+
let project = scope;
|
|
580
|
+
try { project = resolveProjectStore({ projectDir: scope }).projectRoot; } catch { /* probe reports unavailable */ }
|
|
584
581
|
const projName = project.replace(CONSOLE_ROOT + '/Code/', '').replace(CONSOLE_ROOT + '/', '~/');
|
|
585
582
|
const probes = probeMemory(project);
|
|
586
583
|
const health = scoreMemoryHealth({ project: projName, probes });
|
|
@@ -1534,11 +1531,33 @@ function writeCache(file, at, data, scope = null) {
|
|
|
1534
1531
|
*/
|
|
1535
1532
|
let REFRESH_CHILD = null;
|
|
1536
1533
|
const REFRESH_WEDGED_MS = 5 * 60 * 1000;
|
|
1537
|
-
|
|
1538
|
-
|
|
1534
|
+
let REFRESH_STATE = Object.freeze({ status: 'idle', runId: null, error: null });
|
|
1535
|
+
export function refreshState() { return REFRESH_STATE; }
|
|
1536
|
+
export function classifyRefreshState({ disabled = false, running = false, debounced = false, failed = null, runId = null } = {}) {
|
|
1537
|
+
if (disabled) return Object.freeze({ status: 'disabled', runId: null, error: null });
|
|
1538
|
+
if (failed) return Object.freeze({ status: 'failed', runId, error: String(failed) });
|
|
1539
|
+
if (running || debounced) return Object.freeze({ status: 'already-running', runId, error: debounced ? 'debounced' : null });
|
|
1540
|
+
return Object.freeze({ status: 'started', runId, error: null });
|
|
1541
|
+
}
|
|
1542
|
+
export function settleRefreshState({ currentRunId, runId, code = 0, signal = null } = {}) {
|
|
1543
|
+
if (currentRunId !== runId) return null;
|
|
1544
|
+
if (code !== 0 || signal) return classifyRefreshState({ failed: signal ? `signal ${signal}` : `exit ${code}`, runId });
|
|
1545
|
+
return Object.freeze({ status: 'settled', runId, error: null });
|
|
1546
|
+
}
|
|
1547
|
+
function kickRefreshDetailed({ force = false } = {}) {
|
|
1548
|
+
if (process.env.RUVNET_CONSOLE_DISABLE_BACKGROUND_REFRESH === '1') {
|
|
1549
|
+
REFRESH_STATE = classifyRefreshState({ disabled: true });
|
|
1550
|
+
return REFRESH_STATE;
|
|
1551
|
+
}
|
|
1539
1552
|
const now = Date.now();
|
|
1540
|
-
if (REFRESH_CHILD && now - LAST_REFRESH_KICK < REFRESH_WEDGED_MS)
|
|
1541
|
-
|
|
1553
|
+
if (REFRESH_CHILD && now - LAST_REFRESH_KICK < REFRESH_WEDGED_MS) {
|
|
1554
|
+
REFRESH_STATE = classifyRefreshState({ running: true, runId: REFRESH_STATE.runId });
|
|
1555
|
+
return REFRESH_STATE;
|
|
1556
|
+
}
|
|
1557
|
+
if (!force && now - LAST_REFRESH_KICK < 15000) {
|
|
1558
|
+
REFRESH_STATE = classifyRefreshState({ debounced: true, runId: REFRESH_STATE.runId });
|
|
1559
|
+
return REFRESH_STATE;
|
|
1560
|
+
}
|
|
1542
1561
|
LAST_REFRESH_KICK = now;
|
|
1543
1562
|
try {
|
|
1544
1563
|
// cwd = the SERVED project, NOT REPO. This was `cwd: REPO` and it was a real console-honesty bug
|
|
@@ -1553,12 +1572,32 @@ function kickRefresh({ force = false } = {}) {
|
|
|
1553
1572
|
// contract (the 2026-07-17 outage) — only to which project the background compute is about.
|
|
1554
1573
|
const child = spawn(process.execPath, [SELF, '--refresh-cache'], { detached: true, stdio: 'ignore', cwd: process.cwd() });
|
|
1555
1574
|
REFRESH_CHILD = child;
|
|
1575
|
+
const runId = `${process.pid}-${now}`;
|
|
1576
|
+
REFRESH_STATE = classifyRefreshState({ runId });
|
|
1556
1577
|
// unref() only releases the event-loop hold; these listeners still fire while the server lives.
|
|
1557
|
-
child.on('exit', () => {
|
|
1558
|
-
|
|
1578
|
+
child.on('exit', (code, signal) => {
|
|
1579
|
+
// An older child can exit after a newer run has started. It may only settle its own run.
|
|
1580
|
+
const settled = settleRefreshState({ currentRunId: REFRESH_STATE.runId, runId, code, signal });
|
|
1581
|
+
if (!settled) return;
|
|
1582
|
+
REFRESH_CHILD = null;
|
|
1583
|
+
REFRESH_STATE = settled;
|
|
1584
|
+
});
|
|
1585
|
+
child.on('error', (error) => {
|
|
1586
|
+
const settled = settleRefreshState({ currentRunId: REFRESH_STATE.runId, runId, code: 1, signal: error?.message || error });
|
|
1587
|
+
if (!settled) return;
|
|
1588
|
+
REFRESH_CHILD = null;
|
|
1589
|
+
REFRESH_STATE = settled;
|
|
1590
|
+
});
|
|
1559
1591
|
child.unref(); // let it outlive this request; it writes the caches and exits on its own
|
|
1560
|
-
return
|
|
1561
|
-
} catch {
|
|
1592
|
+
return REFRESH_STATE;
|
|
1593
|
+
} catch (error) {
|
|
1594
|
+
REFRESH_CHILD = null;
|
|
1595
|
+
REFRESH_STATE = classifyRefreshState({ failed: error?.message || error });
|
|
1596
|
+
return REFRESH_STATE;
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
function kickRefresh(options = {}) {
|
|
1600
|
+
return kickRefreshDetailed(options).status === 'started';
|
|
1562
1601
|
}
|
|
1563
1602
|
|
|
1564
1603
|
/**
|
|
@@ -1819,10 +1858,12 @@ function findMemoryStores(root) {
|
|
|
1819
1858
|
const p = path.join(dir, e.name);
|
|
1820
1859
|
if (VENDOR.some((m) => (p + '/').includes(m))) continue;
|
|
1821
1860
|
if (e.name === '.swarm') {
|
|
1822
|
-
//
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1861
|
+
// Fleet activity is project memory, so resolve the canonical store rather than selecting a
|
|
1862
|
+
// coordination database by size. The latter is diagnostic-only and can contain unrelated rows.
|
|
1863
|
+
try {
|
|
1864
|
+
const resolved = resolveProjectStore({ projectDir: dir }).canonicalAgentDbPath;
|
|
1865
|
+
if (fs.existsSync(resolved)) out.push({ project: dir, db: resolved });
|
|
1866
|
+
} catch { /* an unresolvable directory is not a project store */ }
|
|
1826
1867
|
continue;
|
|
1827
1868
|
}
|
|
1828
1869
|
if (e.name.startsWith('.') || e.name === 'node_modules') continue;
|
|
@@ -1833,8 +1874,16 @@ function findMemoryStores(root) {
|
|
|
1833
1874
|
return out;
|
|
1834
1875
|
}
|
|
1835
1876
|
function gatherActivity(cwd) {
|
|
1836
|
-
|
|
1837
|
-
|
|
1877
|
+
let project;
|
|
1878
|
+
let db;
|
|
1879
|
+
try {
|
|
1880
|
+
const resolved = resolveProjectStore({ projectDir: projectDirectory({ cwd }) });
|
|
1881
|
+
project = resolved.projectRoot;
|
|
1882
|
+
db = resolved.canonicalAgentDbPath;
|
|
1883
|
+
} catch {
|
|
1884
|
+
const measuredAt = new Date().toISOString();
|
|
1885
|
+
return { generatedAt: measuredAt, project: path.basename(cwd), hasStore: false, continuity: 'unavailable', ...freshnessOf(measuredAt) };
|
|
1886
|
+
}
|
|
1838
1887
|
if (!fs.existsSync(db)) {
|
|
1839
1888
|
// No store to read — the existence check above IS the entire measurement, so it IS the
|
|
1840
1889
|
// observation instant. Stamped here, not with a value taken before the check ran.
|
|
@@ -2254,7 +2303,7 @@ export function preferredSeat(subs) {
|
|
|
2254
2303
|
function gatherState(cwd, { fleet = true } = {}) {
|
|
2255
2304
|
const wiring = wiringSurvey();
|
|
2256
2305
|
const memory = gatherMemory(cwd, { fleet });
|
|
2257
|
-
try { memory.learnings = learnings(); } catch { memory.learnings = null; }
|
|
2306
|
+
try { memory.learnings = learnings({ cwd }); } catch { memory.learnings = null; }
|
|
2258
2307
|
const savings = gatherSavings();
|
|
2259
2308
|
const cfgNow = readJSON(CONFIG_PATH) || {};
|
|
2260
2309
|
// issue #20: the Savings card's "Turn on smart routing" CTA must reflect what was actually saved —
|
|
@@ -3078,8 +3127,8 @@ function startServer({ port = Number(process.env.CONSOLE_PORT) || 7411, open = f
|
|
|
3078
3127
|
// refresh that did not start must not report that it did, so `started` is the child's
|
|
3079
3128
|
// real answer, not a constant.
|
|
3080
3129
|
expireCachesEmbedding([STATE_CACHE, STACK_CACHE, MEMORY_CACHE, CAPABILITY_CACHE]);
|
|
3081
|
-
const
|
|
3082
|
-
return sendJSON(res, 200, { ok:
|
|
3130
|
+
const refresh = kickRefreshDetailed({ force: true });
|
|
3131
|
+
return sendJSON(res, 200, { ok: refresh.status !== 'failed', started: refresh.status === 'started', refreshing: refresh.status === 'started' || refresh.status === 'already-running', refresh });
|
|
3083
3132
|
}
|
|
3084
3133
|
if (url === '/api/undo') return sendJSON(res, 200, undo(body.undoToken));
|
|
3085
3134
|
if (url === '/api/set-lesson') return sendJSON(res, 200, setLesson(body));
|
|
@@ -18,9 +18,10 @@
|
|
|
18
18
|
* with them.
|
|
19
19
|
*/
|
|
20
20
|
import { spawn } from 'node:child_process';
|
|
21
|
+
import { DUAL_HOST_MODEL_IDS } from '../review-model-defaults.mjs';
|
|
21
22
|
|
|
22
23
|
// Pinned on 2026-09-13 against the native hosts (same ids scripts/dual-host-deliberation.mjs pins).
|
|
23
|
-
export const PRODUCER_MODELS =
|
|
24
|
+
export const PRODUCER_MODELS = DUAL_HOST_MODEL_IDS;
|
|
24
25
|
export const PRODUCER_HOSTS = Object.freeze(['claude', 'codex']);
|
|
25
26
|
export const DEFAULT_ROLES = Object.freeze({ generator: 'claude', judge: 'codex' });
|
|
26
27
|
export const CLAUDE_TIMEOUT_MS = 900_000;
|
|
@@ -4,13 +4,23 @@
|
|
|
4
4
|
//
|
|
5
5
|
// node scripts/private-overlay.mjs --root <kbDir> --from <sidecarDir> --store <name> [--store ...]
|
|
6
6
|
// [--dry-run] [--force] [--alias <name>=<nick,nick>] [--card <name>=<file>]
|
|
7
|
+
// [--origin <label>]
|
|
8
|
+
//
|
|
9
|
+
// `--origin` is stamped into SOURCE.json alongside updateManaged:false (omitted when not passed, so
|
|
10
|
+
// every existing caller's output is byte-identical). scripts/ingest-repo.mjs passes `local-ingest`
|
|
11
|
+
// (S3: explicit local ownership) so a repo pulled in on demand is distinguishable from a genuinely
|
|
12
|
+
// private pre-built sidecar; `--from` may equal `--root` for that case (the bytes are already in
|
|
13
|
+
// place — this writer degenerates to registry stamping only, since every file compares byte-equal
|
|
14
|
+
// to itself).
|
|
7
15
|
//
|
|
8
16
|
// WHY THIS FILE EXISTS (measured 2026-09-11/12). kb/forge-update.mjs preserves exactly one thing
|
|
9
17
|
// across a public bundle apply: SOURCE.json store entries flagged `updateManaged:false`
|
|
10
18
|
// (capturePrivateOverlayState, kb/forge-update.mjs:356-358) — the capture then needs a matching
|
|
11
19
|
// RVF-GENERATIONS.json row with a `file` (:369-372) and picks up `## <name>` cards and alias rows
|
|
12
|
-
// (:402-408).
|
|
13
|
-
//
|
|
20
|
+
// (:402-408). The apply path builds its candidate fresh from the extracted bundle
|
|
21
|
+
// (`restorePrivateFilesIntoCandidate`, forge-update.mjs — the one apply path, S1), so every name the
|
|
22
|
+
// bundle lacks is simply absent from that candidate. Nothing anywhere
|
|
23
|
+
// WROTE that flag: forge-refresh's writeSourceManifestFromLedger records public repos only, and
|
|
14
24
|
// ingest-repo.mjs builds from a git checkout with a canonical URL. So private stores that arrive as
|
|
15
25
|
// pre-built sidecars had no writer, sat in the root unflagged, and 0 of 8 survived the 2026-09-10
|
|
16
26
|
// tree replacement. This is that writer. It ADDS entries and never rebuilds top-level identity:
|
|
@@ -20,7 +30,7 @@
|
|
|
20
30
|
import fs from 'node:fs';
|
|
21
31
|
import path from 'node:path';
|
|
22
32
|
import { pathToFileURL } from 'node:url';
|
|
23
|
-
import { sha256File } from './rvf-generation.mjs';
|
|
33
|
+
import { sha256File, projectSourceStore } from './rvf-generation.mjs';
|
|
24
34
|
|
|
25
35
|
export const REQUIRED_SIDECARS = ['.big.rvf', '.big.rvf.embed.json', '.big.rvf.idmap.json', '.meta.json', '.passages.jsonl'];
|
|
26
36
|
export const OPTIONAL_SIDECARS = ['.symbols.json', '-primer.md'];
|
|
@@ -94,7 +104,7 @@ function cardFor({ from, name, meta, cardFile }) {
|
|
|
94
104
|
throw new Error(`${name}: no card source — add ${name}-primer.md to ${from}, or pass --card ${name}=<file>`);
|
|
95
105
|
}
|
|
96
106
|
|
|
97
|
-
function planStore({ root, from, name, force, aliases, cardFile }) {
|
|
107
|
+
function planStore({ root, from, name, force, aliases, cardFile, origin }) {
|
|
98
108
|
if (!NAME_RE.test(name)) throw new Error(`invalid store name: ${name}`);
|
|
99
109
|
const missing = REQUIRED_SIDECARS.filter((suffix) => !isRegular(path.join(from, name + suffix)));
|
|
100
110
|
if (missing.length) throw new Error(`${name}: missing sidecar(s) in ${from}: ${missing.map((suffix) => name + suffix).join(', ')}`);
|
|
@@ -121,10 +131,21 @@ function planStore({ root, from, name, force, aliases, cardFile }) {
|
|
|
121
131
|
}
|
|
122
132
|
const rvf = path.join(from, `${name}.big.rvf`);
|
|
123
133
|
const card = cardFor({ from, name, meta, cardFile });
|
|
134
|
+
const generation = { file: `${name}.big.rvf`, sha256: sha256File(rvf), bytes: fs.statSync(rvf).size, model: embed.model, dimensions: embed.dimensions, sourceCommit: null, builtUtc };
|
|
124
135
|
return {
|
|
125
|
-
name, files, copies, card, aliases,
|
|
126
|
-
|
|
127
|
-
|
|
136
|
+
name, files, copies, card, aliases, generation,
|
|
137
|
+
// S4 (ONE PROVENANCE RECORD): source.sourceCommit/builtUtc are projected from `generation`
|
|
138
|
+
// above (projectSourceStore, scripts/rvf-generation.mjs) rather than a second, independent
|
|
139
|
+
// copy of the same facts. A private store has no selfUpdate/canonicalBundleUrl/builder —
|
|
140
|
+
// those are public-bundle-only fields, so they are simply never passed in the updater here.
|
|
141
|
+
// `origin` is opt-in (omitted entirely when not supplied, never written as null/undefined) so a
|
|
142
|
+
// caller that does not pass it gets the exact byte-identical stamp this writer has always
|
|
143
|
+
// produced. S3 (explicit local ownership): scripts/ingest-repo.mjs passes origin:'local-ingest'
|
|
144
|
+
// so a store this brain pulled in on demand is distinguishable, in SOURCE.json itself, from one
|
|
145
|
+
// that arrived as a genuinely private pre-built sidecar (a bare "private" sourceRepo alone conflated
|
|
146
|
+
// the two before this).
|
|
147
|
+
source: projectSourceStore(name, generation, { updateManaged: false, sourceRepo: 'private',
|
|
148
|
+
canonicalManifestUrl: null, ...(origin ? { origin } : {}) }),
|
|
128
149
|
};
|
|
129
150
|
}
|
|
130
151
|
|
|
@@ -132,7 +153,7 @@ function planStore({ root, from, name, force, aliases, cardFile }) {
|
|
|
132
153
|
* Add private stores to a live brain root. Idempotent; refuses before writing anything; snapshots
|
|
133
154
|
* every registry it is about to change to `<file>.pre-overlay-<epoch>` first; temp+rename writes.
|
|
134
155
|
*/
|
|
135
|
-
export function applyPrivateOverlay({ root, from, stores, dryRun = false, force = false, aliases = {}, cards = {}, now = Date.now }) {
|
|
156
|
+
export function applyPrivateOverlay({ root, from, stores, dryRun = false, force = false, aliases = {}, cards = {}, now = Date.now, origin = null }) {
|
|
136
157
|
root = path.resolve(String(root || ''));
|
|
137
158
|
from = path.resolve(String(from || ''));
|
|
138
159
|
if (!Array.isArray(stores) || !stores.length) throw new Error('at least one --store <name> is required');
|
|
@@ -158,7 +179,7 @@ export function applyPrivateOverlay({ root, from, stores, dryRun = false, force
|
|
|
158
179
|
schemaVersion: 1, kind: 'ruvnet-brain-private-overlay-receipt', at: new Date(now()).toISOString(),
|
|
159
180
|
root, from, dryRun, force, stores: [],
|
|
160
181
|
};
|
|
161
|
-
const plans = stores.map((name) => planStore({ root, from, name, force, aliases: aliases[name], cardFile: cards[name] }));
|
|
182
|
+
const plans = stores.map((name) => planStore({ root, from, name, force, aliases: aliases[name], cardFile: cards[name], origin }));
|
|
162
183
|
for (const plan of plans) {
|
|
163
184
|
const { name } = plan;
|
|
164
185
|
if (!fence.has(name.toLowerCase())) {
|
|
@@ -226,6 +247,7 @@ function parseArgs(argv) {
|
|
|
226
247
|
else if (flag === '--force') options.force = true;
|
|
227
248
|
else if (flag === '--alias') { const [name, list] = pair(next(), flag); options.aliases[name] = list.split(',').map((s) => s.trim()).filter(Boolean); }
|
|
228
249
|
else if (flag === '--card') { const [name, file] = pair(next(), flag); options.cards[name] = path.resolve(file); }
|
|
250
|
+
else if (flag === '--origin') options.origin = next();
|
|
229
251
|
else throw new Error(`unknown argument: ${flag}`);
|
|
230
252
|
}
|
|
231
253
|
if (!options.root || !options.from || !options.stores.length) {
|
|
@@ -7,10 +7,11 @@ import { canonicalJson, digest } from './coverage-integrity.mjs';
|
|
|
7
7
|
import { retrievalOracleExpectationFromPlan, validateRetrievalOracleReview } from './independent-review-receipt.mjs';
|
|
8
8
|
import { validateRetrievalCanaryPlan, validateRetrievalCanaryReceipt } from './retrieval-canary.mjs';
|
|
9
9
|
import { validateNightlyProofReceipt, validateNativeSchedulerSmoke } from './nightly-two-run-proof.mjs';
|
|
10
|
+
import { CLAUDE_FABLE_5_ID, GPT_5_6_SOL_ID, LEGACY_REVIEW_MODEL_IDS } from './review-model-defaults.mjs';
|
|
10
11
|
|
|
11
12
|
export const PUBLIC_VERIFICATION_OS = Object.freeze(['linux', 'macos', 'windows']);
|
|
12
13
|
export const PUBLIC_VERIFICATION_MODES = Object.freeze(['claude', 'codex', 'dual']);
|
|
13
|
-
export const REQUIRED_REVIEW_MODELS =
|
|
14
|
+
export const REQUIRED_REVIEW_MODELS = LEGACY_REVIEW_MODEL_IDS;
|
|
14
15
|
|
|
15
16
|
const HEX40 = /^[a-f0-9]{40}$/;
|
|
16
17
|
const HEX64 = /^[a-f0-9]{64}$/;
|
|
@@ -110,10 +111,10 @@ export function validateIndependentReviewReceipt(review) {
|
|
|
110
111
|
|| typeof review.execution?.invocationDigest !== 'string' || !HEX64.test(review.execution.invocationDigest)) {
|
|
111
112
|
throw new Error('independent review receipt is malformed, below 95, or incomplete');
|
|
112
113
|
}
|
|
113
|
-
if (review.model ===
|
|
114
|
+
if (review.model === CLAUDE_FABLE_5_ID && review.provider !== 'firstParty') {
|
|
114
115
|
throw new Error('Fable 5 review did not use the verified first-party subscription path');
|
|
115
116
|
}
|
|
116
|
-
if (review.model ===
|
|
117
|
+
if (review.model === GPT_5_6_SOL_ID && (review.provider !== 'openai' || typeof review.execution.threadId !== 'string'
|
|
117
118
|
|| !review.execution.threadId || !HEX64.test(String(review.execution.catalogRowSha256 || '')))) {
|
|
118
119
|
throw new Error('GPT-5.6-Sol review lacks live catalog and thread evidence');
|
|
119
120
|
}
|
|
@@ -654,6 +654,15 @@ export function livePublicationAdapter({ root = process.cwd(), candidateRoot = r
|
|
|
654
654
|
}
|
|
655
655
|
}
|
|
656
656
|
const result = await session.search({ query, k, timeoutMs: DEADLINE_MS });
|
|
657
|
+
if (result.error || !result.mcpResult || (Object.hasOwn(result, 'status') && result.status !== 0)) {
|
|
658
|
+
// Same guard as warmupInstalled() above and host-install-matrix.mjs's equivalent canary
|
|
659
|
+
// search: without it, a real timeout on this full-corpus search reaches
|
|
660
|
+
// parseRetrievalResult() with no mcpResult, which throws the generic "UNKNOWN:
|
|
661
|
+
// incompatible MCP response lacks structured retrieval results" — masking a timeout as a
|
|
662
|
+
// data-shape error. This is the release blocker traced from v4.3.28's public verification
|
|
663
|
+
// failure ("retrieval canary acceptance failed for claude", run 35559726522).
|
|
664
|
+
throw new Error(`installed Brain search failed for ${mode} (query="${query}"): ${result.error?.message || `no MCP result within ${DEADLINE_MS}ms`}`);
|
|
665
|
+
}
|
|
657
666
|
return parseRetrievalResult(result.mcpResult, { query, k });
|
|
658
667
|
},
|
|
659
668
|
|
|
@@ -26,7 +26,6 @@ const LANES = Object.freeze({
|
|
|
26
26
|
// Fail design/provenance issues before any long behavioral or platform lane starts.
|
|
27
27
|
preflight: [
|
|
28
28
|
{ kind: 'command', command: process.execPath, args: ['--check', 'scripts/qe/agentic-qe-4.3.mjs'], timeoutMs: 10_000 },
|
|
29
|
-
{ kind: 'command', command: process.execPath, args: ['--check', 'scripts/qe/aggregate-4.3.mjs'], timeoutMs: 10_000 },
|
|
30
29
|
{ kind: 'command', command: npm, args: ['run', 'version:check'], timeoutMs: 60_000 },
|
|
31
30
|
vitest(['tests/qe/gpt56/critical-risk-map.test.mjs'], 60_000),
|
|
32
31
|
],
|
|
@@ -149,7 +149,7 @@ export async function reconstructGists(source, { fetchFn = globalThis.fetch, con
|
|
|
149
149
|
const digest = sha256(bytes);
|
|
150
150
|
if (digest !== file.sha256) fail(`${gistId}/${file.filename} sha256 ${digest} differs from receipt ${file.sha256}`);
|
|
151
151
|
try {
|
|
152
|
-
return new TextDecoder('utf-8', { fatal: true }).decode(bytes);
|
|
152
|
+
return new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(bytes);
|
|
153
153
|
} catch {
|
|
154
154
|
fail(`${gistId}/${file.filename} is not valid UTF-8 text`);
|
|
155
155
|
return undefined; // unreachable; keeps linters happy about a missing return on every path
|
|
Binary file
|