feat(scholar): scholarly-graph federation (OpenAlex + Semantic Scholar + Crossref) with citation-edge provenance#8
Merged
Conversation
…itation-edge provenance Add gather.scholar, a scholarly-graph federation adapter unifying OpenAlex, Semantic Scholar, and Crossref into one intake. Citation edges (references and citations) become first-class provenance receipts (method citation-edge, registered DIRECT on the ladder) recording which provider asserted each link, sealed into the digest alongside the papers. Records dedup by normalized DOI into one unified compiled item whose derived_from points back at every provider's contribution, so no provenance is dropped; a DOI is the only join key, never a fuzzy title match. Pure per-provider parsers, one isolated impure edge with an injectable fetcher, so the whole federation is tested offline over recorded fixtures. CLI: gather scholar QUERY [--providers ...] [--no-federate] [--edges] [--json]. 33 new tests over fixtures; full suite 322 passing; scholar.py ruff + mypy clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes the CI lint gate on this branch; the import fix already landed on main but this branch predated it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Binding packet.get(...) once (instead of calling it twice across the isinstance guard) lets mypy narrow the value to Mapping, clearing the union-attr / arg-type errors on the CI type-check gate. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this ships
gather.scholar, a scholarly-graph federation adapter that unifies OpenAlex + Semantic Scholar + Crossref into gather's intake, riding on the existing accountability layer rather than replacing it.The feature-leadership property: a research-intake organ should reach the scholarly graphs best-in-class, and it does so here without loosening a single accountability claim. Every paper still carries a provenance receipt, every run still folds into a witnessed digest, and the new citation graph is sealed the same way.
Citation edges as first-class provenance
A reference or a citation is not a number on a record; it is an edge with an origin.
citation_edgesturns eachfrom -> tolink into its own re-checkable receipt (method="citation-edge", registered DIRECT on the method ladder), recording which provider asserted it. Edges dedup by(from, to, direction, provider)and fold into the same witnessed digest as the papers, so the seal covers the graph (nodes and links), not just the nodes. An edge witnesses a claim the provider made, never a fact gather verified, and the receipt says so.Dedup by DOI, no provenance dropped
The same paper surfaces from all three graphs.
federatejoins works on their normalized DOI (normalize_doistripshttps://doi.org//doi:prefixes and lowercases) into one unified record. The unified item is a DERIVEDcompileditem whosederived_frompoints back at every provider's contribution, the same discipline as the corpus store: bodies deduped, receipts never. A DOI is the only join key; a title or author overlap never merges two works, and a DOI-less work stays its own record.Zero-dep / offline-testable posture kept
jsonandurllibonly, no new dependency.parse_openalex,parse_semanticscholar,parse_crossref); OpenAlex abstracts are reconstructed from its inverted index.ScholarSourceis the isolated impure edge behind theSourceshape, with an injectable fetcher so the whole federation is driven offline over recorded fixtures in tests. No live network is touched by the suite.CLI
--edgesfolds citation edges into the witnessed digest as first-class receipts.Honest reach labels
crossref-apimethod, never dressed up as the paper.citationsstays honestly empty for those providers; Semantic Scholar carries both directions.abstract_providerrecords which graph it came from.What is shipped vs specced
Fully shipped and tested, end to end: the three parsers, DOI normalization + federation, citation-edge receipts, the
ScholarSourceedge, and the CLIscholarcommand (verified running the full CLI path offline via a stubbed network edge). Nothing is left as a spec stub.Test evidence
New suite
tests/test_scholar.py(33 tests over recorded fixtures, one paper described in all three graphs' native shapes):Full suite green (33 new + 289 pre-existing):
CLI path exercised offline (stubbed
net.http_get):Lint/type on changed files:
ruff checkpasses;mypy src/gather/scholar.pyreports 0 errors (the 5 remaining package-level mypy errors are pre-existing inbrowser_evidence.pyfrom PR #6, untouched here).Limitations (honest)
gather.federation_policyretry/backoff surface would be the natural composition seam for that, left as a follow-up).Docs
README module list, CLI examples, and roadmap updated; CHANGELOG
Unreleasedentry added.🤖 Generated with Claude Code