Documentation
¶
Overview ¶
Package cache stops a session paying twice for the same artifact (§9.3).
Rev 1 did `if cache.SeenRecently(lead) { continue }`, which had two problems. The planner asked for something and got nothing back, so the next replan spawned an equivalent lead and the loop livelocked. And keying at the LEAD level missed the common case: two differently-worded queries surfacing the same page still paid for two fetches.
Both fixes follow from keying on the ARTIFACT — a normalized URL, a DOI, a query hash — and from returning the stored result rather than skipping. A hit is an answer, not an absence.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func DOIKey ¶
DOIKey is the key for an academic identifier.
Present now because §9.3 names it and because AcademicActor (M6) will resolve the same DOI from several routes — a publisher page, an Unpaywall copy, an arXiv mirror — which is the case URL keying cannot fold.
func NormalizeURL ¶
NormalizeURL reduces a URL to a stable identity for the same document.
Deliberately conservative about what it drops. Over-normalizing is worse than under-normalizing here: a cache that treats two different pages as one returns evidence from a page the claim does not cite, which is a correctness failure, whereas a missed hit only costs a fetch.
That is why the path is left alone apart from a trailing slash, and why query parameters other than known tracking tags are kept and sorted rather than stripped — `?id=42` and `?id=43` are different documents, and nothing here can tell which parameters are load-bearing for a given site.
func QueryKey ¶
QueryKey is the key for a research query.
Folds casing, punctuation and whitespace. It does NOT fold word order, which an earlier version did on the reasoning that "MambaByte PG-19 results" and "PG-19 results MambaByte" are the same search. They are — but so, under a sorted key, are these:
"did Acme acquire Beta" / "did Beta acquire Acme" "is drug A safer than drug B" / "is drug B safer than drug A" "does smoking cause cancer" / "does cancer cause smoking"
Word order carries the direction of a relation, and a research question is mostly relations. The consequence was not a missed saving: the second question was completed as skipped_cache and never researched, while the digest was credited with the first one's claims — so the planner could mark it answered and the report would never address it.
Keeping word order costs only the occasional missed hit on a genuinely reordered query, which is the cheap direction to be wrong in.
Types ¶
type Cache ¶
type Cache struct {
// contains filtered or unexported fields
}
Cache stores artifacts for one session.
Session-scoped and in memory, which is a deliberate limit rather than an oversight. A cross-session cache has to answer "how stale is too stale", and that question has a different answer per question type — a settled fact keeps for months, a "current consensus" for days. §14.2's corpus is what would settle it, so the durable version waits for data rather than for a guess.
The within-session win is the one §9.3 actually names, and it needs none of that: a page fetched twenty seconds ago has not changed.
func (*Cache) Get ¶
Get returns a cached artifact.
A nil cache is a miss, so callers need no conditional at every site.
type Entry ¶
type Entry struct {
Key string
// Claims is how much evidence the artifact produced. A count rather than
// the claims themselves: within a session they are already in the store
// under the lead that first found them, and re-inserting copies would
// inflate every claim-count metric §14.3 reads while adding nothing a
// reader or the report can use.
Claims int
// Text is the extracted document, for a URL-keyed entry. This is the field
// that makes two queries converging on one page pay for one fetch.
Text string
// Title and PublishedAt travel with the text so a reused document produces
// the same claim metadata as a freshly fetched one.
Title string
PublishedAt *time.Time
StoredAt time.Time
}
Entry is a cached artifact.