fix(orphans): exclude life/events/ chronicle volume from orphan_ratio (#2264) (#3214)

orphan_ratio's denominator is swamped on auto_chronicle brains by the
machine-generated chronicle events (life/events/<day>-<hash>, written
per eligible event) — no inbound links by design. The shipped policy
already excludes raw/atoms/skills/dreaming/daily and extracts/, but
life/events/ was still counted; on a 1,657-page auto_chronicle brain it
was ~72% of the orphan mass, enough to pin the ratio red.

Add 'life/events/' to DENY_PREFIXES — a scoped prefix, NOT the whole
`life/` first-segment, so human-authored life/diary/ (gbrain capture
--type diary) stays IN the denominator. Same shipped hardcoded-class
mechanism as the existing entries; not the #2215 user-config route
(closed not_planned). Knowledge classes (concepts/people/notes/projects)
also stay in, so genuine graph decay still trips.

Regression in test/orphans-pure-fn.test.ts: life/events/ now excluded
(fails before, passes after); life/diary/ and concepts//notes//projects/
pinned as still-counted. doctor's orphan_ratio uses the same shouldExclude
path (getOrphansData; local + doctor-remote MCP), covered transitively.
This commit is contained in:
Anton Senkovskiy
2026-07-23 14:33:02 -07:00
committed by GitHub
parent 2f4ad2c0a4
commit 0a4f062cac
3 changed files with 44 additions and 0 deletions
+4
View File
@@ -33,6 +33,10 @@ const DENY_PREFIXES = [
'_templates/',
'openclaw/config/',
'extracts/',
// auto_chronicle event volume (life/events/<day>-<hash>) — machine leaf, no
// inbound links by design. Deny-prefix (not whole `life/` first-segment) so
// human-authored life/diary/ stays IN the orphan denominator. (#2264)
'life/events/',
];
const FIRST_SEGMENT_EXCLUSIONS = new Set([
+32
View File
@@ -177,6 +177,38 @@ describe('runDoctor — orphan_ratio check (local surface, D5)', () => {
expect(check!.message).toContain('gbrain extract links --by-mention');
});
test('#2264 — auto_chronicle life/events/ volume is excluded, so it does not trip orphan_ratio', async () => {
// Healthy knowledge graph: 100 fully-linked entity pages (no real decay).
for (let i = 0; i < 100; i++) {
await engine.putPage(`people/person-${i}`, {
type: 'person', title: `Person ${i}`, compiled_truth: 'b', timeline: '', frontmatter: {},
});
}
await engine.putPage('writing/index', {
type: 'note', title: 'Index', compiled_truth: 'index', timeline: '', frontmatter: {},
});
const links = [];
for (let i = 0; i < 100; i++) {
links.push({
from_slug: 'writing/index',
to_slug: `people/person-${i}`,
link_type: 'mentions', link_source: 'markdown', context: '',
});
}
await engine.addLinksBatch(links);
// Machine chronicle volume: 500 life/events/ pages, no inbound links by
// design. Without the exclusion these swamp the denominator (~83% orphan
// → FAIL); excluded, orphan_ratio reflects the healthy knowledge graph.
for (let i = 0; i < 500; i++) {
await engine.putPage(`life/events/2026-08-${i}-evt`, {
type: 'event', title: `Event ${i}`, compiled_truth: 'e', timeline: '', frontmatter: {},
});
}
const report = await runDoctorJson();
const check = findCheck(report, 'orphan_ratio');
expect(check!.status).toBe('ok');
});
test('zero entity pages → vacuous status ok', async () => {
const report = await runDoctorJson();
const check = findCheck(report, 'orphan_ratio');
+8
View File
@@ -216,6 +216,8 @@ describe('shouldExclude — orphan filter regression (preserve curation)', () =>
expect(shouldExclude('dashboards/_index')).toBe(true);
expect(shouldExclude('scripts/build')).toBe(true);
expect(shouldExclude('output/foo')).toBe(true);
// #2264 — auto_chronicle event volume (life/events/…) is a machine leaf.
expect(shouldExclude('life/events/2026-08-01-abc123')).toBe(true);
});
test('first-segment exclusions fire', () => {
@@ -245,6 +247,12 @@ describe('shouldExclude — orphan filter regression (preserve curation)', () =>
expect(shouldExclude('companies/acme')).toBe(false);
expect(shouldExclude('writing/post-1')).toBe(false);
expect(shouldExclude('agents/arya/qa-reports/launch-review')).toBe(false);
// #2264 — knowledge classes must STAY in the denominator so real graph decay still trips.
expect(shouldExclude('concepts/information-architecture')).toBe(false);
expect(shouldExclude('notes/some-note')).toBe(false);
expect(shouldExclude('projects/proj-x')).toBe(false);
// #2264 — only life/events/ is excluded; human-authored life/diary/ stays counted.
expect(shouldExclude('life/diary/2026-08-01-xyz')).toBe(false);
});
});