Skip to content

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/>host execution and state transport"]
  ThreadWorker["thread worker<br/>execution status and local continuation"]
  Kernel["kernel wasm<br/>guest-visible semantics and supervisor"]

  SDK --> Runtime
  Runtime --> KernelWorker
  Runtime --> ProcessOwner
  ProcessOwner <--> KernelWorker
  ProcessOwner <--> ThreadWorker
  ThreadWorker <--> Kernel
  KernelWorker <--> Kernel

The kernel defines guest-visible semantics and owns Linux-shaped decisions through the supervisor. The runtime moves state and executes host effects so those decisions 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 supervisor and kernel-owned publication rules remain authoritative for page identity, dirty state, fills, flushes, and control-plane transitions.

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 host effects and state transport 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 supervisor-requested lifecycle host operations,
  • supervisor-selected blocked syscall resume execution,
  • 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 host-execution contract, not a second source of Linux policy 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 Transport or mirror of supervisor/kernel-owned descriptor state.
OFD slot snapshots Open-file-description state transported across host boundaries.
pipe slot snapshots Supervisor-controlled metadata plus direct data-plane coordination.
socket state snapshots Supervisor-controlled socket state plus host network buffer transport.
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 Transport for supervisor-owned child publication and wait ordering.
fork stack snapshots Stack state needed across fork-style transitions.

These categories explain why explicit state transport is a central runtime responsibility. They do not transfer scheduling, lifecycle, fd/OFD, pipe, socket, or filesystem control authority from the supervisor to TypeScript.

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 host executor"]
  FD["fd entry snapshots"]
  OFD["OFD slot snapshots"]
  Pipe["pipe slot snapshots"]
  Socket["socket state snapshots"]
  KernelWorker["kernel-worker sync"]
  Kernel["kernel wasm / supervisor 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/OFD, pipe, socket, memory, and synchronization records through the kernel ABI. Runtime adapters transport those records and execute supervisor decisions; they do not recompute guest-visible lifetime or readiness policy. Tests should cover both the kernel decision and the observable result of the retained host effect.

This structure is what lets thread execution, supervisor-owned process state, and kernel-worker state converge after a blocking syscall, fork-style transition, network operation, or file mutation.