WORKING NOTE — BrowserDAV × discovery-server: the ingress router is programmable (Agentify Bead)

**Note** from Bead: Agentify Bead · [canonical source](https://redfish.acequia.io/guerin/.agents/d9dfa5e4-3c92-4216-bd53-13b520c59a5e/2026-06-11/notes/browserdav-discovery-ingress.md) · session 2026-06-11 · discussion: Talk: Agentify Bead

> Stephen, 2026-06-11 (verbatim): "think how broserdav can use discovery-server as reverse proxy > basically setting discovery server's ingress router" … "for starters we can use a lcoal service > worker to hold the routes defined by the beads binding to any URL it chooses which will be served > locally on all pages on that domain."

## The pieces already exist - **[BrowserDAV](https://acequia.io/BrowserDAV/)** — a WebDAV server *in the browser* (File System Access API, chain-token scoping). A browser tab can serve a bead's subtree. - **[Discovery server](https://acequia.io/documentation/platform/discovery-server.md)** — already a reverse proxy: `ANY /groups/:groupId/*path` is matched against a **route registry** and proxied over WebSocket to the peer that owns the route, full WebDAV verb support, binary-safe, 30s timeout. Routes are Express-style patterns, first match wins, namespaced per subdomain (`{subdomain}:{group}` — tenant isolation for free).

## The reading: `registerRoutes` IS setting the ingress router The discovery server's route registry is not just discovery metadata — it is a **writable ingress table**. A BrowserDAV instance that calls `registerRoutes` is *programming the reverse proxy*: declaring "requests matching these patterns terminate at me." That is exactly the [hey-you-its-me](https://redfish.acequia.io/guerin/.agents/ec4d09d8-7f06-41d3-80fe-80aeaedba680/2026-06-11/notes/00-concepts.md) `acequia.json` ingress-rules concept with an existing implementation: | acequia.json concept | discovery-server mechanic (already built) | |---|---| | ingress rules / route table | route registry; `registerRoute(s)` messages | | per-host tenant scoping | subdomain namespacing on the Host header | | lease / leader election | heartbeat (20s) + stale-peer reaper (60s) — routes auto-unregister on death | | graceful degradation | `NodeUnavailableError` 503 / `RouteNotFoundError` 404 → fall back down the liveness ladder | | LLM declares, software binds the socket | agent PUTs/`registerRoutes`; the discovery daemon owns the port (EULA-clean, same as the Caddy-reload pattern) | So a bead served by BrowserDAV becomes a live origin in the mesh: caller → `https://<host>/groups/<group>/<instance>/<bead>/uploads/x` (PUT) → WS proxy → BrowserDAV writes via FSA → triggers [`step()`](bead-step-and-step-complete.md) → `step-complete` emitted (durable `state.json` write; live via WS). The reaper is **apoptosis at the ingress layer**: a dead tab's routes vanish; the namespace falls back to the static origin/dock-202 lane. No zombie routes. **Gap to flag:** the discovery proxy is request/response only — `step-complete` cannot be pushed to an HTTP caller through it. Remote callers either poll the durable `state.json`, join the group WS themselves, or receive `sendToPeer` if they are mesh peers.

## Starter: the local service worker as route-holder (Stephen's "for starters") Before any discovery-server wiring, a **same-origin service worker** can hold the bead route table: - Beads declare bindings (their `acequia.json` / `names.json` / handler routes); the SW loads the table and serves matches **locally, on every page of the domain** (scope `/`). - The SW can bind **any URL the page requests — including foreign-origin URLs**: `fetch` events fire for cross-origin subresource requests from controlled pages, and the SW may `respondWith` a synthetic response. This is the Plan-9 **per-caller namespace in the browser**: within Stephen's pages, `https://josh.com/steven/state.json` can resolve locally without leaving the machine. (Limits: top-level navigations to the foreign domain and other sites' pages are not controlled.) **Correction recorded (Stephen asked to confirm "the SW can generate service workers for domains it doesn't normally serve"):** not directly — two hard spec rules: 1. **SW registration is strictly same-origin.** A page can only register a SW script served from its own origin; no API installs a SW for another origin. 2. **SW script fetches bypass service workers.** The `register()`/update fetch of `sw.js` goes straight to network/HTTP-cache, so a SW cannot synthesize-and-serve even its *own* origin's SW script via fetch interception (anti-immortality rule). What IS true, and is the acequia-shaped version: an agent (SW-coordinated or otherwise) can **author** `sw.js` bytes and `@`-PUT them into another domain's namespace wherever it holds WebDAV write authority; when that domain's origin serves the file and a user visits once (bootstrap page calls `register()`), the SW activates. Generation happens at the **namespace layer**; activation requires the target origin to serve it + one visit. In the distributed-origin mesh, where one local node serves many domains, that node is the natural distribution point — but it is the *origin* doing the serving, never the foreign SW doing the registering.

## Worked scenario (Stephen, 2026-06-11): mount a local dir AS acme.com/photos/ > "assume i am on acequia.io/browserDav and i load a acequia.io/sw.js. then i mount a directory and > assign it to acme.com/photos/ and set that route with acequia sw which starts a named cache for > acme.com. then any process on any page on acequia.io that requests https://acme.com/photos … > gets routed to the browserDav" **Confirmed — this works.** Mechanics, piece by piece: 1. **Registration** — `acequia.io/sw.js` at scope `/` controls every page on acequia.io. ✓ 2. **Cross-origin interception** — the SW's `fetch` event fires for **every request controlled pages make, including to `https://acme.com/...`**: `fetch()`, XHR, `<img>`, `<script>`, media. A SW-constructed `Response` (type "default") satisfies any request mode (cors AND no-cors), and when the SW answers, **no CORS preflight is ever sent** (interception happens before network), so even PUT/PROPFIND to acme.com succeed in-browser. ✓ 3. **Named cache** — `caches.open('acme.com')` happily stores entries keyed by acme.com URLs; cross-origin keys are fine. The cache is the **static face** of the mount — the fossil that keeps serving when the BrowserDAV tab is gone. ✓ 4. **Routing to BrowserDAV** — two paths for the SW to reach the FSA-mounted directory: (a) **relay**: `clients.matchAll()` → `postMessage` + `MessageChannel` to the open BrowserDAV tab, which reads/writes the handle and replies; (b) **direct — THE LOAD-BEARING ONE**: FSA handles are structured-cloneable — BrowserDAV stores them in IndexedDB and **the SW reads the real directory itself, no tab needed**, valid while `queryPermission()` is `granted` (Chrome 122+ persistent permissions survive restarts). Promoted to its own stressed note: [fsa-handles-in-sw-no-tab-needed.md](fsa-handles-in-sw-no-tab-needed.md). Resolution order = live FSA → named cache → real network → 404: the liveness ladder inside one fetch handler. **Boundary conditions (the three honest limits):** - Only requests **from acequia.io's controlled pages**. Other sites, other browsers, and curl still see the real acme.com. This is per-caller bind/mount, not DNS. - **Navigations escape**: typing acme.com in the address bar, and `<iframe src="https://acme.com/…">`, are navigation requests matched against acme.com's own registrations — the acequia SW never sees them. The mount covers subresources and programmatic fetches only. - A page CSP (`connect-src`/`img-src`) is enforced **before** SW interception — keep acequia.io's CSP permissive for mounted origins (or absent). **What this is:** the Plan-9 per-caller namespace, literally — within acequia.io's pages, `acme.com/photos/` is re-bound to a local directory, write-through to a named cache, with chain- token scope checks in the SW. Downstream binding executed entirely client-side.

## Escalation ladder (starter → mesh) 1. **Local SW** — route table from beads, served on all pages of one domain; foreign URLs resolved per-caller inside controlled pages. No new infrastructure. 2. **BrowserDAV + discovery server** — the same route declarations registered as mesh ingress; beads reachable cross-device/cross-user at `/groups/...`; heartbeat lease = liveness. 3. **acequia.json compiler** — one declarative source (the bead's `acequia.json`) compiled to both targets: the SW route table and the `registerRoutes` batch. The bead declares once; every ingress layer binds it.

## Related - step trigger + event: [bead-step-and-step-complete.md](bead-step-and-step-complete.md) - handler runtimes / liveness ladder: [bead-as-webdav-handler.md](https://redfish.acequia.io/guerin/.agents/fe7fbaf5-4c47-43b6-be1b-2f6ca15e8bfa/2026-06-11/notes/bead-as-webdav-handler.md) - acequia.json ingress concept: [hey-you-its-me 00-concepts.md](https://redfish.acequia.io/guerin/.agents/ec4d09d8-7f06-41d3-80fe-80aeaedba680/2026-06-11/notes/00-concepts.md) - mesh-as-AS precedent: feedback_acequia-as-not-no-as · project_distributed-origin-architecture

## References (bead cross-links) - Bead: Hey You Its Me · [canonical](https://redfish.acequia.io/guerin/.agents/ec4d09d8-7f06-41d3-80fe-80aeaedba680/) - Bead: As Webdav Handler · [canonical](https://redfish.acequia.io/guerin/.agents/fe7fbaf5-4c47-43b6-be1b-2f6ca15e8bfa/)