docs: diagnose the CARTO basemap outage, adopt the CARTO key, and record the nine-app rollout - #70
Merged
Merged
Conversation
CARTO began burning an "API KEY REQUIRED" watermark into unauthenticated raster tiles from basemaps.cartocdn.com, server-side. The request still returns HTTP 200 with a valid PNG, so nothing errors, logs, or falls back — the map just renders defaced. All nine companion apps are hit on first load, because every one of them puts CartoDB.Positron on its landing site-picker map. Driver-Cascade itself ships no Leaflet map and is unaffected. Adds docs/SUITE-BASEMAP-INCIDENT-2026-08.md with the verified diagnosis, the reproduction, the non-causes eliminated with evidence (package versions, referer/CORS, CSP, app strings), the per-app call-site table, the decided fix (Esri.WorldGrayCanvas plus a CSS-inverted tile pane for dark), the rejected alternatives, and a resume checklist. Records the lesson and a dated handoff entry. Docs only: docs/ and .claude/ are outside DEPLOY_APP_FILES, so no artifact, manifest, or deploy surface moves. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01738HeGPEU9NEoczL8RXnu1
A nine-app audit and a four-lens adversarial review refuted the fix recorded in the previous commit. The diagnosis is unchanged; the remedy moved. Measured: Canvas/World_Light_Gray_Base has had no content update since 2021. At z16 it returns a single-colour blank tile for 17 of NEON's 46 terrestrial sites; SCBI z15 and z16 are byte-identical; above z16 every request is the same "Map data not yet available" placeholder. Verified by hand for SCBI (z13 = 224 distinct colours, z16 = 1). Every other candidate tested is 0/46 blank. So the swap is right for the national pickers at z2-5 and wrong for every plot-scale map, and the proposed maxNativeZoom guard upscales an already-blank tile rather than fixing it. The CSS-invert dark workstream is cancelled, superseded rather than deferred: Canvas/World_Dark_Gray_Base is a real, keyless, labelled dark canvas, verified live. The earlier "there is no keyless dark canvas" claim was false — it asked what leaflet-providers exposes, never what the provider serves. Three further defects: the swap makes marker contrast ~8-10% worse, not better; leaflet.providers emits a stale Esri attribution that omits the OpenStreetMap credit the service's own copyrightText requires, trading a compliant credit line for a non-compliant one; and one app uses the object form providers$CartoDB.Positron, which a quoted-string grep misses. Replaces the plan with a per-map-role one, records the 22 call sites across 9 repos, maps the gate asymmetry (two repos have no ci.yml), and raises two owner questions that block rollout. Docs only: docs/ and .claude/ are outside DEPLOY_APP_FILES. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01738HeGPEU9NEoczL8RXnu1
Owner decision: authenticate the existing basemaps rather than replace
them. Keeping CartoDB.Positron and CartoDB.DarkMatter is the only option
that changes nothing visually — same tiles, same maxZoom 20, same retina,
zero blank tiles, no palette or CSS re-tuning, no contrast or attribution
regression, and ground-beetle's dark theme keeps working. Every defect in
the refutation exists because the alternatives moved the basemap.
Records the key mechanics: it is a ?key= query parameter, and
addProviderTiles() cannot carry it because the pinned CartoDB template
has an {r} slot but no {apikey} placeholder, so it needs a raw addTiles()
with attribution by hand. Adds an add_suite_basemap() helper, keyed with
a keyless Esri-canvas fallback so a missing or revoked key degrades to a
clean canvas instead of a defaced one.
States plainly that the key is not a secret: it rides in the tile URL and
every request is client-side, and CARTO's terms ban server-side proxying,
so it cannot be hidden. Sys.getenv keeps it out of nine public git
histories and makes rotation a Connect Cloud setting — nothing more.
Adds the rollout section: every companion repo has a byte-exact manifest
gate and all app sources are on the deploy surface, so the manifest must
be regenerated in the same commit, never by hand. Two repos have no CI at
all and ship a manifest nothing verifies. Small Mammal forbids automation
pushing to main, so this opens PRs rather than merging them.
Retires the Esri.WorldGrayCanvas swap and the CSS-invert dark canvas, and
keeps the per-role split only as the fallback path.
Docs only: docs/ and .claude/ are outside DEPLOY_APP_FILES.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01738HeGPEU9NEoczL8RXnu1
Tested a Workspace API Access Token scoped to the Maps API against the raster basemap CDN at confirmed origin cache misses under five auth forms; every response was still watermarked. The token is valid, but the maps scope covers CARTO's Maps API, not the public basemap CDN, and CARTO's key page confirms platform credentials only cover in-platform use. External embedding needs the form-issued basemap key. Also records that the CDN ignores the query string in its cache key, so a key smoke test must use an origin-MISS tile. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01738HeGPEU9NEoczL8RXnu1
HubSpot's API-submission path for the basemap key form returns FORM_HAS_RECAPTCHA_ENABLED, so the form must be human-submitted in a browser. The page's own fallback is support-basemaps@carto.com, which issues a key by hand. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01738HeGPEU9NEoczL8RXnu1
The owner obtained the basemap key via the form; it verifies clean at CDN origin misses for both variants, and previously-cached watermarked zooms return clean with it, so the CDN caches keyed responses separately. Records the canary flow: code first, manifest from the pinned CI validator's artifact in a follow-up commit, first run intentionally red at the byte gate. The key itself lives in Connect Cloud variables and the owner's email, never in any repo. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01738HeGPEU9NEoczL8RXnu1
This was referenced Aug 29, 2026
Merged
The Ground Beetle canary merged, deployed green, and the owner confirmed the live map, which closed the last unproven link — the Connect variable really does reach the running app. All eight remaining repos are now patched with PRs open. Records the rollout table, the split default branches checked per repo, and the two no-CI repos handled differently from each other and from the rest: Inverts source-only because its AGENTS.md forbids regenerating the manifest and its release identity hashes the source, Water Chemistry with its one MD5 updated because a stale checksum there would silently drop the fix. Also notes why no ui.R dropdown needed changing: the helper accepts a provider name or a CARTO variant, so choice vectors and defaults stay exactly as they were. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01738HeGPEU9NEoczL8RXnu1
Six of nine are green or merged. Every manifest shuttle showed the same thing: manifest.json was the only file that differed, with all data, indexes and receipts already byte-identical to the branch. Three are blocked on the owner for two distinct reasons. Mosquito is a CI shape gap — its artifact upload is conditional on an earlier failure and sits before the byte gate, so the validated manifest is discarded exactly when it is needed; five siblings upload unconditionally. Breeding Birds and Inverts bind the app source into generated authority, so regenerating needs R in the pinned validator; both PRs carry the validator's own commands verbatim. Also corrects a mistake worth keeping: no ci.yml is not no CI. Inverts and Water Chemistry both validate from refresh-data.yml, and Water Chemistry's connect_cold_start actually cold-boots the deploy bundle. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01738HeGPEU9NEoczL8RXnu1
…nd the last two shuttles Adds section 10 to the incident record and three lessons. Two problems the rollout had not anticipated: - Breeding Birds' master carries an unresolved merge -- nine conflict-marker lines inside manifest.json's files map -- so the file is invalid JSON on the branch Connect Cloud watches, and CI failed on it. Cause and blast radius documented; the repair rides in PR #6. - Water Chemistry's map is blank for a reason that is not in the code. The live app provably runs the merged bytes and all three tile endpoints answer 200, which leaves the key's value: a missing key is loud (CARTO's watermark), a whitespace-padded one is silent. That asymmetry is the lesson worth keeping. Also records how Birds' authority was regenerated without an R runtime -- by reproducing the known-good stamp byte-for-byte first -- and how the Mosquito and Inverts shuttles were validated. Flags the outstanding work plainly: the hardening is in one repo, the other eight still carry the unhardened helper. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01738HeGPEU9NEoczL8RXnu1
…llout Adds section 10.7 and brings the rollout table up to date. Birds' rerun failed at the second stamp verify, and the failure named a real design flaw rather than a mistake: schema-v3 hashes the whole packages block into manifest_contract_sha256, so package metadata the change never touched invalidates the release identity. The open question was whether adopting the validator's manifest converges or loops. It converges, and that is now measured rather than assumed: Birds and Mosquito, different repos and different runners, produced byte-identical Built values for all 83 packages they share, in the same 2026-08-12 window, and Mosquito's rerun after shuttling went green. Those are Posit Package Manager's binary build times for the pinned snapshot, not per-run compiles. Worth keeping for connor/neonize: in Mosquito this drift only trips a byte-diff gate, but in Birds it corrupts the release identity of an unchanged app -- the strongest case yet for promoting compare_manifests.R. Also records how to spot a Connect variable that was missed: that app comes up on the grey Esri canvas instead of Positron. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01738HeGPEU9NEoczL8RXnu1
tgilbert14
marked this pull request as ready for review
August 31, 2026 16:31
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.
What happened
The maps across the NEON explorer suite started showing "API KEY REQUIRED — carto.com/basemaps/apikey" stamped across the basemap.
Root cause: CARTO now burns that watermark into unauthenticated raster tiles from
basemaps.cartocdn.com, server-side. The tile request still returnsHTTP 200with a valid PNG, so nothing errors, logs, or falls back — the map simply renders defaced.Blast radius: all nine companion apps, on first load — every one puts
CartoDB.Positronon its landing/site-picker map, which needs no interaction. Driver-Cascade itself ships no Leaflet map and is unaffected.Decision: take the free CARTO key —
ADOPTAuthenticate the existing basemaps rather than replace them. Keeping
CartoDB.PositronandCartoDB.DarkMatteris the only option that changes nothing visually: same tiles, samemaxZoom = 20, same retina, zero blank tiles, no palette or CSS re-tuning, no contrast or attribution regression, and ground-beetle's dark theme keeps working untouched.One step no agent can do: request the key at https://carto.com/basemaps/apikey — email + domain + one-line description, emailed straight back, no account, no approval queue, 5M tiles/month fair use.
Why not swap the provider — the first plan was refuted
The initial plan (blanket
CartoDB.Positron→Esri.WorldGrayCanvasplus a CSS-invert dark canvas) went through a nine-app audit and a four-lens adversarial review. All four lenses refuted it. Two findings were re-verified by hand:1. The replacement is measurably BLANK at plot scale.
Canvas/World_Light_Gray_Basehas had no content update since 2021.RGB(239,239,239)Across all 46 NEON terrestrial sites, 17 (37%) return a single-colour blank tile at z16. Every other candidate — Esri Topo/Imagery, USGS, OSM, CARTO — is 0/46. The
maxNativeZoom = 16guard upscales an already-blank tile.2. There IS a keyless dark canvas.
Canvas/World_Dark_Gray_Baseis real, keyless and labelled — so the CSS-invert workstream was unnecessary. The earlier "no keyless dark canvas exists" claim was false: it asked whatleaflet-providersexposes, never what the provider serves.Plus: the swap makes marker contrast ~8–10% worse;
leaflet.providersemits a stale Esri attribution omitting the OpenStreetMap credit the service's owncopyrightTextrequires; and one app usesleaflet::providers$CartoDB.Positronin object form, which a string grep misses.Every one of those defects exists because the alternatives moved the basemap. The key doesn't.
The code
addProviderTiles()cannot carry the key — the pinned CartoDB template has an{r}retina slot but no{apikey}placeholder — so it needs a rawaddTiles()with attribution by hand. §4.3 adds anadd_suite_basemap()helper: keyed CARTO when the key is present, keyless Esri canvas when it isn't, so a missing or revoked key degrades to a clean canvas rather than a defaced one.The key is not a secret. It rides in the tile URL and every request is client-side, so it lands in page source regardless; CARTO's terms §9.c bans server-side proxying, so it cannot be hidden.
Sys.getenv("CARTO_BASEMAP_KEY")buys exactly two things — it keeps the key out of nine public git histories, and makes rotation a Connect Cloud setting instead of nine releases.No negative test exists: an invalid key returns the byte-identical watermarked tile as no key at all (same ETag). Only the real key proves the URL form — smoke-test once before any rollout PR.
Rollout constraints found (§5)
ui.R/server.R/R/*.R/global.Rare all on each repo's deploy surface with per-file checksums, so the manifest must be regenerated in the same commit. Never hand-edit it — Small Mammal and Vegetation carry a blessedregenerate-manifest.ymlfor exactly this.neon-my-little-invertsandneon-waterchemistry-analyte-viewer-apphave noci.ymlbut still ship a manifest — nothing catches a stale one. Do them last.DEPLOY.md:9-13forbids automation pushing tomain— open PRs, do not merge them.Changes
docs/SUITE-BASEMAP-INCIDENT-2026-08.md— diagnosis, non-causes eliminated with evidence, the refutation and its measurements, the decision, the helper code, per-repo rollout gates, all 22 call sites, and a resume checklist..claude/agents/LESSONS.md— two lessons: a free tile provider is an uncontracted runtime dependency no manifest pin covers; and "unwatermarked" is not "usable" — decode the tile and count colours, becauseHTTP 200proves neither.docs/BUILD-TEST-HANDOFF.md— three dated[Claude]entries.Deploy safety
Docs only.
docs/and.claude/are outside theDEPLOY_APP_FILESallowlist, so no artifact, manifest, or deploy surface moves.Known limitation
No headless-browser check was possible in this container (Chromium has no egress; every navigation fails
ERR_CONNECTION_RESET, includingexample.com). All evidence is tile-level and source-level — decisive for the cause and the blanking, but nobody has yet seen a fixed app.🤖 Generated with Claude Code
https://claude.ai/code/session_01738HeGPEU9NEoczL8RXnu1