Feat/sign in gate pilot - #17
Merged
Merged
Conversation
leaflet pilots the network's sign-in gate. Clerk was already here and satellite-correct; what was missing was the enforcement seam, and this repo had a second problem the template does not: two parallel access systems that could disagree about the same page. `tier:` and `visibility:` were independent frontmatter keys naming the same four values, feeding two independent ledgers. A page could declare `visibility: auth`, be enforced as public, and show a control-board row that said otherwise, with nothing logged. One declared value now feeds both; `tier:` is canonical and `visibility:` is an accepted alias that warns only when the two disagree. The unification keeps the half of the old system that was working production UX. lib/page_visibility.py is demoted to the control board's override store — live toggles, /var/data persistence, its cards — and lib/access.py resolves the verdict from the board's override first, the frontmatter registration underneath, and the hub's ceiling on top, which only ever restricts. What did NOT come across is that module's resolve_access, which fell open for admin as well as docs; the new seam keeps the boilerplate's split posture (docs open without Clerk, admin closed) and tests/test_access.py pins it against a future re-port. Two lanes, deliberately different. resolve_page_access answers what a BROWSER gets — lib/gate_layouts.py renders the card, whose #auth-gate-* buttons carry the current page in the return trip and so fix the existing card's returnTo leak. check answers what a MACHINE fetch gets and honours ?key= plus the llms_public axis, so /<page>/llms.txt, the crawler document and the prerender keep serving prose through the 30-day window while humans meet the card. A key never unlocks a layout. The legacy llms.txt stub swap is retired with the same reasoning: with a policy wired, registering the real prose and letting the check decide is strictly better, because the check can honour the hub, a key and the axis, and an unauthorised reader gets a document that says how to unlock it rather than a stub. The swap survives only for a boot where no policy could be wired at all. run.py forces the wiring on (force=True) even though every tier is public, which is the whole shipping-dark idea: the verdict path and the prerender's use of it run in production before PAGE_DEFAULT_TIER=auth turns them on, and setting it back to public is the rollback. /, /llms-small.txt and /llms-full.txt are pinned public on both ledgers so that flip cannot take the funnel's front door or the corpus with it. Verified locally: 169 passed on flask, 168 + 1 skip on fastapi (was 111), smoke_test 72/72, flake8 clean. Under PAGE_DEFAULT_TIER=auth with a faked Clerk session: gated page renders the card with no docs DOM, llms.txt serves full prose, a canary page pinned llms_public=false serves the gate doc at 200 and shows it — not the prose — inside #dimll-prerender, a keyed fetch resolves allow and carries ?key= onto same-origin links, a board toggle to hidden reaches both lanes, and /api/agent-key answers 204 with private, no-store. Not verifiable offline: the real Clerk round trip to 2plot.ai and back, and the hub's /api/page-tiers ceiling (no CROSS_APP_WEBHOOK_SECRET) — both are prod acceptance boxes in X402-SYNC-REPORT.md. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Applied to this working tree by the boilerplate session on 2026-08-20 and committed here unchanged; recorded separately from the stage-3 work so the gate pilot's diff stays about the gate. The presence beacon is a second, faster loop beside the rollup, and the two are not interchangeable: presence is display-only and ephemeral hub-side (the live "active now" number), while the rollup remains the only source of the daily figures. It derives its count the way the hub does — distinct human visitor keys inside the session window — so the two can never disagree about what "active" means. 60s default, 30s floor, 0 disables, fail-silent. `SATELLITE_APP_KEY` keeps this repo's deviation and its reason: unlike some satellites it is NOT chained to AD_APP_ID, because this host historically ran AD_APP_ID=dash-leaflet2 against directory key "leaflet", and setting one for the ad network must never silently rename this app's traffic series. lib/network_directory.py is re-copied from the boilerplate verbatim, which resolves this file's own standing instruction: it carried a temporary divergence commenting out pannellum and emojimart while they were NXDOMAIN, with a note saying to re-copy rather than un-comment once they resolved. They have. Twelve peers now, self-exclusion still leaves eleven here. SATELLITE_REPORT_INTERVAL_S=900 in render.yaml — the fleet is on paid instances and the hub board reads near-real-time; the code default stays hourly for anyone running this elsewhere. Verified: 176 passed on flask with these alone, flake8 clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Floor to dash-improve-my-llms >= 2.6.0, which is load-bearing rather than routine — pages/markdown.py now passes `lastmod=` unconditionally, and that argument only exists from 2.6.0. SITEMAP HONESTY. Every entry used to carry a <lastmod> of "today", regenerated on every crawl. A sitemap asserting that 27 pages changed daily is one a search engine learns to discard wholesale, which costs more than having no dates at all. 2.6.0 emits frontmatter `lastmod:` verbatim and omits the tag entirely when a page declares none. All 27 pages are stamped with their real `git log -1 --format=%cs` date — 26 on 2026-07-28, home on 2026-08-01. Deliberately bare YAML dates, not quoted strings: that puts the field validator on the live path, where it belongs, because YAML hands pydantic a datetime.date and Optional[str] rejects it. Never script these from mtimes, which reset on every Docker build and would re-create the exact lie the floor removes. ICONS. This app has never called configure_seo, so its crawler document carried ZERO icons while browsers got six from templates/index.html — the crawler/browser identity drift in its most complete form. 2.6.0 discovers them from the assets tree (favicon_io/ is a covered directory name) and this site's own art becomes its crawler-head identity with nothing declared: verified as six links, all under favicon_io/, none of them template art. Discovery fails SOFT by design, so a renamed directory would take the icons away in silence — tests/test_seo_icons.py is the alarm, and it also pins that the emitted hrefs actually resolve. Found while wiring lastmod through, and fixed in the same call because it is the same defect: the crawler document also had NO og:image (browsers had one) and typed every documentation page as a bare schema.org WebPage. The record now carries title, image_url, schema_type and lastmod, so the two heads agree on identity — content may differ between them, identity may not. Passing `title` was measured, not assumed: it does not double the brand, because the package composes its own "<page> · <site>" only when nothing is declared. The metadata had to be REMEMBERED, not passed once: apply_llms_state re-registers the whole record on every control-board toggle, so lib/ page_visibility.py stores an `extra` dict alongside the prose rather than letting a re-registration depend on the package's merge semantics holding. Still pending, and not blocking: normalized favicon art from CDN source through the boilerplate's scripts/make_favicons.py (absent here). Discovery already serves this site's own favicon_io art, and the webmanifest already carries leaflet's name, description and #2f9e44 theme colour. Verified: 182 passed on flask, 181 + 1 skip on fastapi, smoke 72/72, flake8 clean, against dimll 2.6.0 installed from the local wheel (the sandbox has no PyPI access; the deploy resolves it from the index). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.