Journal 2026-07-14
Graph hub seeding implemented (branch graph-hub-seed, PR pending)
Implemented /plans/graph-hub-seed.md end to end: graphstore SeedFromExport + persisted seed_etags (additive, schema v1), fetch.FetchConditional with explicit if-none-match, seedGraph hooks in demarkus-mcp's mark_backlinks/mark_graph/mark_explore, and per-world seedWorldGraph on the broker through dispatchWithAuth (FetchConditional added to worldDispatcher; trivial passthrough). Local-wins rule held as designed: authoritative = real fetched status plus non-zero CrawledAt, seeded nodes carry zero CrawledAt, and the rule is shared with Merge via extracted dropEdgesFromLocked/upsertEdgeLocked helpers. Never fatal, 5-minute per-host throttle. ParseExport's first production caller; the soul's live /graph.md is still two-column legacy (v6) and seeded fine.
The interesting find: the first live cold-start check returned no backlinks despite a successful seed. Root cause was URL canonicalization, not seeding. The hub aggregate keys rows on canonical mark://host:6309 URLs while mark_backlinks built its key as defaultHost + path, and the plugin's -host flag omits the port. A latent pre-existing mismatch (portless -host crawls vs full-URL queries) that seeding surfaced. Fixed by canonicalizing through resolveURL in markBacklinks, markGraph's start URL, and explore's backlinks section, with a regression test pinning the portless -host case. Recorded in the plan's implementation notes.
Verification: full client + tools suites green, pre-commit clean, live cold start (scratch HOME) answered 4 backlinks for /patterns.md with zero crawls (133 nodes / 190 edges seeded, etag persisted, all zero CrawledAt), then a depth-1 crawl of /conventions.md flipped it authoritative and its enriched edges replaced the seeded row while 130 nodes stayed seeded. FetchConditional got the client's first wire-level test (in-process QUIC listener, self-signed cert).
Roadmap updated (seeding item IMPLEMENTED; flip to DONE on merge). PR 251 bookkeeping (edge-semantics DONE flip, debt.md fetch-callback removal) was already done last session. Changes staged; Fritz commits.
Out of scope confirmed untouched: TUI seeding, Exported-timestamp arbitration, publish-time edge extraction.
PR 253 review round
CodeRabbit posted three findings; all valid, all fixed (details appended to the plan doc): seedGraph now seeds the resolved host from the tool URL instead of only defaultHost, and SeedFromExport's edge-drop set now includes observed seed nodes so a source whose outgoing set went to zero sheds its stale seeded edges (Merge's refreshed criterion, shared via a new observedStatus helper). One test-only guard: seedGraph tolerates a nil client since partial handlers in tests construct without one. Suites and pre-commit green, changes staged.
Issue 222 fix (branch fix/graphstore-merge-preserve-resolved)
Fixed the Merge title/status regression from issue 222: a crawl node with an unobserved status (error/external/blank) no longer clobbers a stored node; the stored copy stays untouched, keeping its CrawledAt so it remains authoritative against seeds. Never-seen nodes are still recorded. Reused the observedStatus predicate from the seeding review round, so Merge's node upsert, its edge-refresh set, and SeedFromExport all share one definition of "actually read". This also closes the seeding amplification path: a stalled federation-agent crawl can no longer publish blanked titles into hub /graph.md (fedcrawl merges through the same code). Tests pin the preserve case, the still-authoritative-against-seed interplay, and that new unfetched nodes are still recorded. Suites and pre-commit green, staged; Fritz commits.