doxa-maps/library/composables/useHostTokenUrl.js
Doxa Maps 1aa9a0ac99 A composite embedded bare dropped tokenUrl, and passed because a ProfileLoader sat above it
PM-tok, PRODUCTION-MATRIX.md:285. `tokenUrl` names a token SERVICE a host points at
instead of pasting a key: the element asks that URL when it was handed no token. Only
ProfileLoader ever read it. A pane composite bakes its children in rather than mounting
them through ProfileLoader, so a composite embedded BARE -- one tag on a partner's page,
which is the entire point of a composite -- dropped a tokenUrl-only config on the floor.

It worked in every place anyone looked, because a ProfileLoader elsewhere on the page
fetched and published the token: the composite was passing an exam somebody else sat.
And the failure wears the wrong face. No token is "No Mapbox access token provided",
0 tiles, no canvas -- identical to a missing `tk`, which IS threaded -- so the natural
reading is that the host mistyped their config rather than that a knob is unread.

The rule lives in library/composables, not in each composite, because it has already
been copied once (ProfileLoader.vue:132) and a third and fourth copy is how one of them
later learns to ask ALWAYS -- which a token service billed per call notices before we do.
The pure decision is split into host-token-url.js with NO framework import, because a
fresh worktree has no node_modules and a module importing vue is unloadable by the gate
that proves it right. Same shape as describeGroup keeping countryName injectable.

check-composite-token-url takes its expectation from ProfileLoader -- the contract a
composite REPLACES -- not from the composites it audits, so it cannot pass by
construction; it exercises the decision rather than grepping for it; and it finds its
subjects by a PROPERTY, a production bundle already forwarding `tk: hostToken`, so a
third composite is covered the day it is written. stack-demo has the same shape and is
excluded by its STAGE, not by name, so promoting it pulls it in automatically.

Negative-tested 5 ways. The fifth found a bug in the gate itself: /tokenUrl/ also matches
`tokenUrlX`, so the check meant to notice the contract being renamed could not fail. It
is \btokenUrl\b now. A guard that cannot fail is the one nobody ever finds by reading.
2026-09-28 15:52:47 +00:00

44 lines
2.4 KiB
JavaScript

/**
* useHostTokenUrl.js — a composite honours `tokenUrl` the way ProfileLoader does.
*
* THE DEFECT (PM-tok, PRODUCTION-MATRIX.md:285). `tokenUrl` is a token SERVICE a host names in its
* profile-config instead of pasting a key: the element asks that URL when it was handed no token. Only
* ProfileLoader ever read it. A pane composite bakes its children in rather than mounting them through
* ProfileLoader, so a composite embedded BARE — the whole point of a composite, one tag on a partner's
* page — dropped a tokenUrl-only config on the floor. With an outer ProfileLoader on the page it worked,
* because THAT fetched and published the token; the composite was passing an exam somebody else sat.
*
* ★ THE FAILURE IS TOKENLESS MAPBOX, WHICH LOOKS LIKE A DIFFERENT BUG. No token means "No Mapbox access
* token provided", 0 tiles and no canvas — the same symptom as a missing `tk`, which IS threaded, so the
* obvious reading is that the host mis-typed their config rather than that a knob is unread.
*
* ★ AND IT LIVES HERE RATHER THAN IN EACH COMPOSITE because there are two composites today and the rule
* has already been copied once (ProfileLoader.vue:132). A third and fourth copy of "ask only when no
* token is at hand" is how one of them later learns to ask always, and a token service billed per call
* notices that before we do.
*/
import { computed, watch } from 'vue'
import { useSharedMapToken, fetchMapToken } from './useSharedMapToken.js'
import { shouldFetchTokenUrl, readHostTokenUrl } from './host-token-url.js'
export { shouldFetchTokenUrl, readHostTokenUrl }
/**
* Wire a composite's host config to the shared token service.
* @param {import('vue').Ref} hostConfig parsed profile-config of the COMPOSITE
* @param {import('vue').Ref} hostToken the host's own `tk`, which always wins
* @returns {import('vue').ComputedRef<string>} the tokenUrl, to forward into children's profile-config
*/
export function useHostTokenUrl(hostConfig, hostToken) {
const sharedToken = useSharedMapToken()
const tokenUrl = computed(() => readHostTokenUrl(hostConfig?.value))
// immediate, because a host that set tokenUrl before mount is the ordinary case, not the edge one.
watch(tokenUrl, (url) => {
if (shouldFetchTokenUrl({ tokenUrl: url, token: hostToken?.value || '', sharedToken: sharedToken.value })) {
fetchMapToken(url)
}
}, { immediate: true })
return tokenUrl
}
export default useHostTokenUrl