Merge remote-tracking branch 'origin/development' into regions-plugin/view-address-p2

This commit is contained in:
Doxa Maps 2026-10-03 07:38:38 +00:00
commit 04057b9177
3 changed files with 158 additions and 6 deletions

View file

@ -0,0 +1,136 @@
#!/usr/bin/env node
/**
* check-dev-leaf-escape.mjs — a dev leaf URL cannot write markup into the page it opens.
*
* THE DEFECT. The dev server's leaf route, /__leaf/<bundle>/<profile>[/<instance>] (and a production embed URL,
* <bundle>/<profile>[/<instance>]/index.html, rewritten onto it), writes the URL's parts into the page it serves: the
* <title> and the element's profile-config='…' attribute. It refused only `..` and `/`, so a quote or a tag in the path
* was written raw, and a crafted link broke out of the attribute on a publicly reachable dev server.
* internal/plugin-gallery/dev-plugin.mjs now refuses any part outside LEAF_PART (the shape every bundle folder, profile
* id and instance id has) before either sink, and escapes the <title> as well.
*
* The plugin's own middleware answers each request (no server, no browser):
* A. every leaf the dev manifest advertises (galleryModel), as /__leaf/… AND as its production embed URL → 200 with
* its element: the rule refuses no real page;
* B. a hostile part (a quote, a double quote, a tag, an ampersand, a space, a backtick, an encoded slash or
* backslash, `..`, `.`, an attribute break-out) in each position, both URL forms → refused here (400) or left to
* Vite, never a page;
* C. escHtml leaves none of & < > " ' raw;
* D. with the part rule set back to the old one (`..` and `/` only), a tag in the profile still reaches the <title>
* escaped: the title has its own escaping, not only the rule in front of it.
* Response bodies are never printed: a leaf page carries the build config's token.
* `--self-test`: the old guard, a rule too strict for a real profile, an unescaped <title> and an escHtml that misses
* the quote must each FAIL.
*/
import { readFileSync, writeFileSync, rmSync } from 'node:fs'
import { join, dirname } from 'node:path'
import { fileURLToPath, pathToFileURL } from 'node:url'
import { discoverSpaRegistry } from './plugin-gallery/discover.mjs'
const HERE = dirname(fileURLToPath(import.meta.url))
const PLUGIN = join(HERE, 'plugin-gallery/dev-plugin.mjs')
const SRC = readFileSync(PLUGIN, 'utf8')
const GUARD = 'parts.some(s => !LEAF_PART.test(s))'
const OLD_GUARD = "parts.some(s => !s || s.includes('..') || s.includes('/'))"
const RULE = 'export const LEAF_PART = /^[A-Za-z0-9][\\w-]{0,63}$/'
const TITLE = '${escHtml(bundle)} · ${escHtml(profile)}'
const ESC_CLASS = `/[&<>"']/g`
// A copy of the plugin, altered or not, imported from beside the original so its relative imports resolve; the
// file is removed as soon as it is loaded.
let seq = 0
async function load(src) {
if (src === SRC) return import(pathToFileURL(PLUGIN).href)
const tmp = join(HERE, 'plugin-gallery', `.tmp-check-dev-leaf-escape-${process.pid}-${++seq}.mjs`)
writeFileSync(tmp, src)
try { return await import(pathToFileURL(tmp).href) } finally { rmSync(tmp, { force: true }) }
}
// The plugin's middleware, answering one GET: { status, body } ('next' = not answered here, Vite would).
function client(mod) {
let mw = null
mod.autoSpaDevPlugin('').configureServer({ middlewares: { use: (f) => { if (!mw) mw = f } } })
return (url) => new Promise((done) => {
const res = { statusCode: 200, setHeader() {}, end: (b) => done({ status: res.statusCode, body: String(b ?? '') }) }
try { mw({ url, method: 'GET', headers: {} }, res, () => done({ status: 'next', body: '' })) }
catch (e) { done({ status: 'threw', body: String(e && e.message) }) }
})
}
// Every /__leaf/ URL the dev manifest advertises, with the tag its page must mount.
function advertised(mod) {
const out = []
const walk = (o, tag) => {
if (!o || typeof o !== 'object') return
for (const v of Object.values(o)) {
if (typeof v === 'string' && v.startsWith('/__leaf/')) out.push({ url: v.split(/[?#]/)[0], tag })
else walk(v, tag)
}
}
for (const b of mod.galleryModel(discoverSpaRegistry()).bundles) walk(b, (b.elements || [])[0])
return [...new Map(out.map((x) => [x.url, x])).values()]
}
const HOSTILE = ["a'b", 'a"b', 'a<b', 'a>b', 'a&b', 'a b', 'a`b', 'a/b', 'a\\b', '..', '.', "x'><img src=x onerror=alert(1)>"]
async function audit(src) {
const f = []
let mod
try { mod = await load(src) } catch (e) { return [`load: the plugin does not import (${e.message})`] }
const get = client(mod)
const leaves = advertised(mod)
if (leaves.length < 20) f.push(`A: only ${leaves.length} advertised leaves found (the manifest walk is broken)`)
for (const { url, tag } of leaves) {
for (const u of [url, url.replace('/__leaf/', '/') + '/index.html']) {
const r = await get(u)
if (r.status !== 200 || !r.body.includes(`<${tag} `)) f.push(`A: ${u} → ${r.status}${r.status === 200 ? `, no <${tag}>` : ''} (a real page refused)`)
}
}
const [b0, p0] = (leaves[0] ? leaves[0].url : '/__leaf/x/x').slice('/__leaf/'.length).split('/')
for (const h of HOSTILE) {
const e = encodeURIComponent(h)
for (const parts of [[e, p0], [b0, e], [b0, p0, e]]) {
const leaf = '/__leaf/' + parts.join('/')
const r = await get(leaf)
if (r.status !== 400) f.push(`B: ${leaf} → ${r.status} (want 400)`)
const embed = '/' + parts.join('/') + '/index.html'
const r2 = await get(embed)
if (r2.status !== 400 && r2.status !== 'next') f.push(`B: ${embed} → ${r2.status} (want 400, or left to Vite)`)
}
}
const esc = mod.escHtml ? mod.escHtml(`&<>"'`) : null
if (esc !== '&amp;&lt;&gt;&quot;&#39;') f.push(`C: escHtml(& < > " ') = ${JSON.stringify(esc)}`)
if (!src.includes(GUARD)) f.push('D: the leaf guard is not the LEAF_PART rule, so the title cannot be tested behind the old one')
else {
let loose
try { loose = await load(src.replace(GUARD, OLD_GUARD)) } catch (e) { f.push(`D: ${e.message}`) }
if (loose) {
const r = await client(loose)(`/__leaf/${b0}/${encodeURIComponent('<b>x')}`)
const title = (/<title>([^]*?)<\/title>/.exec(r.body) || [])[1]
if (r.status !== 200 || title === undefined) f.push(`D: behind the old rule the page did not render (${r.status})`)
else if (title.includes('<b>') || !title.includes('&lt;b&gt;')) f.push('D: behind the old rule a tag in the profile reached the <title> raw')
}
}
return f
}
if (process.argv.includes('--self-test')) {
const poisons = [
['the old guard (`..` and `/` only)', SRC.replace(GUARD, OLD_GUARD), /^B: /],
['a rule too strict for a real profile', SRC.replace(RULE, 'export const LEAF_PART = /^[a-z]{1,63}$/'), /^A: /],
['an unescaped <title>', SRC.replace(TITLE, '${bundle} · ${profile}'), /^D: /],
['an escHtml that misses the quote', SRC.replace(ESC_CLASS, `/[&<>"]/g`), /^C: /],
]
let missed = 0
for (const [name, src, rx] of poisons) {
if (src === SRC) { console.log(`✗ MISSED: ${name} (not planted: its source line was not found)`); missed++; continue }
const f = await audit(src); const hit = f.find((x) => rx.test(x))
console.log(hit ? `✓ caught: ${name} -> ${hit}` : `✗ MISSED: ${name}${f.length ? ` (failed instead: ${f[0]})` : ''}`)
if (!hit) missed++
}
if (missed) process.exitCode = 1
} else {
const f = await audit(SRC)
if (f.length) { for (const x of f.slice(0, 40)) console.log('✗ ' + x); console.log(`FAIL check:dev-leaf-escape (${f.length})`); process.exitCode = 1 }
else console.log(`PASS check:dev-leaf-escape — every advertised dev leaf is served in both URL forms; ${HOSTILE.length} hostile parts are refused in every position of both forms; the <title> escapes on its own`)
}

View file

@ -145,6 +145,18 @@ export function galleryModel(reg) {
}
}
// ── ONE PART OF A LEAF URL ───────────────────────────────────────────────────────────────────
// /__leaf/<bundle>/<profile>[/<instance>] parts are written INTO the leaf page: its <title> and the
// element's profile-config='…' attribute. Refusing only `..` and `/` let a quote or a tag through, so a
// crafted link broke out of the attribute on a publicly reachable dev server. Every bundle folder,
// profile id and instance id in the tree has this shape (the address keys' own pattern,
// library/composables/useViewAddress.js), so anything else is refused before it reaches either sink;
// it also rules out `..` and `/`. check:dev-leaf-escape proves both directions.
export const LEAF_PART = /^[A-Za-z0-9][\w-]{0,63}$/
// For an HTML text node or a quoted attribute value.
export const escHtml = (s) => String(s).replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[c])
// ── THE PRODUCTION EMBED URL, ANSWERED BY THE DEV SERVER ─────────────────────────────────────
// The manifest advertises, and a person copies, tree-relative embed URLs of the shape
// `<bundle>/<profile>/index.html` and `<bundle>/<profile>/<instance>/index.html` — served in
@ -199,8 +211,8 @@ export function leafRewrite(path, treeName = TREE_NAME) {
// urlOwner 'app' (library/composables/useViewAddress.js) for a dev page that is OURS, the rule generate-tree bakes
// into the built leaf: its shell writes ?tab=, so a clicked tab survives a reload and a copied link opens it. A
// FRAMED page drops it: Plugin Studio iframes its map rows from /__leaf/, and a history push from an iframe lands
// in the top page's Back stack. Write the element unowned and put this classic script right after it; it runs
// FRAMED page drops it: a partner page may iframe a leaf (the iframe embed), and a history push from an iframe
// lands in the top page's Back stack. (Plugin Studio mounts its rows directly; its panes are unowned by paneConfig.) Write the element unowned and put this classic script right after it; it runs
// before the deferred module script upgrades the element. `<` is escaped: profile/instance come from the URL.
export function ownIfTopScript(cfg = {}) {
const owned = JSON.stringify(JSON.stringify({ ...cfg, urlOwner: 'app' })).replace(/</g, '\\u003c')
@ -347,8 +359,8 @@ export function autoSpaDevPlugin(token = '') {
try { return decodeURIComponent(s) } catch { return '' }
})
const [bundle, profile, instance] = parts
// Path traversal guard: these land in a filesystem-ish URL below.
if (!bundle || !profile || parts.some(s => !s || s.includes('..') || s.includes('/'))) {
// These land in a filesystem-ish URL and in the page's HTML below: LEAF_PART shapes only.
if (!bundle || !profile || parts.some(s => !LEAF_PART.test(s))) {
res.statusCode = 400; return res.end('bad leaf path')
}
let reg
@ -387,7 +399,7 @@ export function autoSpaDevPlugin(token = '') {
res.setHeader('Cache-Control', 'no-cache')
return res.end([
'<!DOCTYPE html>',
`<html lang="en"><head><meta charset="UTF-8" /><title>${bundle} · ${profile}</title>`,
`<html lang="en"><head><meta charset="UTF-8" /><title>${escHtml(bundle)} · ${escHtml(profile)}</title>`,
'<meta name="viewport" content="width=device-width, initial-scale=1.0" />',
'<link rel="icon" href="data:,">',
leafTok ? `<script>window.MAP_TOKEN=${JSON.stringify(leafTok)};</script>` : '',

View file

@ -69,6 +69,8 @@
"check:describe-group": "node internal/check-describe-group.mjs",
"check:descriptions": "node internal/check-profile-descriptions.mjs",
"check:dev-assets": "node internal/check-dev-assets.mjs",
"check:dev-leaf-escape": "node internal/check-dev-leaf-escape.mjs",
"check:dev-leaf-escape:self-test": "node internal/check-dev-leaf-escape.mjs --self-test",
"check:doc-scripts": "node internal/check-doc-quotes-scripts.mjs",
"check:readme-size": "node internal/check-readme-web-folder-size.mjs",
"check:doc-scripts:self-test": "node internal/check-doc-quotes-scripts.mjs --self-test",
@ -359,7 +361,7 @@
"fix:sri": "node internal/fix-sri.mjs",
"gallery": "node internal/generate-gallery.mjs",
"gates:post": "node internal/run-parallel.mjs --label=post -- check:capability-derived check:drop check:parse verify check:sri check:public-hygiene check:label-joins check:facet-cascade check:facet-one-field check:heartbeat check:kpi-unit check:composite-reg check:descriptions check:no-abs-paths check:no-codename check:dep-names check:composite-srcs check:embed-targets check:nested-token check:i18n-parity check:tabs-localized check:packets-all check:packet-strings-parity check:zone-census check:zoning-declared check:share-policy check:library-index check:csv-shape check:capability-citations check:catalog-identity check:no-orphan-gates check:quoted-scripts check:script-flags check:capability-exemplars check:packet-fixture check:packet-browser check:provider-switch check:locale-reachable check:url-resolver check:manifest-covers check:one-palette check:pin-dim-slider check:legend-source check:clustering-modes check:composite-embed-knobs check:composite-locale-leaks check:linked-vocabulary check:wp-embed check:dashboard-geocoder-provider check:leaf-engine-per-provider check:auto-engine check:engine-contract check:map-props-unit check:composites-mount check:composite-interop check:no-free-identifiers check:map-fly-reduced-motion check:mst-clustering check:list-prop-unit check:profiles-mount check:cdn-pins check:no-eval check:popup-escaping check:i18n-treeshake check:pinned-umd check:one-palette:self-test check:no-cross-plugin-imports check:no-cross-plugin-imports:self-test check:map-attribution check:studio-rail check:approx-pin-note check:embedder-keys check:provider-for check:legend-logical-insets check:legend-logical-insets:self-test check:optional-t-equivalence check:text-highlight check:religion-label-not-value check:i18n-stopped-scope check:no-silent-engine check:no-silent-engine:self-test check:regions-uses-library check:shadow-css-parses check:square-map-controls check:preset-config-merge check:instance-config-keys check:frozen-labels check:plural-forms check:region-fill-repaint check:region-paint-single-source check:poster-print-fallback check:posters-exportable check:country-names check:reduced-motion check:no-dead-library check:universal-props check:readme-size check:tree-relative check:leaf-hints check:snippet-engine check:snippet-engine:self-test check:embed-base check:link-coverage check:no-public-binds check:shared-reached check:tree-weight check:no-fleet-names:self-test check:pin-hint-floor:self-test check:poster-sizes:self-test check:shell-header:self-test check:preview-flag-dev-only:self-test check:poster-type-registry:self-test check:host-floor:self-test check:phone-print-keep:self-test check:pinned-fetch-fresh:self-test",
"gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:doc-scripts check:setup-order check:package-json-unique-keys check:no-repo-pb-hooks check:no-credentials-omit check:no-credentials-omit:self-test check:shadow-reduced-motion check:poster-action-print check:targets check:targets:self-test check:browser-gates-guarded check:pin-selection check:facet-shape check:hardcoded-english:self-test check:list-selection check:selector-fields check:upg-zone-joins check:gospel-set-charts check:gospel-set-fit check:describe-group check:one-scroll-container check:packet-charts check:pins-in-state check:fork-leaders check:circular-layout check:composite-token-url check:language-tree-layout check:i18n-keys-exist check:i18n-never-translated check:packet-variants check:profile-table check:element-bootstrap check:embed-url-absolute check:frame-target check:language-tree-model check:map-menu-anchor check:selection-kinds check:tiered-points check:whiteboard-projection check:mercator-slice check:ring-vocabulary check:map-props-unit check:mobile-gating check:provide-shadowing check:legend-top-offset check:cdn-cache check:plural-forms check:select-zone-race check:script-registry check:map-control-overlap check:poster-attribution check:board-map-options check:gospel-whiteboard check:packet-map-missing-loud check:poster-layout-optin check:ported-globals-identical check:ring-bands check:packet-a1-structure check:india-states check:batch-selection-export check:zone-title-tier-a check:evangelical-share check:map-labels check:packet-offline check:help-reachable check:gated-not-in-tree check:window-globals-released check:legend-data-contract check:no-internal-role-nouns check:poster-fetch-guard check:people-group-records check:sentence-slots check:popup-href-escaped check:popup-pan-controls check:country-map-refetch check:pane-config-reforward check:viewport-meta check:viewport-meta:self-test check:label-key-coverage check:poster-browser-equiv check:poster-render-pinned check:country-sheet-one-path check:country-sheet-one-path:self-test check:side-lanes check:side-lanes:self-test check:poster-section check:poster-section:self-test check:posterverse-shelf check:host-locale-fetch check:print-frame-reclaim check:click-sheet check:coverage-consistent:chained check:coverage-consistent:poison check:dashboard-filter-a11y check:filter-multivalue check:hover-channel check:kit-options-reachable check:layout-key-namespaced check:legend-mirrors check:map-unmount-race check:no-conflict-markers check:series-isolation check:snapshot-guard check:stale-sweep-claim check:surfaces-read-the-tier check:translator-contacts check:wall-wysiwyg check:wall-print-watch check:wall-no-bg-photos check:wall-pan check:template-i18n check:list-pattern-doc check:shadow-style-labels-unique check:grid-image-loading check:retry-fetch check:panel-sheets check:poster-wall-host-seams check:poster-sections check:poster-sections:self-test check:poster-design-catalogue check:poster-design-catalogue:self-test check:upg100-zone-copy check:upg100-zone-copy:self-test check:wall-photo-weight check:no-fleet-names check:pin-hint-floor check:poster-sizes check:upg-count check:shell-header check:pg-name-gate check:preview-flag-dev-only check:poster-type-registry check:host-floor check:phone-print-keep check:pinned-fetch-fresh check:view-address check:view-address:self-test",
"gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:doc-scripts check:setup-order check:package-json-unique-keys check:no-repo-pb-hooks check:no-credentials-omit check:no-credentials-omit:self-test check:shadow-reduced-motion check:poster-action-print check:targets check:targets:self-test check:browser-gates-guarded check:pin-selection check:facet-shape check:hardcoded-english:self-test check:list-selection check:selector-fields check:upg-zone-joins check:gospel-set-charts check:gospel-set-fit check:describe-group check:one-scroll-container check:packet-charts check:pins-in-state check:fork-leaders check:circular-layout check:composite-token-url check:language-tree-layout check:i18n-keys-exist check:i18n-never-translated check:packet-variants check:profile-table check:element-bootstrap check:embed-url-absolute check:frame-target check:language-tree-model check:map-menu-anchor check:selection-kinds check:tiered-points check:whiteboard-projection check:mercator-slice check:ring-vocabulary check:map-props-unit check:mobile-gating check:provide-shadowing check:legend-top-offset check:cdn-cache check:plural-forms check:select-zone-race check:script-registry check:map-control-overlap check:poster-attribution check:board-map-options check:gospel-whiteboard check:packet-map-missing-loud check:poster-layout-optin check:ported-globals-identical check:ring-bands check:packet-a1-structure check:india-states check:batch-selection-export check:zone-title-tier-a check:evangelical-share check:map-labels check:packet-offline check:help-reachable check:gated-not-in-tree check:window-globals-released check:legend-data-contract check:no-internal-role-nouns check:poster-fetch-guard check:people-group-records check:sentence-slots check:popup-href-escaped check:popup-pan-controls check:country-map-refetch check:pane-config-reforward check:viewport-meta check:viewport-meta:self-test check:label-key-coverage check:poster-browser-equiv check:poster-render-pinned check:country-sheet-one-path check:country-sheet-one-path:self-test check:side-lanes check:side-lanes:self-test check:poster-section check:poster-section:self-test check:posterverse-shelf check:host-locale-fetch check:print-frame-reclaim check:click-sheet check:coverage-consistent:chained check:coverage-consistent:poison check:dashboard-filter-a11y check:filter-multivalue check:hover-channel check:kit-options-reachable check:layout-key-namespaced check:legend-mirrors check:map-unmount-race check:no-conflict-markers check:series-isolation check:snapshot-guard check:stale-sweep-claim check:surfaces-read-the-tier check:translator-contacts check:wall-wysiwyg check:wall-print-watch check:wall-no-bg-photos check:wall-pan check:template-i18n check:list-pattern-doc check:shadow-style-labels-unique check:grid-image-loading check:retry-fetch check:panel-sheets check:poster-wall-host-seams check:poster-sections check:poster-sections:self-test check:poster-design-catalogue check:poster-design-catalogue:self-test check:upg100-zone-copy check:upg100-zone-copy:self-test check:wall-photo-weight check:no-fleet-names check:pin-hint-floor check:poster-sizes check:upg-count check:shell-header check:pg-name-gate check:preview-flag-dev-only check:poster-type-registry check:host-floor check:phone-print-keep check:pinned-fetch-fresh check:view-address check:view-address:self-test check:dev-leaf-escape check:dev-leaf-escape:self-test",
"gen:iso3-to-alpha2": "node library/scripts/generate-iso3-to-alpha2.mjs",
"internet:adm1": "node plugin-designs/cities-internet/scripts/build-adm1-internet.mjs",
"library-index": "node internal/generate-library-index.mjs",
@ -505,6 +507,8 @@
"check:window-globals-released": "a component that puts something on window must take it back. Heap snapshots (leak-722ac7789) measured SR+APG mounted twice at 54.2 -> 65.1 MB with live Vue apps 6 -> 8: every unmounted app stayed in memory, each pinned by one window key holding a closure, which keeps the component, its shadow root and the whole app. Flags only values that RETAIN (closures and object references), not warn-once latches or page-wide caches keyed off themselves. NOT in gates:pre: 19 sites remain across other owners' plugins. Run it directly.",
"check:describe-group": "the prayer packet's generated profile sentence (library/packets/describeGroup.js): each GSEC level states its own fact, and an index-ordered language name (\"Arabic, Moroccan\") is un-inverted by library/utils/languageLabel.js without shredding a comma inside parentheses, the 13 names the old split printed reversed. A catalogue arm renders every GSEC level from the REAL packet-strings.json in en and ar and fails on a blank sentence. Headless; in gates:pre.",
"check:descriptions": "every shipped profile must say what it is (reads the BUILT manifest)",
"check:dev-leaf-escape": "a dev leaf URL cannot write markup into the page it opens. The dev server's /__leaf/<bundle>/<profile>[/<instance>] route (and a production embed URL rewritten onto it) writes the URL's parts into the page's <title> and the element's profile-config attribute; it refused only .. and /, so a quote or a tag broke out of the attribute on a publicly reachable dev server. internal/plugin-gallery/dev-plugin.mjs refuses any part outside LEAF_PART and escapes the <title>. Drives the plugin's own middleware (no server): every leaf the dev manifest advertises is served in both URL forms; 12 hostile parts are refused in every position; escHtml escapes & < > \" '; and behind the OLD rule the <title> still escapes. Bodies are never printed (a leaf page carries the token). Sub-5s.",
"check:dev-leaf-escape:self-test": "4 poisons must each make check:dev-leaf-escape fail: the old guard (.. and / only), a part rule too strict for a real profile, an unescaped <title>, an escHtml that misses the quote.",
"check:element-census-vs-live": "what is on ONE side and not the other, ours vs the live doxa.life page we clone: harvests every visible TEXT NODE through every shadow root on both sides and diffs the SETS. OURS-ONLY unaccounted = a scope violation (FAIL), THEIRS-ONLY = a clone omission (FAIL), SANCTIONED = an allowlist entry carrying the ruling + date + card (refused without them), DATA DRIFT = same label, different digits (reported). The negative control is MANDATORY: a known ours-only string must land in the ours-only set or it prints CENSUS IS BLIND and exits 75; an unreachable live page is also 75. Presets: internal/element-census/<name>.json (--preset <name>, default all); ad hoc: --leaf <bundle/profile/index.html[#hash]> --live <url> --neg <text> [--sanctioned <file>]. Promoted from regions' probe; regions passes (list 229 vs 228, the only difference its sanctioned poster toggle). Through browser-slot.sh.",
"check:embed-base": "the shared URL resolver, pinned by ARITHMETIC — no browser, no network, no served tree, so it cannot SKIP itself into irrelevance. It computes what a leaf's own \"../../assets\" means and asserts the resolver agrees. Caught nothing for fifteen ticks because it did not exist: resolveAssetsBase computed one segment too high, every local gate passed (the dev web-root has assets BOTH inside and beside the tree), and the byte-level publish gates went green on the broken publish.",
"check:embed-basemap": "the gospel map's BASEMAP must paint inside a genuinely cross-origin iframe on the token the page BAKES, not one a probe forces on it. Every other browser gate here forces a restricted token (u=doxalife) that 403s on the basemap, then counts PINS — which come from our own GeoJSON and paint with no token at all — so 'the map renders' had never been measured. Runs a negative control every time by rewriting the token on the wire; if the control passes it reports PROVED NOTHING rather than a pass. ~60s, needs a browser.",