Skip to content

Host Bridge Protocol

A DAI cartridge communicates with the outside world through a single interface: the postMessage host bridge.

Core Architectural Tenet

A Cartridge Reports Claims; A Host Records Findings

A cartridge may report only what it can compute from its own internal state—such as DIGEST_MISMATCH, UNVERIFIED_SIGNATURE, or KEY_EXPIRED. These are claims. A host logs them as claims, because a compromised cartridge can claim anything.

Anything requiring external knowledge—such as whether the outer shell was rewritten (SHELL_TAMPERED) or whether a key was placed on a revocation list (KEY_REVOKED)—is a host finding. A host must never push external findings inward for the cartridge to repeat.


Message Specifications (bridgeVersion: 1)

Cartridge to Host Messages

1. DAI_HOST_HANDSHAKE

Dispatched immediately after the cartridge has verified its own digests and mounted the application:

typescript
{
  type: "DAI_HOST_HANDSHAKE",
  payload: {
    bridgeVersion: 1,
    documentUuid: "e2b34208-8f81-4ba2-bf01-cb8dbfbb7a8a",
    verified: true,
    payloadFingerprint: "a948e2bb6174..." // SHA-256 over documentUuid and sorted hashes
  }
}

2. DAI_HOST_SAVE

Dispatched when the application requests state persistence:

typescript
{
  type: "DAI_HOST_SAVE",
  payload: {
    html: "<!doctype html>...",      // Full container document
    databaseBytes: Uint8Array,       // Raw SQLite database bytes
    documentUuid: "e2b34208..."
  }
}

3. DAI_HOST_REFUSED

Dispatched when verification fails and the container halts execution before mounting. Sent unconditionally without waiting for a handshake:

typescript
{
  type: "DAI_HOST_REFUSED",
  payload: {
    bridgeVersion: 1,
    reason: "DIGEST_MISMATCH",
    message: "This container has been modified and will not be run.",
    detail: "app/index.js does not match its digest",
    documentUuid: "e2b34208..." // Optional if manifest unreadable
  }
}

4. DAI_HOST_CLOSING

Dispatched during the pagehide lifecycle event when the container document is being torn down:

typescript
{
  type: "DAI_HOST_CLOSING",
  payload: {
    bridgeVersion: 1,
    documentUuid: "e2b34208..."
  }
}

Host to Cartridge Messages

1. DAI_HOST_HANDSHAKE_ACK

Informs the cartridge that an active host runner is present:

typescript
{
  type: "DAI_HOST_HANDSHAKE_ACK",
  payload: {
    bridgeVersion: 1,
    sessionNonce: "…",        // the value the container sent, echoed
    hostClass: "viewer",      // or "editor"; see the specification §4
    applied: ["origin", "shell", "popup", "network", "socket",
              "evaluation", "inline", "handler", "storage"]
  }
}

applied is the host's claim about which §4 clauses it holds, named by the isolation probe's check ids. A container never acts on it. It is there to be checked: mount the probe in the host, and every claimed clause must come back blocked.

DAI_HOST_USED

Sent once, the first time somebody uses the document — a statement the kit ran on their behalf, or a save. A host that offers to install a document should wait for this: before it, an offer stands in front of a person who has not yet seen the thing work.

typescript
{
  type: "DAI_HOST_USED",
  sessionNonce: "…",
  payload: { bridgeVersion: 1, documentUuid: "…", timings: [ … ] }
}

hostClass says what a save through this host does. A viewer keeps a copy of the database on the device and can export a file; it never claims to have written the file it was given. An editor writes the document in place. A host that omits it is treated as a viewer. The container reports the answer to the application on every save as inPlace.

2. DAI_HOST_SAVE_ACK

Reports whether the host successfully persisted the container:

typescript
{
  type: "DAI_HOST_SAVE_ACK",
  payload: {
    status: "ok" | "error",
    error?: string
  }
}

Refusal Reasons Glossary

When a cartridge halts before mounting, it emits one of the following standard refusal reason codes:

Every name a conforming implementation may refuse with, from the registry in src/refusals.ts — which is the source, and which a test holds this table against, so the two cannot drift. Recoverable says whether the person's work is still in hand, as with a lost race or a busy lock, rather than a file that is not what it claims.

Reason CodeRecoverableMeaning
NO_PAYLOADnoNo payload: probably not a container at all.
PAYLOAD_UNREADABLEnoThe payload did not decode or unzip.
PAYLOAD_TOO_LARGEnoThe archive declares, or inflates to, more than this reader will hold.
MANIFEST_MISSINGnoNo manifest, so nothing can be verified.
MANIFEST_UNREADABLEnoThe manifest is not valid JSON.
UNSUPPORTED_ALGORITHMnoA digest algorithm this reader does not implement.
UNSUPPORTED_CRYPTOnoNo WebCrypto: not a secure context.
SECTION_MISSINGnoA required section is absent; the file is incomplete.
UNSUPPORTED_MANIFEST_VERSIONnoA manifestVersion this reader does not know. The file is not damaged; the host needs updating.
UNSUPPORTED_CAPABILITYnoThe document names a capability this reader does not implement. The file is not damaged; the host needs updating. Never opened without the capability: for rosters, sessions and confidentiality that is the hole the capability closes.
RUNTIME_UNAVAILABLEnoPublished without its engine, for a host that already holds those exact bytes. This one does not.
MALFORMED_SESSION_PROFILEnoA session block without requires:[session], the requirement without the block, or a max_parties that is not a positive integer. The two are one declaration; half of it is malformed, not a plain replicated document to open.
SESSION_EXPORT_INCOMPLETEnoExporting an invite for one session, a kept row named a parent in another session: the source document is malformed, an entity's history having crossed sessions. Refused rather than shipping an invite with a parent that never arrives.
SEAT_ALREADY_BOUNDnoA session seat carries bindings from two or more replicas — two parties opened the same invite. The seat is contested and admits neither, order-free and without a clock deciding it. The creator can revoke the seat and issue a new invite.
SEATS_EXCEED_CAPnoA session declares more seats than its signed max_parties allows. The cap is the creator's signed statement of how many may join, so more seats than the cap is malformed.
MERGE_COVERAGEnoA replicated table is neither an author table nor a named system table, so a merge would converge some tables and silently diverge on it. Refused rather than merged incompletely — a system table added without wiring it into the merge set.
CLOSE_NOT_PERMITTEDnoA session declares close=creator, and a replica that is not the creator tried to close it. Only the creator may end this session; the close is refused rather than written as a row that closes nothing.
CANNOT_RESEATnoA reseat was asked for on a session with no contested seat. Reseating replaces a seat's value, dropping every binding to the old one — a repair for a seat two parties opened, and damage to a healthy one. Refused unless a seat is actually contested.
LINK_DAMAGEDnoThe link does not decode: probably cut or wrapped in transit.
LINK_UNSUPPORTEDnoThe link names a carrier version or dictionary this reader does not have.
LINK_UNRECONSTRUCTABLEnoThe link leaves out an entry expecting this host's copy to match the sealed digest, and it does not.
BLOB_MISMATCHnoThe store returned bytes that do not hash to what the link names.
BLOB_UNDECRYPTABLEnoThe link's key does not open the blob: the link was cut or edited.
STORE_REFUSEDnoA store declined to hold this: not a DAI document, too large, or the sidecar disagrees.
DIGEST_MISMATCHnoAn entry does not match its digest, is missing, or is unlisted.
SECTION_MISMATCHnoThe manifest or application section does not match its digest.
DATA_DAMAGEDnoOnly the database disagrees with its record: an interrupted save. The application is intact.
SHELL_MISSINGnoNo sealed copy of the shell, so the bootloader cannot be checked.
SHELL_MISMATCHnoThe shell does not match the sealed copy inside it.
SIGNATURE_UNVERIFIABLEnoA publisher key is present but there is nothing usable to check.
SIGNATURE_UNSUPPORTEDnoA signature format this reader does not implement.
SIGNED_SET_MISMATCHnoThe signed list and the digest list disagree, in either direction.
UNVERIFIED_SIGNATUREnoThe signature does not verify against the key the file carries.
KEY_EXPIREDnoThe container's expiry has passed.
PUBLISHER_MISMATCHnoSigned by a different key than this host pinned for the document.
NO_APPLICATIONnoVerified, but there is no index.html to run.
SCHEMA_INCOMPATIBLEnoThe data's shape is not one the application declared, and no migration reaches it.
SCHEMA_AHEADyesThe data is newer than the application. Do not migrate backwards; offer read-only or an update.
GENERATION_CONFLICTyesAnother window saved first. The work in hand is still in hand.
LOCK_UNAVAILABLEyesAnother program is saving this document right now.
MOUNT_TIMEOUTnoThe application never reported that it started.
BOOT_FAILEDnoThe bootloader threw.
HOST_REFUSEDnoThe host declined for a reason of its own; see the message.

Codes Deliberately Excluded from Cartridges

The following two codes are host findings and are never emitted by a cartridge:

  1. SHELL_TAMPERED: A cartridge cannot detect its own bootloader being modified because that check would run inside the modified code. Detection belongs solely to an external runner comparing the shell against runtime/container.html.
  2. KEY_REVOKED: Revocation requires knowledge from outside the container file. A cartridge carries no revocation list and has no network capability to retrieve one.

Released under the MIT License. Dynamic Application Interface standard.