ReadonlyoriginOrigin in scheme://host[:port] form. Never a substring, never a bare hostname.
ReadonlyrolesReadonlycacheableOptional ReadonlyauthenticatedTrue when requests to this origin carry credentials and must never be cached.
Optional ReadonlyprefetchTrue when this origin's terms allow its resources to be downloaded AHEAD of use — the deliberate offline preparation of a basemap or a tiled layer. Absent means refused.
⚠️ Distinct from cacheable, which only lets a response the page already requested be
kept. Tile providers ask for the first and some forbid the second — OpenStreetMap's
policy: "Offline use is not permitted on tile.openstreetmap.org". Only operate or license
an origin before declaring it here.
Honoured only with cacheable: true; dropped from an authenticated declaration. An
UNDECLARED application origin is prepared without it; once declared, that origin is
judged on this flag like any other.
An origin the profile declares, with what it may serve and whether it is cacheable.
⚠️ This replaces routing by GUESSWORK. The service worker currently decides by hostname substring (
hostname.includes("tile")matchesmon-site-hostile.tilerie.com), by path sniffing, by a hardcoded provider domain, and by a blanket/api/exclusion that skips the most common path of a data API. None of those can be reviewed, and two of them are exploitable.cacheable: falseis meaningful and must stay expressible: a cross-origin tile provider answering opaquely cannot have its content validated, so it is not cached — a correct decision whose CONSEQUENCE (no raster basemap offline) has to be declared rather than discovered.The invariant, and the ONE thing it does not cover
Declaring any origin refuses every origin left undeclared. Silence is a refusal, not a permission — that is what makes a declaration reviewable, and it governs everything below.
⚠️ It does NOT cover the origin that SERVES the application (arbitrated 07/08/2026). That origin changes with every deployment —
localhost:8766on theportstarget,demo.geoleaf.local.testbehind nginx, production elsewhere — so no PORTABLE profile can write it. Before the exception, a profile that declared its data origins lost the cache of its own shell, i.e. offline altogether: all-or-nothing, where "nothing" was only reachable by declaring nothing at all.The exception is narrow, and the narrowness is the point. It covers what serves the application — profile resources and static assets — and NOT same-origin data. A data API served from the very same origin is still a DATA origin, by the exact motive that grants the exception, and it must be declared like any other. Widening it to "same-origin" would cache an authenticated same-origin response by default, i.e. open one defect while closing another.
🛑 So the invariant is not weakened, it is delimited: silence refuses caching of DATA; the application shell is not data. Enforced in
kernel/storage/sw-core.js(routeRequest), which is a standalone worker and cannot import this file — the parity is held by the source guard in__tests__/storage/sw-core.test.js.The offline PREPARATION (
prefetch) reads the same delimitation from the other side, and a declaration prevails. Left undeclared, the application's own origin is prepared — the exception above. Declared, it is judged on its declaration like any other origin (prefetchVerdict,capabilities/offline/data-origins.ts). Roles are not consulted there, so declaring the application's origin for a same-origin data API also decides for the tiles it serves:prefetch: truekeeps them prepared, its absence refuses them.The PULL of a layer's entities (
offline.source.url) obeys a narrower reading of the same invariant: in a profile that declares, a source is pulled from a declared origin or from the application's own, and refused otherwise (pullVerdict, same file; the pull reportsoriginUndeclared).cacheableandprefetchare not read — the layer naming its source is the request, and what a pull writes is thefeaturesstore, not the Service Worker's cache — so anauthenticatedorigin is pullable. A profile that declares nothing pulls as it always did.The preparation presents what the display presents. Every request it makes goes through the page's
fetchwith the browser's defaultcredentialsmode: the cookies of the application's own origin and no other, plus whatever header the host'sfetchadds — the connector'sBearerunder itsbaseUrl, vector tiles and glyphs included.