The modes (verbs) of STAC management: an app, or a process? (Stac Manager Nextgen)

**Note** from Bead: Stac Manager Nextgen · [canonical source](https://redfish.acequia.io/guerin/.agents/b73189b7-e605-4473-9789-20bcff57ba35/2026-06-21/notes/03-modes-of-stac-management.md) · session 2026-06-21 · discussion: Talk: Stac Manager Nextgen

> Debbie (UX/UI senior designer), binding [`f80f1929` geospatial-ux-ui](https://redfish.acequia.io/guerin/.agents/f80f1929-57ee-43d6-b6c1-4ef594f5fa8e/). > Method: sketch-ux (Buxton) — each mode below leads with a **design question + one alternative**, not a finished answer.

## The reframe (Stephen, 2026-06-21, verbatim) > *"stac-manager is such a broad topic/function… everything we do with spatial temporal modeling, visualization, annotation, etc manages STACs. There is something deep… about managing links of URIs. Something that the cloud services removed from us hiding our data behind proprietary databases and UIs. A webpage is a node with links with a URI. it can be thought of as a 'catalog of links with metadata'. i say this only to back down from creating a 'stac manager'. stac management may be an emergent process of using our system than a single app."* So I'm backing down from "the STAC Manager" too. A STAC is just the web's oldest primitive made spatiotemporal: **a node of typed links with metadata** — `catalog`/`child`/`item`/`asset` are `<a href>` with a bbox and a datetime. The cloud era hid those links inside a vendor DB and rented them back as a UI. Our ecology already refuses that: URI-as-primitive, namespace composed not reified, resources duck-typed, acequia as a peer mesh over a shared file namespace. **If links are first-class, "managing STACs" is not an app — it's what every app does when it reads, names, and writes links.** Below are the distinct *verbs*. For each: what it is, the design question, where it already emerges, and the smallest shared primitive that makes it work *across* apps.

### 1. FORAGE → NAME-A-SET (produce items, curate a collection) - **What:** crawl an external source for matching items, then *name a subset into a STAC collection*. Foraging is `fetch + normalize`; naming is the curatorial act that turns a query result into a durable, citable node. - **Design question:** is "naming a set" a **saved query** (the collection is a live filter that re-runs) or a **frozen snapshot** (the collection pins exact item URIs at a moment)? *Alt:* a query node that *remembers its provenance* — frozen items **plus** the query that produced them, so it's re-runnable AND reproducible. - **Already emergent:** every forager is half of this and never gets to name the set — [`bd08d6d4` wildcad](https://redfish.acequia.io/guerin/.agents/bd08d6d4-76a2-438e-94a3-82314bd70065/), [`5b518c35` wildfire-forager](https://redfish.acequia.io/guerin/.agents/5b518c35-2ce7-474a-a5a8-ec83e2fd0e82/), [`35e928bc` egp-missions](https://redfish.acequia.io/guerin/.agents/35e928bc-200c-4eb7-8248-e7bca9b07859/), [`58c0a5e0` media-forager](https://redfish.acequia.io/guerin/.agents/58c0a5e0-c64e-426e-8da2-ac7f5495d18a/), [`2986ff41` adsb](https://redfish.acequia.io/guerin/.agents/2986ff41-41ea-4629-bc6b-3ce22f55e628/), [`b3461c66` imagery](https://redfish.acequia.io/guerin/.agents/b3461c66-1a49-4f62-8920-d6dcc9838942/) each emit `item.json`s but stop short of the *naming* gesture; [`90f902e0` catalog](https://redfish.acequia.io/guerin/.agents/90f902e0-2e45-4aa1-b998-918ca3688e94/) is where naming wants to live. - **Minimal primitive:** a **`collection.json` writer + a `storagePut(path,data)`** (note 01's driver `put`). "Name a set" = write a `Collection` whose `links` are the foraged item URIs. That's it — no app, a writer every forager already nearly has. ### 2. BROWSE (read the shelf, drill into space/time) - **What:** walk the catalog-of-catalogs, see identity (count, time-extent, storage, dominant asset glyph), drill into the map/timeline. - **Design question:** is the top-level unit a **catalog** (Shelf, note 00 Sketch 1) or an **item on a shared surface** (Sketch 2)? *Alt:* Shelf as the home/entry, map-first layers as the drill-in workspace — they compose; the real fork is which is the *front door*. - **Already emergent:** `STAC-manager.html` + `stac-loader.js` (catalog-of-catalogs union) + `<stac-layer-tree>`/`<stac-timeline>`; the incident-viewer layer-tree grammar this bead's parent (`f80f1929`) already codified. - **Minimal primitive:** **`driver.list()` + `loadStac` over a Response** (note 01 §2). Browsing is read-only link-walking; if every backend returns a real `Response`, one loader browses all of them. ### 3. ASSEMBLE-FROM-STACS → ROP (selection + composition, scoped in time+space) - **What:** pull items *from several catalogs* into one Relevant Operating Picture answering a task question, bounded by bbox + window (note 02, the Sandy-Fire plume). - **Design question:** is a ROP a **new catalog** (a `Collection` of cross-catalog item links you save and reopen) or a **transient view-state** (a URL with filters, never persisted)? *Alt:* a ROP is a *first-class but thin* node — a `Collection` whose links are **transclusions** (refs into other catalogs, §7) plus a bbox/window — so it's saveable, shareable, and re-derivable without copying assets. - **Already emergent:** note 02 already composes a ROP by hand out of six existing beads (fire-progression, AlertWildfire grid, MISB FMV/KLV, heli+ADS-B, panosphere geometry); the "incident STAC references a subset" idea from the camera-STAC work *is* this verb. - **Minimal primitive:** a **link object that can point into another catalog with a spatiotemporal filter** — `{href, rel:"item", bbox, datetime}`. ROP = a `Collection` of such links. The composition is the catalog; there is no "ROP engine." ### 4. PUB/SUB (active items that update and push in realtime) - **What:** some items are *live* — a camera frame, an ADS-B ping, a perimeter that advances. Subscribed apps get pushed updates, not polled snapshots. - **Design question:** does an item **carry its own liveness** (a `live:true` + a channel URI in its metadata, so subscription is a property of the link) or is liveness a **separate subscription registry** apps consult? *Alt:* liveness as a link `rel` — `rel:"latest"` / `rel:"channel"` — so a live item is *still just a node with links*, one of which is "where my updates arrive." - **Already emergent:** the AlertCalifornia grabber and live `grab-adsb-live.mjs` ([`2986ff41`](https://redfish.acequia.io/guerin/.agents/2986ff41-41ea-4629-bc6b-3ce22f55e628/)) already produce updating items; note 01's `acequia-sw.js` named-cache + OPFS sync + Background Sync (`sync-progress`/`offline-ready` events) is the **transport** that can carry a push; geo.camera's SW background-poll is the seed. - **Minimal primitive:** a **named channel + an event** — reuse the StorageManager `EventTarget` (note 01) and the SW `postMessage` bus. A subscription = "watch `stac::<id>`; on change, emit `item-updated`." The smallest version is poll-behind-the-SW; the link just needs a `rel:"channel"`.

### Modes Stephen didn't name (I'd add these) ### 5. ANNOTATE / AUGMENT (create *new* links — tie-points, notes, derived metadata) - **What:** attach something to an item without owning it: a tie-point, a pose correction, a label, a comment. The annotation is itself a node that *links to* the target. - **Design question:** does an annotation live **inside** the item (mutate its `properties`) or **beside** it as a separate node pointing in (a sidecar that links to the immutable original)? *Alt:* sidecar-by-default (Web Annotation model: a node whose `target` is the item URI) — preserves the original, makes annotations shareable/revocable independently, matches our "dock note stays immutable" instinct. - **Already emergent:** [`fecb418a` tie-points / acequia-links](https://redfish.acequia.io/guerin/.agents/fecb418a-6530-48e0-a6fb-c596c664008e/) is exactly this — a tie-point is a soft link between two URIs (two pixels, or a pixel and a geopoint). The KLV/pose sidecars from the foragers, and the `forage:vo`/`view:azimuth` fields the detail panel already renders, are augmentations. - **Minimal primitive:** an **annotation node = `{target: <uri>, motivation, body}`** + the same `put`. A tie-point is the two-target case. This is the verb that most literally *recovers managing links of URIs* — annotation **is** link-creation. ### 6. TRANSFORM / DERIVE (a new item produced from others, with lineage) - **What:** the time-of-arrival raster from perimeters; the triangulated plume column from camera rays; a mosaic from frames. The output is a new item whose metadata records *what it came from*. - **Design question:** is a derived item a **plain item** (just write the output) or a **recipe node** (links to inputs + the transform, so it can re-run when inputs change)? *Alt:* recipe-node — `derived_from` links + a transform ref — so derivation is a *standing relationship*, not a one-shot file (and live inputs (§4) can trigger re-derivation). - **Already emergent:** `progression-maker.mjs` (perimeters→TOA raster), note 02's ray-intersection (poses→column), taos-engine renders. All produce derived assets today; none record machine-readable lineage. - **Minimal primitive:** the STAC **`derived_from` link rel**. Lineage is, again, just links — no provenance DB. ### 7. REFERENCE / TRANSCLUDE (point into another catalog without copying) - **What:** include an item *by reference*. This is what makes #3 (ROP) cheap and #6 (derive) honest — you compose with pointers, not copies. - **Design question:** when a ROP references an item across a storage location, do we **copy/pin** it (offline-safe, but forks the truth) or **link-only** (always-fresh, but breaks offline)? *Alt:* link-by-default + an explicit "pin for offline" that stages bytes via note 01's OPFS/named-cache **without changing the link's identity** — reference and cache are orthogonal. - **Already emergent:** `stac-loader.js` already unions catalog-of-catalogs by following `child`/`item` *links* (it transcludes already); note 01's mount-table/binding framing (project_uri-bind-mount) is transclusion as namespace composition. - **Minimal primitive:** **absolute-URI links + content-negotiation/PROPFIND** so a referenced node resolves the same from any app. Transclusion needs nothing new — it needs us to *stop copying*. ### 8. ACCESS / SHARE (scope a catalog's visibility) - **What:** who may read/write a catalog or item, per location. - **Design question:** is access a property of the **storage location** (note 01's per-location `auth`) or of the **catalog node** (a `.acequia-access.json` carve-out, like the redfish-simtable-strategy bead)? *Alt:* both, composed — location sets the floor, node can narrow — matching the per-caller namespace. - **Already emergent:** note 01 §6 credential placement; the binary-sidecar visibility model the orchestrator is evolving. - **Minimal primitive:** a **`tokenRef` capability handle + a per-node access doc**. Sharing = handing someone a scoped, attenuated token (note 01, reference_token-minting-pattern). ### 9. VERSION / GC-APOPTOSIS (lineage over time; retire stale items) - **What:** items change and items die. A live camera frame from an hour ago is stale; a superseded perimeter has a successor; OPFS fills up. - **Design question:** is staleness **pushed** (item carries a TTL / `expires`, self-apoptoses) or **pulled** (a mayordomo GCs by policy)? *Alt:* both lanes from MEMORY's apoptosis model — TTL for orderly self-termination, orchestrator GC for necrosis cleanup. - **Already emergent:** note 01's quota/LRU eviction + pinning; the orchestrator's GC role; project_apoptosis-vs-necrosis is the doctrine. - **Minimal primitive:** an **`expires`/`pinned` field + LRU eviction in the SW**. Versioning = a `successor`/`predecessor` link rel. (Again: links.)

## What is irreducible vs per-app If management is emergent, the **irreducible shared substrate is three things every app must speak**: 1. **The link object** — a typed, metadata-bearing pointer: `{href, rel, type, bbox?, datetime?}`. Every verb above is *making, following, or filtering links*. This is the atom; it is already STAC's atom. Forage names them; browse follows them; ROP composes them; annotate/derive/version *create* them. 2. **The storage driver** — `get/list/stat/put` returning a real `Response`, identical across webdav/http/fsa/opfs (note 01 §2). This is what lets a link resolve *the same* from any app and any backend — the recovery of "path is the address, backend is swappable." 3. **A channel/event bus** — the StorageManager `EventTarget` + SW `postMessage` (note 01). This is what makes liveness (#4), re-derivation (#6), and GC (#9) *cross-app* instead of polled-in-one-app. Everything else is **per-app and should stay there**: the *renderers* (a thumb grid vs a track+telemetry chart vs a draped plume vs a perimeter raster — note 02's heterogeneity is real and per-source), the *organizing metaphor* (Shelf vs map-first vs mission-tape, note 00 — chosen per task/story), the *domain logic* (ray intersection, TOA interpolation, KLV normalization), and the *task framing* (a ROP is composed differently for an incident commander vs a desk analyst).

## The meta design-question: is there any app left to build? **My position: no monolith — a small shared library + conventions, and many thin apps.** The "STAC Manager" dissolves into: - a **`links.js`** (read/write/filter the link object + collection/item writers), - a **`storage.js` driver** (note 01, generalizing the EGP `storagePut`), - an **`acequia-sw.js`** channel/cache tier, - and a handful of **`--acq-*`-styled web components** (`<stac-layer-tree>`, `<stac-timeline>`, `<stac-menu>`) that already exist. Every "app" (the Shelf, the ROP, each forager, the incident-viewer) becomes a thin composition over those three primitives. The one thing genuinely worth *building* is not an app but a **convincing worked instance** — the Sandy-Fire plume ROP (note 02) — *built as a thin client over the shared library*, precisely to prove the library is enough and the app is disposable. The Buxton question to settle by sketching, not arguing: **does any verb above actually need state the link object can't carry?** If pub/sub, lineage, and access all reduce to *more link rels + an event*, then Stephen is right — STAC management is emergent, and the deliverable is a library and a set of conventions, not a manager. My bet is that it does reduce. The risk to watch: liveness (#4) and conflict-resolution on write-back (note 01 Q3) are the two places a link-only model strains — sketch those first. > Filed as my professional position: **"STAC management" is a process, not an app.** Build the three primitives and one worked ROP; resist the monolith.

## References (bead cross-links) - Bead: Geospatial Ux Ui · [canonical](https://redfish.acequia.io/guerin/.agents/f80f1929-57ee-43d6-b6c1-4ef594f5fa8e/) - Bead: Wildcad · [canonical](https://redfish.acequia.io/guerin/.agents/bd08d6d4-76a2-438e-94a3-82314bd70065/) - Bead: Wildfire Forager · [canonical](https://redfish.acequia.io/guerin/.agents/5b518c35-2ce7-474a-a5a8-ec83e2fd0e82/) - Bead: Egp Missions · [canonical](https://redfish.acequia.io/guerin/.agents/35e928bc-200c-4eb7-8248-e7bca9b07859/) - Bead: Media Forager · [canonical](https://redfish.acequia.io/guerin/.agents/58c0a5e0-c64e-426e-8da2-ac7f5495d18a/) - Bead: Bead · [canonical](https://redfish.acequia.io/guerin/.agents/2986ff41-41ea-4629-bc6b-3ce22f55e628/) - Bead: Wildfire Imagery Acquisition & Catalog Ops · [canonical](https://redfish.acequia.io/guerin/.agents/b3461c66-1a49-4f62-8920-d6dcc9838942/) - Bead: Catalog · [canonical](https://redfish.acequia.io/guerin/.agents/90f902e0-2e45-4aa1-b998-918ca3688e94/) - Bead: Tie Points · [canonical](https://redfish.acequia.io/guerin/.agents/fecb418a-6530-48e0-a6fb-c596c664008e/)