fix(citation): repair search-inside against the live IA contract#165
Draft
tieguy wants to merge 1 commit into
Draft
fix(citation): repair search-inside against the live IA contract#165tieguy wants to merge 1 commit into
tieguy wants to merge 1 commit into
Conversation
Two defects in the Layer-2 grounding path, both caused by the Internet
Archive's full-text contract having moved away from what this module and
its fixtures encode.
1. Zero matches on a wide query produced a false `not_supported`.
The endpoint returns zero matches for the *entire* query when any single
term is absent from that item's OCR index. Because an empty result is what
`NoMatches` reports, and the caller turns that into `not_supported`
(PRD-0009 resolved Q4), one unindexed word could turn a well-supported
claim into a verdict against the citation.
Observed live on `onoriginofspeci00darw` (2026-07-23), the 1871 D. Appleton
printing, which spells it "favored": `natural selection preservation slight`
returns 493 matches, adding `favourable` returns 0, and `favourable` alone
returns 0 - while `preservation` alone returns 25. Spelling variants, OCR
damage and hyphenation breaks all collapse the same way.
`search_query_ladder` now yields progressively narrower queries (6 terms,
then the 3 most distinctive, then the single most distinctive) and
`run_search_ladder` stops at the first rung with matches. `NoMatches` is
reached only when every rung is empty, so `not_supported` means the scan
genuinely lacks the claim's rarest word. The prior comment claiming term
selection "only affects recall, never grounding" was true when snippets
were merely evidence, and stopped being true once an empty result became a
verdict.
2. Highlight markers leaked into supposedly verbatim OCR.
The live endpoint emits `<IA_FTS_MATCH>...</IA_FTS_MATCH>`; only the
historical `{{{...}}}` form was stripped. That put markup into bytes
`SearchInsideMatch::text` documents as exactly what the OCR holds - it
reached the verifier prompt, and it broke `page_of_passage` quote location
for any quote spanning a match boundary. Both forms are stripped now.
The test fixtures encoded the dead shapes, which is why both survived; they
now carry the live form, with the historical form asserted separately.
Also corrects the module header, which claimed snippet search "typically
works even for lending-restricted scans". It does not: the endpoint 403s
print-disabled/lendable items. Measured across 130 book editions cited by
seven en.wikipedia articles, 11 had an exact groundable scan and every one
was `lendable`. That is a reach limit, not a correctness problem - a 403 is
`Unreachable`, never `not_supported`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CQDLheiqLry2xc8gYSAEG8
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.
Two defects in the Layer-2 book-grounding path (
citation/search_inside.rs), both caused by the Internet Archive's full-text contract having moved away from what this module — and its test fixtures — encode. Found while porting this logic to another codebase and exercising it against the live endpoints.1. Zero matches on a wide query produced a false
not_supportedThe endpoint returns zero matches for the entire query when any single term is absent from that item's OCR index. Because an empty result is what
NoMatchesreports, and the caller turns that intonot_supported(PRD-0009 resolved Q4), one unindexed word could turn a well-supported claim into a verdict against the citation.Reproducible today against
onoriginofspeci00darw— the 1871 D. Appleton printing of On the Origin of Species, which uses the American spelling "favored":Five terms appearing on hundreds of pages are zeroed by one sixth term the index does not hold. Spelling variants, OCR damage (this scan contains
sufBciently,indi\4duals) and hyphenation breaks all collapse the same way.Fix.
search_query_ladderyields progressively narrower queries — the capped term list, then the three most distinctive terms, then the single most distinctive — andrun_search_ladderstops at the first rung returning matches.NoMatchesis reached only when every rung is empty, sonot_supportednow means the scan genuinely lacks the claim's rarest word. At most three requests, and only when earlier ones come back empty.The prior comment on
search_queryheld that term selection "only affects recall, never grounding". That was true while snippets were merely evidence; it stopped being true once an empty result became a verdict.2. Highlight markers leaked into supposedly verbatim OCR
The live endpoint emits
<IA_FTS_MATCH>…</IA_FTS_MATCH>; only the historical{{{…}}}form was stripped. A live response:{"text": "\"The only distinct moaning of the word '<IA_FTS_MATCH>natural</IA_FTS_MATCH>' is..."}SearchInsideMatch::textis documented as "exactly what the OCR holds", so this put markup into the bytes we hand the verifier. Two consequences: the tags reach the prompt, andpage_of_passagelocates quotes vialocate_quote/locate_quote_fuzzyagainst text containing them — a quote spanning a match boundary can fail to locate and get attributed to the wrong page.Fix. Both marker forms are stripped.
Why these survived
The fixtures encoded the dead shapes:
SEARCH_HITSused{{{…}}}, so the marker assertion passed against a form the API no longer emits. The fixtures now carry the live form, with the historical form asserted separately, plus a check that no marker survives into text documented as verbatim.Worth noting the 2026-07-12 contract smoke did catch the
itemURL→/borrowdrift; it just didn't cover marker syntax or query semantics.Also in this commit
The module header claimed snippet search "typically works even for lending-restricted scans, so grounding needs no borrow". The second half holds; the first does not — the endpoint returns 403 for print-disabled/lendable items, so in practice only
full accessscans can be grounded.Spot-checked on 2026-07-23 by walking the book citations of seven en.wikipedia articles (Charles Darwin, Jane Austen, Isaac Newton, Battle of Waterloo, On the Origin of Species, Herodotus, Charles Dickens) through catalog resolution and scan availability: of 104 citations resolving to a record, 72 had no archive.org scan at all, 10 had only a different-edition scan, and 11 had an exact groundable scan — every one of those 11
lendable, all 403. This is a convenience sample, not a survey of the encyclopedia, and the header states it as a dated measurement.This is a reach limit rather than a correctness problem: a 403 is
Unreachableand degrades toSourceUnavailable, never tonot_supported.Testing
cargo test -p sp42-citation— 343 passing.cargo test --workspace --exclude sp42-desktop— 1101 passing, 0 failures.cargo fmt --all -- --checkand pedanticclippyon the crate — clean.scripts/ci-all.shcould not be run to completion locally:glib-sysfails its build script for want of system GTK/glib headers on this machine, which only affectssp42-desktop. CI should cover it.run_search_ladderwas extracted fromprepare_book_groundingbecause inlining the loop trippedclippy::too_many_lines; an extraction seemed better than an#[allow], but it is easy to reshape if you'd prefer it otherwise.One existing test needed its stub queue extended —
page.rs::indexed_scan_with_no_snippets_is_not_supported_never_unavailablequeued a single search response and the ladder issues three for that claim. Its assertion is unchanged.Not addressed
extract_ocaidrejects the/stream/path form that live full-access items actually carry (http://www.archive.org/stream/onoriginofspeci00darw). ThefromRecordfallback inopenlibrary.rsrecovers the ocaid in the case observed, so it only bites when a/stream/item lacksfromRecordand multiple records make the sole-record fallback ambiguous. Left out to keep this focused; happy to add it here or separately.Draft
Draft because fix 1 changes verdicts the module has been emitting. CONTRIBUTING asks for a PRD on operator-facing behavior changes — my reading is that this restores PRD-0009 Q4's stated intent rather than altering it, but that is a maintainer call and I would rather ask than assume.
🤖 Generated with Claude Code
https://claude.ai/code/session_01CQDLheiqLry2xc8gYSAEG8