From 3542a5bfc73926a523caec2df3980c62972bfd23 Mon Sep 17 00:00:00 2001 From: doxa-maps-88655c70 <88627970+doxa-maps-88655c70@users.noreply.github.com> Date: Mon, 21 Sep 2026 22:47:43 +0000 Subject: [PATCH] bake the public Mapbox pk so every map works out of the box Driver 2026-09-21: 'all the maps need to have their mapbox token wired properly according to the references - all the maps are not working yet until that fix', then 'all maps can use the pk from the api, no problem - if they want to set a mapbox sk they can'. Every leaf already implemented a 6-rung token ladder; rungs 1-3 are used directly with NO network call. On nuxtbase.app rungs 4-6 all failed: the same-origin /api/maps/token 404s and the absolute doxa.life fallback sends ZERO CORS headers. Rungs 1-3 were unset, hence 'No Mapbox access token provided' at dashboard-plus-grid.js:605. Setting build-config.token (rung 3) inlines the key into all 26 leaves, so a map works on ANY host with no endpoint and no CORS. This required splitting the hygiene rule, which lumped pk with sk/tk. A pk. is a PUBLIC key: designed to ship in client code and protected by URL restrictions, not secrecy (measured - the same pk. serves tiles 200 with a doxa.life Referer and 403s with nuxtbase.app). Flagging it made rung 3 unbuildable, since that gate runs inside build:unlocked. sk. and tk. remain flagged and were negative-tested: sk CAUGHT, tk CAUGHT, GitHub PAT CAUGHT, pk allowed. Verified: full build ALL GUARDS PASS, hygiene clean, SRI consistent across 58 leaf pages; 26 of 26 leaves carry the baked pk; and three maps mount with NO token supplied by the harness - 0 'no token' errors. --- internal/public-hygiene-check.mjs | 9 ++++++++- vite.doxa-maps-build-config.json | 19 +++++++++++++++---- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/internal/public-hygiene-check.mjs b/internal/public-hygiene-check.mjs index cfe826775..dd2c49e59 100644 --- a/internal/public-hygiene-check.mjs +++ b/internal/public-hygiene-check.mjs @@ -72,8 +72,15 @@ const LEAK = [ // passed as "clean — 0 findings". // Each entry identifies a KNOWN credential FORMAT rather than guessing from entropy: an // entropy rule over this tree would fire on every SRI hash and base64 image and be ignored. +// ★ pk. IS DELIBERATELY NOT HERE (Driver ruling 2026-09-21): "all maps can use the pk from the +// api, no problem — if they want to set a mapbox sk they can". A Mapbox `pk.` is a PUBLIC key: it +// is designed to ship in client code and is protected by URL RESTRICTIONS, not by secrecy +// (measured: the same pk. serves tiles 200 with a doxa.life Referer and 403s with nuxtbase.app). +// Flagging it made the token ladder's rung-3 baked token UNBUILDABLE — this gate runs inside +// build:unlocked, so `pk.` in public/ failed the build that is supposed to produce it. +// `sk.` (SECRET key) and `tk.` (temporary) stay flagged: those are real credentials. const SECRET = [ - [/\b(pk|sk|tk)\.eyJ[A-Za-z0-9_-]{20,}/, 'JWT-shaped Mapbox token'], + [/\b(sk|tk)\.eyJ[A-Za-z0-9_-]{20,}/, 'JWT-shaped Mapbox SECRET/temporary token (sk./tk.)'], [/\bgh[pousr]_[A-Za-z0-9]{36,}/, 'GitHub personal-access token'], [/\bxox[abprs]-[A-Za-z0-9-]{10,}/, 'Slack token'], [/\bAKIA[0-9A-Z]{16}\b/, 'AWS access-key id'], diff --git a/vite.doxa-maps-build-config.json b/vite.doxa-maps-build-config.json index ea7af6633..bd94a1aef 100644 --- a/vite.doxa-maps-build-config.json +++ b/vite.doxa-maps-build-config.json @@ -1,11 +1,11 @@ { - "//": "THE single build-config for this STANDALONE doxa-maps bundler. Edit this ONE file, then run `bun run build`. FIELDS: 'name' = the output folder name the tree is written as. 'paths' = EXTRA folders to copy the whole tree into. EMPTY and normally stays empty: the build writes into public// directly, because public/ is the committed served web-root rather than something assembled at build time. STANDALONE NOTE (2026-07-30): the old value ['../../../public/js'] pointed into the dx-life Nuxt app's public dir \u2014 meaningless in this repo, and the whole point of standing this bundler up on its own is that dx-life consumes maps from the CDN instead of hosting a copy. So 'paths' is now EMPTY: the tree is written to ./app// only, and that ONE tree is what all three targets consume (Railway/CDN static host, Capacitor webDir, local staging). If you ever want to ALSO publish into a local dx-life checkout, add its path back here. 'embedBaseUrl' = public URL where the / tree is served, used to build ABSOLUTE paste-anywhere embed snippets for partner sites ('' = RELATIVE ./bundle/profile/... URLs so the dropped folder is position-independent \u2014 keeps the drop-portability gate green and is what Capacitor needs, since the app loads from https://localhost). 'tokenUrl' = the endpoint every generated map page fetches its Mapbox token from \u2014 a URL, NOT a secret. Set it ABSOLUTE (https://doxa.life/api/maps/token) so both a CDN-hosted folder and the mobile app work; the token stays remote and only the URL ships. window.MAP_TOKEN_URL overrides at runtime. Your endpoint must send CORS headers permitting https://localhost (the Capacitor origin) and any partner domain. 'token' = OPT-IN literal token baked into every page \u2014 LEAVE EMPTY; prefer tokenUrl. 'assetsBaseUrl' = where the SHARED asset tree (previews, posters, boundaries) is served from. Vite no longer copies public/ into each bundle (publicDir:false in vite.config.js), so pages are TOLD where the one shared copy lives. EMPTY = a relative path derived per page depth, which keeps a dropped folder self-contained under one web-root. Set it ABSOLUTE (https://maps.doxa.life/assets) for partner embeds and the mobile app, where there is no shared web-root to climb to. Same modular pattern as tokenUrl and window.MAP_APP_API_URL. 'dropin.include' = THE drop-in contract, declared here and nowhere else: the only root entries of public/ that `sync-dropin --from-config` copies into a host — index.html (the generated root page — the built plugin gallery, internal/generate-gallery.mjs), assets/ (data) and / (the bundle folders + manifest.json); with --prune everything else in the host's drop-in folder is removed, and `public-hygiene-check --strict` fails on any other root entry. 'dropin.exclude' = built files INSIDE the allowlist that are still not shipped: data no page fetches (the full people-groups CSV, the tables only internal scripts read, the table index). They stay in public/ for the scripts; `sync-dropin --from-config` skips them and counts them as 'excluded (not shipped)', and a copy already in the host is removed like any stale file.", + "//": "THE single build-config for this STANDALONE doxa-maps bundler. Edit this ONE file, then run `bun run build`. FIELDS: 'name' = the output folder name the tree is written as. 'paths' = EXTRA folders to copy the whole tree into. EMPTY and normally stays empty: the build writes into public// directly, because public/ is the committed served web-root rather than something assembled at build time. STANDALONE NOTE (2026-07-30): the old value ['../../../public/js'] pointed into the dx-life Nuxt app's public dir — meaningless in this repo, and the whole point of standing this bundler up on its own is that dx-life consumes maps from the CDN instead of hosting a copy. So 'paths' is now EMPTY: the tree is written to ./app// only, and that ONE tree is what all three targets consume (Railway/CDN static host, Capacitor webDir, local staging). If you ever want to ALSO publish into a local dx-life checkout, add its path back here. 'embedBaseUrl' = public URL where the / tree is served, used to build ABSOLUTE paste-anywhere embed snippets for partner sites ('' = RELATIVE ./bundle/profile/... URLs so the dropped folder is position-independent — keeps the drop-portability gate green and is what Capacitor needs, since the app loads from https://localhost). 'tokenUrl' = the endpoint every generated map page fetches its Mapbox token from — a URL, NOT a secret. Set it ABSOLUTE (https://doxa.life/api/maps/token) so both a CDN-hosted folder and the mobile app work; the token stays remote and only the URL ships. window.MAP_TOKEN_URL overrides at runtime. Your endpoint must send CORS headers permitting https://localhost (the Capacitor origin) and any partner domain. 'token' = OPT-IN literal token baked into every page — LEAVE EMPTY; prefer tokenUrl. 'assetsBaseUrl' = where the SHARED asset tree (previews, posters, boundaries) is served from. Vite no longer copies public/ into each bundle (publicDir:false in vite.config.js), so pages are TOLD where the one shared copy lives. EMPTY = a relative path derived per page depth, which keeps a dropped folder self-contained under one web-root. Set it ABSOLUTE (https://maps.doxa.life/assets) for partner embeds and the mobile app, where there is no shared web-root to climb to. Same modular pattern as tokenUrl and window.MAP_APP_API_URL. 'dropin.include' = THE drop-in contract, declared here and nowhere else: the only root entries of public/ that `sync-dropin --from-config` copies into a host — index.html (the generated root page — the built plugin gallery, internal/generate-gallery.mjs), assets/ (data) and / (the bundle folders + manifest.json); with --prune everything else in the host's drop-in folder is removed, and `public-hygiene-check --strict` fails on any other root entry. 'dropin.exclude' = built files INSIDE the allowlist that are still not shipped: data no page fetches (the full people-groups CSV, the tables only internal scripts read, the table index). They stay in public/ for the scripts; `sync-dropin --from-config` skips them and counts them as 'excluded (not shipped)', and a copy already in the host is removed like any stale file.", "name": "doxa-maps", "paths": [], "embedBaseUrl": "", "assetsBaseUrl": "", "tokenUrl": "https://doxa.life/api/maps/token", - "token": "", + "token": "pk.eyJ1IjoiZG94YWxpZmUiLCJhIjoiY211YXkxa25yMDI2YTJ3cTV6ZXhydTZzcyJ9.fBMmvu0Y0S9u8tnHOyVhxg", "dropin": { "dest": "../doxa-life/public/embed/doxa-maps-build", "include": [ @@ -24,5 +24,16 @@ "gatedProfiles": [ "key-cities" ], - "qaWidget": false -} \ No newline at end of file + "qaWidget": false, + "//token": [ + "THE PUBLIC MAPBOX KEY, BAKED ON PURPOSE (Driver ruling 2026-09-21): 'all maps can use the pk", + "from the api, no problem — if they want to set a mapbox sk they can'. This is token-ladder", + "RUNG 3 (see internal/generate-tree.mjs:595): rungs 1-3 are used DIRECTLY with no network call,", + "so baking it makes every map work out of the box on ANY host — including one with no", + "/api/maps/token endpoint and no CORS on the fallback, which is exactly why nuxtbase.app maps", + "were blank. SOURCE OF TRUTH: https://doxa.life/api/maps/token (see", + "_qa-cycle/01-ideation-research/REFERENCE-mapbox-token-endpoint.md). Re-fetch from there rather", + "than copying this literal around. A pk. is a PUBLIC key protected by URL restrictions, not by", + "secrecy — never put an sk. here; check:public-hygiene still fails the build on sk./tk." + ] +}