feat: add native module realtime transport
This commit is contained in:
@@ -12,9 +12,9 @@ transport), and Phase 5C (module database access) have landed. **Phase 5D (secur
|
||||
against an independent security audit) is complete: all five passes have landed and the final
|
||||
independent whole-repository re-audit has closed, under the current trusted-contributor,
|
||||
not-yet-deployed threat model — and Phase 6 is unblocked and underway.** Phase 6 has since
|
||||
completed through 6D, and **Phase 7A passes 1 (the minimal operational module lifecycle), 2 (the module configuration and
|
||||
secrets contract), 3 (host-provided module object storage), and 4 (direct browser object delivery)
|
||||
have landed.** The operational
|
||||
completed through 6D, and **Phase 7A passes 1 (the minimal operational module lifecycle), 2 (the
|
||||
module configuration and secrets contract), 3 (host-provided module object storage), 4 (direct
|
||||
browser object delivery), and 5 (native module realtime transport) have landed.** The operational
|
||||
requirements that remain are prerequisites for two specific *future* activities — executing
|
||||
untrusted CI contributions, and a public production launch — and neither gates Phase 6; see the
|
||||
pass 5 closure below and
|
||||
@@ -340,15 +340,17 @@ GET /api/v1/module-ui → which module UIs may be mounted, and where
|
||||
@ekklesia/module-ui-host → dynamic import, identity check, mount into a host-owned container
|
||||
↓
|
||||
context.api.request("records") → /api/v1/modules/example/records
|
||||
context.realtime.subscribe("state") → host-owned live state transport
|
||||
```
|
||||
|
||||
The new inventory endpoint returns exactly what building a `ModuleUiHostContext` needs — id, version,
|
||||
artifact URL, module API root, and the viewer's granted permissions **for that module** — and nothing
|
||||
else: no filesystem paths, and no `name`/`icon`/`folder`/`order`, because navigation metadata is a
|
||||
design that does not exist yet. It is authenticated (`core.modules.ui.read`, baseline
|
||||
The inventory endpoint returns exactly what building a `ModuleUiHostContext` needs — id, version,
|
||||
artifact URL, module API root, module realtime root, and the viewer's granted permissions **for that
|
||||
module** — and nothing else: no filesystem paths, and no `name`/`icon`/`folder`/`order`, because
|
||||
navigation metadata is a separate design. It is authenticated (`core.modules.ui.read`, baseline
|
||||
`authenticated`) precisely because it is the module enumeration the anonymous artifact route
|
||||
deliberately withholds. Both URLs are produced by the server's own canonical helpers, so the browser
|
||||
never rebuilds Ekklesia's module prefixes and either can change without touching host or module code.
|
||||
deliberately withholds. All three URLs are produced by the server's own canonical helpers, so the
|
||||
browser never rebuilds Ekklesia's module prefixes and any can change without touching host or module
|
||||
code.
|
||||
|
||||
**The shell statically imports no module.** Every module it can mount arrives at run time from the
|
||||
inventory, and its bundle is checked for exactly one dynamic import — the generic loader's — with no
|
||||
@@ -594,10 +596,39 @@ and rewritten to another. The origin a browser uses is therefore a signing input
|
||||
(`S3_DELIVERY_ENDPOINT`), defaulting to `S3_ENDPOINT` — so the standard deployment configures nothing
|
||||
and delivery simply works. A reverse proxy in front of storage must pass the original `Host` through
|
||||
unchanged; it may terminate TLS, because the scheme is not signed. Buckets stay private: an unsigned
|
||||
request for the same object is refused, and a test asserts it. **Signed write and delete URLs, and a
|
||||
realtime refresh transport, are deliberately deferred.** See
|
||||
request for the same object is refused, and a test asserts it. **Signed write and delete URLs are
|
||||
deliberately deferred.** See
|
||||
[ADR 0031](docs/architecture/decisions/0031-direct-object-delivery.md).
|
||||
|
||||
Phase 7A pass 5 adds native, host-neutral module realtime
|
||||
([ADR 0032](docs/architecture/decisions/0032-native-module-realtime-transport.md)). During
|
||||
`register`, a module declares a small, module-local channel and the permission that protects it;
|
||||
during `start`, running work, and HTTP handlers, it publishes bounded JSON state through its own
|
||||
scoped publisher. The publisher is inert by the time that module's stop hook begins. Core keeps only
|
||||
the latest state and one pending update per subscriber. A module
|
||||
must publish an initial state before startup completes, so every accepted subscriber receives one
|
||||
atomic authoritative snapshot and then monotonically sequenced updates without a GET-then-subscribe
|
||||
race. Slow clients never block publishers and intermediate states may be coalesced.
|
||||
|
||||
Ekklesia carries that contract over permission-gated Server-Sent Events, with cookie authentication,
|
||||
15-second transport-owned keepalives, a five-minute reauthorization cycle, browser-native reconnect,
|
||||
and a fresh snapshot on every reconnection. The module UI API remains transport-neutral:
|
||||
|
||||
```ts
|
||||
const subscription = context.realtime.subscribe<SlideshowState>("state", {
|
||||
onSnapshot: (state) => render(state),
|
||||
onUpdate: (state) => render(state),
|
||||
});
|
||||
|
||||
// Return this from `mount`, or call it from your unmount cleanup.
|
||||
subscription.close();
|
||||
```
|
||||
|
||||
There is no durable replay, global topic namespace, cross-module subscription, query-string token,
|
||||
Socket.IO contract, or MQTT integration. Direct object-delivery URLs are opaque JSON strings and
|
||||
pass through realtime unchanged. The final unauthenticated/device principal for public displays and
|
||||
display/kiosk hosting itself remain a later pass.
|
||||
|
||||
**Still not the finished Dashboard.** There is no navigation editor or drag and drop, no nested
|
||||
folders, no icons or colors, no Dashboard Home or widgets, no design system, no client-side routing,
|
||||
and no module-management, settings, or storage UI — module enablement and configuration are an API and
|
||||
|
||||
Reference in New Issue
Block a user