Navigation surface: a page map with both numbers, and edges a model can follow
The deterministic structure work got substantially stronger today and stayed almost entirely unreachable. A model could read a whole document, Ctrl+F it, read one section by handle, and see a flat rendered outline. It could not ask for a page, walk to a sibling, or follow a single cross-reference -- so 4,414 resolved references were invisible to the only consumer that could use them, and a contents page citing "Indemnification ... 47" was a dead end. THE PAGE MAP. The engine already detects printed page labels (universal-legal-pdf-engine `_assign_printed_page_labels`, header/footer region, ambiguity left unresolved as a diagnostic), and compileLegalPdfSourceDoc already lays both numbers onto the text plane -- `anchor: page=<physical>` with the printed label in `aliases`. Nothing needed porting. The loss was downstream: the renderer prints `printed || physical` as one `[page N]` marker, and extractLocalDocument kept only `.text`, so every consumer saw one number and could not tell which sense it was. extractLocalDocument now builds the map where the artifact is still in hand. Recovered-from-markers is the fallback for text whose artifact is not (journal bodies, A2AJ reports) and is labelled `source: "markers"`, with a numeric marker carried under BOTH readings because the renderer genuinely collapsed them -- claiming one would invent provenance. Page requests resolve against both schemes. Front matter printed i-viii makes "1" name two different sheets; a table of contents means the second and a viewer means the first, so that request is REFUSED as ambiguous and the caller qualifies it (`printed:1`, `pdf:1`). Ranges resolve endpoint by endpoint and take the span by position, because a range across a numbering change is not arithmetic on either label. library_find gains `pages`: "47", "12-18", "3,5,9", "printed:iv - printed:2". The filter runs on offsets AFTER the match so every `at` stays a document offset that library_read offset= accepts -- scoping narrows where you look, never renumbers what you find -- and the internal cap is raised first, since applying max_results before the filter returns nothing whenever the leading hits sit outside the scope. library_links is new: stand at a provision and get its ancestors, siblings, children, the references it makes and the references made to it, each as a handle library_read accepts. Without a section it returns the reference census and the hubs. A whole-document abstention is surfaced as `abstained` with its note, because zero edges and "this skeleton is too thin to address" are different answers and only one of them means the document has no references. Two defects the tests found, both fixed here: - "overlaps the page" is not "printed on the page". A node's span runs to the next heading, so the last section of page 1 laps onto page 2 by the marker line alone. pageSections splits `starts` from `continuedFrom`. - a rendition whose provider compiler has not landed carries no blocks; that is "no pages", not a crash inside a read tool. extractLocalDocument's return gains `pages` (additive; the existing shape assertion is updated). No existing behaviour changes: section reads, windowed reads, unscoped find and outline are untouched. 22 unit tests plus one end-to-end tool test over a mocked artifact whose front matter is printed "i". tsc clean; localAssistantTools, localPdfLookup, legalCrossReference and legalTextSkeleton suites pass (69 tests). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H9ToHYJVDxfeJcJwdzrP2H
| Repository | eliziff/Beaver |
|---|---|
| Author | Eli Ziff <eliasziff@gmail.com> |
| Authored | |
| Parents | 1d09f681 |
| Stats | 4 files changed , +1115 , -7 |
| Part of | SourceDoc / DOCX engine: redlines, tracked changes, editing benchmark |
Capture this commit into my fork
Download a Markdown prompt that tells Claude how to port this
exact commit into your working tree. Run it via
claude -p < capture-commit-559b3841.md
from inside the repo you want the change in.