170. A browser computes with the portable core, and the server verifies by recomputing
Context
On 2026-10-16 the owner asked for a browser to take part in the mesh. The goal is that a person opens a project's page, opts in, and their browser computes bounded tasks for the project, with every result verified and visible.
An audit of master d116266803 found the following.
The building blocks. There is one crate (apps/majordomus-cli) with no workspace, no
wasm32 target and no browser binding. Canonical JSON is written twice
(mesh/journal.rs and capability/executor.rs).
What the mesh and the server allow today.
- The mesh executes nothing a peer sends (ADR 0050,
docs/MESH.md). - A trusted mesh key is a full peer that claims, hands over and publishes.
- A caller beyond loopback reads and writes nothing (ADR 0126).
executions.reportstores what its reporter says and recomputes nothing.- The server authenticates no caller, and serves no TLS.
- The live channel is one-way and same-origin.
- The content-security policy allows a same-origin Worker but no WebAssembly compilation.
So a browser cannot be a mesh runtime without becoming a full peer. It cannot write without an authentication this repository does not have. And nothing it reports is checked.
Decision
- One portable core. The computations whose answer must agree wherever they run live in
apps/majordomus-core.- The first are canonical JSON, SHA-256 and the canonical digest.
- The crate does no I/O and keeps no clock or randomness.
- It depends only on what the executable already takes (
serde_jsonwith its features,sha2), so no lock gains a package and ADR 0032's boundary holds. - It compiles for the host and for
wasm32-unknown-unknown. - On wasm32 it exports a C ABI of three functions:
mj_alloc,mj_canonical_digestandmj_free. There is no binding generator. - The executable's two canonical-JSON writers become calls into it.
- One fixture judges every runtime.
fixtures/canonical-digest.v1.jsonrecords inputs, their canonical bytes, and digests computed outside the code under test. The native crate, the executable and the wasm32 module, executed rather than only compiled, must each reproduce every row. A port that orders keys by UTF-16 or formats numbers its own way fails a named row. - A browser's result is a claim, and the server recomputes it.
- Verification is a read: the server computes the same function on the same input with the
same core and answers
matchormismatch. - It changes nothing, so ADR 0126 admits it from any caller, and no authentication is needed to verify.
- Agreement among browsers is never proof. The server's recomputation is.
- Verification is a read: the server computes the same function on the same input with the
same core and answers
- A browser is not a mesh runtime. It holds no mesh key, takes no claim, and appends
nothing to the journal. Until the open question below is decided, a browser computes
only:
- on a page this server serves, same-origin;
- with the core's own functions;
- on inputs the page gives it. Nothing a browser returns enters a record, a ledger, the journal or an execution.
Open, and not decided here
The owner decides these; until then the recording half of browser compute fails closed.
- How a remote browser is authenticated, and how it is granted one narrow, expiring task scope, for example a server-signed lease naming the task, the input digest and an expiry. This is the "token or key-based authorization" ADR 0126 leaves open.
- How a browser's result reaches executions, the journal and the Observatory with its own author rather than the relaying node's. This is ADR 0126's second open point.
- TLS: a public HTTPS page cannot call a plain-HTTP server.
- Enrollment, consent records, resource limits and the volunteer page. These are community compute itself, built on the answers above.
Consequences
- A second runtime exists for the canonical digest. Whether two implementations agree is decided by an executed fixture, not by code review.
scripts/ci/crate-boundarynamesmajordomus-coreas an allowed path dependency, with this ADR as the reason.rust-toolchain.tomlnames the wasm32 target, so every machine that builds can prove the module compiles.- The verification route and the Worker page (I2785) are the first visible use. They prove that a browser computes and the server agrees; they record nothing.
Alternatives rejected
- Make the browser a mesh runtime with its own key. A trusted key is a full peer: it claims, hands over and publishes. Granting that to a page visitor is the opposite of a narrow grant.
- Port canonical JSON to JavaScript. JavaScript orders object keys by UTF-16 code unit
and formats numbers differently from
serde_json. Two implementations would diverge silently, and the fixture'sutf8-key-orderandfloatsrows exist to catch exactly that. wasm-bindgen. It adds packages to both locks for three functions a C ABI exports with none.- Trust the reporter (
executions.reportas it is). Nothing would check the result, and every caller beyond loopback is refused a write anyway.