feat: add display kiosk host
CI / verify (pull_request) Successful in 38s
CI / infrastructure-smoke (pull_request) Successful in 20s
CI / migration-smoke (pull_request) Successful in 59s

This commit is contained in:
ekklesia
2026-08-23 20:50:56 -05:00
parent be5ed7853d
commit 2e0fe059c3
80 changed files with 4868 additions and 397 deletions
+25 -1
View File
@@ -9,7 +9,9 @@ Related: [ADR 0006](../architecture/decisions/0006-versioned-capabilities-and-co
(module database access), [ADR 0020](../architecture/decisions/0020-module-runtime-and-migration-execution-hardening.md)
(module routing/lifecycle and migration execution),
[ADR 0032](../architecture/decisions/0032-native-module-realtime-transport.md)
(native module realtime), and
(native module realtime),
[ADR 0033](../architecture/decisions/0033-display-kiosk-host-contract.md)
(authenticated display hosting), and
[Module development](../module-development/README.md).
## Installed modules are trusted compiled backend code
@@ -47,6 +49,7 @@ cooperative. None of them constrains a module that is not:
| 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 |
| Display declaration, permission, and exact publication record | Correct role selection and normal authorization for an eligible/running module — not browser 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 |
@@ -155,6 +158,27 @@ shell owns the container, the loading state, and every error, so a failing modul
rather than the shell. None of that is containment: once imported, a module UI is ordinary same-realm
JavaScript, exactly as described above.
### A display page is authorized; its artifact bytes are not authority
Manifest version 6 binds a display artifact to one permission declared by the same module
([ADR 0033](../architecture/decisions/0033-display-kiosk-host-contract.md)). The declaration rejects
a `public` baseline: the current display host requires an ordinary authenticated cookie principal,
and public/device identity is not approximated with an anonymous exception. Both the stable
`/display/<module-id>` page and `/api/v1/module-displays/<module-id>` descriptor evaluate that exact
permission through the normal Core evaluator. Client permission hints remain presentation only.
The versioned display artifact is anonymous code just like primary UI code. It is served only from
the frozen eligible/running publication registry, by exact module identity and version, and never
falls back to primary UI or opens the module directory. Fetching it grants no module HTTP, realtime,
storage, or data authority. Disabled and dependency-blocked modules receive no page, descriptor, or
artifact route; this is operational exposure control, not sandboxing.
The full-viewport host is a separate same-realm surface, not an iframe or containment boundary. It
uses normal cookie authorization, the same scoped API/realtime context, and opaque signed object URLs
without logging, proxying, extending, or re-signing them. It does not invoke the browser Fullscreen
API. Display inventory, device credentials, public principals, variants, and targeting are deferred
rather than silently granted.
### Dashboard arrangement is not an access control
Dashboard groups and placements are presentation configuration