Prologue
Introduction
Meet zipRACK
zipRACK is a standard for small, sandboxed web applets and the hosts that run them. The standard is named by its format string, ziprack/1. You build an applet once against @ziprack/sdk, and it runs on any host that speaks that format.
The name carries a double meaning. "zip" is the archive your applet ships as, and it is also zip as in tiny: the standard asks you for a micro footprint, a static bundle that declares what it needs and nothing more. RACK stands for Runtime for Applications, Content, and Knowledge, which is everything the host provides on your applet's behalf.
At its core, zipRACK makes one promise: an applet declares what it needs; it never integrates for it. Nothing in a package holds an endpoint, a key or a credential. Where an applet names an outside service, such as a video provider or a model hint, the Rack decides whether to honour it and builds the integration itself. That is what makes the same zip portable from one Rack to the next.
The Shape of a Rack
zipRACK divides an interactive experience into two halves and draws a hard line between them.
A Rack is any host that implements ziprack/1. It authenticates people, stores every byte an applet produces, makes every outbound call on the applet's behalf, and holds the real-time connection.
An applet is a zip containing a static web app. Once it is uploaded, the Rack validates and extracts it, then serves it inside a sandboxed iframe. Your applet owns layout, branding, media, navigation and interaction, the entire participant-facing experience, and nothing else. It holds no credentials, opens no outbound connections, and has no database:
┌─ Rack ─────────────────────────────────────────────────────┐
│ auth · attempts · records · files · prompts · rooms │
│ ┌─ chrome ────────────────────────────────────────────┐ │
│ │ ┌─ iframe (opaque origin, no allow-same-origin) ──┐ │ │
│ │ │ applet: index.html + assets │ │ │
│ │ │ @ziprack/sdk ⇄ postMessage ⇄ shell │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘Every Rack is standalone, with its own data, its own domain and its own capability set. Your applet runs on any Rack that speaks the format string ziprack/1 and implements the capability versions your manifest declares.
The Trust Boundary
Every Rack renders your applet inside an iframe with the following attributes:
<iframe sandbox="allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox"
allow="autoplay; fullscreen; picture-in-picture; encrypted-media; clipboard-write">Note the absence of allow-same-origin. The applet document has an opaque origin: no cookies, no localStorage, no IndexedDB, no credentialed requests, and no way to call the Rack over HTTP. Everything your applet needs, it asks for over postMessage:
await Rack.activity.answer('intro.connection', 5);That call becomes a ziprack message posted to the parent window. The Rack checks it against your applet's manifest and refuses anything the manifest did not declare. The applet asks; the Rack decides, enforces and persists.
Every capability works this way. An AI call never leaves the frame as HTTP: it leaves as a postMessage the Rack fulfils on the server. Your applet never holds an API key, a channel token or a database handle. The Runtime Environment covers the rules your applet runs under.
Objects
Let's take a look at the objects the standard defines, and the capability each one belongs to:
| Object | What it is | Capability |
|---|---|---|
| Applet | One experience or app, identified by an immutable slug. Has many versions. |
core |
| Version | One uploaded zip. Immutable. Exactly one is current. | core |
| Participant | The person in front of the applet, resolved from the Rack's own users, or a guest. | core |
| Role | A name the participant holds in an applet: built in (member, guest, applet-author, rack-administrator) or declared by the manifest and assigned by the Rack. Sent at launch. |
core |
| Attempt | One participant's run. Holds status, state, segment. Pinned to the version it started on. |
attempt@1 |
| Statement | Append-only record: actor did verb on activity with result. The audit trail and the uniform export. | activity@1 |
| Record | A mutable row in a declared collection, at a declared scope. Working state that carries over to new versions. | db@1 |
| Stored file | A participant upload, addressable only once scanned clean. | storage@1 |
| Room | A shared session: an applet plus its members, each participant or facilitator. |
broadcast@1 |
| Profile attribute | A Rack-owned fact about the participant, released only if requested and permitted. | auth@1 |
Statements and records coexist because they answer different questions. A statement is what happened; a record is what is currently true. Statements are append-only and uniformly shaped, so every applet's statements export in the same shape. Records are mutable and arbitrarily shaped, which is why db@1 requires a JSON Schema: the schema is what buys back the column headers.
Capabilities
A capability is a named, versioned surface your applet may call. There are seven. Your applet declares the ones it uses in its manifest, and the Rack refuses at runtime any call the manifest did not declare:
| Capability | Covers | Entry point |
|---|---|---|
auth@1 |
Who the participant is; released profile attributes | Rack.auth.user() |
attempt@1 |
Lifecycle, resume state, completion, version pinning | Rack.attempt.complete() |
activity@1 |
Append-only statements and the uniform export | Rack.activity.answer(id, value) |
db@1 |
CRUD records in declared collections | Rack.db.collection('ideas').create({…}) |
storage@1 |
Participant file uploads | Rack.storage.put('photos', file) |
ai@1 |
Declared prompt templates: analysis and chat | Rack.ai.ask('summarize', vars) |
broadcast@1 |
Rooms, presence, facilitator control, shared state | Rack.broadcast.join('main') |
An experience is an applet that declares attempt@1 and activity@1; an app is one that declares db@1 and not attempt@1. Capability versions move independently of the format string. See Versioning.
Two things need no capability at all: declared media (Rack.media.embedUrl(id)) and Rack.eject(), which asks the Rack to leave the applet. Roles are core too, so Rack.auth.hasRole(name) works whatever your manifest declares.