State Model¶
Tidemark's runtime has to keep browser-side worker state and kernel-visible guest state coherent. The state model defines what is owned by the kernel, what is mirrored or transported by the runtime, and what is only application or SDK policy.
State Owners¶
flowchart TB
SDK["SDK<br/>provider and application policy"]
Runtime["Runtime API<br/>public process and filesystem methods"]
KernelWorker["kernel worker<br/>canonical runtime-facing FS and lifecycle state"]
ProcessOwner["process owner<br/>selected process state and scheduler"]
ThreadWorker["thread worker<br/>execution status and local continuation"]
Kernel["kernel wasm<br/>guest-visible semantics"]
SDK --> Runtime
Runtime --> KernelWorker
Runtime --> ProcessOwner
ProcessOwner <--> KernelWorker
ProcessOwner <--> ThreadWorker
ThreadWorker <--> Kernel
KernelWorker <--> Kernel
The kernel defines guest-visible semantics. The runtime moves state between workers so those semantics can survive browser worker boundaries and async host events.
Shared Memory And Page Cache¶
The runtime allocates a SharedArrayBuffer-backed page cache during
Runtime.create and passes it to kernel worker and process creation paths. The
kernel environment receives that page cache when WebAssembly instances are
created.
The current runtime creates the page cache during runtime initialization, passes it into kernel-worker and process creation paths, and exposes snapshot and diagnostic operations around it. The important design point is that shared file state is a named runtime/kernel substrate, not incidental per-worker data.
The page cache is not an application-visible storage API. It is part of the runtime and kernel substrate used to make filesystem and process state visible across workers.
Filesystem Layers And Snapshots¶
The runtime exposes filesystem operations such as write, bulk write, symlink, read, readlink, mkdir, stat, readdir, snapshot loading, and file layer application. The SDK builds on that with resolver, cache, and provider flows.
sequenceDiagram
participant App as Application
participant SDK as SDK provider layer
participant Resolver as resolver/cache
participant Runtime as Runtime.fs
participant KW as kernel worker
participant Kernel as kernel memfs exports
App->>SDK: install provider-backed file layer
SDK->>Resolver: resolve manifest and blobs
Resolver-->>SDK: manifest + blob URLs or Requests
SDK->>SDK: fetch, cache, validate entries
SDK->>Runtime: applyFileLayer(entries)
Runtime->>KW: apply-file-layer(entries)
KW->>Kernel: update memfs/page-cache state
Kernel-->>KW: updated state
KW-->>Runtime: file-layer-applied
Runtime->>Runtime: refresh runtime FS snapshot
The SDK provider interfaces let applications choose where file layer data comes from. The runtime receives concrete entries; it does not need to know which package manager, registry, or upstream system produced them.
Runtime Filesystem Read Path¶
Runtime reads first use the most recent published runtime filesystem snapshot
when possible. If the entry is unavailable or stale, the runtime asks the kernel
worker through read-file.
flowchart TB
Read["Runtime.readFile(path)"]
Snapshot["runtime FS snapshot"]
Hit{"file data present?"}
ReturnSnapshot["return snapshot bytes"]
RPC["kernel-worker read-file"]
ReturnRPC["return kernel-worker bytes"]
Read --> Snapshot
Snapshot --> Hit
Hit -- yes --> ReturnSnapshot
Hit -- no --> RPC
RPC --> ReturnRPC
This read path exists alongside explicit mutation paths that refresh the runtime snapshot after writes, symlinks, directory creation, and layer application.
Kernel-Worker RPC¶
The runtime uses request ids for kernel-worker RPCs so filesystem and lifecycle operations can be pipelined safely. The current request set includes:
- kernel initialization,
- single and bulk file writes,
- file layer application,
- symlink creation,
- read-file and readlink,
- mkdir, stat, and readdir,
- filesystem snapshot load and snapshot export,
- process registration and lifecycle operations,
- blocked syscall resume paths,
- process and page-cache diagnostic operations.
flowchart LR
Runtime["Runtime API call"]
Request["KernelWorkerRequest<br/>requestId + typed payload"]
Handler["kernel-worker handler"]
Action["fs, lifecycle, sync, debug operation"]
Response["KernelWorkerOutbound<br/>requestId + typed result"]
Runtime --> Request
Request --> Handler
Handler --> Action
Action --> Response
Response --> Runtime
This is a contract, not an implementation detail hidden behind a single mutable global object.
Snapshot Categories¶
The runtime message types show which state must cross worker boundaries:
| Snapshot or state | Purpose |
|---|---|
KernelRuntimeState |
Kernel-visible runtime state passed between kernel and workers. |
| fd entry snapshots | File descriptor to open-file-description mapping and fd flags. |
| OFD slot snapshots | Open file description state needed across process transitions. |
| pipe slot snapshots | Pipe control state and data-plane coordination. |
| socket state snapshots | Socket table and network buffer state. |
| guest memory write snapshots | Guest memory changes that must be replayed or synchronized. |
| kernel memory write snapshots | Kernel-side memory changes surfaced from execution. |
| child-exit records | Parent-visible child lifecycle records for wait-style behavior. |
| fork stack snapshots | Stack state needed across fork-style transitions. |
These categories explain why process orchestration is a central runtime responsibility. Browser workers isolate execution, but guest processes expect a coherent Linux-like process and file descriptor model.
FD, OFD, Pipe, And Socket Synchronization¶
The implementation treats file descriptor and kernel-object state as structured state, not as loose JavaScript side data.
flowchart TB
Thread["thread worker status"]
Owner["process owner status handler"]
FD["fd entry snapshots"]
OFD["OFD slot snapshots"]
Pipe["pipe slot snapshots"]
Socket["socket state snapshots"]
KernelWorker["kernel-worker sync"]
Kernel["kernel wasm state"]
Thread --> FD
Thread --> OFD
Thread --> Pipe
Thread --> Socket
FD --> Owner
OFD --> Owner
Pipe --> Owner
Socket --> Owner
Owner --> KernelWorker
KernelWorker --> Kernel
The current implementation exposes fd and OFD state through the kernel ABI, collects fd/OFD, pipe, socket, memory, and sync-effect state when execution stops, and applies that state through process-owner and kernel-worker synchronization. Tests should cover the observable result of those transfers, not the filenames that happen to implement them.
This structure is what lets thread execution, process ownership, and kernel worker state converge after a blocking syscall, fork-style transition, network operation, or file mutation.