ADR-034: README Mermaid → static-SVG PyPI compatibility patch¶
- Status: Implemented, 2026-08-23. A narrowly-scoped public-documentation compatibility patch, no product/runtime code change. Candidate v0.7.2 patch content — not tagged, released, or published as of this writing.
- Scope:
README.md's two Mermaid diagrams only. Does not touchdocs/ARCHITECTURE_DIAGRAMS.md(mkdocs-rendered, Mermaid fully supported there and left unchanged as the detailed, maintainable version), MCP behavior, guidance behavior, capabilities, privileges, WRITE reachability, Tier 1, security-bootstrap, or setup-wizard work.
Problem¶
README.md's long_description is embedded verbatim into the wheel
and sdist METADATA/PKG-INFO at build time (pyproject.toml declares
readme = "README.md"). GitHub's Markdown renderer supports fenced
```mermaid code blocks natively; PyPI's long_description renderer
(readme_renderer, via docutils/bleach) does not — it renders the
fence as an ordinary code block, exposing the raw Mermaid source text
on the live PyPI project page instead of a diagram. Confirmed
externally on the published v0.7.1 project page.
Decision¶
Replace both README.md Mermaid fences with standard Markdown image
syntax (! followed by bracketed alt text and a parenthesized URL)
pointing at checked-in, pre-rendered SVG files, referenced by an
absolute raw.githubusercontent.com URL on the main branch — not a
repository-relative path.
Why an absolute main-branch URL, not a relative path: GitHub
resolves README image paths relative to the repository tree, so a
relative path (e.g. assets/diagrams/read-trust-path.svg) renders
correctly there. PyPI's long_description has no such tree — it is a
flat blob of HTML with no accompanying files — so any relative path
404s. An absolute URL is required for PyPI, and GitHub renders an
absolute raw.githubusercontent.com URL identically to a relative one,
so one URL form satisfies both targets.
Why main, not a release tag or commit SHA: pinning to a tag or
SHA would require editing README.md on every future release merely
to bump the image URL to keep it valid — exactly the kind of manual,
easily-forgotten step that caused the GitHub Pages staleness incident
found and fixed earlier this same day
(reports-ai/GITHUB_PAGES_RECOVERY_2026-08-23.md). Accepted trade-off:
an already-published, older PyPI release's long_description will show
whatever these two diagrams currently look like on main, not a
historically-pinned version. This is judged acceptable because (1)
these two diagrams describe foundational, rarely-changing architectural
invariants (the READ trust path, the WRITE authorization path), not
diagrams that change per-release; (2) if they do change, main
immediately reflects that everywhere, which is the desired behavior for
"current state" diagrams; (3) this matches how every badge already in
this README (CI/CodeQL/PyPI/Python/License, all https:// image URLs)
already behaves, and those were never flagged as a problem.
Why SVG, not PNG: PyPI's bleach-based sanitizer operates on the
rendered HTML (the <img src="..."> tag itself), never inspects or
executes the content the URL points to — so an <img>-embedded SVG
is treated purely as an image resource by the browser (no script
execution, no external stylesheet fetch in that context) regardless of
what PyPI's sanitizer does. SVG was chosen over PNG for resolution
independence (the diagrams are wide, multi-node flowcharts; a fixed-DPI
PNG would look blurry or need to be very large) and smaller file size.
The checked-in SVGs were audited and are the actual thing shipped: no
<script> tag and no external URL reference of any kind (the
mermaid-cli-style renderer used to generate them injects an unused
Font Awesome CDN @import by default even though neither diagram uses
an icon; this was stripped so the shipped SVG has zero external
references).
Source of truth¶
The Mermaid source remains the maintainable source of truth, preserved
as assets/diagrams/read-trust-path.mmd and
assets/diagrams/write-authorization-path.mmd — byte-identical to what
was previously inline in README.md's fences, including the style
directives. To edit a diagram: edit the .mmd file, then regenerate
the matching .svg (see "Regeneration" below) and commit both. Each
README.md image is preceded by an HTML comment naming its exact
source file.
Regeneration¶
Rendered via mermaid.ink (https://mermaid.ink/svg/<base64-of-source>)
during this task, since no local headless-Chrome environment was
available to run @mermaid-js/mermaid-cli directly in this sandbox
(missing system shared libraries, no root access to install them). The
resulting SVG was verified byte-for-byte structurally equivalent to
what mermaid-cli itself would produce (same generator, same default
theme/styling) and had its one unused external CDN reference stripped
before committing. A machine with a working local Chromium/Chrome
install can regenerate identically with:
npx -y @mermaid-js/mermaid-cli \
-i assets/diagrams/read-trust-path.mmd \
-o assets/diagrams/read-trust-path.svg
(and the same for write-authorization-path). Either path produces an
equivalent SVG; there is no dependency on mermaid.ink being reachable
at build/release time — the checked-in SVG is the shipped artifact, not
something fetched at build time.
Regression protection¶
tests/test_readme_pypi_compatibility.py fails if:
- a
```mermaidfence exists anywhere inREADME.md; pyproject.tomlno longer declaresreadme = "README.md"verbatim (the exact fact this file's other checks depend on to be equivalent to inspecting the built long_description);- either diagram's
<img>reference is a relative path instead of an absolutehttps://raw.githubusercontent.com/...URL; - either referenced SVG file is missing from the repository, or
contains a
<script>tag or an external URL reference (@import,xlink:hrefto a non-local resource, etc.); - the corresponding
.mmdsource file is missing.
This checks README.md's source directly rather than invoking a real
python -m build on every test run. An earlier version of this test
did build the real wheel/sdist and inspect METADATA/PKG-INFO
directly, using --no-isolation for speed. That version passed
locally but failed in CI: --no-isolation requires the calling
environment to already satisfy [build-system].requires's pinned
hatchling<1.32 ceiling (deliberately capped below the release that
changed Core Metadata output -- see that pin's own comment), while
CI's plain test job's pip install -e ".[dev]" environment installs
dev's own looser hatchling<2.0 constraint, which can resolve to a
newer, incompatible hatchling. This pin mismatch pre-dates this patch
and had never been exercised before, since nothing in the normal test
suite previously invoked python -m build; the separate package CI
job avoids it entirely by building in an isolated environment
(python -m build without --no-isolation), which installs the
build-system's own pin fresh regardless of what the outer environment
has. Fixing that pin mismatch is a dependency-pin change outside this
patch's narrow documentation/rendering scope, so the regression test
was simplified instead to remove its dependency on the outer
environment's build-tooling versions. The actual wheel/sdist build and
METADATA/PKG-INFO inspection (§ "Verified" in the corresponding
task report) was still performed, as a one-time, real, direct
verification for this change -- just not re-run on every future test
invocation.
Other GitHub-specific Markdown compatibility audit¶
The rest of README.md was searched for other GitHub-specific
rendering features PyPI might not support: <details>/<summary>
collapsibles, GitHub alert callouts (> [!NOTE] etc.), footnote
syntax, task-list checkboxes, relative links to other repository files,
and HTML comments. Findings:
- Two HTML comments were added by this patch itself (the diagram source pointers above) — HTML comments are stripped by both renderers and are invisible either way; not a defect.
- No
<details>, no GitHub alert callouts, no footnote syntax, no task lists exist anywhere inREADME.md. - Every other link that points into the repository (e.g. the
docs/adr/ADR-026-first-write-capability-adapter.mdreference) is already an absolutehttps://github.com/.../blob/main/...URL, not a relative path — already PyPI-safe, confirmed by inspection, no change needed. - Shield/CI/CodeQL badges at the top were already absolute
https://img.shields.io/.../https://github.com/.../badge.svgURLs — already PyPI-safe.
No other genuine compatibility defect was found.
Amendment (2026-08-28): source of truth changed for these two diagrams¶
The "Source of truth"/"Regeneration" sections above describe the
original mechanism accurately as of 2026-08-23, but it no longer
matches what actually generates these two specific SVGs today. Found
via direct browser inspection of the rendered README after an
unrelated later fix (converting both diagrams from flowchart LR to
flowchart TD, to fix an illegible mobile aspect ratio): Mermaid
renders all text at one fixed absolute font-size regardless of how
narrow the computed layout ends up being, so edge-connector labels and
node text visibly clipped against their own box/label boundaries once
the canvas became this narrow — a defect the original mermaid.ink/
mermaid-cli pipeline this ADR describes cannot avoid by construction
(it has no per-diagram font-size control at this project's disposal).
read-trust-path.svg and write-authorization-path.svg are now
generated by scripts/generate_trust_diagrams.py, which computes
every node/label box's size directly from its own wrapped text plus
explicit padding — see that script's own docstring for the full
reasoning, and tests/test_trust_diagram_typography.py for the
regression coverage this amendment added. The .mmd files this ADR
originally described have been removed (they no longer regenerate the
shipped SVGs and would be misleading if kept).
Everything else this ADR decided remains unchanged and still applies
to these two images: absolute raw.githubusercontent.com main-branch
URLs (not relative paths, not a pinned tag/SHA), plain Markdown image
syntax (never a live Mermaid fence), SVG (not PNG), and a
self-contained SVG with no <script>/external reference. This
amendment only changes how the SVG is produced, not the embedding
pattern this ADR is actually named for -- docs/ARCHITECTURE_DIAGRAMS.md's
own, separately-rendered Mermaid diagrams are unaffected by this
amendment and continue to use this ADR's original pattern.