mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-27 22:15:33 +00:00
* feat(engine): listEnrichCandidates source-aware candidate selection (#1700) One SQL query per engine: thin-filter + per-page source-correct inbound-link count (to_page_id = p.id, mentions excluded) + enriched_at recency guard + whitelisted ORDER BY (ENRICH_ORDER_SQL) + LIMIT, returning a lightweight projection (no page bodies). EnrichCandidate/EnrichCandidatesOpts types. pg + pglite parity, pinned by engine-parity.test.ts. * feat(enrich): gbrain enrich --thin brain-internal grounded synthesis (#1700) Develops stub pages at scale by consolidating scattered brain knowledge (search + backlinks + facts + raw_data) into one grounded gateway.chat call per page. Resumable (op-checkpoint), budget-capped (best-effort under --workers), per-page advisory lock, put_page write-through. CLI + thin-client refuse + Minion handler. Includes codex-review fixes: sanitizeContext neutralizes the <context> envelope delimiters (P1 injection escape); background fan-out idempotency key carries the run fingerprint (P1); post-hoc budget-overage flag via new BudgetTracker.cap getter (P1); checkpoint flush on budget exhaustion (P2). Accepts the documented best-effort in-flight-cancel limitation (D5) with an explicit code note. * feat(cycle): enrich_thin opt-in autopilot phase (#1700) Default-OFF trickle around runEnrichCore: develops a few thin pages per source per tick so the brain compounds over time. Per-source cap enforced as min(per-source, brain-wide remaining) with brain-wide total + walltime caps (P2 fix: per-source max_cost_usd was parsed but never enforced). Wired into CyclePhase / ALL_PHASES / PHASE_SCOPE / NEEDS_LOCK + dispatch. * chore: bump version and changelog (v0.42.6.0) gbrain enrich --thin batch enrichment (#1700) + codex-review fixes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
101 lines
4.2 KiB
TypeScript
101 lines
4.2 KiB
TypeScript
/**
|
|
* v0.41.39 (#1700) — `enrich_thin` cycle phase tests.
|
|
*
|
|
* Hermetic: drives `runPhaseEnrichThin` against PGLite. The dry-run path
|
|
* exercises candidate enumeration + per-source caps WITHOUT a chat gateway
|
|
* (dry-run never calls the LLM), so no API key / no mock.module is needed.
|
|
*/
|
|
import { describe, test, expect, beforeAll, afterAll, beforeEach } from 'bun:test';
|
|
import { PGLiteEngine } from '../src/core/pglite-engine.ts';
|
|
import { resetPgliteState } from './helpers/reset-pglite.ts';
|
|
import { runPhaseEnrichThin } from '../src/core/cycle/enrich-thin.ts';
|
|
|
|
let engine: PGLiteEngine;
|
|
|
|
beforeAll(async () => {
|
|
engine = new PGLiteEngine();
|
|
await engine.connect({});
|
|
await engine.initSchema();
|
|
});
|
|
|
|
afterAll(async () => {
|
|
await engine.disconnect();
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
await resetPgliteState(engine);
|
|
});
|
|
|
|
async function seedStub(slug: string, title: string) {
|
|
await engine.putPage(slug, {
|
|
type: 'person' as never,
|
|
title,
|
|
compiled_truth: 'Stub page.',
|
|
timeline: '',
|
|
frontmatter: {},
|
|
});
|
|
}
|
|
|
|
describe('runPhaseEnrichThin', () => {
|
|
test('disabled by default → skipped with enable hint', async () => {
|
|
const r = await runPhaseEnrichThin(engine, {});
|
|
expect(r.status).toBe('skipped');
|
|
expect(r.details.reason).toBe('disabled');
|
|
expect(String(r.details.enable_hint)).toContain('cycle.enrich_thin.enabled true');
|
|
});
|
|
|
|
test('enabled with no thin pages → ok, nothing enriched (dry-run, no spend)', async () => {
|
|
await engine.setConfig('cycle.enrich_thin.enabled', 'true');
|
|
// No stub pages seeded → zero candidates; dry-run never calls the LLM, so
|
|
// this is deterministic regardless of whether a chat gateway is configured.
|
|
const r = await runPhaseEnrichThin(engine, { dryRun: true });
|
|
expect(r.status).toBe('ok');
|
|
const perSource = r.details.per_source as Record<string, { pages_enriched: number; candidates_considered: number }>;
|
|
expect(perSource['default'].pages_enriched).toBe(0);
|
|
expect(perSource['default'].candidates_considered).toBe(0);
|
|
});
|
|
|
|
test('enabled + dry-run respects max_pages_per_tick cap', async () => {
|
|
await engine.setConfig('cycle.enrich_thin.enabled', 'true');
|
|
await engine.setConfig('cycle.enrich_thin.max_pages_per_tick', '2');
|
|
for (let i = 0; i < 5; i++) await seedStub(`people/p${i}-example`, `P${i} Example`);
|
|
|
|
const r = await runPhaseEnrichThin(engine, { dryRun: true });
|
|
expect(r.status).toBe('ok');
|
|
const perSource = r.details.per_source as Record<string, { candidates_considered: number; pages_enriched: number }>;
|
|
const def = perSource['default'];
|
|
expect(def).toBeTruthy();
|
|
// Cap of 2 applied at the SQL limit; never enumerates all 5.
|
|
expect(def.candidates_considered).toBeLessThanOrEqual(2);
|
|
// dry-run never writes.
|
|
expect(def.pages_enriched).toBe(0);
|
|
|
|
// Pages remain stubs (no writes in dry-run).
|
|
const page = await engine.getPage('people/p0-example', { sourceId: 'default' });
|
|
expect(page!.compiled_truth.trim()).toBe('Stub page.');
|
|
});
|
|
|
|
test('details carry config knobs for observability', async () => {
|
|
await engine.setConfig('cycle.enrich_thin.enabled', 'true');
|
|
await engine.setConfig('cycle.enrich_thin.order', 'updated');
|
|
const r = await runPhaseEnrichThin(engine, { dryRun: true });
|
|
expect(r.status).toBe('ok');
|
|
expect(r.details.order).toBe('updated');
|
|
expect(r.details.max_pages_per_tick).toBe(3); // default
|
|
expect(Array.isArray(r.details.types)).toBe(true);
|
|
});
|
|
|
|
test('per-source max_cost_usd is read and surfaced (P2#2)', async () => {
|
|
// Before Fix E, max_cost_usd was parsed into cfg but never passed to
|
|
// runEnrichCore nor surfaced — one source could drain the whole tick.
|
|
// Now it's the per-source ceiling (min(per_source, brain_wide_remaining))
|
|
// and appears in details. Defaults: per-source 1.0, brain-wide 5.0.
|
|
await engine.setConfig('cycle.enrich_thin.enabled', 'true');
|
|
await engine.setConfig('cycle.enrich_thin.max_cost_usd', '0.5');
|
|
const r = await runPhaseEnrichThin(engine, { dryRun: true });
|
|
expect(r.status).toBe('ok');
|
|
expect(r.details.max_cost_usd).toBe(0.5);
|
|
expect(r.details.max_total_cost_usd).toBe(5.0); // brain-wide default unchanged
|
|
});
|
|
});
|