The review of 2026-10-09: a digest over Go's JSON of a struct changes when a tag is renamed or a field
added, and another language cannot reproduce it. An act is now a flat map of named strings, and both
digests are over length-prefixed values in a fixed order, versioned; a test fails when a field is added
to Ask or Option without being added to the digest.
A warrant named the ask by id and the option by id, and the asker acted on whatever its record held
for them. Now the ask carries, for every option above acknowledge, the digest of exactly what it
performs (Option.Binds, ActDigest), the router puts the digest of the ask it took in the warrant
(Warrant.AskDigest), For refuses a warrant whose digest is not that of the ask the asker keeps, and
Option.Performs refuses an act the option did not bind. A warrant then authorises one act, as the
person was shown it, once.
A merge-check.sh, the repository's layer of the mesh's merge check (mesh/repo-check), in
the TypeScript toolchain: install from the lock file, type-check, run the suite. The Go,
Python, Rust and C bindings are said as not tested there, never passed silently.
A handler takes an event by returning and asks for it again by throwing;
any mesh/event answer but an error is taken. Written down in the README
where event handling is, with what follows for a handler: throw only when
the work was not done, and be safe to run twice.
Both SDKs added the handler before asking the runtime and kept it when the
runtime refused, so a module that asks again until it is taken (every Go
consumer does) ran its handler once per attempt for every event. A refused
subscribe now removes its handler; one the Go runtime did not answer in
time is kept, since the runtime may have bound it.
Five passes are 25 s, shorter than a real hiccup. And a provider's remove can
destroy — a ledger file, a public DNS record — so an adapter without a retire
of its own is mark-only: the consumer keeps its access, is marked and said, and
remove is reached only by a person's delete (hq ADR 0230).
The hourly release let a mistaken bulk unassignment drain one consumer an hour
with nobody deciding. Per novox/hq ADR 0230 a consumer is retired (disabled,
marked, data kept) only after the same unasked set in five consecutive passes;
more than 3, or more than half of those held, waits for approve or reject.
Deletion is a separate act of a person, of a retired consumer only.
Issue 241 withdrew seven consumers in one pass; 0.1.10 refuses a file it
cannot read, and a file read whole that names nobody still withdrew
everybody. A pass that would withdraw more than withdrawAtOnce consumers
(1), or more than withdrawFraction (0.5) of those held, now withdraws
nothing and says so; while the mesh goes on not asking, one is let go every
releaseEveryMs (an hour). A consumer asked for again is kept.
An unreadable, unparsable or unrecognised contributions file was read as "no consumer asks", and the
reconcile pass then removed every consumer it had applied. On 2026-10-04 one such read made the
postgres provider drop every database on the control node. Such a pass now applies and removes
nothing and says why, once; only a file read with a given list withdraws a consumer, and every
removal is logged before it is made.
The harness says 'secret not readable yet' when it cannot open a consumer's
sealed credential, which is true and ordinary on the first pass. It said it
4330 times in three hours on the control machine while two consumers were
never provisioned, because a permission the host will never grant reads
exactly like a file that has not arrived.
After about a minute it says so plainly instead, once and then rarely, and
names what to look at: who owns the file and who this process runs as. The
ownership itself is the controller's to fix.
0.1.9.
Two pull requests bumped 0.1.6 to 0.1.7 within four minutes of each other —
#18 (a module's state through the runtime) and #10 (a provisioner is told
what the mesh derived). Both built and both published @novox/mesh-sdk@0.1.7,
with different contents. The store kept the first.
So the toolchain installs a 0.1.7 that has no `derived`, and every module
compiled in it fails on a field main's source says is there. minio is the
first to hit it; novox's push is refused because of it.
A version is a name for one set of bytes. This takes the next one.
state(name) / stdio.State(name): get, put, delete, keys and watch over the
runtime's mesh/state verbs. A watch's handler is registered under the id it
is asked with, so its current values — which arrive before the watch is
answered — and its changes reach it and no other watch.
Provision gains `derived`; the harness keeps it per applied login so remove()
is told it too, and includes it in the change hash — a provider that renames
what it derives has given the consumer a different resource.
Version 0.1.7. Rebased onto main after the bundles refactor; the record this
cites was written as 0188 and renumbered to 0201, because ADR 0188 on main is
now the bundles record.
Subscribe(pattern, handler) asks the runtime for the module's events and answers each mesh/event
only after the matching handlers returned nil, so the runtime acknowledges it on the bus then and
not before; Ask(key, body) calls a tool as the module. Topic matching is the TypeScript SDK's.
What the TypeScript SDK's stdio loop does, in Go: an empty name serves as MESH_SERVED_MODULE; each
call runs beside the loop so a tool waiting on the runtime does not deadlock it; Emit asks the
runtime (mesh/publish) with the same envelope and headers. README says how to install it and how
releases are tagged.
The stdio channel is the bundle's bus: subscribe asks the runtime (mesh/subscribe), which binds the
module's durable consumer and hands each event over as mesh/event — answered once every matching
handler has taken it, as an error when one failed so the runtime does not acknowledge it; request
asks the runtime (mesh/ask). The session starts as the stdio module loads when the runtime launched
the process, because a handler module subscribes at import, before the launcher says what it
serves; initialize and tools/list are answered once it has. topicMatches as the runtime's. 0.1.6.
A launched bundle holds no bus connection. The stdio loop binds a broker whose publish asks the
runtime (mesh/publish) and resolves when the runtime has published; asking, answering and
subscribing are refused by name. Tool calls run beside the loop, so a tool awaiting the runtime's
answer to its emit does not deadlock the line that carries it. 0.1.5.
serveRegisteredOverStdio lists the registrations of the module named by MESH_SERVED_MODULE by
their own names and every other registration as <seat>.<verb>. Before, the first registration
was taken for the module's own, so a bundle registering its seat first listed the seat's verbs as
its own tools. Contributors are given the process's environment, the bundle's own. 0.1.4.
A node's runtime serves many modules in one process; a module's composed paths and words must
reach its own tools and no other's. The runtime knows which bundle made which registration, so
it says which environment each contributor gets. collectTools(env) is unchanged. 0.1.3.
The node's runtime launches a tools bundle as a process and speaks MCP over stdio to it, so a tool
may be written in any language and the transport stays in the runtime (ADR 0039's refusal, kept).
Each SDK is the loop and the tool type and nothing else: `@novox/mesh-sdk/stdio` (serveStdio, and
serveRegisteredOverStdio for a bundle that already registers the in-process way), python/, go/,
rust/ and c/ beside it, each with an example answering `greet` and the seat verb `node-lamp.on`.
Skeletons, by the operator's direction: the bare minimum one bundle per language needs to be built
and answer, proven here by each example answering tools/list and tools/call on stdio.
Tasks 3.1 and 3.3 of novox/hq ADR 0116. One fixture directory, read by
every implementation's own runner rather than copied into each — a fixture
copied twice is two fixtures, and two fixtures drift.
The README settles what ADR 0074's byte-for-byte can and cannot mean. The
envelope is exact: the subject, the required headers, each name and format,
because those are what two implementations get wrong invisibly. The body is
not: it is the module's payload, and Go sorts a map's keys where JavaScript
keeps insertion order, so demanding identical bytes would commit every
implementation to a canonical JSON encoder to buy a property the mesh never
uses. Said plainly, because read strictly it would have sent somebody
writing one.