Merge PR #2630: feat(autopilot): wire the nightly conversation-parser probe into the loop

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Sinabina
2026-07-13 11:28:00 -07:00
co-authored by Claude Fable 5
8 changed files with 438 additions and 18 deletions
+102
View File
@@ -0,0 +1,102 @@
/**
* Tests for the parser-probe audit trail + the 24h rate-limit gate.
*
* Uses GBRAIN_AUDIT_DIR override pointed at a tmpdir for hermeticity
* (same pattern as audit-slug-fallback.serial.test.ts). Serial because
* the env override is process-global.
*/
import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
import { mkdtempSync, rmSync, readdirSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import {
computeParserProbeAuditFilename,
logParserProbeEvent,
parserProbeRanWithin,
readRecentParserProbeEvents,
type ParserProbeAuditEvent,
} from '../src/core/audit-parser-probe.ts';
let auditDir: string;
let savedEnv: string | undefined;
beforeEach(() => {
auditDir = mkdtempSync(join(tmpdir(), 'parser-probe-audit-'));
savedEnv = process.env.GBRAIN_AUDIT_DIR;
process.env.GBRAIN_AUDIT_DIR = auditDir;
});
afterEach(() => {
if (savedEnv === undefined) delete process.env.GBRAIN_AUDIT_DIR;
else process.env.GBRAIN_AUDIT_DIR = savedEnv;
rmSync(auditDir, { recursive: true, force: true });
});
function makeEvent(overrides: Partial<ParserProbeAuditEvent> = {}): ParserProbeAuditEvent {
return {
schema_version: 1,
ts: new Date().toISOString(),
outcome: 'pass',
fixtures_total: 12,
fixtures_passed: 12,
recall_mean: 0.98,
participants_recall_mean: 0.97,
adversarial_false_positives: 0,
failed_fixture_ids: [],
...overrides,
};
}
describe('parser-probe audit trail', () => {
test('log + readRecent round-trip', () => {
logParserProbeEvent(makeEvent({ outcome: 'fail', reason: '2 fixture(s) failed' }));
const events = readRecentParserProbeEvents(7);
expect(events.length).toBe(1);
expect(events[0]!.outcome).toBe('fail');
expect(events[0]!.reason).toBe('2 fixture(s) failed');
const files = readdirSync(auditDir);
expect(files.length).toBe(1);
expect(files[0]).toMatch(/^parser-probe-\d{4}-W\d{2}\.jsonl$/);
});
test('filename uses ISO-week rotation with the parser-probe prefix', () => {
// Year-boundary edge pinned by the shared writer's own tests; here we
// pin the prefix wiring.
expect(computeParserProbeAuditFilename(new Date('2026-07-06T12:00:00Z'))).toBe(
'parser-probe-2026-W28.jsonl',
);
});
test('readRecent filters by window', () => {
const old = new Date(Date.now() - 10 * 86400000).toISOString();
logParserProbeEvent(makeEvent({ ts: old }));
expect(readRecentParserProbeEvents(7).length).toBe(0);
});
});
describe('parserProbeRanWithin — 24h rate-limit gate', () => {
const DAY_MS = 24 * 60 * 60 * 1000;
test('false when no runs are audited', () => {
expect(parserProbeRanWithin(DAY_MS)).toBe(false);
});
test('true when a run landed within the window', () => {
logParserProbeEvent(makeEvent({ ts: new Date(Date.now() - 60_000).toISOString() }));
expect(parserProbeRanWithin(DAY_MS)).toBe(true);
});
test('false when the last run is older than the window', () => {
logParserProbeEvent(makeEvent({ ts: new Date(Date.now() - 25 * 3600_000).toISOString() }));
expect(parserProbeRanWithin(DAY_MS)).toBe(false);
});
test('non-pass outcomes also hold the window (mirrors quality-probe semantics)', () => {
logParserProbeEvent(makeEvent({
outcome: 'no_embedding_key',
ts: new Date(Date.now() - 3600_000).toISOString(),
}));
expect(parserProbeRanWithin(DAY_MS)).toBe(true);
});
});
@@ -0,0 +1,77 @@
/**
* Source-shape regression tests for the autopilot wiring of
* `runConversationParserNightlyProbe` (step 4.6).
*
* Same rationale as autopilot-nightly-probe-wiring.test.ts: the loop is
* hard to drive end-to-end, so these pin the structural protections —
* the dual-plane flag read, the D10 tokenmax mode-gate, the package-root
* fixture resolution, the audit-flood guard, and the try/catch posture.
*
* The probe's own gate/scoring logic is pinned by the module's unit
* tests; the audit trail by audit-parser-probe.serial.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: conversation-parser probe', () => {
test('invokes the phase module and the audit trail', () => {
expect(SOURCE).toContain(`runConversationParserNightlyProbe`);
expect(SOURCE).toContain(`conversation-parser/nightly-probe`);
expect(SOURCE).toContain(`logParserProbeEvent`);
expect(SOURCE).toContain(`audit-parser-probe`);
});
test('flag reads dual-plane: DB row (gbrain config set) wins, file plane fallback', () => {
expect(SOURCE).toContain(`getConfig('autopilot.conversation_parser_probe.enabled')`);
expect(SOURCE).toContain(`cfg?.autopilot?.conversation_parser_probe?.enabled === true`);
});
test('D10 mode-gate present: tokenmax brains run the probe by default', () => {
expect(SOURCE).toMatch(/parserEnabled \|\| searchMode === 'tokenmax'/);
});
test('fixtures resolve from the gbrain package root, NOT the brain repoPath', () => {
// The committed fixtures live in the gbrain source tree; resolving
// them against sync.repo_path would point into the user's brain repo.
expect(SOURCE).toMatch(/fileURLToPath\(new URL\('\.\.\/\.\.', import\.meta\.url\)\)/);
expect(SOURCE).toContain(`'conversation-formats', 'all.jsonl'`);
expect(SOURCE).toContain(`'conversation-formats', 'adversarial.jsonl'`);
});
test('missing fixtures skip quietly (no audit row, once-per-process stderr note)', () => {
// Compiled-binary installs carry no source tree; writing failure rows
// would flip doctor to WARN on every binary install.
expect(SOURCE).toContain(`parserProbeFixtureWarned`);
});
test('rate_limited outcomes are NOT audit-logged (flood guard)', () => {
expect(SOURCE).toMatch(/outcome !== 'rate_limited'\) logParserProbeEvent\(result\)/);
});
test('rate-limit gate delegates to the audit module, not inline event reads', () => {
expect(SOURCE).toContain(`parserProbeRanWithin(24 * 60 * 60 * 1000)`);
});
test('LLM-key gate reads gateway.isAvailable("chat") in-process', () => {
expect(SOURCE).toContain(`isAvailable('chat')`);
});
test('probe call wrapped in try/catch that does NOT bump consecutiveErrors', () => {
expect(SOURCE).toMatch(/catch[\s\S]*?autopilot\.parser_probe[\s\S]*?do NOT bump consecutiveErrors/);
});
test('DI shape: the exact 7 fields of the parser probe NightlyProbeDeps', () => {
expect(SOURCE).toContain(`isEnabled:`);
expect(SOURCE).toContain(`searchMode:`);
expect(SOURCE).toContain(`hasLlmKey:`);
expect(SOURCE).toContain(`resolveFixturePath:`);
expect(SOURCE).toContain(`resolveAdversarialPath:`);
expect(SOURCE).toContain(`shouldSkipForRateLimit:`);
expect(SOURCE).toContain(`now:`);
});
});
+51
View File
@@ -0,0 +1,51 @@
/**
* Tests for computeConversationParserProbeHealthCheck — the pure function
* behind doctor's conversation_parser_probe_health check, which replaced
* the v0.41.13.0 hardcoded "Skipped" stub when the autopilot wiring
* landed. Mirrors the branch coverage style of the quality-probe check.
*/
import { describe, expect, test } from 'bun:test';
import { computeConversationParserProbeHealthCheck } from '../src/commands/doctor.ts';
const ev = (outcome: string, reason?: string, ts = new Date().toISOString()) => ({
outcome,
ts,
...(reason !== undefined ? { reason } : {}),
});
describe('computeConversationParserProbeHealthCheck', () => {
test('disabled + no events → ok with paste-ready enable hint', () => {
const check = computeConversationParserProbeHealthCheck(false, []);
expect(check.status).toBe('ok');
expect(check.message).toContain('gbrain config set autopilot.conversation_parser_probe.enabled true');
});
test('enabled + no events yet → ok, next run by autopilot', () => {
const check = computeConversationParserProbeHealthCheck(true, []);
expect(check.status).toBe('ok');
expect(check.message).toContain('no probe events');
});
test('disabled flag but events exist (tokenmax mode-gate ran it) → events win over the hint', () => {
const check = computeConversationParserProbeHealthCheck(false, [ev('pass')]);
expect(check.status).toBe('ok');
expect(check.message).toContain('all pass');
});
test('any non-pass outcome in the window → warn, latest surfaced with reason', () => {
const check = computeConversationParserProbeHealthCheck(true, [
ev('pass'),
ev('adversarial_false_positive', '1 adversarial fixture(s) parsed to non-empty'),
]);
expect(check.status).toBe('warn');
expect(check.message).toContain('adversarial_false_positive');
expect(check.message).toContain('parsed to non-empty');
});
test('all pass → ok with run count', () => {
const check = computeConversationParserProbeHealthCheck(true, [ev('pass'), ev('pass')]);
expect(check.status).toBe('ok');
expect(check.message).toContain('2 probe run(s)');
});
});