What is wrong
expandRange hard-codes one tradition's spelling for every tradition. scripts/compile.ts:68-74:
case 'chapter_verse': {
const out: string[] = [];
for (let ch = 1; ch <= range.counts.length; ch++) {
const verses = range.counts[ch - 1];
for (let v = 1; v <= verses; v++) out.push(`${ch}.${v}`);
}
Every multi-part kind does the same: book_line at :48, book_chapter at :56, book_chapter_verse at :64.
The text defines how a citation is spelled. The schema does not. A citation system states that spelling in prose — data/systems/bible-book-chapter-verse.yaml opens with "OSIS locator: Book.Chapter.Verse" — and locator_regex validates against it. assertValidLocator (compile.ts:189-204, called from emitBlockReferences at compile.ts:366) is where the mismatch surfaces, but the regex is the guard, never the definition. A tradition whose separator is not . therefore cannot use references_range at all.
The build fails loudly and correctly. This is not silent corruption. But the author is left with no way forward except listing every locator by hand.
Reproduction
Branch feat/quran in textrefs/registry (draft PR, textrefs/registry#23). It adds a system whose canonical form is 2:255:
locator_regex: '^(?<sura>[1-9]|[1-9][0-9]|10[0-9]|11[0-4]):(?<verse>[1-9][0-9]{0,2})$'
and a work that uses references_range: - kind: chapter_verse with the 114 sura verse counts. npm run compile:data:
Error: quran-sura-verse: locator "1.1" does not match locator_regex
at assertValidLocator (/…/scripts/compile.ts:191:9)
at emitBlockReferences (/…/scripts/compile.ts:366:3)
at compileRegistry (/…/scripts/compile.ts:592:16)
Why it matters
data/AGENTS.md:16 names this exact case as the one references_range is for: "references_range: when the canonical set is regular and complete". The Qurʾān's verse space is regular, complete, closed and source-defined — 114 suras, 6,236 verses. It is the textbook instance, and it is the one shape the field cannot express.
The workaround is to list all 6,236 locators under references:. That is legal — assertValidLocator only checks the regex and rejects / — but it puts a generated table in a hand-authored file and loses the reason the field exists.
The alternative is to spell the Qurʾān 2.255. Because the tradition defines the spelling, that locator is not merely unconventional. It is wrong.
Is a colon otherwise safe?
Checked end to end, and yes:
- UUIDv5 seed —
compile.ts:298-304 joins [workKey, systemKey, locator] with \n. Byte-transparent.
- Reference IRI —
https://textrefs.org/id/ref/{uuid}. The locator never appears in an IRI.
- Alias keys —
compile.ts:405,408. quran/2:255 is a fine key.
:// in an alias key — settled, no new guard needed. The alias table tells cite-aliases from external mapping identifiers by testing for :// (compile.ts:810-813, and the filter at src/pages/cite/[...alias].astro:7). A locator can never carry that substring, because assertValidLocator rejects any / (compile.ts:199) and :// contains one. Verified against the compiler: a separator of :// fails with locator "1://1" contains "/", which the /cite/ alias grammar cannot represent.
- URL building —
AliasList.astro:12 and src/pages/id/ref/[uuid]/index.astro:69-71 both use new URL('/cite/' + alias + '/', site). The path is absolute, so no scheme ambiguity, and : is a valid pchar under RFC 3986.
- Build artifact — the static route writes
dist/cite/quran/2:255/index.html. Fine on APFS and Linux; would break if dist/ ever has to exist on NTFS.
- Finder —
parseQuery (src/lib/find.ts:143-152) takes the last whitespace token containing a digit. 2:255 qualifies.
Proposed fix
The separator is a property of the citation system, not of one work's range entry. It belongs beside locator_regex and the description that defines it. SystemSource.chapter_sizes (source-schema.ts:236) is the precedent: structural facts about a tradition already live on the system.
# data/systems/quran-sura-verse.yaml
key: quran-sura-verse
locator_regex: '^(?<sura>[1-9]|[1-9][0-9]|10[0-9]|11[0-4]):(?<verse>[1-9][0-9]{0,2})$'
separator: ':'
The regex keeps validating. The separator only tells the expander how to join the parts it generates.
An earlier draft of this issue proposed an optional separator on each multi-part references_range kind. That is the wrong home. It lets one system be spelled 1.1 under one work and 1:1 under another, with no single place that states what the tradition does.
Touches:
scripts/source-schema.ts:224-237 — an optional separator on SystemSource, defaulting to ..
scripts/compile.ts:37-103 — expandRange takes the system. system is already destructured at :348 and in scope at the call site :353-355, beside the assertValidLocator(locator, system) at :366.
src/content/docs/get-started/authoring.md:246-371 — document the field. That section is the authoring contract, and it lists every range kind and its fields.
The default keeps every existing locator byte-identical, so no reference UUID moves. #98 confirmed this for the per-range design: the compiled bundle stayed byte-identical across all 86,477 records, apart from the created timestamp in datapackage.json. A system-level default of . behaves the same way.
Filing rather than implementing, since this changes the authored data contract.
Scope
Not in v0.1.0. references_range takes no changes in this release, so textrefs/registry#23 is parked rather than pending.
Related
What is wrong
expandRangehard-codes one tradition's spelling for every tradition.scripts/compile.ts:68-74:Every multi-part kind does the same:
book_lineat:48,book_chapterat:56,book_chapter_verseat:64.The text defines how a citation is spelled. The schema does not. A citation system states that spelling in prose —
data/systems/bible-book-chapter-verse.yamlopens with "OSIS locator:Book.Chapter.Verse" — andlocator_regexvalidates against it.assertValidLocator(compile.ts:189-204, called fromemitBlockReferencesatcompile.ts:366) is where the mismatch surfaces, but the regex is the guard, never the definition. A tradition whose separator is not.therefore cannot usereferences_rangeat all.The build fails loudly and correctly. This is not silent corruption. But the author is left with no way forward except listing every locator by hand.
Reproduction
Branch
feat/quranintextrefs/registry(draft PR, textrefs/registry#23). It adds a system whose canonical form is2:255:and a work that uses
references_range: - kind: chapter_versewith the 114 sura verse counts.npm run compile:data:Why it matters
data/AGENTS.md:16names this exact case as the onereferences_rangeis for: "references_range:when the canonical set is regular and complete". The Qurʾān's verse space is regular, complete, closed and source-defined — 114 suras, 6,236 verses. It is the textbook instance, and it is the one shape the field cannot express.The workaround is to list all 6,236 locators under
references:. That is legal —assertValidLocatoronly checks the regex and rejects/— but it puts a generated table in a hand-authored file and loses the reason the field exists.The alternative is to spell the Qurʾān
2.255. Because the tradition defines the spelling, that locator is not merely unconventional. It is wrong.Is a colon otherwise safe?
Checked end to end, and yes:
compile.ts:298-304joins[workKey, systemKey, locator]with\n. Byte-transparent.https://textrefs.org/id/ref/{uuid}. The locator never appears in an IRI.compile.ts:405,408.quran/2:255is a fine key.://in an alias key — settled, no new guard needed. The alias table tells cite-aliases from external mapping identifiers by testing for://(compile.ts:810-813, and the filter atsrc/pages/cite/[...alias].astro:7). A locator can never carry that substring, becauseassertValidLocatorrejects any/(compile.ts:199) and://contains one. Verified against the compiler: a separator of://fails withlocator "1://1" contains "/", which the /cite/ alias grammar cannot represent.AliasList.astro:12andsrc/pages/id/ref/[uuid]/index.astro:69-71both usenew URL('/cite/' + alias + '/', site). The path is absolute, so no scheme ambiguity, and:is a validpcharunder RFC 3986.dist/cite/quran/2:255/index.html. Fine on APFS and Linux; would break ifdist/ever has to exist on NTFS.parseQuery(src/lib/find.ts:143-152) takes the last whitespace token containing a digit.2:255qualifies.Proposed fix
The separator is a property of the citation system, not of one work's range entry. It belongs beside
locator_regexand the description that defines it.SystemSource.chapter_sizes(source-schema.ts:236) is the precedent: structural facts about a tradition already live on the system.The regex keeps validating. The separator only tells the expander how to join the parts it generates.
An earlier draft of this issue proposed an optional
separatoron each multi-partreferences_rangekind. That is the wrong home. It lets one system be spelled1.1under one work and1:1under another, with no single place that states what the tradition does.Touches:
scripts/source-schema.ts:224-237— an optionalseparatoronSystemSource, defaulting to..scripts/compile.ts:37-103—expandRangetakes the system.systemis already destructured at:348and in scope at the call site:353-355, beside theassertValidLocator(locator, system)at:366.src/content/docs/get-started/authoring.md:246-371— document the field. That section is the authoring contract, and it lists every range kind and its fields.The default keeps every existing locator byte-identical, so no reference UUID moves. #98 confirmed this for the per-range design: the compiled bundle stayed byte-identical across all 86,477 records, apart from the
createdtimestamp indatapackage.json. A system-level default of.behaves the same way.Filing rather than implementing, since this changes the authored data contract.
Scope
Not in v0.1.0.
references_rangetakes no changes in this release, sotextrefs/registry#23is parked rather than pending.Related
textrefs/registry#23— add the Qurʾān with a sura-verse citation system.