feat: add display kiosk host
This commit is contained in:
@@ -14,7 +14,8 @@ independent whole-repository re-audit has closed, under the current trusted-cont
|
||||
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), 4 (direct
|
||||
browser object delivery), and 5 (native module realtime transport) have landed.** The operational
|
||||
browser object delivery), 5 (native module realtime transport), and 6 (the authenticated display/
|
||||
kiosk host) 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
|
||||
@@ -22,7 +23,8 @@ pass 5 closure below and
|
||||
public module contracts, independently versioned capabilities, static module manifests,
|
||||
deterministic automatic discovery, compiled server loading, a staged capability registry,
|
||||
dependency-ordered lifecycle with rollback, module-owned Drizzle table contracts, deterministic
|
||||
schema aggregation, and a cloneable backend-only module template. Migration artifacts have
|
||||
schema aggregation, and a cloneable module-product template with optional primary and display UI.
|
||||
Migration artifacts have
|
||||
module-owned Drizzle contracts, deterministic offline discovery, explicit destructive-change
|
||||
declarations, and exact checksums. Core provides read-only applied-history planning and a locked,
|
||||
whole-batch transactional PostgreSQL runner with exact external destructive approval matching. A
|
||||
@@ -64,7 +66,7 @@ accounts/API tokens remain later work. See [Authorization](docs/development/auth
|
||||
[ADR 0013](docs/architecture/decisions/0013-core-authorization-and-owner-bootstrap.md).
|
||||
|
||||
Phase 5A wires module-declared permissions into that same authorization system: the module manifest
|
||||
(version 3 at the time, version 4 today) declares permissions as complete
|
||||
(version 3 at the time, version 6 today) declares permissions as complete
|
||||
`{id, baseline, description}` declarations (never a bare string), and `apps/api` discovers every installed module and composes one immutable platform
|
||||
permission registry — Core's own declarations plus every module's — before constructing its
|
||||
database pool, Better Auth instance, or HTTP server. A module's declared permission is then
|
||||
@@ -255,8 +257,9 @@ returns an unmount function. A host needs an element and a context, never a matc
|
||||
|
||||
The new `@ekklesia/module-ui-sdk` package (no runtime dependencies; its one dependency on
|
||||
`@ekklesia/contracts` is type-only) carries that contract for module authors and hosts alike. The
|
||||
host context is deliberately four members — `moduleId`, `api`, `principal`, `permissions` — and
|
||||
nothing else. **A module UI asks its host for its own API by relative path** (`api.request("posts")`)
|
||||
host context is deliberately five members — `moduleId`, `api`, `principal`, `permissions`, and
|
||||
`realtime` — and nothing else. **A module UI asks its host for its own API by relative path**
|
||||
(`api.request("posts")`)
|
||||
and never constructs `/api/v1/modules/<id>/...`, which is Ekklesia's private deployment detail; that
|
||||
one rule is what keeps a future standalone host possible, and the test suite proves it by mounting
|
||||
one fixture artifact under two hosts with different origins and URL shapes. **Client-side permission
|
||||
@@ -479,7 +482,8 @@ start({ config }) {
|
||||
reported `blocked · configuration_missing · bucket` — never imported, never able to take startup down
|
||||
from inside its own `start` hook — and its required dependents block behind it through ADR 0028's
|
||||
existing mechanism. Doctor and `pnpm modules:validate` report the same thing while still importing no
|
||||
module code. That forces `manifestVersion: 5`, a hard cutover in this repository's usual style.
|
||||
module code. That change introduced `manifestVersion: 5` as a hard cutover; the current strict
|
||||
manifest is version 6 after ADR 0033 added the optional display declaration.
|
||||
|
||||
**Ekklesia does not store secrets, and does not pretend to.** There is no encryption at rest here and
|
||||
no key management, so a `secret` key's value is supplied by the deployment
|
||||
@@ -626,15 +630,48 @@ 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.
|
||||
pass through realtime unchanged.
|
||||
|
||||
Phase 7A pass 6 adds the smallest complete **authenticated display/kiosk host**
|
||||
([ADR 0033](docs/architecture/decisions/0033-display-kiosk-host-contract.md)). Manifest version 6
|
||||
adds an optional, independently declared display role:
|
||||
|
||||
```json
|
||||
{
|
||||
"entrypoints": {
|
||||
"display": {
|
||||
"artifact": "./dist/display/index.js",
|
||||
"permission": "announcements.display.view"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The permission must be declared by that module and have an `authenticated` or `restricted` baseline;
|
||||
public/device identity remains deferred. Primary UI and display UI are independently optional, must
|
||||
use paths distinct from every other entrypoint, and use the same
|
||||
`defineModuleUi({ id, version, mount })` ABI and five-member `ModuleUiHostContext` with **no mode
|
||||
flag**. The canonical builder reads fixed sources (`src/ui/index.tsx` and
|
||||
`src/display/index.tsx`) and atomically emits every declared role.
|
||||
|
||||
Ekklesia publishes the display bundle at
|
||||
`GET /api/v1/module-ui/<module-id>/<version>/display`, serves a stable permission-gated page at
|
||||
`/display/<module-id>`, and exposes that page's permission-gated descriptor at
|
||||
`GET /api/v1/module-displays/<module-id>`. The display surface is a separate full-viewport browser
|
||||
application with no Dashboard chrome, no static module imports, and manual retry on failure. Only
|
||||
eligible, running modules receive routes; manifest changes take effect on restart. Artifact bytes
|
||||
remain anonymous trusted code, while the page and descriptor use normal cookie authentication and
|
||||
the module-owned display permission. Realtime subscriptions and opaque signed object-delivery URLs
|
||||
flow through the unchanged host context. Display inventory/management, variants and targeting,
|
||||
public/device identity, browser Fullscreen API behavior, a standalone host, a shared CSS system, the
|
||||
Announcements migration, and Companion integration remain deferred.
|
||||
|
||||
**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
|
||||
a CLI, not a screen, and object storage is deployment configuration. Sign-in UI, a production CSP and deployment for the built shell, asset publication beyond
|
||||
the UI entrypoint, React runtime sharing, the standalone host, and every real feature module also do
|
||||
not exist.
|
||||
a CLI, not a screen, and object storage is deployment configuration. Sign-in UI, a production CSP
|
||||
and deployment for the built shells, general sidecar asset publication, React runtime sharing, the
|
||||
standalone host, and every real feature module also do not exist.
|
||||
|
||||
**Security model.** Installed modules are trusted compiled backend code that runs arbitrary
|
||||
JavaScript in the API process and shares its PostgreSQL role — table prefixes, manifests, permission
|
||||
|
||||
Reference in New Issue
Block a user