READING EDITION / RESEARCH PREVIEW

This is a static guide, not a live service status. Public remote writes and execution remain disabled. No JavaScript is needed to read this page.

Cap’n Web pipeline experiment

Status: SHIPPED read-only root capability tree, HTTP batching, WebSocket transport, WebRTC adapter, official SQLite WASM MessagePort body, local resume intent/outbox, instrumentation, and failure tests. PREVIEW-GATED authenticated CloudMailbox effects. PLANNED cockpit mailbox migration and additional carrier schedulers.

Cap’n Web is the live conversation between bodies. It is not the durable memory.

CURSO/0 signed cursor and capability records
                 │
                 ▼
local SQLite semantic intent + outbox row      DURABLE
                 │
                 ▼
Cap’n Web MessagePort / HTTP batch / WebRTC    PIPELINED
                 │
                 ▼
existing CloudMailbox policy and SQLite effect DURABLE
                 │
                 ▼
receipt linked to the local outbox row         DURABLE

Transport is not identity. Identity is not authority. An RPC capability reference is not a mailbox, cursor, or Self identity. Losing the RPC session may discard live stubs; it must not discard the signed record, local intent, checkpoint, cloud effect, or receipt needed to resume.

One endpoint, two small capability trees

Ordinary GET / and HEAD / still serve the cockpit. Same-origin POST / is the universal HTTP-batch path; a WebSocket upgrade at the same root is optional. There is no second experimental HTTP endpoint and no global pipeline Durable Object.

The remote tree is:

CursorsApi.fabric() / mailbox(handle) / cursor(id)
Fabric.resolve(pointer) / place(requirements)
Mailbox.head(proof) / sync(after, proof) / append(envelope, proof) / receipt(id, proof)
CursorBody.resume(signedCursor) / checkpoint()

The dedicated SQLite Worker exposes only body operations over upstream Cap’n Web MessagePort:

LocalBody.describe()
LocalBody.mailbox(handle)
LocalBody.cursor(id)
LocalBody.compileLua(source)

Mailbox.head() / sync(after) / enqueue(envelope) / receipt(id[, accepted])
CursorBody.checkpoint() / enqueueResume(cursor, proof)

The local tree wraps the official @sqlite.org/sqlite-wasm Worker and backend registry from the SQLite body foundation. It does not create a second connection owner, schema, dependency, or VFS policy. Schema setup and ATTACH remain internal. Callers never receive raw write SQL.

SemanticOutboxScheduler recovers the pending envelope through those capabilities and selects the first available named carrier. HTTP batch ships now; LAN, WebRTC, and REST fallback adapters can implement the same bounded delivery interface. It intentionally does not race carriers after an ambiguous partial effect: the durable outbox remains pending until a fresh proof can read back or retry the idempotent envelope.

Complete path

The browser experiment at /capnweb.html exercises the shape below:

MessagePort
  → cursor.enqueueResume(...)
  → mailbox.db transaction
      cursor_resume_intents + cursor_outbox
  → POST / Cap’n Web batch
      mailbox.append(envelope, signedDeliverProof)
      mailbox.receipt(append.id, signedSyncProof)   // no intermediate await
  → existing CloudMailbox deliver policy
  → sender outbox-mirror read-back
  → local mailbox.receipt(effectId, accepted)
  → local outbox complete

The two short-lived proofs use the existing Cursors-Envelope format and are signed for their exact REST-equivalent method and path. The edge adapter forwards them into the existing CloudMailbox router. That path still verifies Ed25519 mailbox identity, consumes nonces, checks persisted sender/recipient profiles, applies friend/stranger rate policy, and enforces mailbox/rule quotas. Cap’n Web does not mint a new grant format.

Remote effects require CURSORS_CAPNWEB_WRITE_MODE=preview-authenticated. Both production and the ordinary stateless PR-preview configurations set it to disabled. wrangler.capnweb-preview.jsonc is the explicit route-free opt-in: it uses the separate curso-rs-capnweb-preview Worker name and therefore separate preview Durable Object namespaces. Read-only root capabilities remain available without write authority.

Bounds before amplification

Every batch reserves limits before expensive work:

An append reserves 28 calls before storage: directory checks, target mailbox, sender mirror, and the existing maximum of 24 reply rules. A dependent receipt reserves two more. The instrumentation reports the conservative reservation as 30. Existing CloudMailbox per-sender hourly limits, inbox/mirror quotas, rule limits, nonce replay checks, and expiry still apply inside that ceiling.

Pipelined versus durable

Results include an instrumentation record. pipelined means live scheduling, lookup, verification, or transport. durable is emitted only after a local SQLite transaction, CloudMailbox acceptance, sender-mirror read-back, or local receipt transaction.

The partial-failure test lets CloudMailbox accept the append, then supplies a receipt proof signed for the wrong method. The second stage fails, the local outbox remains pending, and a fresh Cap’n Web session with a new correct proof recovers the sender-mirror receipt. Byte-identical local retries are idempotent; conflicting reuse of a cursor, envelope, effect, or receipt ID aborts the transaction.

Measurements

Run npm run measure:capnweb for the deterministic protocol-body harness. For CloudMailbox append → sender-mirror receipt:

Measurement current REST Cap’n Web HTTP batch
browser round trips / Worker requests 2 1
request body bytes 113 1,113
response body bytes 439 1,979
independently gzipped protocol bodies 469 1,186
simulated 40 ms RTT 80 ms 40 ms
simulated 150 ms RTT 300 ms 150 ms
worst-case DO calls reserved 30 30

The result is intentionally unromantic: batching halves the network latency turns but costs more bytes, and it does not erase backend policy work.

The current production Vite build reports:

Browser asset raw gzip
isolated Cap’n Web demo entry 4.13 KiB 1.86 KiB
shared identity/proof helper chunk 65.59 KiB 18.78 KiB
official SQLite WASM 864.75 KiB 401.93 KiB
dedicated SQLite body Worker 303.03 KiB build output does not report gzip

The cockpit’s main entry does not absorb the SQLite Worker/WASM payload; the demo loads it only when the local body is opened.

SQLite and VFS boundary

Local writable state remains on official SQLite WASM opfs, opfs-sahpool, or memory backends selected once by the Worker. Local self.db, mailbox.db, world.db, cache.db, and outbox.db may be attached to that connection. A Cloudflare Durable Object database is never attached remotely. Only semantic envelopes, tombstones, cursor records, checkpoints, and receipts cross carriers—never WAL frames or database pages.

Read-only fetched snapshots are a separate follow-up. sql.js-httpvfs and its 2021 HTTP-range design remain valuable prior art, but neither they nor wa-sqlite or sqlite-wasm-http are declared fastest before a common Chromium workload exists.

Explicit exclusions