Skip to content

v0.44.1.0 fix(extract): subject-scope link-type inference + reserved-slug fuzzy guard - #2408

Closed
JiraiyaETH wants to merge 1 commit into
garrytan:masterfrom
JiraiyaETH:fix/link-inference-subject-scope
Closed

JiraiyaETH wants to merge 1 commit into
garrytan:masterfrom
JiraiyaETH:fix/link-inference-subject-scope

Conversation

@JiraiyaETH

@JiraiyaETH JiraiyaETH commented Jun 25, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Three correctness fixes to the deterministic (no-LLM) link-type extractor:

1. Subject-scoped page-role prior (src/core/link-extraction.ts). inferLinkType's page-role prior previously scanned the whole page for role keywords ("investor", "works at") and stamped the verb (invested_in / works_at / advises) on every company link on the page — so a third party's role described on someone else's page leaked onto the page subject (e.g. "Alice invested in Acme" on Bob's page recorded Bob as an Acme investor). The prior now fires only when the role's clause subject is the page subject (new optional selfName arg), keeps distinct people who merely share a name token apart (token-subset match), scans every role mention so an earlier third-party reference can't suppress the subject's own later role, and falls back to mentions otherwise. Legacy callers without selfName keep prior behavior.

2. Reserved-slug fuzzy guard (src/core/link-extraction.ts). makeResolver's findByTitleFuzzy path could let a weak/partial wikilink or attendee name cross the 0.55 similarity floor and resolve to an internal _-prefixed page (e.g. a leftover test fixture). Reserved slugs are now excluded as fuzzy-resolution targets.

3. Filesystem extraction path now applies the guard (src/commands/extract.ts). The FS-source path (extract.links_fs → extractLinksFromFile, used by gbrain extract and the autopilot cycle) had its own markdown scanner + a directory-based inferTypeByDir that bypassed the canonical extractPageLinks/inferLinkType entirely — so fixes (1) and (2) never reached the FS path. FS extraction now routes through extractPageLinks with the parsed body + frontmatter (so selfName derives and the guard fires); the FS-relative fallback uses the canonical inferLinkType; the duplicate directory-based inference is removed. This was the path that re-created the mis-typed edges in practice.

Test Coverage

Regression tests across test/link-extraction.test.ts + test/extract.test.ts (full extraction sweep: 279 pass / 0 fail), covering: third-party suppression (investor / employee / advisor arms), subject-attributed roles still inferring, shared-token people kept apart, pronoun-led clauses, scan-all-matches (third-party mentioned first), reserved-slug fuzzy rejection (+ control), selfName derivation, and the FS path applying the subject-scope guard (third-party → mentions; subject-title-match → invested_in). Legacy behavior pinned. The dir→verb inference shift on the FS path is asserted by the updated FS tests.

Review

  • Specialists (Testing + Maintainability): 2 informational findings, both auto-fixed.
  • Adversarial (Claude): no ReDoS, no crashes, fails safe to legacy. Found a shared-token name collision → fixed (token-subset match).
  • Codex structured review: GATE PASS (no P1); one P2 (earlier third-party match suppressing the subject's own later role) → fixed (scan-all-matches).
  • FS-path gap (caught in live verification): investigated + fixed via Codex (xhigh).

Known limitations

Both fail safe to the pre-guard "apply" (never worse than before), noted in the helper's doc-comment:

  • A non-Latin clause subject (CJK/Cyrillic) isn't parsed by the Latin clause-head regex.
  • A role written as "<Name> - <role>" splits at the list-marker boundary.

Test plan

  • bun run typecheck — clean
  • Full extraction sweep (link-extraction, extract, extract-fs, edge-extractor, reconcile-links, …) — 279 pass / 0 fail

🤖 Generated with Claude Code

JiraiyaETH pushed a commit to JiraiyaETH/gbrain that referenced this pull request Jul 15, 2026
… + FS-path routing (garrytan#2408)

inferLinkType takes selfName and applies the page-role prior only when the role's clause subject is the page subject (was a whole-page scan that leaked a third party's role onto the subject). The FS-source extract path routes through canonical extractPageLinks instead of the dir-based inferTypeByDir bypass, so the guard reaches 'gbrain extract' + the autopilot cycle.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… + FS-path routing (garrytan#2408)

inferLinkType takes selfName and applies the page-role prior only when the role's clause subject is the page subject (was a whole-page scan that leaked a third party's role onto the subject). The FS-source extract path routes through canonical extractPageLinks instead of the dir-based inferTypeByDir bypass, so the guard reaches 'gbrain extract' + the autopilot cycle.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@JiraiyaETH
JiraiyaETH force-pushed the fix/link-inference-subject-scope branch from f84979a to b40c9f1 Compare July 15, 2026 07:16
@time-attack time-attack added needs-review Needs a human skim to classify p3 P3: low priority / needs glance labels Jul 16, 2026
@Bananapple

Copy link
Copy Markdown

Confirming this fixes a real, reproducible problem on a production brain — and that fix #3 (the FS-path gap) is the load-bearing one for remote/MCP setups.

Setup: personal brain, ~145 pages (people/companies/meetings), Postgres/Supabase, driven by an external agent over MCP + gbrain sync (not local in-process put_page). On this topology the write-time auto-link is correctly skipped for remote callers, so link typing falls entirely to the sync/autopilot FS path — extractLinksFromFile → inferTypeByDir.

Symptom: the people/ → companies/ ⇒ works_at default mis-typed ~50 of ~58 person→company edges as works_at:

  • the brain owner recorded as works_at 22 companies he had only researched (acquisition targets, portfolio comparisons, deal contacts);
  • people/ronny-junge-larsen → works_at → companies/codeit, where the source text has him as a banker advising on the CodeIT succession deal, not an employee.

Running the canonical inferLinkType over the same brain (extract --source db) instead yields 8 works_at person→company edges, all correct — matching this PR's premise that the FS path "re-created the mis-typed edges in practice."

Also confirms fix #1 (whole-page role prior): the owner's page carries Iron-Law back-links to other people's roles ("Managing Partner at Axcel", "investor in Griptel"). The old page-wide prior read those and stamped invested_in on every company link on his page — the exact third-party leak this PR subject-scopes.

Can't run the v0.44 test suite directly (brain is on v0.37), but the mechanism reproduces exactly as described and the code paths match. This is the right fix.

JiraiyaETH pushed a commit to JiraiyaETH/gbrain that referenced this pull request Jul 20, 2026
… + FS-path routing (garrytan#2408)

inferLinkType takes selfName and applies the page-role prior only when the role's clause subject is the page subject (was a whole-page scan that leaked a third party's role onto the subject). The FS-source extract path routes through canonical extractPageLinks instead of the dir-based inferTypeByDir bypass, so the guard reaches 'gbrain extract' + the autopilot cycle.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@time-attack

Copy link
Copy Markdown
Collaborator

Merging — I confirmed on master that the page-role prior scans globalContext (the whole page) and stamps the verb on every person→company link, so "Alice invested in Acme" on Bob's page records Bob as an Acme investor. That is false data in the graph itself, which is the artifact the whole system exists to serve. I also like that you chased the FS path: routing extractLinksFromFile through the canonical extractPageLinks instead of its own scanner is the root-cause fix rather than patching one caller. The documented non-Latin and Name - role limitations are acceptable since both fail safe to the pre-guard behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-review Needs a human skim to classify p3 P3: low priority / needs glance

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants