Architecture · self-hosting implementation

The application is WebAssembly. The browser boundary is Limen.

This site uses an F#/.NET WebAssembly reference engine.

The site uses the architecture it describes. Static HTML and CSS remain browser-native. Limen owns DOM bindings and browser effects. A tiny transport loads a .NET WebAssembly runtime. All application state, transitions, capabilities, obligations, effect interpretation, and view projection live in the engine — on this site, written in F#. Limen does not require F#: the engine role is defined by the boundary, not by a language.

Runtime path

Four layers. One authority boundary.

Every arrow crosses plain serializable data or browser-native structure.

HTML + CSS

Semantics, labels, layout, focusable controls, responsive presentation.

site/pages/*.html
Limen BrowserKernel

Turns interactions into semantic events, applies projections, executes effects.

src/kernel/
WASM transport

Loads .NET and forwards one JSON message in, one JSON result out.

site/app/wasm-engine-transport.ts
WASM application engine

Owns meaning: state, legal transitions, evidence, authority, obligations, capabilities, projection. F#/.NET on this site.

site/fsharp/Limen.Site.Engine/

The only C# in the site application is the tiny [JSExport] marshalling shim required by .NET's source-generated JavaScript export mechanism. It contains no application state or decision: it forwards the serialized message directly to Limen.Site.Engine.Dispatch.handle.

The contract

The boundary carries evidence, never application objects.

Defined by the npm package in src/protocol.ts.

Browser → engine
type BrowserToEngineMessage =
  | { kind: "Initialize"; protocolVersion; capabilities; location; handshake? }
  | { kind: "Event"; event: SemanticEvent }
  | { kind: "EffectResult"; result: EffectResult }
  | { kind: "LocationChanged"; location: BrowserLocation }
  | { kind: "CapabilityFact"; capability; version; fact };

// No DOM node.
// No callback.
// No shared object identity.
Engine → browser
type EngineToBrowserMessage = {
  view: ViewState;
  effects: EffectRequest[];
  cancellations: CorrelationId[];
};

// Projection + explicit requests.
// The browser never asks what they mean.

This separation is what lets the application move across a real WebAssembly boundary. The TypeScript package does not need F# types, and this site’s F# engine does not need DOM types. Both sides agree only on the serialized protocol — which is why any WASM language that can read and write it can take the engine role.

Language-neutral contract

One contract. Each guest enforces it in its own compiler.

No guest language is canonical. F# is the reference implementation, not a protocol requirement.

        language-neutral Limen contract
                   │
     ┌─────────────┼─────────────┐
     ▼             ▼             ▼
  F# WASM       C# WASM      Rust WASM
     │             │             │
     └─────────────┼─────────────┘
                   ▼
             browser host
           (TypeScript today)
                   │
                   ▼
             browser APIs
The contract model: implemented today versus accepted architecture
PropertyImplemented on main todayAccepted and in progress
Contract sourceThe TypeScript protocol in src/protocol.ts; the F# engine mirrors it with handwritten typesOne language-neutral source of truth (#15, #52)
Guest representationsTypeScript and handwritten F#Generated F#, C#, Rust, and TypeScript bindings; generated code is read-only (#52, #55)
Guest compiler enforcementF# engines build with warnings as errors, so an unhandled union case fails the buildRust closed enums and results; C# generated closed hierarchies, nullable analysis, and analyzers (#55)
No raw browser objects across the boundaryThe protocol is plain JSON-serializable data; engine-purity checks ban browser APIs in the enginesA mechanical check that protocol definitions contain no DOM or browser types (#51)
Version agreementInitialize carries a protocolVersionA contract fingerprint handshake before normal traffic; mismatch fails explicitly (#52)
Cross-language conformanceF# engine tests; kernel and federation testsShared golden vectors every guest must pass (#17, #32)

JSON is the baseline wire format. The WebAssembly Component Model and WIT may become an additional backend, but Limen does not depend on them or on any one guest toolchain. Requirements and status are tracked in the capability parity specification (#15) and the guardrail specification (#51).

Authority

Who is allowed to know what.

If two layers disagree, the owner in this table wins.

Authority by layer
LayerOwnsMust not decide
HTMLDocument semantics, labels, native controlsRelease legality, retry policy, state transitions
CSSPresentation, focus appearance, responsive compositionWhether an action is legal
BrowserPaint, layout, caret, scroll, native input mechanicsApplication state
Limen kernelBinding, event transport, HTTP/storage/clipboard/navigation mechanismsWhat an event, status code, route, or effect means
WASM transportRuntime loading and serialization crossingAny domain decision
WASM application engine
F# on this site
Application truth, transitions, capabilities, obligations, interpretation, projectionDirect browser API access

The hard cases

Why this is more than state management.

These are the cases the site now runs through F#.

Capability

Approval is not a button state

Tests and security evidence must both pass before F# projects approval capability. The DOM receives the result; it does not derive it.

Staleness

Older evidence cannot win a race

The F# engine carries the latest request identity. A late result for an older request is observable and discarded rather than silently overwriting current state.

Unknown effect

Timeout is not failure

After dispatch, a timeout becomes reconciliation-required state. No retry capability exists until authoritative evidence resolves what happened.

Missing capability

No browser escape hatch

If focus control or clipboard read is not in the protocol, application code cannot quietly reach for a DOM/global API. It becomes an explicit protocol decision.

Compute in code

Closed types make the legal state space visible.

This site’s release demo, written in F#, is not built from four independent flags. Deployment can be not started, in flight, deployed, failed, or reconciliation required. The transition function controls which moves exist.

  1. 01
    Evidence changes are legal only before approval.
  2. 02
    Approval exists only after both required evidence gates pass.
  3. 03
    Deployment exists only after approval.
  4. 04
    Only the matching correlation may complete an in-flight effect.
  5. 05
    OutcomeUnknown creates reconciliation, not retry.

Package versus site

What Limen ships, and what this repository demonstrates.

These are deliberately separate claims.

Distribution boundaries
SurfaceImplementationWhat it establishes
npm packageTypeScript kernel + protocol + reference engine; F# lifecycle CLI binariesA browser boundary with explicit effects and a repository verification surface; it does not prescribe the engine language
Limen websiteStatic HTML/CSS + TypeScript Limen mechanics + F# WebAssembly application engineThe boundary can host a non-TypeScript engine in the same repository and drive nontrivial UI state
Chrona/time-entry consumerF# domain/engine + .NET WebAssembly transportAn independent application has also crossed the same boundary

None of those rows proves performance, development speed, defect reduction, or token savings. Those require controlled measurement. The evidence page keeps those boundaries explicit.

Verification

The site architecture is part of CI now.

A self-hosting demo that can silently fall back is not evidence.

F# authority checkCI rejects browser/JS interop in the F# engine and application logic in the C# export shim
F# engine testsrelease gating · stale evidence · unknown effect · serialized dispatch
Artifact checksdotnet.js + .wasm required in dist-site
Negative assertionlegacy site/app/engine.js must not exist
Binding checksHTML bindings/events cross-checked against F# source vocabulary