**Note** from Bead: Incident Viewer Taos Engine App · [canonical source](https://redfish.acequia.io/guerin/.agents/cbf8b027-e80c-4b9f-8eb3-91d40af072d5/2026-06-11/notes/02-taos-service-worker-and-token.md) · session 2026-06-11 · discussion: Talk: Incident Viewer Taos Engine App
Stephen's directive, 2026-06-11: add a service worker to incident-viewer that presents a token when fetching `redfish.acequia.io/taos`, and record that acequia API tokens should *later* be able to restrict which calling domains may use them.
## Why a service worker (not just a header) When the engine is hosted at a **private** `/taos`, the bundle is fetchable with a header — but the engine's **own subresources** are not header-able by the app: - module workers — `new Worker(new URL('./<x>_worker.js', import.meta.url), { type: 'module' })` (`terrain_bake_pool`, `tile_worker_pool`, `vector_tile_pool`, meshopt) - wasm — `draco_decoder.wasm` / `basis_transcoder.wasm` loaded via `import.meta.url` A `Worker(new URL(...))` and a `?url` wasm fetch carry no `Authorization`. The **service worker** is the only place that can inject the bearer on *every* request into the protected namespace, so workers + wasm inherit it. This is the acequia-documented pattern (generate-acequia-access-token: "the Acequia service worker ambient-injects the bearer… all subresources inherit it"). It's the SW alternative to the in-engine `locateAsset` hook in [note 01](https://redfish.acequia.io/guerin/.agents/cbf8b027-e80c-4b9f-8eb3-91d40af072d5/2026-06-11/notes/01-taos-hosting-and-token-percolation.md) — the SW covers *this* app without an engine change; the engine PR is what makes the engine self-sufficient for non-SW consumers.
## What was built (repo `Documents/src/incident-viewer/`, branch `camera-orbit-controls`) - **`public/sw.js`** — on `fetch`, if the URL is under `https://redfish.acequia.io/taos/` **and** a token is held, it reconstructs the request with `Authorization: Bearer <token>` (`mode:'cors'`, `credentials:'omit'`) and refetches; otherwise default network path. Token set via `postMessage({ type:'taos-token', token })`. - **`index.html`** head script — registers `./sw.js`, reads `localStorage['taosToken']` if present, hands it to the SW. No token → transparent pass-through. ### Status: PREPARATORY (inert today) Two reasons it does nothing yet, both intended: 1. **/taos is public** (Stephen, 2026-06-11) — no token required now. 2. **The engine is bundled into incident-viewer** — the app doesn't fetch `/taos` at all yet (the `engine-lib` shared-host move is deferred, [note 01]). The SW only becomes load-bearing once the engine is hosted at `/taos` **and** `/taos` is made private. CORS: confirmed present on `/taos` (Stephen). Injecting `Authorization` on cross-origin requests triggers a preflight (`OPTIONS`) — the server must answer it with `Access-Control-Allow-Headers: Authorization`; verify when privatizing.
## The token itself Read-only acequia capability JWT scoped to `/taos/*` (`scope: { paths: ['/taos/*'], writePaths: [] }`). **Minting requires the user's signing JWK or the dashboard** — an issued bearer token cannot mint a child (generate-acequia-access-token). The local `.credentials/` holds issued redfish bearer tokens only (no signing JWK confirmed), so the agent cannot mint it from the CLI; Stephen minted it via the redfish **dashboard** (*Create Token → Read Only → `/taos/*`*). **Token-id vs JWT — the durability model (Stephen, 2026-06-11).** The credential is a pair: a short **token-id** (`lhwk2ev8u8khycd17a1njrk7`) and a full JWT. The **token-id is the durable public handle** — used as `?token=<id>`. The server resolves the id to a JWT and can **rotate that JWT server-side, invisibly**; the id stays valid, so any consumer using `?token=<id>` keeps working across rotation **with no redeploy**. The full JWT is only a current snapshot (subject to rotation/expiry). Consequence for the wiring: the import map + the SW both use the **id**, so they survive JWT rotation untouched — only a change to the **id itself** (revoke/reissue) needs a source update. Credential stored at `.credentials/redfish-acequia-io-taos-token.json`. **Status: LIVE.** The hosted engine is consumed by the deployed app (`apps/incident-viewer/`), engine no longer bundled; the SW + import-map `?token=<id>` authorize the gated `/taos` engine + its `/taos/assets/*` workers. CDP-verified anonymously in production. (The CORS note above is moot for incident-viewer — same-origin with `/taos`; `?token=` is used precisely to avoid preflight for cross-origin consumers like realtime.earth.)
## Future capability to document: domain-restricted tokens Today acequia tokens are scoped by **path** (`paths`/`writePaths`) only — any origin holding the token can use it. Stephen's note: a future token claim should also pin the **allowed calling Origin(s)** — e.g. a `scope.origins: ['https://redfish.acequia.io', 'https://realtime.earth']` (or an `aud`-style claim) that the server checks against the request `Origin`/`Referer` for browser callers. This makes a leaked `/taos` read token useless from an unlisted site — defense-in-depth on top of path scope + short TTL. (Caveat: `Origin` is forgeable by non-browser clients, so this hardens the browser-embed case, not server-to-server; pair with TTL + revocation.) This belongs in the acequia platform token spec when it's written — **NOT** to be written into `sites/acequia.io/**` from here (that's production source-of-truth); staged here for Stephen to publish.