Distillate

Text to evidenceLesson 2openfrom namaq

Who owns a page

A reader asked whether /sources lets you read the book, and the answer turned out to be a question about ownership.

A reader asked whether /sources lets you read the book. The answer turned out to be a question about ownership, and the ownership turned out to be wrong.

The symptom

Open volume 5 of the Siyar in /sources, read every entry the contents lists, and you have read 128 printed pages of it — in five separate runs, with a 21-page hole after Aisha's entry that nothing on screen mentions. Volumes 1 and 2 are complete. Volume 3 and volumes 6 through 28 are marked لم يُقرأ بعد, honestly. Only the holes inside a volume that has been read are silent.

The interesting part is why the same UI is honest at one level and silent at the other. It isn't an oversight. The volume list can be honest because the edition declares its volumes, so something exists to be missing from. Inside a volume, nothing declares the pages — so there is no absence to detect.

Follow it down

Why does nothing declare the pages? Because until now a page belonged to the account that extracted it:

SourceAccountPage {
  accountId    // the entry this page was read for
  sequence     // its position within that entry
  printedPage  // nullable
}

A page's identity was (entry, ordinal). The book's own coordinates — volume and printed page — were along for the ride. So the reader could page through an entry and had no notion of paging through a volume, and a volume had no notion of which of its pages existed.

The evidence that settled it

Printed page 230 of volume 4 exists twice, at the same Shamela id 1656: 413 characters under Zaid ibn Harithah, 402 under Abdullah ibn Rawahah. Neither contains the other. The entry boundary falls inside the page, so each batch kept its own half.

Zaid's copy,  tail:  … قَالَتْ: أَنَا لِزَيْدِ بنِ حَارِثَةَ. إِسْنَادُهُ حَسَنٌ.
Rawahah's copy, head: ٣٧ - عَبْدُ اللهِ بنُ رَوَاحَةَ بنِ ثَعْلَبَةَ …

Forty-one pages are split this way, across 67 of the 88 batches, and three more are held twice in full where the work places a short entry inside a longer one. Which means a printed page's text depended on who read it — the same mistake ADR 0010 already rules out for values, arrived at from a different direction.

The decision

A printed page is a fact about the edition, so it belongs to the volume:

SourceVolume ──owns──> SourcePage   (volumeId, printedPage)  one page, one text
SourceAccount ─spans─> a range of them
Citation ──> Passage ──> Page ──> Volume     (the account is not in this chain)

The entry survives — it is a real thing in the book, and notInSource needs a bearer — but it stops owning text and becomes a span. Read ADR 0018 for the argument and the plan for the work.

What falls out for free

Once a page is keyed by volume and printed page, a passage anchor can be derived — paragraph n of the page is <volume>/<printedPage>-p<n> — instead of declared in a list and zipped to the text by position. That deletes the drift class lesson 0001 is about: not by guarding it, but by removing the second copy that could fall out of step. Stable anchors also stop Citation.passageId churning on every re-import.

Retrieval

Why could the contents view mark an unread volume but not an unread stretch inside one?

Retrieval

Under the new model, what does Hamzah's account span?

The move worth stealing

The question you asked was about a reading experience. The answer was a key: a page was identified by who read it rather than by where it sits in the book. Every symptom — no continuous reading, no detectable gaps, split text, five misfilings nothing caught — followed from that one choice, and all of them resolve together when the key changes.

When a UI can't tell you something, it is often because the model was never asked to know it.

Primary source: ADR 0018, then the plan — read the acceptance criteria as a description of what "the book is readable" means concretely.

Card: Source pipeline card, which still describes the model as it stands today.