项目文件夹

文件
wehub-resource-sync 23f7624596
ADR-166 MCP Bridge Security Lock / Static-source security lock (push) Failing after 0s
ADR-166 MCP Bridge Security Lock / Compose default binds loopback + Mongo has auth (push) Failing after 2s
CodeQL Advanced / Analyze (rust) (push) Failing after 0s
ADR-166 MCP Bridge Security Lock / plugin-agent-federation bindHost default (push) Failing after 1s
ADR-166 MCP Bridge Security Lock / Runtime behavior — 401 + terminal gate + fail-closed (push) Failing after 4s
business-pods-smoke / smoke (push) Failing after 1s
all-plugins-smoke / smoke-all (push) Failing after 2s
CI/CD Pipeline / Security & Code Quality (push) Failing after 1s
CI/CD Pipeline / Test Suite (ubuntu-latest) (push) Failing after 1s
CI/CD Pipeline / Build & Package (macos-latest) (push) Has been skipped
CI/CD Pipeline / Build & Package (ubuntu-latest) (push) Has been skipped
CI/CD Pipeline / Build & Package (windows-latest) (push) Has been skipped
CI/CD Pipeline / Documentation & Examples (push) Failing after 1s
Clone Tracker (14-day rolling) / Snapshot clones for ruflo ecosystem (push) Failing after 1s
CodeQL Advanced / Analyze (actions) (push) Failing after 1s
CodeQL Advanced / Analyze (javascript-typescript) (push) Failing after 1s
federation-peer-rust / stable-noop (push) Failing after 1s
metaharness-ci / score (push) Failing after 1s
metaharness-ci / router-compat (push) Failing after 0s
metaharness-ci / similarity-tests (push) Failing after 0s
no-agentbbs-smoke / smoke-without-agentbbs (push) Failing after 1s
V3 CI/CD Pipeline / Build V3 (windows-latest) (push) Has been skipped
codex-integration-audit / Codex integration audit (push) Failing after 1s
helpers-manifest-guard / guard (push) Failing after 1s
🔗 Cross-Agent Integration Tests / 🤝 Agent Coordination Tests (push) Has been skipped
🔗 Cross-Agent Integration Tests / 🧠 Memory Sharing Integration (push) Has been skipped
🔗 Cross-Agent Integration Tests / 🛡️ Fault Tolerance Tests (push) Has been skipped
🔗 Cross-Agent Integration Tests / ⚡ Performance Integration Tests (push) Has been skipped
metaharness-ci / mcp-scan (push) Failing after 1s
metaharness-ci / eject-dryrun (push) Failing after 1s
metaharness-ci / metaharness-real-data (push) Failing after 0s
no-cli-optdep-bloat-2561 / guard (push) Failing after 1s
no-metaharness-smoke / smoke-without-metaharness (push) Failing after 1s
no-phantom-agentic-flow-subpath / guard (push) Failing after 1s
🔄 Automated Rollback Manager / 🚨 Failure Detection (push) Failing after 1s
V3 CI/CD Pipeline / Plugin hooks smoke / ubuntu-latest / Node 22 (push) Failing after 1s
V3 CI/CD Pipeline / ruflo-graph-intelligence build + test smoke (#2044, ADR-123) (push) Failing after 1s
CVE Audit Gate / Audit root (critical-blocking) (push) Failing after 2s
cost-tracker-smoke / smoke (push) Failing after 3s
oia-audit-weekly / audit (push) Failing after 2s
ruflo-agent-smoke / ruflo-agent structural smoke (push) Failing after 1s
📊 Status Badges Update / 📊 Update Status Badges (push) Failing after 1s
V3 CI/CD Pipeline / Static regression guards (#2267 YAML + (push) Failing after 1s
V3 CI/CD Pipeline / Test V3 Packages (push) Failing after 0s
V3 CI/CD Pipeline / agent_execute provider routing smoke (#2042) (push) Failing after 0s
CVE Audit Gate / Audit v3 (critical-blocking) (push) Failing after 1s
federation-peer-rust / stable-native (push) Failing after 2s
🔗 Cross-Agent Integration Tests / 🚀 Integration Test Setup (push) Failing after 2s
neural-trader-smoke / runtime-smoke (push) Failing after 1s
V3 CI/CD Pipeline / Build V3 (macos-latest) (push) Has been skipped
V3 CI/CD Pipeline / Build V3 (ubuntu-latest) (push) Has been skipped
V3 CI/CD Pipeline / Type Check V3 (push) Failing after 1s
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / ubuntu-latest / Node 24 (push) Failing after 1s
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / ubuntu-latest / Node 22 (push) Failing after 2s
V3 CI/CD Pipeline / browser rvf create flag smoke (#2015) (push) Failing after 0s
V3 CI/CD Pipeline / Dependency review (#2046) (push) Has been skipped
V3 CI/CD Pipeline / Supply-chain audit (#2046) (push) Failing after 0s
V3 CI/CD Pipeline / witness marker drift smoke (#2021) (push) Failing after 1s
V3 CI/CD Pipeline / neural-trader portfolio CG smoke (#2068, ADR-126 Phase 3) (push) Failing after 1s
V3 CI/CD Pipeline / neural-trader backtest signing smoke (#2068, ADR-126 Phase 4) (push) Failing after 1s
V3 CI/CD Pipeline / kg-extract type-import classification smoke (#2049) (push) Failing after 0s
V3 CI/CD Pipeline / witness verify precondition smoke (#1880) (push) Failing after 2s
V3 CI/CD Pipeline / neural-trader pipeline risk-gate smoke (#2068, ADR-126 Phase 5) (push) Failing after 0s
V3 CI/CD Pipeline / neural-trader feature attribution smoke (#2068, ADR-126 Phase 6) (push) Failing after 0s
V3 CI/CD Pipeline / plugin-registry signature verification smoke (#1922, CWE-347) (push) Failing after 4s
V3 CI/CD Pipeline / memory stats legacy-DB smoke (#2120) (push) Failing after 4s
V3 CI/CD Pipeline / github deprecated actions smoke (#2089, ADR-127 Phase 3) (push) Failing after 1s
V3 CI/CD Pipeline / graph query + pathfinder smoke (ADR-130 P2+P5) (push) Has been skipped
V3 CI/CD Pipeline / graph trajectory hooks smoke (ADR-130 P3) (push) Has been skipped
V3 CI/CD Pipeline / graph plugin adapter smoke (ADR-130 P4) (push) Has been skipped
V3 CI/CD Pipeline / graph benchmark (ADR-130 P6) (push) Has been skipped
V3 CI/CD Pipeline / statusline generator delegation smoke (#2195) (push) Failing after 1s
V3 CI/CD Pipeline / wizard init regression guard (#2206 (push) Failing after 1s
V3 CI/CD Pipeline / memory no-stray-db smoke (ADR-125 P7) (push) Failing after 1s
V3 CI/CD Pipeline / github-safe injection smoke (#2089, ADR-127 Phase 1) (push) Failing after 1s
V3 CI/CD Pipeline / github actions pin smoke (#2089, ADR-127 Phase 1) (push) Failing after 1s
V3 CI/CD Pipeline / github attribution opt-in smoke (#2089, ADR-127 Phase 4) (push) Failing after 1s
V3 CI/CD Pipeline / pre-bash hook safety smoke (#2017) (push) Failing after 1s
V3 CI/CD Pipeline / Memory import smoke / ubuntu-latest (push) Failing after 0s
V3 CI/CD Pipeline / MCP protocol smoke / ubuntu-latest (push) Failing after 2s
V3 CI/CD Pipeline / ruvllm WASM auto-init smoke (#2086) (push) Failing after 4s
V3 CI/CD Pipeline / MCP paired-tool round-trip smoke (#1889) (push) Failing after 1s
V3 CI/CD Pipeline / Plugin package install-safety (#1902/#1903/#1904) (push) Failing after 1s
V3 CI/CD Pipeline / Tool description discoverability (ADR-112) (push) Failing after 3s
V3 CI/CD Pipeline / CLI npx-install smoke (#1147 / (22) (push) Failing after 1s
V3 CI/CD Pipeline / CLI npx-install smoke (#1147 / (24) (push) Failing after 1s
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / ubuntu-latest (push) Failing after 2s
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / ubuntu-latest (push) Failing after 1s
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / ubuntu-latest (push) Failing after 1s
V3 CI/CD Pipeline / Vector-index dimension audit (#1947) (push) Failing after 0s
V3 CI/CD Pipeline / Hook-command install safety (#1921) (push) Failing after 1s
V3 CI/CD Pipeline / ToolOutputGuardrail smoke (ADR-131, (push) Failing after 1s
V3 CI/CD Pipeline / init-bundle invariants smoke (#2095, ADR-128 Phase 5) (push) Failing after 1s
V3 CI/CD Pipeline / wasm provider bridge smoke (ADR-129 P1) (push) Failing after 2s
V3 CI/CD Pipeline / wasm gallery CRUD smoke (ADR-129 P3) (push) Failing after 1s
V3 CI/CD Pipeline / wasm plugin bridge smoke (ADR-129 P4) (push) Failing after 0s
V3 CI/CD Pipeline / wasm compose smoke (ADR-129 P2) (push) Failing after 4s
V3 CI/CD Pipeline / graph schema smoke (ADR-130 P1) (push) Failing after 0s
Validate Marketplace / validate (push) Failing after 1s
🔍 Verification Pipeline / 🚀 Setup Verification (push) Failing after 1s
🔍 Verification Pipeline / 🛡️ Security Verification (push) Has been skipped
🔍 Verification Pipeline / 📝 Code Quality (push) Has been skipped
🔍 Verification Pipeline / 🧪 Test Verification (${{ matrix.os }}, Node ${{ matrix.node }}) (push) Has been skipped
🔍 Verification Pipeline / 🏗️ Build Verification (push) Has been skipped
🔍 Verification Pipeline / 📚 Documentation Verification (push) Has been skipped
CVE Audit Gate / High-severity report (warn only) (push) Has been cancelled
🔄 Automated Rollback Manager / 🔄 Execute Rollback (push) Has been cancelled
🔄 Automated Rollback Manager / ✅ Post-Rollback Verification (push) Has been cancelled
🔄 Automated Rollback Manager / 📊 Rollback Monitoring (push) Has been cancelled
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook execution smoke (#2132) / windows-latest (push) Has been cancelled
🔄 Automated Rollback Manager / ⏳ Manual Rollback Approval (push) Has been cancelled
V3 CI/CD Pipeline / MCP protocol smoke / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Memory import smoke / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows hook shim smoke (#2132) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Windows init hooks smoke (#2132) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / macos-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / ubuntu-latest (push) Has been cancelled
V3 CI/CD Pipeline / Witness verify (signed manifest) / windows-latest (push) Has been cancelled
V3 CI/CD Pipeline / Publish to npm (alpha) (push) Has been cancelled
V3 CI/CD Pipeline / Smoke (no better-sqlite3) / macos-latest / Node 22 (push) Has been cancelled
V3 CI/CD Pipeline / Plugin hooks smoke / macos-latest / Node 22 (push) Has been cancelled
CI/CD Pipeline / Deploy & Release (push) Has been cancelled
CI/CD Pipeline / CI Status (push) Has been cancelled
🔗 Cross-Agent Integration Tests / 📊 Integration Test Report (push) Has been cancelled
🔄 Automated Rollback Manager / 🔍 Pre-Rollback Validation (push) Has been cancelled
🔍 Verification Pipeline / ⚡ Performance Verification (push) Has been cancelled
🔍 Verification Pipeline / 📊 Verification Report (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:02:19 +08:00

331 行
12 KiB
JavaScript
可执行文件

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
// _harness.mjs — shared invocation helper for the metaharness/harness CLIs.
//
// All ruflo-metaharness skills shell out to the upstream CLI rather than
// linking the library — this honors ADR-150's architectural constraint
// (MetaHarness must remain a removable augmentation, never a required
// runtime dep) while still giving us "deep integration" through a single
// vetted bridge that every skill imports from.
//
// CONTRACT
// - `runMetaharness(args, opts)` — invoke the `metaharness` binary
// - `runHarness(args, opts)` — invoke the sibling `harness` binary
// - both return `{ stdout, stderr, exitCode, json|null, durationMs, degraded, reason? }`
// - `--json` flag is appended automatically when `opts.json !== false`
// - subprocess hard timeout (default 60s) — captured in opts.timeoutMs
// - when the package cannot be resolved/installed, returns degraded result
// with `degraded: true, reason: 'metaharness-not-available'` — never
// throws (ADR-150 graceful-degradation rule #3). A subprocess killed by
// the timeout reports `reason: 'metaharness-timeout'` instead (matches
// the sibling _darwin/_redblue helpers).
//
// SECURITY + PERF (supersedes the iter-27 npx-with-@latest-dist-tag path)
// =========================================================================
// The pre-consolidation implementation shelled to `npx -y` with the @latest
// dist-tag. Two problems, both fixed here:
// 1. SECURITY (HIGH): @latest means a compromised upstream publish executes
// arbitrary code on user machines on the very next skill invocation.
// The version is now PINNED to METAHARNESS_PIN_VERSION (tilde range —
// patch updates only) and only bumped deliberately, in lock-step with
// optionalDependencies in @claude-flow/cli + ruflo package.json.
// 2. PERF: @latest forced an npm-registry metadata check on EVERY call.
// Resolution is now (a) an already-installed local metaharness
// satisfying the pin (walk-up node_modules — free), then (b) a ONE-TIME
// `npm install --prefix ~/.ruflo/metaharness-cache-<pin>` versioned
// cache (same proven pattern as _redblue.mjs / gepa.mjs), after which
// every call is a plain `node <abs-path-to-cli>` spawn — zero network.
//
// BOTH BINARIES: the `metaharness` package ships two bins (`metaharness`
// and `harness`); their entry paths are read from the resolved package.json
// bin map rather than hardcoded, so upstream layout changes inside the
// pinned range cannot silently break us.
import { spawnSync, spawn } from 'node:child_process';
import { existsSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
import {
classifyDegraded,
ensureCachedInstall,
findLocalPackageDir,
injectJson,
makeDegradedEmitter,
parseTrailingJson,
} from './_invoke.mjs';
const DEFAULT_TIMEOUT_MS = 60_000;
// Pinned semver range. Bump in lock-step with optionalDependencies in
// @claude-flow/cli/package.json + ruflo/package.json. NEVER @latest.
const METAHARNESS_PKG = 'metaharness';
const METAHARNESS_PIN_VERSION = '~0.3.0';
const REASON_NOT_AVAILABLE = 'metaharness-not-available';
/**
* Resolve absolute paths for the two bins shipped by the metaharness
* package. Memoized per process — the whole point is that after the first
* resolution (or one-time cache install) every call is local-only.
*/
let RESOLVED = null;
function resolveMetaharnessBins() {
if (RESOLVED) return RESOLVED;
// (a) already-installed local copy satisfying the pin — free.
const localDir = findLocalPackageDir(METAHARNESS_PKG, METAHARNESS_PIN_VERSION);
if (localDir) {
const bins = readBinMap(localDir);
if (bins) return (RESOLVED = { ok: true, bins, source: 'local' });
}
// (b) one-time versioned cache install of the pinned range.
const cached = ensureCachedInstall({ pkg: METAHARNESS_PKG, pinVersion: METAHARNESS_PIN_VERSION });
if (!cached.ok) {
return (RESOLVED = {
ok: false,
reason: REASON_NOT_AVAILABLE,
stderr: cached.stderr ?? cached.error ?? '',
stdout: cached.stdout ?? '',
});
}
const bins = readBinMap(cached.pkgDir);
if (!bins) {
return (RESOLVED = {
ok: false,
reason: REASON_NOT_AVAILABLE,
stderr: `metaharness package at ${cached.pkgDir} is missing the expected bin map (metaharness + harness)`,
stdout: '',
});
}
return (RESOLVED = { ok: true, bins, source: 'cache' });
}
/** Read both bin entry points from the package's bin map. */
function readBinMap(pkgDir) {
try {
const pj = JSON.parse(readFileSync(join(pkgDir, 'package.json'), 'utf-8'));
const bin = pj.bin || {};
if (!bin.metaharness || !bin.harness) return null;
const metaharness = join(pkgDir, bin.metaharness);
const harness = join(pkgDir, bin.harness);
if (!existsSync(metaharness) || !existsSync(harness)) return null;
return { metaharness, harness };
} catch {
return null;
}
}
function degradedResult(resolved, start) {
return {
stdout: resolved.stdout ?? '',
stderr: resolved.stderr ?? '',
exitCode: 127,
json: null,
durationMs: Date.now() - start,
degraded: true,
reason: resolved.reason ?? REASON_NOT_AVAILABLE,
};
}
/**
* Async variant (iter 56) — used by oia-audit.mjs to parallelize its 5
* subprocess calls, dropping worst-case wall-clock from 5×TIMEOUT to
* 1×TIMEOUT. Identical return shape to the sync path.
*/
function execBinAsync(binName, args, opts = {}) {
const start = Date.now();
const resolved = resolveMetaharnessBins();
if (!resolved.ok) return Promise.resolve(degradedResult(resolved, start));
return new Promise((resolve) => {
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
const wantJson = opts.json !== false;
const argv = injectJson(args, wantJson);
const p = spawn('node', [resolved.bins[binName], ...argv], {
stdio: ['ignore', 'pipe', 'pipe'],
cwd: opts.cwd,
env: { ...process.env, ...(opts.env || {}) },
shell: false,
});
let stdout = '', stderr = '';
p.stdout?.on('data', (d) => { stdout += d.toString(); });
p.stderr?.on('data', (d) => { stderr += d.toString(); });
let timedOut = false;
const timer = setTimeout(() => {
timedOut = true;
try { p.kill('SIGTERM'); } catch { /* ignore */ }
}, timeoutMs);
p.on('error', (e) => {
clearTimeout(timer);
resolve({
stdout, stderr: stderr + String(e?.message ?? e),
exitCode: 127, json: null, durationMs: Date.now() - start,
degraded: true, reason: REASON_NOT_AVAILABLE,
});
});
p.on('close', (code) => {
clearTimeout(timer);
const durationMs = Date.now() - start;
const classified = classifyDegraded(stderr, timedOut ? null : code, 'metaharness');
if (classified.degraded) {
resolve({
stdout, stderr,
exitCode: code ?? 127, json: null, durationMs,
degraded: true, reason: classified.reason,
});
return;
}
resolve({
stdout, stderr,
exitCode: code ?? 0,
json: wantJson ? parseTrailingJson(stdout) : null,
durationMs,
degraded: false,
});
});
});
}
/** Sync invocation of one of the two resolved bins via `node <abs-path>`. */
function execBin(binName, args, opts = {}) {
const start = Date.now();
const resolved = resolveMetaharnessBins();
if (!resolved.ok) return degradedResult(resolved, start);
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
const wantJson = opts.json !== false;
const argv = injectJson(args, wantJson);
const r = spawnSync('node', [resolved.bins[binName], ...argv], {
stdio: ['ignore', 'pipe', 'pipe'],
encoding: 'utf-8',
timeout: timeoutMs,
cwd: opts.cwd, // iter 27 — let callers redirect $CWD (mint.mjs needs this)
env: { ...process.env, ...(opts.env || {}) },
shell: false,
});
const durationMs = Date.now() - start;
const stdout = r.stdout || '';
const stderr = r.stderr || '';
const classified = classifyDegraded(stderr, r.status, 'metaharness');
if (classified.degraded) {
return {
stdout, stderr,
exitCode: r.status ?? 127,
json: null,
durationMs,
degraded: true,
reason: classified.reason,
};
}
return {
stdout, stderr,
exitCode: r.status ?? 0,
json: wantJson ? parseTrailingJson(stdout) : null,
durationMs,
degraded: false,
};
}
export function runMetaharness(args, opts) {
return execBin('metaharness', args, opts);
}
export function runHarness(args, opts) {
// The `harness` binary ships inside the same `metaharness` package —
// resolved from the package.json bin map above.
return execBin('harness', args, opts);
}
export function runMetaharnessAsync(args, opts) {
return execBinAsync('metaharness', args, opts);
}
export function runHarnessAsync(args, opts) {
return execBinAsync('harness', args, opts);
}
/**
* iter 63 — single source of truth for severity ranks across the
* metaharness plugin family. Pre-iter-63, three scripts (oia-audit,
* audit-trend, mcp-scan) maintained their own SEVERITY_RANK literal,
* each missing different keys, each producing different NaN-compare
* behavior on unknown severities. Iter 62 fixed oia-audit; iter 63
* propagates the fix and consolidates.
*
* Mapping rationale:
* clean / info → 0 (no harm)
* low → 1
* medium / warn → 2
* high / error → 3
* critical → 4 (explicit elevation above high)
*
* `rankSeverity(s)` is the safe accessor — returns 0 for any unknown
* string instead of `undefined`, eliminating the NaN-compare hazard
* (`undefined > 3` evaluates to false → unknown severities silently
* ignored in reduce expressions).
*/
export const SEVERITY_RANK = Object.freeze({
clean: 0, info: 0,
low: 1,
medium: 2, warn: 2,
high: 3, error: 3,
critical: 4,
});
export function rankSeverity(s) {
if (s == null) return 0;
return SEVERITY_RANK[String(s).toLowerCase()] ?? 0;
}
/**
* iter 50 — parse `harness mcp-scan` text output into structured findings.
*
* Upstream `harness mcp-scan` emits plain text even with --json:
*
* harness mcp-scan — <path>
*
* [INFO] No MCP security issues found
* Policy is default-deny with safe capability grants and an audit log.
*
* Result: INFO (1 finding, 0 high)
*
* Closes the iter-49-flagged gap where audit-trend.mjs reads
* `json.findings` expecting an array, but mcp-scan's r.json was null.
* Used by BOTH mcp-scan.mjs (the wrapper) and oia-audit.mjs (composite
* audit) so the structured-findings invariant holds across the pipeline.
*/
export function parseMcpScanText(stdout) {
const findings = [];
const lines = (stdout || '').split('\n');
let current = null;
for (const line of lines) {
const m = /^\s*\[([A-Z]+)\]\s+(.+?)\s*$/.exec(line);
if (m) {
if (current) findings.push(current);
current = { severity: m[1].toLowerCase(), message: m[2] };
} else if (current && /^\s{6,}\S/.test(line)) {
const cont = line.trim();
if (cont) current.message += ' ' + cont;
} else if (current && line.trim() === '') {
findings.push(current);
current = null;
}
}
if (current) findings.push(current);
const resultMatch = /Result:\s+([A-Z]+)\s+\((\d+)\s+finding/i.exec(stdout);
const summary = resultMatch ? {
overallSeverity: resultMatch[1].toLowerCase(),
totalCount: parseInt(resultMatch[2], 10),
} : null;
return { findings, summary };
}
// Convenience emitter for skill scripts — keep the boilerplate out of
// each skill so they focus on argument parsing + exit-code semantics.
// Exit 0 — ADR-150 architectural constraint says ruflo continues to
// function when MetaHarness is absent. Skills emit a structured
// degraded payload rather than failing.
export const emitDegradedJsonAndExit = makeDegradedEmitter(METAHARNESS_PKG, METAHARNESS_PIN_VERSION);
export const METAHARNESS_VERSION_PIN = `${METAHARNESS_PKG}@${METAHARNESS_PIN_VERSION}`;
/** Expose resolution for diagnostics/tests (which path served the bins). */
export function metaharnessResolution() {
const r = resolveMetaharnessBins();
return r.ok ? { ok: true, source: r.source, bins: r.bins } : { ok: false, reason: r.reason };
}