--- layer: as-is status: implemented code: [mesh-controller internal/catalogue/state.go, mesh-controller internal/broker/state.go, mesh-controller cmd/mesh-controller/push.go, mesh-tools node-tools/internal/bus/state.go, mesh-tools node-tools/internal/launch/launch.go, mesh-sdk src/state, mesh-sdk go/state.go] updated: 2026-10-04 decisions: - 02-DECISIONS/0201-a-module-keeps-its-current-state-in-key-value-buckets-it-declares-and-reaches-through-the-runtime.md --- # A module's state, as it runs **A module keeps the current value of something on the bus, and every machine sees it — including one that joins later.** Since 2026-10-04 a manifest may say `state` (buckets the module owns) and `reads` (another module's, as `.`). The first two modules to use it are the operator's agent on a machine and its licence manager; on the day this was written, three buckets existed on the bus. ## What runs - **The controller** creates a key-value bucket `_` for every declared state, from the catalogue — on every start and, since the first module that declared state found it missing, on every push before the memberships that name it. A bucket nothing declares any more is reported and kept. Every bucket carries the mesh's caps: 256 KiB a value, 64 MiB a bucket. - **The grants**: the machine's runtime is granted, for each bucket a module it carries owns, writing under the bucket's own subjects and reading; for a bucket it only reads, reading. Measured once built, with the composed grants loaded into a server: a reader's write is refused by the server. - **The membership** issued to each assignment lists its buckets by the names the module uses, and whether it may write. - **The runtime** answers `mesh/state.get`, `put`, `delete`, `keys` and `watch` on the bundle's channel. A watch hands the current values — none that is deleted — then every change, each naming the watch it belongs to; it is answered once the current values are delivered. The runtime refuses, with the reason, a state the module was not issued, a reader's write, a key the bus cannot hold, and a value carrying a field named like a credential. - **The SDKs**: `state(name)` in TypeScript, `stdio.State(name)` in Go (tag `go/v0.1.7` and later). ## What the first live use showed - **A refused request is a timeout, not a refusal.** The bus reloads a machine's grants a moment after the push that changed them; a bundle that asks in between waits out its deadline. A module that watches at start therefore watches beside its handshake and asks again until the state answers — the agent module needed two to seven attempts on its first start on each machine. - **A late machine reads the whole set.** A server registered for every machine before one machine was assigned the module reached that machine from the current values at its start. - **The secrets guard is partial and works for what it covers**: an entry carrying an `Authorization` header was refused on the live bus. A sealed value is plain text to an inspector, and is not caught. ## How it is checked The controller's catalogue and broker tests (names, grants, memberships, a bucket asserted in place against a real server); the runtime's tests over a real bus (current values without deletions, refusals, the TypeScript SDK through the runtime); `module check` names a read whose owner on the shelf keeps no such state.