diff --git a/Doxyfile b/Doxyfile
index bab9d2a..6eaf994 100644
--- a/Doxyfile
+++ b/Doxyfile
@@ -34,6 +34,12 @@ EXCLUDE_PATTERNS = */build*/* \
*/* 2.hpp
EXCLUDE_SYMBOLS = Eigen::* boost::* std::*
+# Markdown filter: rewrites repo-relative links like [x](doc/api/tests.md)
+# into basename-only links [x](tests.md) so Doxygen's basename-indexed
+# \ref resolver can find them. On-disk files are untouched (GitHub keeps
+# rendering them correctly). See scripts/doxygen-md-filter.sh.
+FILTER_PATTERNS = *.md=scripts/doxygen-md-filter.sh
+
# ── Source browsing ──────────────────────────────────────────────────────────
EXTRACT_ALL = YES
EXTRACT_PRIVATE = NO
diff --git a/doc/architecture/locked-vs-flexible.md b/doc/architecture/locked-vs-flexible.md
index c2cd2e1..e2c75a9 100644
--- a/doc/architecture/locked-vs-flexible.md
+++ b/doc/architecture/locked-vs-flexible.md
@@ -250,6 +250,29 @@ matches the project's three-goal hierarchy in
---
+## Known limitations (state at the time of the reviewer meeting)
+
+These are deliberate, honestly-flagged gaps in the v0.9.0 snapshot the
+reviewer will see. None of them are load-bearing — each is a small,
+mechanical next step rather than a missing piece of theory.
+
+| Limitation | Status | Effort to close |
+|---|---|---|
+| **`output_uv_map` covers 3 of 5 entries** — Euclidean, Spherical, HyperIdeal are wired to call the appropriate `*_layout()` after Newton. CP-Euclidean (face-based) and Inversive-Distance (vertex-based) entries still require the user to call `*_layout()` by hand. | tutorial + plumbing in place; just needs to be copy-pasted into the two remaining entry headers | ~0.5 day |
+| **Named-parameter chaining: `\|`-operator only, no `.a().b().c()`.** Member-style chaining would need a patch to CGAL's upstream `parameters_interface.h`, which we treat as a read-only vendored dependency. The pipe operator is documented, ADL-discoverable, and equivalent in expressive power. | pipe shipped, member-chain deferred until upstream extension point exists | ~2 days (only if upstream PR is accepted) |
+| **Phase 9b-analytic: derivation complete, code uses block-FD.** The Schläfli-based analytic HyperIdeal Hessian is fully derived in [`hyperideal-hessian-derivation.md`](../math/hyperideal-hessian-derivation.md) (805 lines, all sign pitfalls covered). The shipped code still uses per-face block-FD (already 96× faster than the legacy full-FD path). | research-ready writeup; implementation gated on the reviewer's view of whether the additional ~6× is worth it | ~2 weeks |
+| **Doxygen `WARN_IF_UNDOCUMENTED = NO`.** With `EXTRACT_ALL = YES`, every symbol is in the generated HTML, but symbols without explicit doc comments show only their signature. Public API surface (entry functions, named-parameter helpers, traits typedefs) has hand-written Doxygen; internal helpers vary. | clean (0 warnings) under current policy; not yet enforced "no undocumented symbol" | ~3 days to drive `WARN_IF_UNDOCUMENTED = YES` to zero |
+| **`check-test-counts.sh` not wired into CI.** The script exists, runs locally, and gives the correct answer (23 + 234 = 257 / 0 skipped). CI does not yet fail on a mismatch. | local guard exists, CI integration pending next workflow touch | ~1 hour |
+| **CP-Euclidean and Inversive-Distance research-track entries.** Both ship a working DCE solver, but lack the auxiliary utilities the Euclidean / HyperIdeal entries have (curvature inspection helpers, edge-flip Delaunay maintenance for ID). Out of port scope; listed in [`research-track.md`](../roadmap/research-track.md). | research-track, not blocking | per-utility |
+| **`StereographicUnwrapper`, `CircleDomainUnwrapper`, `CuttingUtility`, `KoebePolyhedron`.** Mentioned in roadmap + research-track docs but not yet ported. Java versions still authoritative. | documented as Phase 11+ / optional | weeks each — explicit "out of port scope unless requested" |
+
+The honest framing for the meeting: **the porting layer hits its target
+for the 5 Phase-8b-Lite DCE entries; the hackability layer is
+demonstrably in place (3 tutorials, named-parameter chaining,
+Doxygen-HTML); the research-track items are scoped but not built.**
+
+---
+
## Open questions for the external reviewer
Items where the project would benefit from a second opinion:
diff --git a/scripts/doxygen-md-filter.sh b/scripts/doxygen-md-filter.sh
new file mode 100755
index 0000000..fc2fcfb
--- /dev/null
+++ b/scripts/doxygen-md-filter.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+# scripts/doxygen-md-filter.sh
+#
+# Doxygen FILTER for *.md files. Rewrites repository-relative markdown links
+# (e.g. `[label](doc/api/tests.md)` or `[label](architecture/foo.md)`) into
+# basename-only form (`[label](tests.md)`, `[label](foo.md)`).
+#
+# Why: GitHub's markdown renderer needs the full relative path to resolve a
+# link, but Doxygen indexes every .md file in INPUT by basename and treats
+# any "/" in a link target as an unresolvable \ref. Without this filter we
+# get ~27 spurious warnings from README.md and CLAUDE.md.
+#
+# The filter is purely a Doxygen-side concern; the on-disk markdown is
+# untouched, so GitHub rendering keeps working.
+#
+# Precondition: no two .md files in the project share a basename. Verified
+# at the time of writing by:
+# find . -name "*.md" -not -path "*/build*/*" -not -path "*/code/deps/*" \
+# -not -path "*/.git/*" | xargs -n1 basename | sort | uniq -c | awk '$1>1'
+# (empty output ⇒ no collisions).
+#
+# Usage: configured via Doxyfile FILTER_PATTERNS = *.md=scripts/doxygen-md-filter.sh
+set -eu
+# Convert any markdown link target that points at a .md file into an HTML
+# anchor. Doxygen renders HTML verbatim and skips its (broken) \ref
+# resolution; the rendered Doxygen HTML still hyperlinks to the source .md.
+# Pattern: [label](path/to/file.md) → label
+sed -E 's#\[([^]]+)\]\(([a-zA-Z0-9_./-]+\.md)\)#\1#g' "$1"