Text to evidenceLesson 1openfrom namaq
Where text becomes citable
A source reaches Namaq as five nested things, and only one of them is a design decision you could still change cheaply.
A source reaches Namaq as five nested things, and only one of them is a design decision you could still change cheaply. This lesson finds that one.
The shape, in one pass
Open data/history/batches/aisha-bint-abi-bakr/ and you have the whole
model in front of you: a batch.json, a summary.md, and
accounts/aisha-bint-abi-bakr/ holding 67 printed pages as 001.md …
067.md, each with a .notes.md beside it for that page's editorial
footnotes.
Nesting, outermost first:
source one edition of a work (al-Risalah's Siyar, ed. al-Asad) + its 28 volumes
account that work's entry for one subject ← unique per (source, subjectKind, subjectSlug)
page one printed page of that entry ← bodyMarkdown + notesMarkdown
passage one paragraph of that page ← addressed by an anchor: 135-p2
claim one value the model holds ← field: "fullName"
citation the pinpoint link between them ← passageAnchor + excerptArabic
The reader shows the top three. A profile shows the bottom two. Nothing in the app renders a claim without the passage underneath it, which is the point of ADR 0009: evidence attaches to a subject slug, not to a profile page, so a graph-only person can carry citations before anyone writes their profile.
Four of these five are barely decisions
Source, account, page and claim are each pinned by something outside Namaq's control. A page is
a page because the book printed it that way — ADR
0016 says so outright, and a citation's pageReference depends on it. An account is
unique per subject because a work has one entry per person. A claim must name a field because
history:validate rejects one that names nothing.
The passage is the exception. Nothing outside the repo says a paragraph is the citable unit, or
that 135-p2 is what addresses it. That was chosen — and the way it was chosen is the
interesting part.
The choice: an anchor is bound to its paragraph by position
The page file is plain markdown. It contains no anchors, no ids, no markers:
١٩ - عَائِشَةُ بِنْتُ الصِّدِّيْقِ أَبِي بَكْرٍ التَّيْمِيَّةُ …
بِنْتُ الإِمَامِ الصِّدِّيْقِ الأَكْبَرِ … القُرَشِيَّةُ، التَّيْمِيَّةُ …
وَأُمُّهَا: هِيَ أُمُّ رُوْمَانَ بِنْتُ عَامِرٍ …
The anchors live in batch.json, as a bare list:
"passages": [{ "anchor": "135-p1" }, { "anchor": "135-p2" }, { "anchor": "135-p3" }, …]
And they are joined to the text by index, nothing else
(src/lib/history/batchSchema.ts):
export function passageExcerpts(page, files) {
const paragraphs = (files[page.bodyFile] ?? '').split(/\n{2,}/)…;
return new Map((page.passages ?? []).map((p, index) => [p.anchor, paragraphs[index] ?? '']));
}
So 135-p2 means "the second paragraph on this page" and means nothing else. The
name looks like an identifier; it is an ordinal wearing a costume.
What that buys
The page file stays exactly the book's text. No markup for an extractor to get wrong, no ids to keep in sync by hand, and a re-extraction that reproduces the same paragraphs reproduces the same anchors for free. Compare the alternative — anchors written into the markdown — and you have made the source text a Namaq format, which cuts against the source text is the book.
What it costs, and how the cost is contained
Insert one paragraph in the middle of a page and every anchor after it now excerpts the following paragraph. Citations keep resolving. Nothing errors. They just quietly point one paragraph off.
The validator's answer is a count, not a checksum:
if (declared > 0 && declared !== paragraphs) {
issues.push({ …, message: `declares ${declared} passage(s) but "${page.bodyFile}" has ${paragraphs} paragraph(s)` });
}
That is the design lesson worth carrying: excerptArabic on the citation is not a
convenience copy of the passage. It is the check digit on a positional binding.
Retrieval
A page file gains a paragraph mid-page, and an anchor is appended to the list. What catches it?
Retrieval
Why does the page markdown carry no anchors of its own?
Bring it back to your question
The group headings — شهداء بدر, السابقون الأولون — now have a precise answer rather than a rule
of thumb. A heading is an index line with no paragraph, so it yields no passage; with no
passage there is no passageAnchor, and a citation without one cites a page and no
text. The martyrdom is a real model value, but the evidence for it has to come from a paragraph
inside that person's own account, which is where al-Dhahabi actually says it.
The heading is not excluded because it is a heading. It is excluded because the citable unit is
a paragraph, and that is the decision you could revisit — PassageKind already
distinguishes NOTE from BODY, so a third kind is a small change. Whether
it should exist is a design question for a later lesson.
Read next
Primary source: docs/data-pipelines.md — the authored-layer map this whole model is described by; read the three hand-authored paths and which of them are one-way. Then ADR 0010 for why files outrank rows at all.
Card: Source pipeline card — the full artifact-to-screen table, the commands, and the rules that decide what may exist.