mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-27 22:15:33 +00:00
fix: 8 root-cause fixes from /investigate (v0.14.2) (#259)
* fix: 8 root-cause fixes from /investigate wave
Consolidated bundle of bug fixes from /investigate on the 8 deferred bugs.
Each fix was designed to go at the structural gap, not the symptom. Codex
verified 20 load-bearing claims on the plan; 12 triggered plan revisions.
Bug 2 — GBRAIN_POOL_SIZE env knob + init finally blocks (no auto-detect).
Covers both the singleton pool (db.ts) and instance pool (import.ts:140).
Bug 3 — Centralize migration ledger writes in apply-migrations runner.
Removed appendCompletedMigration from v0_11_0, v0_12_0, v0_12_2,
v0_13_0, v0_13_1. Added 3-partial wedge cap + --force-retry reset.
'complete wins' preserved; no partial can regress a completed migration.
Bug 5 — v0.14.0 migration registered. src/commands/migrations/v0_14_0.ts
ships Phase A (ALTER minion_jobs.max_stalled SET DEFAULT 3) + Phase B
(pending-host-work ping for shell-jobs adoption).
Bug 6/10 — jsonb_agg(DISTINCT ...) in legacy traverseGraph (both engines).
Presentation-level dedup; schema still preserves provenance rows.
Bug 7 — doctor --fast reads DB URL source via getDbUrlSource() in config.ts.
Precise message: 'Skipping DB checks (--fast mode, URL present from env)'
replaces the misleading 'No database configured'.
Bug 8 — max_stalled default bumped 1→3 in schema-embedded.ts, pglite-schema.ts,
schema.sql (new installs). v0_14_0 Phase A ALTER for existing installs.
autopilot-cycle handler yields to event loop between phases so the
worker's lock-renewal timer fires on huge brains. (Deep AbortSignal
threading through runEmbedCore/runExtractCore/runBacklinksCore/performSync
deferred to v0.15 queue polish.)
Bug 9 — Gate sync.last_commit on no-failures across all three sync paths
(incremental, full via runImport, gbrain import git continuity).
recordSyncFailures() helper + ~/.gbrain/sync-failures.jsonl with
dedup key path+commit+error-hash. New flags: --skip-failed (ack) +
--retry-failed (re-attempt). Doctor surfaces unacknowledged failures.
Bug 11 — brain_score breakdown fields on BrainHealth (embed_coverage_score,
link_density_score, timeline_coverage_score, no_orphans_score,
no_dead_links_score); sum equals brain_score by construction.
dead_links now on the type (resolves featuresTeaserForDoctor drift).
orphan_pages kept as 'islanded' (no inbound AND no outbound) and
docs updated to match — explicit semantic instead of doc drift.
New tests: test/traverse-graph-dedup.test.ts, test/sync-failures.test.ts,
test/brain-score-breakdown.test.ts, test/migration-resume.test.ts,
test/migrations-v0_14_0.test.ts. Extended: migrate, doctor, apply-migrations.
All 1696 unit tests pass locally. postgres-jsonb E2E regression unchanged
(none of these touch the JSONB write surface).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: v0.14.2 CHANGELOG + CLAUDE.md; align migration-flow E2E with runner-owned ledger
CHANGELOG: v0.14.2 entry in the standard release-summary format
(two-line headline + lead + numbers table + "what this means" +
"To take advantage of v0.14.2" self-repair block + itemized
changes grouped by reliability / observability / graph correctness /
new migration / tests / deferred-to-v0.15).
CLAUDE.md: new "Key commands added in v0.14.2" section covers
--skip-failed, --retry-failed, --force-retry, GBRAIN_POOL_SIZE env,
and the new doctor checks (sync_failures, brain_score breakdown).
Migration orchestrator docs updated to describe v0_14_0.ts + the
runner-owned ledger contract from Bug 3.
test/e2e/migration-flow.test.ts: three assertions updated to match
the Bug 3 contract — orchestrators no longer append to completed.jsonl
directly, so direct-orchestrator E2E calls leave the ledger empty.
Preferences assertions remain (that's still the orchestrator's side
of the contract). Runner's ledger write is covered by the unit suite
(test/apply-migrations.test.ts + test/migration-resume.test.ts).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
ebfbd5e6f7
commit
b5fa3d044a
+33
-11
@@ -31,11 +31,14 @@ export class PostgresEngine implements BrainEngine {
|
||||
// Lifecycle
|
||||
async connect(config: EngineConfig & { poolSize?: number }): Promise<void> {
|
||||
if (config.poolSize) {
|
||||
// Instance-level connection for worker isolation
|
||||
// Instance-level connection for worker isolation. resolvePoolSize lets
|
||||
// GBRAIN_POOL_SIZE cap below the caller's requested size when set — the
|
||||
// env var is a user escape hatch, so it wins.
|
||||
const url = config.database_url;
|
||||
if (!url) throw new GBrainError('No database URL', 'database_url is missing', 'Provide --url');
|
||||
const size = Math.min(config.poolSize, db.resolvePoolSize(config.poolSize));
|
||||
this._sql = postgres(url, {
|
||||
max: config.poolSize,
|
||||
max: size,
|
||||
idle_timeout: 20,
|
||||
connect_timeout: 10,
|
||||
types: { bigint: postgres.BigInt },
|
||||
@@ -540,7 +543,14 @@ export class PostgresEngine implements BrainEngine {
|
||||
)
|
||||
SELECT DISTINCT g.slug, g.title, g.type, g.depth,
|
||||
coalesce(
|
||||
(SELECT jsonb_agg(jsonb_build_object('to_slug', p3.slug, 'link_type', l2.link_type))
|
||||
-- jsonb_agg(DISTINCT ...) collapses duplicate (to_slug, link_type)
|
||||
-- edges that originate from different provenance (markdown body
|
||||
-- vs frontmatter vs auto-extracted). The underlying links table
|
||||
-- preserves every row with its origin_page_id / link_source —
|
||||
-- the dedup is presentation-only for the legacy traverseGraph
|
||||
-- aggregation. traversePaths has its own in-memory dedup at a
|
||||
-- different layer. See plan Bug 6/10.
|
||||
(SELECT jsonb_agg(DISTINCT jsonb_build_object('to_slug', p3.slug, 'link_type', l2.link_type))
|
||||
FROM links l2
|
||||
JOIN pages p3 ON p3.id = l2.to_page_id
|
||||
WHERE l2.from_page_id = g.id),
|
||||
@@ -893,9 +903,12 @@ export class PostgresEngine implements BrainEngine {
|
||||
|
||||
async getHealth(): Promise<BrainHealth> {
|
||||
const sql = this.sql;
|
||||
// dead_links omitted (always 0 under ON DELETE CASCADE on link FKs).
|
||||
// orphan_pages now matches PGLite definition: no inbound links (regardless of outbound).
|
||||
// stale_pages aligned to PGLite definition (page updated_at < latest timeline entry).
|
||||
// Bug 11 doc-drift fix — orphan_pages means "islanded" (no inbound AND
|
||||
// no outbound links), aligning both engines with the user-facing
|
||||
// definition. The type comment previously said "no inbound" but the
|
||||
// SQL required both — docs now match code so users can trust the
|
||||
// number. A hub page that links out to many but has no back-references
|
||||
// is working as intended, not an orphan.
|
||||
const [h] = await sql`
|
||||
WITH entity_pages AS (
|
||||
SELECT id, slug FROM pages WHERE type IN ('person', 'company')
|
||||
@@ -943,13 +956,16 @@ export class PostgresEngine implements BrainEngine {
|
||||
|
||||
// brain_score: 0-100 weighted average
|
||||
const linkDensity = pageCount > 0 ? Math.min(linkCount / pageCount, 1) : 0;
|
||||
const timelineCoverage = pageCount > 0 ? Math.min(pagesWithTimeline / pageCount, 1) : 0;
|
||||
const timelineCoverageWhole = pageCount > 0 ? Math.min(pagesWithTimeline / pageCount, 1) : 0;
|
||||
const noOrphans = pageCount > 0 ? 1 - (orphanPages / pageCount) : 1;
|
||||
const noDeadLinks = pageCount > 0 ? 1 - Math.min(deadLinks / pageCount, 1) : 1;
|
||||
const brainScore = pageCount === 0 ? 0 : Math.round(
|
||||
(embedCoverage * 0.35 + linkDensity * 0.25 + timelineCoverage * 0.15 +
|
||||
noOrphans * 0.15 + noDeadLinks * 0.10) * 100
|
||||
);
|
||||
// Per-component points. Sum equals brainScore by construction.
|
||||
const embedCoverageScore = pageCount === 0 ? 0 : Math.round(embedCoverage * 35);
|
||||
const linkDensityScore = pageCount === 0 ? 0 : Math.round(linkDensity * 25);
|
||||
const timelineCoverageScore = pageCount === 0 ? 0 : Math.round(timelineCoverageWhole * 15);
|
||||
const noOrphansScore = pageCount === 0 ? 0 : Math.round(noOrphans * 15);
|
||||
const noDeadLinksScore = pageCount === 0 ? 0 : Math.round(noDeadLinks * 10);
|
||||
const brainScore = embedCoverageScore + linkDensityScore + timelineCoverageScore + noOrphansScore + noDeadLinksScore;
|
||||
|
||||
return {
|
||||
page_count: pageCount,
|
||||
@@ -958,12 +974,18 @@ export class PostgresEngine implements BrainEngine {
|
||||
orphan_pages: orphanPages,
|
||||
missing_embeddings: Number(h.missing_embeddings),
|
||||
brain_score: brainScore,
|
||||
dead_links: deadLinks,
|
||||
link_coverage: Number(h.link_coverage),
|
||||
timeline_coverage: Number(h.timeline_coverage),
|
||||
most_connected: (connected as { slug: string; link_count: number }[]).map(c => ({
|
||||
slug: c.slug,
|
||||
link_count: Number(c.link_count),
|
||||
})),
|
||||
embed_coverage_score: embedCoverageScore,
|
||||
link_density_score: linkDensityScore,
|
||||
timeline_coverage_score: timelineCoverageScore,
|
||||
no_orphans_score: noOrphansScore,
|
||||
no_dead_links_score: noDeadLinksScore,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user