mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-27 22:15:33 +00:00
* feat(minions): pool-recovery audit + reconnect reason-threading + shared drain helper (#1685 GAP B, 5A) - pool-recovery-audit.ts: reap_detected (CONNECTION_ENDED) vs reconnect_other; recovered/failed split - postgres-engine reconnect(ctx?) classifies the triggering error so only true pooler reaps are tagged (CODEX #8) - retry.ts reconnect callback widened to thread the error; retry-matcher isConnectionEndedError - runExtractAtomsDrainForSource shared helper (cycleLockIdFor + withRefreshingLock) — one drain path (5A) - supervisor-audit readRecentSupervisorEvents (current+prev ISO week, CODEX #7) - extract-atoms-drain PROTECTED; autopilot.auto_drain.* config keys * feat(doctor): worker_oom_loop + pool_reap_health checks + cause-ranked top_issues (#1685 GAP A/B/C) - computeWorkerOomLoopCheck: unions supervisor rss_watchdog + minion_jobs watchdog-abort (CODEX #5), cap fallback to resolveDefaultMaxRssMb (CODEX #6) - computePoolReapHealthCheck: reaps-not-recovering fail, thrash warn - doctor-cause-rank rankIssues: tier ordering + grounded downstream_of (CODEX #9) + drift guard (4A) - supervisor causeStr + queue_health cross-reference worker_oom_loop (DRY 1C) - register both checks in doctor-categories ops * feat(autopilot): per-source extract_atoms auto-drain + handler + dream --drain refactor (#1685 GAP D) - autopilot per-source gate: enabled + !packDeclares + backlog>threshold + daily cap; time-sloted idempotency key (CODEX #2) - extract-atoms-drain Minion handler (thin wrapper, LockUnavailableError -> deferred) - dream --drain routes through the shared helper (5A) * chore: bump version and changelog (v0.42.12.0) #1685 brain-health-as-solved-problem: cause-ranked doctor, worker_oom_loop line, per-source auto-drain, pool-reap health. Layers on #1678/#1735. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(todos): file #1685 GAP E + remote-path follow-ups (v0.42.12.0) * fix(#1685): pre-landing review — multi-source auto-drain, honest pool-reap signal, lock-renewal reap labeling - autopilot: drop maxWaiting (coalesces by name+queue not source → only one source drained + cap over-count); pre-check idempotency key so only genuinely-new sources submit+count - pool_reap_health: fail on reconnect FAILURES (the real signal), not reaps>0&&failures>0 (false causality when a recovered reap + unrelated failure co-occur) - lock-renewal-tick threads its triggering error to reconnect() so a CONNECTION_ENDED pooler reap is labeled reap_detected not reconnect_other (pool_reap_health now fires for the #1678 incident path) * chore: re-version v0.42.12.0 → v0.42.16.0 (#1685) Slot collision avoidance per queue. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: restore slim CLAUDE.md + move #1685 entries to KEY_FILES.md (fix check:doc-history) The master merge wrongly kept the pre-restructure 577KB CLAUDE.md; the check:doc-history guard caps it at 60KB. Take master's slim CLAUDE.md and record the #1685 files (doctor-cause-rank, pool-recovery-audit, worker_oom_loop + pool_reap_health checks, auto-drain, 5A helper) as current-state prose in docs/architecture/KEY_FILES.md (no release markers). llms regenerated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
55 lines
2.4 KiB
TypeScript
55 lines
2.4 KiB
TypeScript
/**
|
|
* #1685 GAP D — autopilot auto-drain wiring regression guards.
|
|
*
|
|
* The submission is inline in the autopilot tick body, so these are
|
|
* source-shape assertions (the proven `autopilot-*-wiring.test.ts` pattern).
|
|
* The load-bearing one is CODEX #2: the idempotency key MUST carry a time slot,
|
|
* else queue.add returns the first completed job forever and the source never
|
|
* drains again.
|
|
*/
|
|
import { describe, test, expect } from 'bun:test';
|
|
import { readFileSync } from 'fs';
|
|
import { join } from 'path';
|
|
|
|
const SRC = readFileSync(join(import.meta.dir, '../src/commands/autopilot.ts'), 'utf8');
|
|
|
|
describe('autopilot auto-drain wiring', () => {
|
|
test('CODEX #2: idempotency key includes a UTC-day time slot (not static)', () => {
|
|
expect(SRC).toContain('autopilot-extract-atoms-drain:${src.id}:${utcDay}');
|
|
// A static key would be the regression — guard against the bare form.
|
|
expect(SRC).not.toContain('`autopilot-extract-atoms-drain:${src.id}`');
|
|
});
|
|
|
|
test('CODEX #1: submits with allowProtectedSubmit', () => {
|
|
expect(SRC).toMatch(/extract-atoms-drain[\s\S]{0,800}allowProtectedSubmit: true/);
|
|
});
|
|
|
|
test('CODEX #3: enumerates sources and counts backlog per source', () => {
|
|
expect(SRC).toContain('loadAllSources(engine)');
|
|
expect(SRC).toContain('countExtractAtomsBacklog(engine, src.id)');
|
|
});
|
|
|
|
test('gates on pack NOT declaring extract_atoms (the silent-backlog condition)', () => {
|
|
expect(SRC).toContain("packDeclaresPhase(engine, 'extract_atoms')");
|
|
});
|
|
|
|
test('gates on the enabled flag and a daily spend cap (DECISION 3C)', () => {
|
|
expect(SRC).toContain('autopilot.auto_drain.enabled');
|
|
expect(SRC).toContain('autopilot.auto_drain.max_usd_per_day');
|
|
expect(SRC).toContain('maxJobsToday');
|
|
});
|
|
|
|
test('is Postgres-gated (PGLite has no worker surface)', () => {
|
|
expect(SRC).toMatch(/engine\.kind === 'postgres'[\s\S]{0,400}auto_drain/);
|
|
});
|
|
|
|
test('CODEX impl #4: no maxWaiting (it coalesces by name+queue, not source)', () => {
|
|
// maxWaiting would return source A's waiting job for source B's submit,
|
|
// never queuing B and over-counting the cap. The per-source idempotency key
|
|
// is the dedup; a pre-check on it avoids counting idempotency-hit re-submits.
|
|
const drainBlock = SRC.slice(SRC.indexOf("'extract-atoms-drain'"));
|
|
expect(drainBlock.slice(0, 900)).not.toContain('maxWaiting');
|
|
expect(SRC).toContain('WHERE idempotency_key = $1 LIMIT 1');
|
|
});
|
|
});
|