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
290 lines
14 KiB
TypeScript
290 lines
14 KiB
TypeScript
/**
|
|
* distill-oracle.test.ts — unit tests for the tiered `resolved` labeler.
|
|
*
|
|
* Every external boundary is MOCKED: the Tier-1 SSH runner is injected (no real
|
|
* ssh), and the Tier-2 Fable harness is injected (no real claude / spend). The
|
|
* suite asserts:
|
|
* • the ZERO-EXTERNAL-CALL default path (no opts ⇒ dry-run + structural proxy)
|
|
* • provenance tagging on every tier (oracle:test-exec / judge:fable / proxy:structural)
|
|
* • tier fallback order (oracle → fable → proxy) per trajectory
|
|
* • Tier-1 command construction + remote parameterization (no hard-coded host)
|
|
* • graceful degradation (probe failure, harness omission → proxy fallback)
|
|
*/
|
|
|
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
import {
|
|
labelResolved,
|
|
reflectFailures,
|
|
buildOraclePlan,
|
|
hasMechanicalSpec,
|
|
resolveRemote,
|
|
createSshOracleRunner,
|
|
type Trajectory,
|
|
type OracleRunner,
|
|
type CommandExec,
|
|
} from '../../src/services/distill-oracle.js';
|
|
import type { FableHarness, JudgeResult, ReflectResult } from '../../src/services/fable-harness.js';
|
|
|
|
// A trajectory with a mechanical spec (SWE-bench shape).
|
|
const specTraj = (id: string): Trajectory => ({
|
|
id,
|
|
task: 'Fix the null deref in parser',
|
|
output: 'export function parse(x){ return x?.value ?? null; }',
|
|
testSpec: { failToPass: ['test_parse_null'], repo: 'acme/parser', baseCommit: 'abc123', patch: 'diff --git a b' },
|
|
});
|
|
|
|
// A trajectory with NO mechanical spec (only task/output).
|
|
const proseTraj = (id: string, output = 'export function add(a,b){ return a+b; }'): Trajectory => ({
|
|
id,
|
|
task: 'Implement add',
|
|
output,
|
|
});
|
|
|
|
/** A runner spy that records calls and returns canned outcomes. */
|
|
function spyRunner(overrides: Partial<OracleRunner> = {}): OracleRunner & { probeCalls: number; evalCalls: number } {
|
|
const r = {
|
|
probeCalls: 0,
|
|
evalCalls: 0,
|
|
async preflight() { r.probeCalls++; return { ok: true, reason: 'ok' }; },
|
|
async runEval() { r.evalCalls++; return { resolved: true, reason: 'FAIL_TO_PASS green' }; },
|
|
...overrides,
|
|
} as OracleRunner & { probeCalls: number; evalCalls: number };
|
|
return r;
|
|
}
|
|
|
|
/** A harness stub with only the methods labelResolved uses. */
|
|
function stubHarness(verdicts: JudgeResult[], reflections: ReflectResult[] = []): FableHarness {
|
|
return {
|
|
isEnabled: () => true,
|
|
judgeBatch: vi.fn(async () => verdicts),
|
|
reflectFailures: vi.fn(async () => reflections),
|
|
getSpentUsd: () => 0,
|
|
} as unknown as FableHarness;
|
|
}
|
|
|
|
describe('labelResolved — ZERO-external-call default path', () => {
|
|
it('labels a spec trajectory via dry-run preflight + structural proxy, touching nothing', async () => {
|
|
const runner = spyRunner();
|
|
const harness = stubHarness([]);
|
|
const [out] = await labelResolved([specTraj('a')], { runner, harness });
|
|
|
|
// Provenance is the structural proxy — the honest weakest tier.
|
|
expect(out.resolvedBy).toBe('proxy:structural');
|
|
// The oracle plan is attached as a DRY-RUN preflight (transparency)…
|
|
expect(out.oraclePreflight?.dryRun).toBe(true);
|
|
expect(out.oraclePreflight?.plan.evalCommands.length).toBeGreaterThan(0);
|
|
// …but NEITHER the SSH runner NOR the Fable harness was ever invoked.
|
|
expect(runner.probeCalls).toBe(0);
|
|
expect(runner.evalCalls).toBe(0);
|
|
expect(harness.judgeBatch).not.toHaveBeenCalled();
|
|
expect(typeof out.resolved).toBe('boolean');
|
|
expect(typeof out.resolvedConfidence).toBe('number');
|
|
});
|
|
|
|
it('labels a prose trajectory via structural proxy only', async () => {
|
|
const [good] = await labelResolved([proseTraj('g')]);
|
|
expect(good.resolvedBy).toBe('proxy:structural');
|
|
expect(good.resolved).toBe(true); // clean code passes structural signals
|
|
|
|
const [bad] = await labelResolved([proseTraj('b', 'I cannot help with that.')]);
|
|
expect(bad.resolvedBy).toBe('proxy:structural');
|
|
expect(bad.resolved).toBe(false); // refusal → not resolved
|
|
});
|
|
|
|
it('preserves passthrough fields and ids', async () => {
|
|
const t: Trajectory = { id: 'keep', task: 'x', output: 'export const y=1;', extra: 42 };
|
|
const [out] = await labelResolved([t]);
|
|
expect(out.id).toBe('keep');
|
|
expect((out as { extra?: number }).extra).toBe(42);
|
|
});
|
|
});
|
|
|
|
describe('labelResolved — Tier 1 mechanical oracle (execute)', () => {
|
|
it('runs the real eval and tags oracle:test-exec on success', async () => {
|
|
const runner = spyRunner();
|
|
const [out] = await labelResolved([specTraj('a')], { remote: 'testhost', execute: true, runner });
|
|
expect(out.resolvedBy).toBe('oracle:test-exec');
|
|
expect(out.resolved).toBe(true);
|
|
expect(out.resolvedConfidence).toBe(1);
|
|
expect(out.oraclePreflight?.dryRun).toBe(false);
|
|
expect(runner.probeCalls).toBe(1);
|
|
expect(runner.evalCalls).toBe(1);
|
|
});
|
|
|
|
it('propagates a failing eval as resolved=false with oracle provenance', async () => {
|
|
const runner = spyRunner({ async runEval() { return { resolved: false, reason: 'still red' }; } });
|
|
const [out] = await labelResolved([specTraj('a')], { remote: 'testhost', execute: true, runner });
|
|
expect(out.resolvedBy).toBe('oracle:test-exec');
|
|
expect(out.resolved).toBe(false);
|
|
expect(out.resolvedReason).toContain('still red');
|
|
});
|
|
|
|
it('falls through to the proxy when the preflight probe fails (non-fatal)', async () => {
|
|
const runner = spyRunner({ async preflight() { return { ok: false, reason: 'ssh unreachable' }; } });
|
|
const [out] = await labelResolved([specTraj('a')], { remote: 'testhost', execute: true, runner });
|
|
expect(out.resolvedBy).toBe('proxy:structural'); // probe failed → fell through
|
|
expect(runner.evalCalls).toBe(0); // never ran the eval after a failed probe
|
|
expect(out.oraclePreflight?.probe?.ok).toBe(false);
|
|
expect(out.oraclePreflight?.fellThroughBecause).toContain('preflight failed');
|
|
});
|
|
});
|
|
|
|
describe('labelResolved — Tier 2 Fable judge (opt-in + budget)', () => {
|
|
it('judges prose trajectories via the harness and tags judge:fable', async () => {
|
|
const harness = stubHarness([{ id: 'g', resolved: true, confidence: 0.77, reason: 'looks correct' }]);
|
|
const [out] = await labelResolved([proseTraj('g')], { fableJudge: true, maxBudgetUsd: 1, harness });
|
|
expect(out.resolvedBy).toBe('judge:fable');
|
|
expect(out.resolved).toBe(true);
|
|
expect(out.resolvedConfidence).toBeCloseTo(0.77, 2);
|
|
expect(out.resolvedReason).toBe('looks correct');
|
|
expect(harness.judgeBatch).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('does NOT call fable when fableJudge is set but no budget cap is given', async () => {
|
|
const harness = stubHarness([{ id: 'g', resolved: true, confidence: 1, reason: 'x' }]);
|
|
const [out] = await labelResolved([proseTraj('g')], { fableJudge: true, harness }); // no maxBudgetUsd
|
|
expect(out.resolvedBy).toBe('proxy:structural');
|
|
expect(harness.judgeBatch).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('falls back to proxy for any id the model omits', async () => {
|
|
const harness = stubHarness([{ id: 'g0', resolved: false, confidence: 0.2, reason: 'wrong' }]); // omits g1
|
|
const out = await labelResolved([proseTraj('g0'), proseTraj('g1')], { fableJudge: true, maxBudgetUsd: 1, harness });
|
|
expect(out[0].resolvedBy).toBe('judge:fable');
|
|
expect(out[1].resolvedBy).toBe('proxy:structural');
|
|
});
|
|
});
|
|
|
|
describe('labelResolved — tier fallback ORDER', () => {
|
|
it('spec + execute:false + fableJudge ⇒ dry-run oracle THEN fable (order preserved)', async () => {
|
|
const runner = spyRunner();
|
|
const harness = stubHarness([{ id: 'a', resolved: true, confidence: 0.9, reason: 'judged' }]);
|
|
const [out] = await labelResolved([specTraj('a')], { fableJudge: true, maxBudgetUsd: 1, runner, harness });
|
|
// Tier 1 was dry-run (no exec) so it fell through to Tier 2 (fable).
|
|
expect(out.resolvedBy).toBe('judge:fable');
|
|
expect(out.oraclePreflight?.dryRun).toBe(true); // Tier-1 plan still attached
|
|
expect(runner.evalCalls).toBe(0); // never executed
|
|
});
|
|
|
|
it('mixed batch: spec+execute → oracle, prose → fable, refusal via fable', async () => {
|
|
const runner = spyRunner();
|
|
const harness = stubHarness([{ id: 'p', resolved: false, confidence: 0.3, reason: 'incomplete' }]);
|
|
const out = await labelResolved([specTraj('s'), proseTraj('p')], {
|
|
remote: 'h', execute: true, fableJudge: true, maxBudgetUsd: 1, runner, harness,
|
|
});
|
|
const byId = Object.fromEntries(out.map((o) => [o.id, o.resolvedBy]));
|
|
expect(byId['s']).toBe('oracle:test-exec');
|
|
expect(byId['p']).toBe('judge:fable');
|
|
});
|
|
});
|
|
|
|
describe('buildOraclePlan — command construction & remote parameterization', () => {
|
|
it('builds a SWE-bench eval plan and injects the remote host (never hard-coded)', () => {
|
|
const plan = buildOraclePlan(
|
|
{ failToPass: ['t_a', 't_b'], passToPass: ['t_c'], repo: 'acme/x', baseCommit: 'deadbeef', patch: 'diff' },
|
|
'my-remote-host',
|
|
);
|
|
expect(plan.kind).toBe('ssh-swebench-eval');
|
|
expect(plan.remote).toBe('my-remote-host');
|
|
const all = [...plan.probeCommands, ...plan.evalCommands].join('\n');
|
|
expect(all).toContain('my-remote-host');
|
|
expect(all).toContain('--fail-to-pass');
|
|
expect(all).not.toMatch(/ruvultra|\d+\.\d+\.\d+\.\d+/); // no hard-coded host or IP
|
|
expect(plan.probeCommands.some((c) => c.includes('command -v docker'))).toBe(true);
|
|
});
|
|
|
|
it('builds a darwin bench-suite plan when benchSuite/benchCase present', () => {
|
|
const plan = buildOraclePlan({ benchSuite: 'shield', benchCase: 'cve-42' }, 'h');
|
|
expect(plan.kind).toBe('ssh-darwin-bench');
|
|
expect(plan.evalCommands.join(' ')).toContain('darwin bench run');
|
|
expect(plan.evalCommands.join(' ')).toContain("--suite 'shield'");
|
|
});
|
|
|
|
it('renders a <remote> placeholder (not a real host) when no remote is configured', () => {
|
|
const plan = buildOraclePlan({ evalCommand: 'pytest -q' }, null);
|
|
expect(plan.remote).toBeNull();
|
|
expect(plan.evalCommands.join(' ')).toContain('<remote>');
|
|
expect(plan.evalCommands.join(' ')).toContain('pytest -q');
|
|
});
|
|
});
|
|
|
|
describe('createSshOracleRunner — mocked SSH exec', () => {
|
|
it('runs each plan command via sh -c and reports resolved on exit 0', async () => {
|
|
const calls: Array<{ cmd: string; args: string[] }> = [];
|
|
const exec: CommandExec = async (cmd, args) => {
|
|
calls.push({ cmd, args });
|
|
return { stdout: '{"passed":true}', stderr: '', code: 0 };
|
|
};
|
|
const runner = createSshOracleRunner(exec);
|
|
const plan = buildOraclePlan({ failToPass: ['t'], repo: 'r' }, 'host');
|
|
const probe = await runner.preflight(plan);
|
|
expect(probe.ok).toBe(true);
|
|
const outcome = await runner.runEval(plan, { failToPass: ['t'] });
|
|
expect(outcome.resolved).toBe(true);
|
|
// Every shell-out went through `sh -c <fully-rendered-command>`.
|
|
expect(calls.every((c) => c.cmd === 'sh' && c.args[0] === '-c')).toBe(true);
|
|
});
|
|
|
|
it('interprets json passed:false as NOT resolved', async () => {
|
|
const exec: CommandExec = async () => ({ stdout: '{"passed":false}', stderr: '', code: 0 });
|
|
const runner = createSshOracleRunner(exec);
|
|
const plan = buildOraclePlan({ evalCommand: 'run' }, 'host');
|
|
const outcome = await runner.runEval(plan, { evalCommand: 'run' });
|
|
expect(outcome.resolved).toBe(false);
|
|
});
|
|
|
|
it('reports probe failure (non-zero exit) without throwing', async () => {
|
|
const exec: CommandExec = async () => ({ stdout: '', stderr: 'connection refused', code: 255 });
|
|
const runner = createSshOracleRunner(exec);
|
|
const plan = buildOraclePlan({ evalCommand: 'run' }, 'host');
|
|
const probe = await runner.preflight(plan);
|
|
expect(probe.ok).toBe(false);
|
|
expect(probe.reason).toContain('probe failed');
|
|
});
|
|
|
|
it('preflight is false when no remote is configured', async () => {
|
|
const exec: CommandExec = vi.fn(async () => ({ stdout: '', stderr: '', code: 0 }));
|
|
const runner = createSshOracleRunner(exec as unknown as CommandExec);
|
|
const plan = buildOraclePlan({ evalCommand: 'run' }, null);
|
|
const probe = await runner.preflight(plan);
|
|
expect(probe.ok).toBe(false);
|
|
expect(exec).not.toHaveBeenCalled(); // no remote ⇒ no shell-out at all
|
|
});
|
|
});
|
|
|
|
describe('helpers', () => {
|
|
it('hasMechanicalSpec detects actionable specs only', () => {
|
|
expect(hasMechanicalSpec(specTraj('a'))).toBe(true);
|
|
expect(hasMechanicalSpec({ id: 'x', testSpec: { benchSuite: 's' } })).toBe(true);
|
|
expect(hasMechanicalSpec({ id: 'x', testSpec: { repo: 'only-repo' } })).toBe(false);
|
|
expect(hasMechanicalSpec(proseTraj('p'))).toBe(false);
|
|
});
|
|
|
|
describe('resolveRemote', () => {
|
|
const saved = process.env.RUFLO_DISTILL_REMOTE;
|
|
beforeEach(() => { delete process.env.RUFLO_DISTILL_REMOTE; });
|
|
afterEach(() => { if (saved === undefined) delete process.env.RUFLO_DISTILL_REMOTE; else process.env.RUFLO_DISTILL_REMOTE = saved; });
|
|
|
|
it('prefers explicit opt, then env, then null', () => {
|
|
expect(resolveRemote('explicit')).toBe('explicit');
|
|
process.env.RUFLO_DISTILL_REMOTE = 'from-env';
|
|
expect(resolveRemote()).toBe('from-env');
|
|
delete process.env.RUFLO_DISTILL_REMOTE;
|
|
expect(resolveRemote()).toBeNull();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('reflectFailures', () => {
|
|
it('is a no-op ($0) without a budget cap', async () => {
|
|
const out = await reflectFailures([{ id: 'a', task: 't', output: 'o' }]);
|
|
expect(out).toEqual([]);
|
|
});
|
|
|
|
it('delegates to an enabled harness', async () => {
|
|
const harness = stubHarness([], [{ id: 'a', failureClass: 'oops', diagnosis: 'd', mutationHint: 'm' }]);
|
|
const out = await reflectFailures([{ id: 'a', task: 't', output: 'o' }], { harness });
|
|
expect(out).toEqual([{ id: 'a', failureClass: 'oops', diagnosis: 'd', mutationHint: 'm' }]);
|
|
});
|
|
});
|