Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 35 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ DASH_LEAFLET2_BASE_URL=http://localhost:8050
# SATELLITE_TRAFFIC_URL=https://2plot.ai/api/satellite/traffic
# SATELLITE_REPORT_INTERVAL_S=3600
# SATELLITE_REPORT_DELAY_S=90
#
# Live "active now" presence beacon (POST /api/satellite/active, URL derived
# from SATELLITE_TRAFFIC_URL). Display-only and ephemeral hub-side; the
# rollup above stays the source of the daily numbers. 0 disables; floor 30s.
# SATELLITE_PRESENCE_INTERVAL_S=60
# SATELLITE_PRESENCE_URL=https://2plot.ai/api/satellite/active
# Behind Cloudflare the country header is already present, so the ip-api.com
# fallback lookup is redundant latency; "0" disables it.
# ANALYTICS_GEO_LOOKUP=0
Expand Down Expand Up @@ -101,6 +107,33 @@ DASH_LEAFLET2_BASE_URL=http://localhost:8050
# Set this to work on the board locally without Clerk. NEVER set in production.
# ALLOW_UNGATED_ADMIN=1

# --- Optional: page visibility ----------------------------------------------
PAGE_DEFAULT_VISIBILITY=public
# --- Optional: the interactive gate (lib/access.py) -------------------------
# Baseline tier for pages whose frontmatter declares none:
# public | auth | admin | hidden. `auth` puts a sign-in card in front of every
# documentation page; `/`, /llms-small.txt and /llms-full.txt stay public
# (pinned in run.py). PAGE_DEFAULT_VISIBILITY is the same knob under this
# site's older name and is still read — set one, not both.
#
# Locally this does nothing visible without the CLERK_* keys above: with Clerk
# unavailable every tier except `hidden` falls open, because documentation
# must not brick over a missing credential. To see the gate, set the Clerk
# keys, or read tests/test_access.py, which drives it with a fake session.
PAGE_DEFAULT_TIER=public
# PAGE_DEFAULT_VISIBILITY=public

# The SECOND axis: whether a gated page's MACHINE twin (/<page>/llms.txt, the
# crawler document, the prerender) stays open anyway. Unset = open, which is
# the current network posture — humans meet the sign-in card while agent and
# crawler demand keeps being measured. Setting it to 0 is the phase-4 agent
# flip and closes every page that did not pin `llms_public:` in frontmatter.
# LLMS_PUBLIC_DEFAULT=0

# Where the control board writes its overrides. An override beats both the
# frontmatter tier and PAGE_DEFAULT_TIER; only the hub's ceiling outranks it.
# In production this points at a persistent disk so toggles survive a deploy.
# PAGE_VISIBILITY_FILE=page_visibility.json

# Tiers for the two corpus documents, independent of PAGE_DEFAULT_TIER so
# gating the interactive site never silently gates the corpus.
# LLMS_SMALL_TIER=public
# LLMS_FULL_TIER=public
76 changes: 74 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,83 @@ will move until v2 leaves alpha upstream.
## [Unreleased]

The fleet's x402 instrumentation sync (1.3.x) — measurement only, no payment
or gating code, per the network's "instrument first, price later" rule.
Documentation site and network wiring only; no `dl2.*` component changed.
code — followed by the **sign-in gate pilot**, which this site runs first for
the network. Documentation site and network wiring only; no `dl2.*` component
changed, and `pip install dash-leaflet2` is untouched by any of it.

### Added

- **The sitemap stopped lying.** Every entry used to claim the page changed
today, regenerated on every crawl — a sitemap asserting that 27 pages change
daily is one search engines learn to discard wholesale. Each page now
publishes the real date its prose last changed, and a page that declares no
date gets no date: truth or silence.

- **Google now sees this site's own icons.** The crawler document carried no
favicon at all — browsers got six, Googlebot got zero, which is why search
showed a generic globe. It also had no social image and described every
documentation page as an untyped generic web page. All three now match what
a browser gets. (Requires `dash-improve-my-llms` 2.6.0, which discovers the
icons from `assets/favicon_io/` with nothing declared.)

- **A live "active now" figure** on the hub's dashboard, from a lightweight
presence beacon alongside the existing hourly rollup. Display-only — the
daily numbers still come from the rollup, which now reports every 15 minutes
rather than hourly.

- **The network directory caught up with the fleet**: muicharts, flexlayout
and llms.2plot.dev added, and pannellum/emojimart restored now that they
resolve — twelve peers, no dead links.

- **A sign-in gate, shipped dark.** Documentation pages can now require an
account. Nothing is gated yet: the site deploys with the gate wired and
every verdict answering "allow", so the whole path runs in production
before the single environment variable (`PAGE_DEFAULT_TIER=auth`) that
turns it on — and setting that variable back is the entire rollback.

A signed-out visitor on a gated page gets a **sign-in card at HTTP 200**,
not a redirect and not a 404: the URL stays shareable, and "Create free
account" now carries the current page in its return trip, so a visitor
lands back where they started instead of on the primary's home page. That
return leak is the one user-visible bug this pass fixes today.

**Machine surfaces stay open.** `/<page>/llms.txt`, the crawler document
and the prerender keep serving prose to agents and crawlers while humans
meet the card — a deliberate 30-day posture, switched network-wide later
with `LLMS_PUBLIC_DEFAULT=0` rather than per-page edits.

- **`GET /api/agent-key`** — the person-to-agent handoff. Copying a page's
`llms.txt` URL while signed in now carries a key, so the link still
resolves when it is pasted into an assistant, whose fetch arrives with no
session cookie. Signed out, the copy button behaves exactly as before.

- **The network's page-tier ceiling.** 2plot.dev can now restrict a page
across the network; this site may lock a page down further but can never
open one the network gated. A hub outage changes nothing for a signed-in
reader — sessions resolve locally — and resolves to "gated" for anyone
else, never to publishing restricted prose and never to a dead site.

### Changed

- **One access system instead of two.** `tier:` and `visibility:` in a page's
frontmatter were independent fields naming the same four values, so a page
could declare one tier and be enforced at another, with a control-board row
that quietly disagreed. `tier:` is now canonical, `visibility:` is an
accepted alias, and one declared value feeds both. `PAGE_DEFAULT_TIER` is
likewise the canonical spelling of `PAGE_DEFAULT_VISIBILITY`, which is
still read so the running service does not change posture underneath a
deploy.

The control board keeps everything it did — live toggles, four tiers, and
overrides that outlive a deploy — and its override is still the most
authoritative local word on a page. What moved out of it is the decision
itself, into `lib/access.py`.

- **Admin surfaces now fail closed everywhere.** Documentation still falls
open when Clerk is unavailable — it must never brick over a missing
credential — but the retired resolver fell open for admin pages too. Only
`/admin/control-board`'s own double gate stopped that mattering.

- **Analytics: Gen-1 single-module tracker retired for the boilerplate's
trio.** `lib/analytics_tracker.py` (per-request JSON ledger),
`lib/traffic_rollup.py` (the hub's own daily v2+v3 definitions — its
Expand Down
46 changes: 41 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,39 @@ The site de-risks; the package is the durable artifact. Keep both working.
boilerplate's trio; it replaced the Gen-1 single-module tracker in the
1.3.x instrumentation sync — `traffic_rollup._SKIP` must stay byte-identical
to the boilerplate's) · `lib/auth.py` → Clerk satellite of 2plot.ai ·
`lib/page_visibility.py` + `pages/control_board.py` → `/admin/control-board`,
four tiers re-checked every render. Full reference in `DEPLOYMENT.md`. These
are shared drop-in modules — when fixing a bug in `ad_client.py` or the
analytics trio, the fix probably belongs in the other satellites too
(canonical source: `../dash-documentation-boilerplate`).
`lib/access.py` (+ `page_tiers` / `hub_client` / `gate_layouts` / `agent_key`)
→ the gate · `lib/page_visibility.py` + `pages/control_board.py` →
`/admin/control-board`. Full reference in `DEPLOYMENT.md`. These are shared
drop-in modules — when fixing a bug in `ad_client.py` or the analytics trio,
the fix probably belongs in the other satellites too (canonical source:
`../dash-documentation-boilerplate`).

## The gate (this repo is the fleet's pilot)

`lib/access.py` is the enforcement engine; `lib/page_visibility.py` was demoted
to the control board's **override store + UX** and no longer resolves access or
wraps layouts. A verdict resolves from three inputs, in order: the board's
override (most local, wins — that is what a live toggle is), the frontmatter
registration in `page_tiers`, then the hub's ceiling, which only ever restricts.

Two lanes, deliberately different: `resolve_page_access` answers what a BROWSER
gets (`gate_layouts` renders the card), `check` answers what a MACHINE fetch
gets (`/<page>/llms.txt`, crawler HTML, prerender) and honours `?key=` plus the
`llms_public` axis. A key never unlocks a layout.

Frontmatter: `tier:` is canonical, `visibility:` is an accepted alias for the
same four values, and ONE declared value feeds both ledgers — they were
independent keys before this pass, which let a page declare one tier and be
enforced at another.

Two postures that look like bugs and are not: docs fall **open** without Clerk
(documentation must not brick over a missing credential) while admin fails
**closed**; and a hub failure resolves to `gated`, never `allow`, never `deny`.

Shipped **dark**: `run.py` wires the policy with `force=True` even though every
tier is public, so the verdict path (and the prerender's use of it) runs in
production before `PAGE_DEFAULT_TIER=auth` turns it on. That env flip is the
whole change, and flipping it back is the rollback.

## Commands

Expand Down Expand Up @@ -140,6 +168,14 @@ race fixed there. Its `clerk-backend-api<8` cap (widened in 1.0.1) is what lets
is hand-maintained (registers `_js_dist`); everything else in `dash_leaflet2/` is generated.
- **Showcase JS** (`assets/leaflet2_maps.js`): a new example = one `DEMOS` entry + one
`docs/<slug>/{<slug>.md, example.py}` pair (`example.py` exports `component`). JS→Python uses `toStore()` (hardened `set_props` with retry — don't bypass it).
- **`lastmod:` rides the prose.** Every `docs/<slug>/<slug>.md` declares a
sitemap date; `dash-improve-my-llms` >= 2.6.0 emits it verbatim and omits the
tag when absent. Edit a page's prose → bump its `lastmod` in the SAME commit.
Never script these from file mtimes (they reset on every Docker build, which
re-creates the every-page-changed-today sitemap the 2.6.0 floor exists to
end). The initial values came from `git log -1 --format=%cs -- <file>`.
`tests/test_seo_icons.py` fails if the sitemap ever emits a date no page
declared, and if crawler-head icon discovery comes back empty.

## More detail

Expand Down
Loading
Loading