Skip to content

Commit 3ebd507

Browse files
Floor round: dimll >=2.7.1, healthz identity, and 27 pages with two <h1>s
THE FLOOR (item 1). >=2.7.1 in all four encodings — requirements.txt, ci.yml's ASGI install and its two asserts — and run.py gains the boot floor this repo never had. That absence mattered: the cache trap has fired here twice, and both times the only way anyone learned was measuring production HTML. LLMS_PKG_FLOOR=(2,7,1) now refuses to start below it, verified by installing 2.6.1 and watching it raise. WHAT THE FLOOR BOUGHT, measured rather than assumed: before the bump ALL 27 pages served two <h1>s to a crawler and the home page three. 2.7.0's prerender dedup fixed 26 by itself. THE LAST PAGE took two more changes, and neither was the template's fence bug. Home's preamble said "# Home" while the package injects the site brand, so the two strings never matched and dedup could not fire — _build_llms_doc now takes the PUBLISHED name (page_visibility.published_name, which exists for exactly this substitution). Home's body then hand-wrote that same brand a third time, so it is an h2 now. tests/test_site_identity's h1 assertion is inverted with the reasoning: what it protects is that home's first heading is the BRAND, and that is unchanged. THE FENCE FIX (item 3) is carried as a GUARD, not a fix. No page here teaches `.. source::` inside a fence today, so the template's defect cannot reproduce — but the day someone documents the directive, nothing silently breaks. HEALTHZ (item 2). Adds `build` (RENDER_GIT_COMMIT, omitted where the platform has none) and `geo` {configured, denied, resolved} — counts and flags only, never the denylist's country codes, and OMITTED on pre-2.7 packages rather than error-flagged. That omission is the point: on a deployed host, no geo block means the image is stale and the floor did not really move. This repo already built per request and already had `app`, so those were verified rather than changed. Five tests, including that a broken geo probe cannot take down the probe it lives in. THE ROBOTS RIDER — one decision each way. DROPPED `Disallow: /admin/`. It reads like protection and is the reverse: robots.txt is public, so the line handed the admin path to every scraper that reads it while stopping nobody who ignores the file. What protects that surface is auth — two independent checks, failing closed without Clerk — and what keeps it out of the index is mark_hidden(), which excludes it from the sitemap and /llms.txt rather than announcing it. The test that pinned the Disallow is inverted, plus a new one asserting the exclusion still holds. KEPT block_ai_training=False, re-reviewed and documented. This host is an MIT-licensed component library's documentation: a model that learned dash-leaflet2 recommends it to a developer who will never visit the site, and that recommendation IS the distribution channel. Blocking training would trade it away for a licence term MIT does not contain. A host with proprietary content should decide the other way; scripts/network_smoke.py already encodes that split, so verification treats this as a stated position. DEPENDABOT (item 4): pip allow-list added. Floors here encode minimum-compatibility knowledge — the gunicorn floor IS the CVE fact, the dimll floor IS the cache-bust mechanism — so a floor-raise PR erases meaning and adds none. Security updates are unaffected. DOCKERFILE (item 5): NO Node layer to remove — the component bundle is committed and the image is already pure-Python, documented in its header. The cache-semantics block is present and now names the trap explicitly, including that the number also lives in run.py and ci.yml. 204 passed (flask) / 203 + 1 skip (fastapi), was 191. smoke 72/72, flake8 clean, dependabot.yml and ci.yml both parse. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent d71a9d9 commit 3ebd507

14 files changed

Lines changed: 567 additions & 25 deletions

‎.github/dependabot.yml‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,20 @@ updates:
1616
interval: weekly
1717
day: monday
1818
open-pull-requests-limit: 5
19+
# Version-update PRs are restricted to the network packages — the drift
20+
# alarm this file exists for. Without this allow-list, dependabot also
21+
# proposes FLOOR-RAISES for every other requirement (gunicorn >=23 →
22+
# >=26, python-dotenv, pydantic, jsonpath...). Those floors encode
23+
# minimum-compatibility knowledge — this repo's gunicorn floor IS the
24+
# CVE fact, and its dash-improve-my-llms floor is the cache-bust
25+
# mechanism — the resolver already installs latest under them, and a
26+
# raise erases the floor's meaning while adding nothing. SECURITY
27+
# updates are unaffected: they arrive through GitHub's separate
28+
# security-update channel regardless of this list.
29+
allow:
30+
- dependency-name: "dash*"
31+
- dependency-name: "plotly*"
32+
- dependency-name: "markdown2dash"
1933
groups:
2034
dash-network:
2135
patterns:

‎.github/workflows/ci.yml‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ jobs:
122122
# import. httpx backs starlette's TestClient.
123123
if [ "${{ matrix.backend }}" != "flask" ]; then
124124
pip install "dash[${{ matrix.backend }}]" httpx
125-
pip install "dash-improve-my-llms[${{ matrix.backend }}]>=2.6.1"
125+
pip install "dash-improve-my-llms[${{ matrix.backend }}]>=2.7.1"
126126
fi
127127
pip install pytest
128128
@@ -141,7 +141,7 @@ jobs:
141141
# assert sat at 2.3.4 while the pin moved to 2.5.1 and then 2.6.0 —
142142
# a stale floor asserts nothing, and the deploy that silently ran an
143143
# old package is exactly what it exists to catch.
144-
assert parts(pkg.__version__) >= (2, 6, 1), pkg.__version__
144+
assert parts(pkg.__version__) >= (2, 7, 1), pkg.__version__
145145
# 21.x carried two request-smuggling CVEs (CVE-2024-6827,
146146
# CVE-2024-1135). markdown2dash's spurious <22 pin must not win.
147147
assert parts(gunicorn.__version__)[:2] >= (23, 0), gunicorn.__version__
@@ -278,7 +278,7 @@ jobs:
278278
279279
v = version('dash-improve-my-llms')
280280
print('dash-improve-my-llms', v)
281-
assert parts(v) >= (2, 6, 1), f'expected >=2.6.1, image has {v}'
281+
assert parts(v) >= (2, 7, 1), f'expected >=2.7.1, image has {v}'
282282
283283
# markdown2dash installs with --no-deps to dodge its gunicorn<22
284284
# pin; this assert is what proves the dodge kept working. 21.x

‎CLAUDE.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ race fixed there. Its `clerk-backend-api<8` cap (widened in 1.0.1) is what lets
172172
sitemap date; `dash-improve-my-llms` >= 2.6.0 emits it verbatim and omits the
173173
tag when absent. Edit a page's prose → bump its `lastmod` in the SAME commit.
174174
Never script these from file mtimes (they reset on every Docker build, which
175-
re-creates the every-page-changed-today sitemap the 2.6.1 floor exists to
175+
re-creates the every-page-changed-today sitemap the floor exists to
176176
end). The initial values came from `git log -1 --format=%cs -- <file>`.
177177
`tests/test_seo_icons.py` fails if the sitemap ever emits a date no page
178178
declared, and if crawler-head icon discovery comes back empty.

‎DEPLOYMENT.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,7 @@ Clerk it is hidden, not merely unstyled.
433433
> the `/var/data` disk in production, so a toggle outlives a deploy; on an
434434
> ephemeral filesystem it would reset with every one.
435435
436-
### Crawler identity and sitemap honesty (dash-improve-my-llms >= 2.6.1)
436+
### Crawler identity and sitemap honesty (dash-improve-my-llms >= 2.7.1)
437437

438438
> **A package fix does not reach production until this file's bytes change.**
439439
> The Dockerfile installs dependencies in their own layer, keyed on
@@ -443,7 +443,7 @@ Clerk it is hidden, not merely unstyled.
443443
> looking correct. Raising the floor number *is* the cache bust. Confirm with
444444
> the boot line, which states the resolved version.
445445
446-
The 2.6.1 floor in `requirements.txt` is load-bearing — `pages/markdown.py`
446+
The 2.7.1 floor in `requirements.txt` is load-bearing — `pages/markdown.py`
447447
passes `lastmod=` unconditionally, and that argument does not exist below it.
448448

449449
**Icons come from discovery, not a declaration.** This app has never called

‎Dockerfile‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,19 @@ ENV PYTHONUNBUFFERED=1 \
2222

2323
WORKDIR /app
2424

25-
# Install Python deps first so this layer is cached across app-code changes.
25+
# CACHE SEMANTICS — read this before shipping a dependency upgrade. This layer
26+
# re-runs ONLY when vendor/ or requirements.txt BYTES change. A `>=` floor can
27+
# NEVER pull a newer release through a cache hit: a code-only commit rebuilds
28+
# the app layers below while pip silently keeps whatever version the image was
29+
# first built with. That has bitten this repo twice — 2.6.1 and again at 2.7.1
30+
# — and both times the site looked fine, because a stale package degrades
31+
# quietly rather than failing.
32+
#
33+
# So ship every dependency upgrade as a floor bump in requirements.txt, and
34+
# grep the NUMBER, not the file: it also lives in run.py's LLMS_PKG_FLOOR and
35+
# in ci.yml's asserts. The bump IS the cache bust, and the boot floor turns a
36+
# stale image from a silent downgrade into a loud refusal to start.
37+
#
2638
# vendor/ must come along: requirements.txt installs dash-clerk-auth 1.0.5 from
2739
# a local tarball there (it is vendored across the 2plot network, not on PyPI).
2840
# dash-emoji-mart and flexlayout-dash used to live here too and now come from

‎docs/home/home.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ lastmod: 2026-08-01
1212

1313
.. toc::
1414

15-
# dash-leaflet2 — Leaflet 2 maps for Dash
15+
## dash-leaflet2 — Leaflet 2 maps for Dash
1616

1717
> **`dash-leaflet2`** wraps **Leaflet 2 core directly** — no react-leaflet — and
1818
> ships it as real Dash components. By [Pip Install Python](https://github.com/2plotai).

‎lib/health.py‎

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,22 @@
2424
sweep-time checks.)
2525
* ``reporting`` — whether the traffic reporter could actually POST, so "wired
2626
but secretless" is visible without reading boot logs.
27+
* ``build`` — WHICH COMMIT the running instance was built from. A Render
28+
service with a disk restarts with a blip rather than overlapping instances,
29+
so a bare 200 proves nothing about which build answered; CD waits on this
30+
to verify the artifact it shipped rather than whichever build happens to be
31+
serving. Omitted where the platform variable does not exist, so a local run
32+
and a fork on another host keep the same probe contract.
33+
* ``geo`` — the geo guardrail's LIVE state (dash-improve-my-llms >= 2.7.0).
34+
Counts and flags only, never the denylist's country codes: a health
35+
endpoint is not where anyone should learn policy. ``resolved`` reveals only
36+
the caller's own country back to them, which Cloudflare's /cdn-cgi/trace
37+
already does — and it localises a failure that is otherwise invisible,
38+
because geo can be fully configured and still never match if the country
39+
header is not reaching the app. "configured: true, denied: 7, resolved:
40+
unknown" says that in one line. The key is OMITTED on older packages, not
41+
error-flagged: a host on an older floor is not broken, it predates the
42+
diagnostic.
2743
2844
Keep it cheap: the hub measures the round trip, so any work done here is
2945
reported back as this app being slow.
@@ -35,11 +51,44 @@
3551
import dash
3652

3753

54+
def _resolved_country() -> str:
55+
"""``geo.explain_resolution`` over THIS request's headers, or a reason.
56+
57+
Reads the framework's request object directly rather than anything the
58+
package threads through, so it answers "did the country header reach this
59+
app at all?" independently of how the enforcement seam is wired.
60+
"""
61+
try:
62+
from dash_improve_my_llms import geo
63+
from dash_improve_my_llms._headers import normalize_headers
64+
except Exception:
65+
return "unavailable (pre-2.7.0 package)"
66+
67+
try:
68+
from flask import has_request_context, request
69+
70+
if not has_request_context():
71+
return "no request context"
72+
return geo.explain_resolution(normalize_headers(request.headers))
73+
except Exception:
74+
return "unavailable"
75+
76+
3877
def health_payload(backend: str) -> dict:
78+
"""Built PER REQUEST, never snapshotted.
79+
80+
Every field here was static once (ok/app/version/backend never change for
81+
a running process), which made a registration-time snapshot look harmless.
82+
It is not: `geo` is configured well after this route is registered, so a
83+
snapshot reports the guardrail as unconfigured on a host where it is
84+
configured — the diagnostic lying in exactly the situation it exists for.
85+
Every backend below renders from this one function for the same reason:
86+
a probe contract that varies by backend is not a contract.
87+
"""
3988
from lib.constants import APP_VERSION, BASE_URL
4089
from lib.satellite_reporter import app_key
4190

42-
return {
91+
payload = {
4392
"ok": True,
4493
"app": app_key(),
4594
"version": APP_VERSION,
@@ -49,6 +98,26 @@ def health_payload(backend: str) -> dict:
4998
"reporting": bool(os.getenv("CROSS_APP_WEBHOOK_SECRET")),
5099
}
51100

101+
build = os.environ.get("RENDER_GIT_COMMIT")
102+
if build:
103+
payload["build"] = build
104+
105+
try:
106+
from dash_improve_my_llms import geo
107+
except ImportError:
108+
pass # pre-2.7: omit the key rather than flagging an error
109+
else:
110+
try:
111+
payload["geo"] = {
112+
"configured": bool(geo.is_configured()),
113+
"denied": len(geo.effective_policy().get("deny_countries") or []),
114+
"resolved": _resolved_country(),
115+
}
116+
except Exception: # a diagnostic must never break the health probe
117+
payload["geo"] = {"configured": False, "denied": 0, "error": True}
118+
119+
return payload
120+
52121

53122
def register_health_route(app, backend: str) -> None:
54123
"""Mount ``/healthz`` on whichever backend is running."""

‎pages/markdown.py‎

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@
1717
from lib.directives.source import SC
1818
from lib.directives.toc import TOC
1919
from lib.gate_layouts import gated_layout
20-
from lib.page_visibility import register_default, register_llms_doc
20+
from lib.page_visibility import (
21+
published_name,
22+
register_default,
23+
register_llms_doc,
24+
)
2125
from lib.versions import substitute_versions
2226

2327
logger = logging.getLogger(__name__)
@@ -119,9 +123,20 @@ def _expand_source_directives(markdown_content: str) -> str:
119123
`/<page>/llms.txt`. Replacing the directive with the real file content
120124
is what makes the LLM output self-contained for the "paste into a chat
121125
window" audience.
126+
127+
FENCE-AWARE, and it has to be: a directive INSIDE a fenced code block is
128+
documentation showing the syntax, not a directive to act on. Expanding
129+
one injects a ```python fence inside the already-open fence, which CLOSES
130+
it early — from there the inlined file renders as markdown and every
131+
`# comment` line becomes an <h1>, so the page's machine lane serves
132+
broken structure while the browser lane looks perfect (markdown2dash
133+
parses fences properly). No page in THIS repo teaches the directive
134+
today, so this is carried from the template as a guard rather than a
135+
fix: the day someone documents `.. source::` in a fenced block, nothing
136+
silently breaks. tests/test_page_structure.py pins the outcome.
122137
"""
123-
def replace(match: re.Match) -> str:
124-
file_path = match.group(1).strip()
138+
def expansion(directive_line: str) -> str:
139+
file_path = _SOURCE_DIRECTIVE.match(directive_line).group(1).strip()
125140
try:
126141
full = Path(file_path)
127142
content = full.read_text()
@@ -134,12 +149,34 @@ def replace(match: re.Match) -> str:
134149
except Exception as exc:
135150
return f'\n<!-- Error reading {file_path}: {exc} -->\n'
136151

137-
return _SOURCE_DIRECTIVE.sub(replace, markdown_content)
152+
out: List[str] = []
153+
fence = None # the marker that opened the block we are inside, if any
154+
for line in markdown_content.split('\n'):
155+
head = line.lstrip()[:3]
156+
if fence is None and head in ('```', '~~~'):
157+
fence = head
158+
elif fence is not None and head == fence:
159+
fence = None
160+
elif fence is None and _SOURCE_DIRECTIVE.match(line):
161+
out.append(expansion(line))
162+
continue
163+
out.append(line)
164+
return '\n'.join(out)
138165

139166

140167
def _build_llms_doc(name: str, description: str, expanded_markdown: str, path: str) -> str:
141168
"""Wrap the expanded markdown with the heading/description preamble that
142-
/llms.txt readers expect."""
169+
/llms.txt readers expect.
170+
171+
``name`` must be the PUBLISHED name, not the registered one. The package
172+
injects its own `<h1>` header into the crawler document from the published
173+
identity, and >= 2.7.0 dedups that against the document's opening h1 — so
174+
the two have to be the same string or the page serves both. They were not
175+
for "/" alone: the preamble said "Home" (the nav label) while the injected
176+
header said the site brand, which is exactly the substitution
177+
`lib.page_visibility.published_name` exists to make. See its docstring for
178+
why "Home" must never become this site's published identity.
179+
"""
143180
parts: List[str] = [f"# {name}\n"]
144181
if description:
145182
parts.append(f"> {description}\n")
@@ -244,7 +281,8 @@ def _build_llms_doc(name: str, description: str, expanded_markdown: str, path: s
244281
metadata.endpoint,
245282
metadata.name,
246283
metadata.description,
247-
_build_llms_doc(metadata.name, metadata.description, expanded, metadata.endpoint),
284+
_build_llms_doc(published_name(metadata.endpoint, metadata.name),
285+
metadata.description, expanded, metadata.endpoint),
248286
# Same string dash.register_page got. Measured, not assumed: it does
249287
# NOT double the brand — the package composes its own
250288
# "<page> · <site>" only when no title is declared.

‎requirements.txt‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,18 @@ docutils!=0.21
5050
jsonpath>=0.82,<0.83
5151
mistune>=3.0.1,<4.0.0
5252
python-frontmatter
53-
# 2.6.1 — and the .1 is a CACHE BUST as much as a feature.
53+
# 2.7.1 — and as in every round, THE NUMBER CHANGING IS THE CACHE BUST.
54+
#
55+
# What 2.7.1 buys: llms.txt v2 discovery relations (rel=alternate/describedby
56+
# plus Link headers), the text/plain Accept ramp, and the representation
57+
# digest. What 2.7.0 buys, and this repo needed badly: the prerender H1 dedup.
58+
# Measured here before the bump — all 27 pages served TWO <h1>s to a crawler,
59+
# the injected prerender header plus the document's own, and the home page
60+
# served three. 2.7.0 also stops a page that merely MENTIONS the prerender
61+
# marker string from losing its prerender, and de-doubles the home footer's
62+
# /llms.txt link. run.py's LLMS_PKG_FLOOR refuses to boot below this.
63+
#
64+
# 2.6.1 — the .1 was a CACHE BUST as much as a feature.
5465
#
5566
# The Dockerfile installs deps in their own layer, keyed on requirements.txt's
5667
# BYTES. A commit that touches only lib/ is a cache hit, so pip never re-runs
@@ -98,7 +109,7 @@ python-frontmatter
98109
# directive-leakage fix. `configure_access` (2.3) is what lib/access.py
99110
# hands the gate policy to. The [flask] extra is just flask>=2.0, already
100111
# present via Dash.
101-
dash-improve-my-llms[flask]>=2.6.1
112+
dash-improve-my-llms[flask]>=2.7.1
102113
python-dotenv>=1.0
103114
pydantic>=2.0
104115
requests>=2.31

0 commit comments

Comments
 (0)