zensical build --strict validates Markdown-to-Markdown links, but it does not
check links whose target is a non-Markdown asset. A relative link to a PDF under
docs/ can point at nothing and the build still reports "No issues found", so a
dead asset link ships silently through the five-step gate.
Verified empirically on 2026-08-03 while adding docs/slides/etheorem-proposal-deck.pdf.
A probe post carrying the wrong depth:
[deck](../../slides/etheorem-proposal-deck.pdf)
built clean under --strict and rendered to href="../../../slides/…", which
resolves above the site root and 404s. The correct source form is one ../
(links resolve against the source file's location in docs/, not the rendered
URL depth), but nothing in the gate distinguishes the two.
This gets worse as more assets land: the deck is the first, and per-page social
cards and any future downloads have the same exposure.
Proposed fix
Add a tools/ check to the build gate that walks rendered href/src
attributes in site/, keeps the ones pointing at local non-.md targets,
resolves each against the emitted file tree, and exits non-zero on a miss.
Slots in alongside check_jsonld.py as a sixth gate step.
Acceptance criteria
- A broken relative asset link anywhere under
docs/ fails the gate with the
offending page and href named.
- Correct asset links (the deck, theme CSS, favicon, social card) pass.
- External
http(s):// and mailto: targets are skipped, not fetched.
zensical build --strictvalidates Markdown-to-Markdown links, but it does notcheck links whose target is a non-Markdown asset. A relative link to a PDF under
docs/can point at nothing and the build still reports "No issues found", so adead asset link ships silently through the five-step gate.
Verified empirically on 2026-08-03 while adding
docs/slides/etheorem-proposal-deck.pdf.A probe post carrying the wrong depth:
built clean under
--strictand rendered tohref="../../../slides/…", whichresolves above the site root and 404s. The correct source form is one
../(links resolve against the source file's location in
docs/, not the renderedURL depth), but nothing in the gate distinguishes the two.
This gets worse as more assets land: the deck is the first, and per-page social
cards and any future downloads have the same exposure.
Proposed fix
Add a
tools/check to the build gate that walks renderedhref/srcattributes in
site/, keeps the ones pointing at local non-.mdtargets,resolves each against the emitted file tree, and exits non-zero on a miss.
Slots in alongside
check_jsonld.pyas a sixth gate step.Acceptance criteria
docs/fails the gate with theoffending page and href named.
http(s)://andmailto:targets are skipped, not fetched.