# Hermodr Hermodr is the Eve browser lowering. It resolves providers through Odin's catalog, reads those providers for their state, renders their surfaces to browsers, and relays operator intent back as typed CultMesh command documents. It owns the browser edge. It does not own truth. ## Authority - **Providers** own their state and serve it. Hermodr reads; it never accepts. - **Odin** owns discovery: what exists, what schema speaks it, where to reach it. Odin is not in the data path, and nothing here reads state *from* Odin. - **Hermodr** owns session, transport, lowering, and the relay of intent. Everything it holds about provider state is a projection carrying a sequence number. Its staleness marks are display facts, not withdrawal decisions. A lowering runtime cannot make something true by rendering it. ## The round trip Intent leaves by `POST /hermodr/commands/eve`, which resolves a `cultmesh://` target from the request or the provider catalog, wraps the body as `gamecult.eve.command.v1`, stamps `publishedBy: "hermodr-browser-lowering"`, and publishes to the provider's own advertised route. The provider decides whether to act. Eve's client refuses any response that is not a correlated `gamecult.eve.command_receipt.v1` in state `reconciled`. Consequence returns as newly published provider state, by the same subscription as every other change. That is the whole point of the split: intent and consequence travel the same road in opposite directions, and neither passes through a store Hermodr owns. ## Running it ```bash npm start -- --host 127.0.0.1 --port 8798 ``` Needs a reachable Odin catalog and `CULTLIB_ROOT` pointing at a CultLib checkout. Provider read cadence is `--provider-poll-interval-ms`, default 2000. ```bash npm test # unit tests, no live Verse required npm run check ``` ## Static lowering The live lowering binds to a provider and updates, which is right for an operator dashboard and structurally unavailable to anyone else: an anonymous visitor to a public page is not a Verse participant and never will be. Rendering a surface ahead of time removes the mesh from the delivery path, so the page becomes bytes any web server can hand out — no mesh, no runtime, no daemon. ```bash npm run lower -- surface.json --out page.html cat surface.json | node src/lower-surface.cjs > page.html ``` It renders through the same `renderEveSurface` the browser uses, under jsdom, rather than through a second emitter walking the same graph. A separate emitter would drift, and the drift would stay invisible until someone compared two pages side by side. **Controls that would publish a command are rendered disabled and labelled.** A static page has no back-channel; a button that looks live is lying about one. They are marked rather than deleted, because deleting them would misrepresent the surface — the capability exists, it just is not reachable from a snapshot. jsdom is needed only for this, and only at build time. It resolves from Hermodr's own install if present, otherwise from the Eve checkout, which already carries it for the renderer's own tests. ## Known shape Provider state is read by polling, wrapped in lifecycle semantics that emit snapshot, update, and withdrawn events with sequence numbers. Polling is a first cut: CultMesh ships `cultMeshPollingDocumentWatcher` for exactly this shape, and a push subscription belongs here eventually. It does not change the authority, which is why the swap was made before the move. One invariant is easy to get wrong and quiet when you do: **a failed read is not a withdrawal.** A provider restart or a dropped packet must not render as the provider retracting state it still holds. Read errors are reported and the previous value stands; only genuine absence withdraws. Both paths are tested. ## History Hermodr was an organ of Odin, and until recently it ran its own provider-session ingress and accepted publications into a private store. A surface reached a browser only if its provider bound to Hermodr specifically, so a provider had to publish twice: once where the Verse expected it, and once into Hermodr so an operator could watch their own command land. That ingress is gone. The extraction happened after it was removed, so what moved here was a bridge rather than a second acceptance authority wearing one's name. `GameCult/Odin` holds the coordinator and the Rust organs. `GameCult/CultLib` holds the swarm document catalog and the CultNet transports this depends on.