feat: add native module realtime transport
This commit is contained in:
@@ -7,7 +7,9 @@ document's guarantees.
|
||||
Related: [ADR 0006](../architecture/decisions/0006-versioned-capabilities-and-compiled-module-runtime.md)
|
||||
(compiled module runtime), [ADR 0016](../architecture/decisions/0016-module-database-access.md)
|
||||
(module database access), [ADR 0020](../architecture/decisions/0020-module-runtime-and-migration-execution-hardening.md)
|
||||
(module routing/lifecycle and migration execution), and
|
||||
(module routing/lifecycle and migration execution),
|
||||
[ADR 0032](../architecture/decisions/0032-native-module-realtime-transport.md)
|
||||
(native module realtime), and
|
||||
[Module development](../module-development/README.md).
|
||||
|
||||
## Installed modules are trusted compiled backend code
|
||||
@@ -44,6 +46,7 @@ cooperative. None of them constrains a module that is not:
|
||||
| `ekklesia.module.json` manifest and `manifestVersion` | Declared compatibility and dependency ordering |
|
||||
| Permission declarations and the composed registry | Which permissions *this* module's own routes may name, checked when Core mounts them |
|
||||
| Route validation, overlap rejection, body limits | Deterministic, unambiguous HTTP routing and request bounds |
|
||||
| Scoped realtime channels, publishers, and subscriptions | Correct module-local state wiring, permission enforcement, and finite fanout — not process isolation |
|
||||
| `Object.freeze` on contexts and services | Prevents accidental mutation; a module can still reach the underlying objects |
|
||||
| The `ModuleDatabase` facade | A typed, ergonomic surface — not a privilege reduction, since `execute` is arbitrary SQL |
|
||||
| Capability registry | Explicit inter-module wiring, not access control |
|
||||
@@ -316,6 +319,37 @@ see the URL it is fetching.
|
||||
an unsigned request for the same object is refused, and an integration test asserts it so that a
|
||||
future change cannot "fix" delivery by opening a bucket up.
|
||||
|
||||
### Realtime is permission-gated state delivery, not a trust boundary
|
||||
|
||||
A module may declare a local realtime channel protected by one of its own manifest permissions. Core
|
||||
closes both the server publisher and browser subscription over that module id: neither API accepts a
|
||||
caller-supplied module namespace, global topic, wildcard, broker address, origin, URL, permission, or
|
||||
credential. This prevents accidental cross-module wiring by a cooperating module. It does not
|
||||
sandbox hostile installed code; trusted same-process backend code could bypass the facade, and
|
||||
trusted same-realm UI could call browser globals directly.
|
||||
|
||||
Ekklesia's SSE endpoint resolves the ordinary session cookie and evaluates the channel's fixed
|
||||
permission before allocating a subscriber. There is no query-string token and no second realtime
|
||||
authentication path. A stream is closed after five minutes so native EventSource reconnects and
|
||||
session, grant-expiry, revocation, and baseline policy are evaluated again; authorization is not
|
||||
continuously queried between those boundaries. Disabled and dependency-blocked modules have no
|
||||
stream route at all. Public or device/display identity remains deliberately unsolved, so no channel
|
||||
is made anonymous merely to support a future kiosk.
|
||||
|
||||
Fanout is finite: 16 channels per module, 100 subscribers per channel, 64 KiB of validated serialized
|
||||
JSON per publication, one latest snapshot per channel, and one coalescing pending slot per client.
|
||||
A slow browser cannot block a publisher or create an unbounded queue. The endpoint rejects implicit
|
||||
`HEAD` before allocation, disconnect/cancel closes its Core subscription, and server shutdown closes
|
||||
streams before finite HTTP drain. Each scoped publisher becomes inert immediately before its own
|
||||
module's stop hook.
|
||||
|
||||
Core never adds configuration, secret values, database values, storage credentials, or delivery URLs
|
||||
to a realtime document. It transports only what trusted module code explicitly publishes. A module
|
||||
must therefore treat realtime like any other authorized response: include only data every principal
|
||||
holding the channel permission may receive. If it intentionally includes a signed delivery URL, that
|
||||
URL remains a bearer capability and is forwarded byte-for-byte; fixed transport errors never reflect
|
||||
payloads, URLs, permissions, runtime exceptions, paths, or stacks.
|
||||
|
||||
### Client-side permission checks are not a security boundary
|
||||
|
||||
A host may tell a module UI what permissions it believes the viewer holds
|
||||
|
||||
Reference in New Issue
Block a user