Skip to main content

Publishing & serving an app

A channel app lives on your machine's pages shelf — and a machine can hold many apps, each addressed by its own page slug. An app is more than markup: a published version carries a title, the HTML front end, an optional Python back end, and any supporting files. Publishing is versioned and auditable, exactly like the rest of the framework: nothing is silently overwritten.

The shape of a published app

{
"title": "My App",
"html": "…the front end…",
"pythonScript": "…optional server-side back end…",
"files": ["…supporting assets…"]
}

The pythonScript is what makes a channel full-stack rather than a static document — it's the app's back end, size-capped and run server-side. An app with no Python is a perfectly good pure-HTML channel; an app with it is a full application. Both are the same primitive.

Publish, version, roll back

Apps are managed under your machine (owner writes, zsm_… admin key), and every publish is an immutable version:

GET /api/state-machines/YOUR_MACHINE/pages # list your apps
GET /api/state-machines/YOUR_MACHINE/pages/APP # the latest published version
POST /api/state-machines/YOUR_MACHINE/pages/APP/publish # publish a new version
GET /api/state-machines/YOUR_MACHINE/pages/APP/history # every version (owner-only)
POST /api/state-machines/YOUR_MACHINE/pages/APP/rollback # restore a prior version
POST /api/state-machines/YOUR_MACHINE/pages/APP/unpublish # take it down

History is kept and rollback restores — the same discipline as contract versions. (Agents can publish too — the MCP surface exposes zeq_publish_page.)

Serve — public, sandboxed, audited

The machine serves its published apps publicly, under a strict sandbox:

GET /s/YOUR_MACHINE/… # your app, served to the world

Every delivery runs under a strict Content-Security-Policy sandbox, and — because everything on a machine is observable — even a read appends a row to the machine's audit chain, so an app's traffic shows up in the explorer like any other activity.

Compute — without a key

Inside a served app, call the physics relatively, and the node fills in your machine's identity for you:

// inside an app served at /s/YOUR_MACHINE/…
const r = await fetch("./sdk/zeq/compute", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ operators: ["KO42", "NM19"], inputs: { m: 10, a: 3 } }),
});
// → the same verified envelope as /api/zeq/compute — value 30 N, proof included

./sdk/zeq/<op> resolves to the Site SDK proxy (/s/<machine>/sdk/zeq/<op>), which forwards to the real compute API in the machine's own context. That's the security property: the app ships no key, the browser can't leak one, and yet every visitor gets real, verified physics that lands on the owner's audit chain. The contract surface is proxied the same way at /s/<machine>/sdk/contracts/….

Why this shape

The machine is the trust boundary; a channel is a face on it. The front end is state (versioned, auditable), the back end and compute run in the machine's context (metered, proven), and nothing an app shows can claim more than the machine can prove. Delete the app and the machine — its chain, contracts, and proofs — is untouched.