Saltar al contenido principal

State machines

A state machine is the computational backend for a single user.

The Zeq framework is, first, a state machine substrate — your hash-linked, Zeqond-stamped backend where computation actually runs. Sitting on top of that substrate is a new mathematical language — operators, the master equation, KO42 — that you compose against. Your compute calls come out of your state machine. Your audit entangled state is your state machine. Your encryption boundary, your two API keys (the zeq_ak_* you build applications with and the zsm_* your devices post observations through), and the per-call regulatory envelopes are all bound to it. One user, one state machine.

This page is the concept. The wire-level walk-through is in Spin up your first state machine.


Anatomy

A state machine has six things attached to it:

  • An owner ZID (ZEQ07XXXXXXXXX) — the equation-derived identity that created it.
  • A slug ([a-z0-9-]{1,64}) — the public, URL-routable handle. Every chain endpoint scopes via /:slug.
  • A genesis Zeqond — the integer Zeqond number at spin-up. Used as the seed for the entangled state's first prev_hash.
  • A parent origin — the tenant origin (zeq-dev, your self-hosted origin, etc.). Lets the framework run multi-tenant without slug clashes.
  • A visibility flagis_public exposes the read endpoints (/state, /explore, /explore/sse, /pohc/validate, the aggregator stream) without auth. Private machines require viewer role or above.
  • At least one API key — minted on spin-up, prefixed zsm_, hashed at rest. Every call attributable.

The DB row for this lives in state_machines (alongside state_machine_api_keys, state_machine_roles, audit_log, contracts, contract_transitions, tally_supply, tally_tokens).


Roles

Five roles, in increasing authority:

RoleReadWrite eventsSubmit transitionsManage keysManage rolesDelete machine
nonepublic only
vieweryes
operatoryesyesyes
adminyesyesyesyesyes
owneryesyesyesyesyesyes

Roles are stored per-state-machine — the same ZID can be owner of one machine, operator of another, viewer of a third. The middlewares requireAuth (rejects anonymous) and optionalAuth (allows public reads when is_public=true) gate every chain endpoint; helpers canRead(machine, zid) and canWrite(machine, zid, "operator") enforce the matrix.

API key scopes mirror this: viewer, operator, admin. A minted key carries one scope; a key that's viewer can never write. Owners always hold at least one admin-scope key.


The entangled state

Every state machine has its own audit log — the rows in audit_log filtered by originId = parentOrigin:slug. Each row has:

{
id: bigserial
originId: "zeq-dev:my-iot-fleet"
zeqondNumber: bigint // when it happened, framework time
unixTimestamp: numeric // mirror in Unix seconds
phase: real in [0,1) // (unix mod 0.777) / 0.777
stateHash: char(64) hex // sha256(payload) or supplied hash
prevHash: char(64) hex // chains back to the previous row
transitionId: uuid // unique per row
transitionType: text // "event"|"state"|"file"|"connection"|"contract"|...
proofDigest: char(64) hex? // when the row carries a ZeqProof
zspEnvelope: jsonb? // when the row carries a ZSP-sealed payload
createdAt: timestamptz
}

The first row's prevHash is the genesis seedsha256(slug || ":" || genesisZeqond). Every row after that links: prevHash[N] = stateHash[N-1]. Tampering with any row breaks the entangled state at that row's successor.

Validation is one call:

curl -sS https://zeqapi.com/api/chain/my-iot-fleet/pohc/validate \
-H "Authorization: Bearer ${ZSM_KEY}"

The handler in chain.ts walks the rows in order, recomputes each prevHash, and reports:

{ "ok": true, "valid": true, "count": 17, "broken_at": null,
"genesis_zeqond": "2287439210" }

Validation runs in time linear in the row count and is bounded by ?from=A&to=B if you want to spot-check a window.


What gets stored vs. what gets hashed

The framework runs on a hash-commitment-only doctrine for free-tier machines.

  • The event, state, file, and connection write endpoints accept either a pre-computed hash (preferred — bytes never cross the wire) or a payload (server hashes it and discards the bytes).
  • The entangled state row only persists the 64-char hex digest, the type metadata, the Zeqond number, and the previous hash.
  • Raw bytes are NEVER stored unless the caller explicitly sends a HITE-sealed sealed_event envelope — and even then, the envelope is sealed with the caller's key, not the framework's.
  • Logs emit a clientIdHash and the Zeqond — never the raw ZID, IP, or payload.

This is the AT-REST-ENCRYPTION-POLICY. The framework proves that something happened at Zeqond N with payload-hash h. You keep the bytes. The entangled state is the witness.

If you want server-side bytes — for a hosted contract that needs to read the input later, for example — you opt in via the contract engine, which wraps the payload in a ZSP envelope before persisting (see Contracts).


How a state machine binds to an API key

The binding is 1:1, enforced at three layers:

  1. Mint. POST /api/chain/:slug/keys (or auto-mint at spin-up) writes a row in state_machine_api_keys with state_machine_id = machine.id. The raw key is shown once; only sha256(raw) is persisted.
  2. Resolve. Every authenticated route runs authenticateKey middleware: it hashes the bearer, looks up the row, joins to the state machine, populates res.locals.authZid, res.locals.authPlan, res.locals.authStateMachineId.
  3. Enforce. Every write helper (gateWrite in chain.ts, the contract router, the tally router) checks canWrite(machine, zid, requiredRole) against the resolved ZID. A key minted for one machine cannot write to another, even if you put it in the wrong header.

This is why the doctrine is "every compute comes out of a state machine" — there's no privileged admin key floating outside the binding. Even self-hosted setups follow this; the dev auth-v3 flow auto-creates a state machine and an admin key on registration so you're never key-less.


Public vs. private machines

A is_public: true machine exposes its read surface (state, block, explore, explore/sse, pohc/validate) to anonymous callers and gets fanned out into the public aggregator stream at /api/chain/aggregate/sse. zeqstate.com's observer subscribes to that single stream rather than opening N per-machine SSE connections.

A is_public: false machine returns 401/403 on read attempts without a viewer-role token. Private machines are still on the entangled state — their hashes commit into the same audit log — they just don't appear in the public registry or the aggregate stream.

Toggle is an entangled state-admin action: PATCH /api/chain/:slug with {"is_public": true}.


What it means in practice

When you build on the framework, you stop thinking in users and start thinking in state machines.

  • Building an IoT fleet? One state machine per device, or one per fleet. Each device's heartbeat is a transition; the entangled state is the audit trail.
  • Building an LLM agent? One state machine per agent. Every prompt → every model call → every tool call is a transition. The entangled state proves what the agent saw and what it did.
  • Building a marketplace? One state machine for the contract template, instances per deal. Each deal's escrow → release → settle is a contract transition with a tally token minted on each successful step.
  • Embedding analytics on a marketing site? One state machine for the site. Beacons hit /api/embed/:slug/ingest; transitions land on the entangled state; the dashboard reads the entangled state, not a third-party warehouse.

One key per machine. One chain per machine. One owner per machine. The framework's authority surface is exactly that: this row, signed by this owner, at this Zeqond. Everything else is a function of that.


Next