Architecture
The Strata workspace separates each responsibility into one package.
Package structure
Section titled “Package structure”packages/├── strata_protocol/ # WebSocket message types and JSON conversion (both)├── strata_document/ # Typed LWW-register schemas (target: both)├── strata_server/ # mist + beryl + shelf (target: erlang only)├── strata_client/ # Sans-IO client state machine (target: both)├── strata_component/ # Typed headless component contracts (target: both)├── strata_transport/ # Browser WebSocket transport (target: javascript)├── strata_transport_gun/ # Gun WebSocket transport (target: erlang)└── strata_admin/ # Admin page (target: javascript only)Dependency graph
Section titled “Dependency graph”strata_protocol (no strata deps)strata_document (no strata deps)strata_server → strata_protocolstrata_client → strata_protocol, strata_documentstrata_component → strata_client, strata_documentstrata_transport → strata_clientstrata_transport_gun (no strata deps; uses gluegun)strata_admin → strata_client (via JS target)Core concepts
Section titled “Core concepts”WebSocket protocol (v0)
Section titled “WebSocket protocol (v0)”Strata sends versioned JSON messages over WebSocket. The client and server
both use strata_protocol to convert these messages to and from JSON. This
keeps the message types consistent.
Client → Server: join, leave, delta, create, delete,
presence_set, ping
Server → Client: welcome, snapshot, delta, deleted,
presence_state, presence_diff, error, pong
Document model
Section titled “Document model”- Root type: an
ORMapfromlattice_maps. strata_documentprovides optional typed fields and record schemas for an ORMap that contains LWW registers.- One store actor owns all document state.
- Beryl owns each socket and document-topic join.
The current server is single-node. Its store owns document state, deletion links, and tombstones for that instance, and its PubSub scope is node-local. CRDT merging between clients does not provide server-to-server replication. Connecting BEAM nodes or enabling distributed PubSub alone would not coordinate store ownership, checkpoints, or atomic create/delete operations. Multi-node support remains deferred in #6. See single-node deployment before running independent instances.
Composition
Section titled “Composition”An app can model several document kinds and link them by id: one document
(a composite) holds typed references to others. create and delete are
serialized in one store-owner turn across the parent and a child; delete cascades to every
descendant. See Composing Documents.
Three kinds of relationship
Section titled “Three kinds of relationship”| Term | Meaning | Who records it |
|---|---|---|
| Reference | An application field names another document ID. It grants no deletion authority or event-routing claim. | Application CRDT state |
| Deletion owner / parent | The single recorded parent of an owned child. A delete through this parent removes the child and its recorded descendants. | Server create and checkpoint |
| Event-routing owner | The one component instance that receives a document's events in a host. | Host routing index |
Application references: workspace --> board --> card shortcuts -----------> card
Server deletion tree: workspace --> board --> card shortcuts (no deletion link to card)
Host routing index: board --> Board instance card --> Board instance shortcuts --> Shortcuts instanceDeleting board removes card, even though shortcuts still references it.
The remaining reference dangles. The host routes the card's deleted event
to its Board instance; the event-routing claim did not grant deletion rights.
References can cross deletion subtrees. The server does not derive deletion
links from CRDT fields.
Use Create(parent, child, ...) for a new owned child. To reference an
existing document without taking ownership, write its ID into application
state with a normal document edit. Do not send Create for that target.
An owned child has exactly one deletion parent; an unparented root has none.
DocId(tag) provides compile-time schema separation. Serialization retains
the string ID, not the phantom tag. doc_id does not validate an incoming
runtime ID, check its stored schema, or authorize deletion. Application
decoders and host routing checks remain necessary.
See the host-author contract for event-routing rules and persistent deletion identity for tombstone retention and the ban on deleted-ID reuse.
Components and hosts
Section titled “Components and hosts”strata_component packages a collaborative feature as a typed headless
definition. A definition contains config codecs, initial state, local updates,
replicated event handling, typed outputs, and an event-routing ownership query.
Typed output ports, input ports, and bindings define compiler-checked integration points between components. The application host owns the shared connection, routes events by document ID, handles component outputs, and mounts UI adapters. The package does not own a socket, storage, presence, or a UI framework.
The current workspace example uses one browser host. The host owns one
WebSocket connection, one root PageStore, a dictionary of component models,
and a document-id routing map. The host routes each document event to the
component that claims that id for routing. Components return client commands for the host to
send. Local updates can also return typed outputs.
The host uses closed sum types for supported component kinds and bindings. It does not use a runtime component registry. Its direct workflow bindings exist only in the current browser session and run best-effort. Closing or crashing the browser loses routes and pending work; a disconnect can leave sent commands with unknown outcomes. The server does not resume workflow work. The package does not include runtime discovery, a persisted connection graph, or durable intent. See the workflow delivery boundary.
Document sync
Section titled “Document sync”- Join and reconnect: The server sends a
snapshotwith encoded ORMap state. - Steady state: Applications send encoded ORMap state in
deltaframes. The store validates and merges the state. The channel sends accepted merged state to other sockets withberyl.broadcast_from. Each peer must merge that state into its local document.
Strata v0 reads one shared secret from STRATA_TOKEN. The client sends the
token as ?token=<secret>. The server checks it before the upgrade completes.
The wire protocol does not have an auth frame.
Presence
Section titled “Presence”beryl stores presence for each document. strata_protocol defines the
presence data. It can include a cursor, selection, name, and color.
Persistence
Section titled “Persistence”One store actor holds document state, deletion parent links, and tombstones.
Shelf saves them together in an atomic store.dets checkpoint. Recovery
validates ORMap state and deletion links before the server starts.
Acceptance precedes persistence; see Durability and Recovery.