Files
gbrain/test/autopilot-nightly-probe-wiring.test.ts
b91350d778 fix(autopilot,eval): nightly quality probe enable path + conversation-parser probe wire-up (takeover of #2629, #2630) (#3094)
* fix(autopilot,eval): nightly quality probe enable path works end-to-end + wire conversation-parser probe

Takeover of #2629 and #2630 (rebased onto master; dropped the
test/engine-find-trajectory.test.ts hunk both PRs carried — master
already ships the equivalent gateway-dims fix).

#2629 — nightly quality probe enable path:
- autopilot + doctor read the probe flag dual-plane (DB config row from
  'gbrain config set' wins, ~/.gbrain/config.json fallback) via new
  resolveProbeEnabled/resolveProbeMaxUsd helpers
- resolveRepoRoot prefers the gbrain package root where the committed
  fixture lives, not the brain repoPath
- rate_limited skips no longer write an audit row every autopilot cycle
- eval-longmemeval strips 'provider:' recipe ids before raw Anthropic SDK
  calls and emits the gold answer for downstream judges
- cross-modal batch folds the gold answer into the judge task; probe
  passes QA-shaped dimensions instead of the agent-response rubric
- DEFAULT_SLOTS slot A moves to openai:gpt-5.2 (gpt-4o left the recipe);
  new consistency test pins every default slot to its recipe

#2630 — conversation-parser nightly probe wire-up:
- autopilot step 4.6 invokes runConversationParserNightlyProbe (dual-plane
  flag + D10 tokenmax mode-gate, package-root fixtures, 24h gate, audit
  trail via new src/core/audit-parser-probe.ts)
- doctor's conversation_parser_probe_health replaces the hardcoded
  'Skipped' stub with a real pure-function check over the audit trail

Co-authored-by: p3ob7o <p3ob7o@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(pricing): add openai:gpt-5.2 canonical entry for the new default slot A

DEFAULT_SLOTS slot A moved to openai:gpt-5.2, which had no CANONICAL_PRICING
entry — estimateCost silently dropped slot A from the --max-usd pre-flight
and est_cost_usd audit rows (~1/3 under-count on the default panel). Rates
from the OpenAI recipe chat touchpoint (verified 2026-04-20). Also refresh
the --slot-a-model help text default and pin a pricing-presence assertion
in the DEFAULT_SLOTS consistency test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Sinabina <sinabina@Sinabinas-MacBook-Pro-4.local>
Co-authored-by: p3ob7o <p3ob7o@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Garry Tan <garrytan@gmail.com>
2026-07-23 12:26:33 -07:00

92 lines
4.6 KiB
TypeScript

/**
* Source-shape regression tests for the v0.41 autopilot wiring of
* `runNightlyQualityProbe`.
*
* The autopilot loop is hard to drive end-to-end without spinning a real
* daemon (database, queue, gateway, etc). These tests pin the structural
* shape of the wiring — the feature flag check, the try/catch, the DI
* shape passed to runNightlyQualityProbe — so future refactors can't
* silently strip the protections without a CI signal.
*
* The pure decision logic lives in shouldRunNightly (already pinned by
* tests in nightly-quality-probe.test.ts).
*/
import { describe, test, expect } from 'bun:test';
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
const AUTOPILOT_SRC = resolve('src/commands/autopilot.ts');
const SOURCE = readFileSync(AUTOPILOT_SRC, 'utf-8');
describe('autopilot wiring: nightly quality probe', () => {
test('imports runNightlyQualityProbe from the phase module', () => {
expect(SOURCE).toContain(`runNightlyQualityProbe`);
expect(SOURCE).toContain(`nightly-quality-probe`);
});
test('uses the eng-D2 adapter module (not direct subprocess of eval-longmemeval/cross-modal)', () => {
expect(SOURCE).toContain(`nightly-probe-adapters`);
expect(SOURCE).toContain(`runLongMemEvalForProbe`);
expect(SOURCE).toContain(`runCrossModalBatchForProbe`);
});
test('feature flag gate present: dual-plane read (DB row wins, file plane fallback)', () => {
// Per D10: the scheduler ONLY checks the feature flag. The 24h rate-limit
// lives inside runNightlyQualityProbe itself (no scheduler-side precheck).
// The flag resolves through resolveProbeEnabled so `gbrain config set
// autopilot.nightly_quality_probe.enabled true` (the doctor hint, DB
// plane) and ~/.gbrain/config.json (file plane) BOTH work — a file-only
// read made the printed hint a silent no-op.
expect(SOURCE).toContain(`getConfig('autopilot.nightly_quality_probe.enabled')`);
expect(SOURCE).toMatch(/resolveProbeEnabled\(dbEnabled,\s*cfg\?\.autopilot\?\.nightly_quality_probe\?\.enabled\)/);
});
test('NO scheduler-side rate-limit check (D10 simplification)', () => {
// Codex round-1 #11 caught: scheduler-side rate-limit duplicates phase-internal logic.
// The wiring code MUST NOT call shouldRunNightly directly OR read recent events
// before invoking the phase.
expect(SOURCE).not.toContain(`shouldRunNightly(`);
expect(SOURCE).not.toContain(`readRecentQualityProbeEvents(`);
});
test('probe call wrapped in try/catch that does NOT bump consecutiveErrors', () => {
// The try/catch around the probe must log the error but never crash the loop.
// We verify the structural pattern: the probe call is inside a try block,
// the catch block calls logError, and consecutiveErrors is not bumped inside the catch.
expect(SOURCE).toMatch(/try\s*\{\s*[^}]*nightly_quality_probe/);
expect(SOURCE).toMatch(/catch[\s\S]*?autopilot\.nightly_probe[\s\S]*?do NOT bump consecutiveErrors/);
});
test('DI shape: isEnabled / hasEmbeddingProvider / resolveMaxUsd / resolveRepoRoot / runLongMemEval / runCrossModalBatch / now', () => {
// The exact 7 fields of NightlyProbeDeps.
expect(SOURCE).toContain(`isEnabled:`);
expect(SOURCE).toContain(`hasEmbeddingProvider:`);
expect(SOURCE).toContain(`resolveMaxUsd:`);
expect(SOURCE).toContain(`resolveRepoRoot:`);
expect(SOURCE).toContain(`runLongMemEval:`);
expect(SOURCE).toContain(`runCrossModalBatch:`);
expect(SOURCE).toContain(`now:`);
});
test('resolveRepoRoot prefers the gbrain package root (committed fixture home), not the brain repoPath', () => {
// The DI harness in nightly-quality-probe.test.ts passes process.cwd()
// (= the gbrain repo in CI), which papered over the wiring passing
// repoPath (= sync.repo_path, the user's BRAIN repo, where the fixture
// never exists). Pin the package-root resolution + existence check.
expect(SOURCE).toMatch(/fileURLToPath\(new URL\('\.\.\/\.\.', import\.meta\.url\)\)/);
expect(SOURCE).toContain(`'longmemeval-nightly.jsonl'`);
expect(SOURCE).toMatch(/fixtureAtPkgRoot \? pkgRoot : repoPath/);
});
test('hasEmbeddingProvider reads from gateway.isAvailable("embedding") (codex round-2 #12 — in-process, not subprocess)', () => {
expect(SOURCE).toContain(`isAvailable('embedding')`);
expect(SOURCE).toContain(`gateway`);
});
test('max_usd resolves dual-plane (default = 5 pinned by resolveProbeMaxUsd unit tests)', () => {
expect(SOURCE).toContain(`getConfig('autopilot.nightly_quality_probe.max_usd')`);
expect(SOURCE).toMatch(/resolveProbeMaxUsd\(dbMaxUsd,\s*cfg\?\.autopilot\?\.nightly_quality_probe\?\.max_usd\)/);
});
});