Summary
When doc_build flattens multiple source Markdown files into a single document, two headings that share a title (in different source files) collapse to the same slug anchor. An intra-document link ([...](#some-heading)) then resolves to the first occurrence in flatten order instead of the intended section. The build succeeds with no warning.
Minimal self-contained repro (no external repo needed):
a.md
# Doc A
## Shared Heading
A's section.
b.md
# Doc B
See [Shared Heading](#shared-heading).
## Shared Heading
B's section.
Build a.md + b.md through the flatten pipeline in that order. In the output, the link in Doc B — which targets its own "Shared Heading" — instead jumps to Doc A's section, because both slug to #shared-heading and the first occurrence wins. Swap the order and the bug moves with it, confirming it's flatten-order-dependent, not content-dependent.
How we found it
Reported on aousd/geometry-wg (PR #48, by @miguelh-nvidia): two different schema files there each define a ## Velocity-Based Interpolation section, and one file's self-referential links land in the other schema in the built PDF. Caught by manual PDF inspection, not CI. Linked here for provenance — the minimal repro above reproduces it standalone, so no access to that repo is needed to investigate. The pattern generalizes: parallel schema docs intentionally reuse titles like Overview, Validation, Example, Extent, Properties of …, so any bare same-slug cross-reference among them is silently misrouted, and duplicates without a cross-reference are latent.
Why the build gate didn't catch it
The gate confirms the artifact builds; it does not validate anchor uniqueness or link resolution, so a broken cross-reference passes CI.
Proposed fix (either or both)
- Namespace anchors per source file during flatten — prefix generated anchors with the source-file stem (e.g.
a__shared-heading) and rewrite each file's intra-file links to match, so identical titles in different files stay distinct. Fixes all current and future collisions without changing source prose.
- Add an anchor/link check to the build gate — fail (or warn) on a duplicate anchor that is targeted by a link, and on any link whose target anchor is missing/ambiguous. Catches regressions at PR time.
Option 1 is the durable fix; option 2 prevents recurrence.
Summary
When
doc_buildflattens multiple source Markdown files into a single document, two headings that share a title (in different source files) collapse to the same slug anchor. An intra-document link ([...](#some-heading)) then resolves to the first occurrence in flatten order instead of the intended section. The build succeeds with no warning.Minimal self-contained repro (no external repo needed):
a.mdb.mdBuild
a.md+b.mdthrough the flatten pipeline in that order. In the output, the link in Doc B — which targets its own "Shared Heading" — instead jumps to Doc A's section, because both slug to#shared-headingand the first occurrence wins. Swap the order and the bug moves with it, confirming it's flatten-order-dependent, not content-dependent.How we found it
Reported on
aousd/geometry-wg(PR #48, by @miguelh-nvidia): two different schema files there each define a## Velocity-Based Interpolationsection, and one file's self-referential links land in the other schema in the built PDF. Caught by manual PDF inspection, not CI. Linked here for provenance — the minimal repro above reproduces it standalone, so no access to that repo is needed to investigate. The pattern generalizes: parallel schema docs intentionally reuse titles likeOverview,Validation,Example,Extent,Properties of …, so any bare same-slug cross-reference among them is silently misrouted, and duplicates without a cross-reference are latent.Why the build gate didn't catch it
The gate confirms the artifact builds; it does not validate anchor uniqueness or link resolution, so a broken cross-reference passes CI.
Proposed fix (either or both)
a__shared-heading) and rewrite each file's intra-file links to match, so identical titles in different files stay distinct. Fixes all current and future collisions without changing source prose.Option 1 is the durable fix; option 2 prevents recurrence.