fix(health): count 'entity' pages in graph health metrics (#2639)

Reland of #2639, reverted with its batch in 68e4cebd. getHealth's
entity_pages CTE and the top-linked-pages query only match the legacy
'person' and 'company' types, so brains using the gbrain-base-v2 pack's
'entity' type report 0% entity link/timeline coverage in `gbrain health`.
Add 'entity' to both queries in both engines (PGLite + Postgres, in
lockstep per the engine-parity rule).

Reland fix (the batch-red root cause): the original PR's test expected
the entities/project-x page to appear in orphan_pages, but #3023's shared
orphan-reporting policy (landed before #2639 merged) excludes the
'entities' first segment from orphan reporting, so the test failed on
master. Orphan expectations now account for the policy exclusion.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Tyler Robinson
2026-07-23 15:38:27 -07:00
committed by Garry Tan
co-authored by Claude Fable 5
parent ca47c054b8
commit add9aa22bf
3 changed files with 12 additions and 9 deletions
+2 -2
View File
@@ -5261,7 +5261,7 @@ export class PGLiteEngine implements BrainEngine {
// dashboard, v0.10.3 metrics give entity-page-level granularity.
const { rows: [h] } = await this.db.query(`
WITH entity_pages AS (
SELECT id, slug FROM pages WHERE type IN ('person', 'company')
SELECT id, slug FROM pages WHERE type IN ('entity', 'person', 'company')
)
SELECT
(SELECT count(*) FROM pages) as page_count,
@@ -5289,7 +5289,7 @@ export class PGLiteEngine implements BrainEngine {
SELECT p.slug,
(SELECT count(*) FROM links l WHERE l.from_page_id = p.id OR l.to_page_id = p.id)::int as link_count
FROM pages p
WHERE p.type IN ('person', 'company')
WHERE p.type IN ('entity', 'person', 'company')
ORDER BY link_count DESC
LIMIT 5
`);
+2 -2
View File
@@ -5364,7 +5364,7 @@ export class PostgresEngine implements BrainEngine {
// dashboard health.
const [h] = await sql`
WITH entity_pages AS (
SELECT id, slug FROM pages WHERE type IN ('person', 'company')
SELECT id, slug FROM pages WHERE type IN ('entity', 'person', 'company')
)
SELECT
(SELECT count(*) FROM pages) as page_count,
@@ -5389,7 +5389,7 @@ export class PostgresEngine implements BrainEngine {
SELECT p.slug,
(SELECT count(*) FROM links l WHERE l.from_page_id = p.id OR l.to_page_id = p.id)::int as link_count
FROM pages p
WHERE p.type IN ('person', 'company')
WHERE p.type IN ('entity', 'person', 'company')
ORDER BY link_count DESC
LIMIT 5
`;
+8 -5
View File
@@ -1434,6 +1434,7 @@ describe('PGLiteEngine: getHealth graph metrics', () => {
await engine.putPage('people/alice', { ...testPage, type: 'person', title: 'Alice' });
await engine.putPage('people/bob', { ...testPage, type: 'person', title: 'Bob' });
await engine.putPage('companies/acme', { ...testPage, type: 'company', title: 'Acme' });
await engine.putPage('entities/project-x', { ...testPage, type: 'entity', title: 'Project X' });
});
test('link_coverage = 0 when no links exist', async () => {
@@ -1442,17 +1443,17 @@ describe('PGLiteEngine: getHealth graph metrics', () => {
});
test('link_coverage = % of entity pages with >= 1 inbound link', async () => {
// Acme gets 1 inbound link (from Alice), Alice/Bob get 0 inbound.
// 1 of 3 entity pages has inbound links -> 33%.
// Acme gets 1 inbound link (from Alice); Alice/Bob/Project X get 0 inbound.
// 1 of 4 entity pages has inbound links -> 25%.
await engine.addLink('people/alice', 'companies/acme', '', 'works_at');
const h = await engine.getHealth();
expect(h.link_coverage).toBeCloseTo(1 / 3, 2);
expect(h.link_coverage).toBeCloseTo(1 / 4, 2);
});
test('timeline_coverage = % with >= 1 timeline entry', async () => {
await engine.addTimelineEntry('people/alice', { date: '2026-01-15', summary: 'Joined' });
const h = await engine.getHealth();
expect(h.timeline_coverage).toBeCloseTo(1 / 3, 2);
expect(h.timeline_coverage).toBeCloseTo(1 / 4, 2);
});
test('most_connected lists top entities by link count', async () => {
@@ -1465,7 +1466,9 @@ describe('PGLiteEngine: getHealth graph metrics', () => {
});
test('orphan_pages: pages with neither inbound nor outbound links', async () => {
// All 3 pages start with no links. Expect 3 orphans.
// All 4 pages start with no links, but entities/project-x is excluded
// from orphan reporting by the shared orphan policy ('entities' is a
// first-segment exclusion in orphan-policy.ts). Expect 3 orphans.
const h = await engine.getHealth();
expect(h.orphan_pages).toBe(3);