mesh
The mesh of this process: discovery — authenticated observations of other running Majordomus instances in one registry — and cooperation — authenticated links to trusted runtimes of the same repository, replicating sessions, claims, handovers and reviews through one journal every runtime folds into the same state. Discovery grants nothing; a link is admitted per peer, and nothing a peer sends executes anything here.
The mesh, at a glance
Whether the mesh runs in this process and why not when it does not; this node's public identity; every discovery provider with its state and counters; the registry's tallies; and the refused datagrams by reason. The one status every surface shows.
- query
- experimental
- benchmark required
- cache disabled
- MCP tool majordomus_mesh
- MCP resource {"name": "mesh", "uri": "majordomus://mesh"}
No parameters.
Try it
The port is whatever the server logged; 8741 is the one it asks for first. The values are the operation's first example.
curl -s "http://127.0.0.1:8741/api/v1/mesh"
Tested by apps/majordomus-cli/tests/http_serve.rs, which replays the route with these values against a server on a real socket.
Responses
| status | body | meaning |
| 200 |
MeshStatus |
The mesh, at a glance: the result. |
| 400 |
ErrorBody |
invalid_input: The input does not fit the schema: a parameter of the wrong type, an unknown one, or a value the capability rejects. |
| 404 |
ErrorBody |
not_found: The input names something the repository does not hold. |
| 500 |
ErrorBody |
internal: The capability failed for a reason of its own; the message names it. |
| default |
ErrorBody |
Any other failure the transport reports: 405 method_not_allowed for another method on this path, 400 invalid_input for a body that is not a JSON object. |
Claim a scope across the mesh
Claim repository paths for a session. An exclusive claim that meets a live exclusive claim of another session — on this runtime or any runtime this one has heard — is refused as `claim_conflict` with the claims it meets; an advisory claim is recorded and its overlaps reported. A claim lives while its session is open and its runtime beats: a crashed holder's claim expires everywhere on its own. Writes this runtime's journal only.
- command
- experimental
- benchmark waived: transient_state
- cache disabled
- MCP tool majordomus_mesh_claim
Request body — ClaimInput, JSON
example exclusive
{"session":"bench","scope":["docs"]}Try it
The port is whatever the server logged; 8741 is the one it asks for first. The values are the operation's first example.
curl -s -X POST "http://127.0.0.1:8741/api/v1/mesh/claims" -H "content-type: application/json" -d '{"session":"bench","scope":["docs"]}'Tested by apps/majordomus-cli/tests/http_serve.rs, which replays the route with these values against a server on a real socket.
Responses
| status | body | meaning |
| 200 |
Written |
Claim a scope across the mesh: the result. |
| 400 |
ErrorBody |
invalid_input: The input does not fit the schema: a parameter of the wrong type, an unknown one, or a value the capability rejects. |
| 404 |
ErrorBody |
not_found: The input names something the repository does not hold. |
| 422 |
ErrorBody |
refused: The command was understood and turned down for the reason the message gives. |
| 500 |
ErrorBody |
internal: The capability failed for a reason of its own; the message names it. |
| default |
ErrorBody |
Any other failure the transport reports: 405 method_not_allowed for another method on this path, 400 invalid_input for a body that is not a JSON object. |
Release a claim
Release a claim this runtime's current run holds, by its key. A claim written elsewhere is refused as `not_own`: only its holder releases it, and a dead holder's claim expires instead. Writes this runtime's journal only.
- command
- experimental
- benchmark waived: transient_state
- cache disabled
- MCP tool majordomus_mesh_release
example unknown
Try it
The port is whatever the server logged; 8741 is the one it asks for first. The values are the operation's first example.
curl -s -X POST "http://127.0.0.1:8741/api/v1/mesh/claims/release" -H "content-type: application/json" -d '{"claim":"none/c-0"}'Tested by apps/majordomus-cli/tests/http_serve.rs, which replays the route with these values against a server on a real socket.
Responses
| status | body | meaning |
| 200 |
Written |
Release a claim: the result. |
| 400 |
ErrorBody |
invalid_input: The input does not fit the schema: a parameter of the wrong type, an unknown one, or a value the capability rejects. |
| 404 |
ErrorBody |
not_found: The input names something the repository does not hold. |
| 422 |
ErrorBody |
refused: The command was understood and turned down for the reason the message gives. |
| 500 |
ErrorBody |
internal: The capability failed for a reason of its own; the message names it. |
| default |
ErrorBody |
Any other failure the transport reports: 405 method_not_allowed for another method on this path, 400 invalid_input for a body that is not a JSON object. |
Cooperation, at a glance
Whether this runtime cooperates and why not when it does not: its runtime and stream, the repository identity links are matched on, the link protocol and features, the heartbeat and expiry, every linked peer with its link state, last exchange, round trip and failures, every refused candidate with the rule that refused it, and the counters of handshakes, syncs, reconnects, expiries, replicated and refused events.
- query
- experimental
- benchmark required
- cache disabled
- MCP tool majordomus_mesh_cooperation
No parameters.
Try it
The port is whatever the server logged; 8741 is the one it asks for first. The values are the operation's first example.
curl -s "http://127.0.0.1:8741/api/v1/mesh/cooperation"
Tested by apps/majordomus-cli/tests/http_serve.rs, which replays the route with these values against a server on a real socket.
Responses
| status | body | meaning |
| 200 |
CooperationStatus |
Cooperation, at a glance: the result. |
| 400 |
ErrorBody |
invalid_input: The input does not fit the schema: a parameter of the wrong type, an unknown one, or a value the capability rejects. |
| 404 |
ErrorBody |
not_found: The input names something the repository does not hold. |
| 500 |
ErrorBody |
internal: The capability failed for a reason of its own; the message names it. |
| default |
ErrorBody |
Any other failure the transport reports: 405 method_not_allowed for another method on this path, 400 invalid_input for a body that is not a JSON object. |
The mesh self-check
Every prerequisite proved on this machine alone — the declaration parses, the identity loads and, under an allowlist, is on it, the repository has a mesh identity, a UDP socket binds, the multicast group joins, broadcast enables, and the discovery and link protocols sign and verify end to end in memory — and then the server's own verdict: in a shared server, whether an enabled declaration actually activated the mesh and, when it did not, the server's reason, which fails the report. Each failed check names its impact and its remedy. Deterministic, no second node required.
- query
- experimental
- benchmark required
- cache disabled
- MCP tool majordomus_mesh_doctor
- $ mesh doctor
No parameters.
Try it
The port is whatever the server logged; 8741 is the one it asks for first. The values are the operation's first example.
curl -s "http://127.0.0.1:8741/api/v1/mesh/doctor"
Tested by apps/majordomus-cli/tests/http_serve.rs, which replays the route with these values against a server on a real socket.
Responses
| status | body | meaning |
| 200 |
MeshDoctorReport |
The mesh self-check: the result. |
| 400 |
ErrorBody |
invalid_input: The input does not fit the schema: a parameter of the wrong type, an unknown one, or a value the capability rejects. |
| 404 |
ErrorBody |
not_found: The input names something the repository does not hold. |
| 500 |
ErrorBody |
internal: The capability failed for a reason of its own; the message names it. |
| default |
ErrorBody |
Any other failure the transport reports: 405 method_not_allowed for another method on this path, 400 invalid_input for a body that is not a JSON object. |
The cooperation journal
The journal's events above a Lamport stamp, in Lamport order, at most a page: each with its stream, sequence, stamp, repository, signing key, kind and body. The answer's lamport is the next page's `after`.
- query
- experimental
- benchmark required
- cache disabled
- MCP tool majordomus_mesh_events
Query parameters
| name | type | required | description | examples |
| after |
integer (uint64) |
no |
Only events whose Lamport stamp is above this. |
|
| limit |
integer (uint64) |
no |
At most this many (default 100, at most 1000). |
default: 100 |
| newest |
boolean |
no |
The newest `limit` events above `after` instead of the first ones: what a reader who
wants the journal as it stands now asks for. Still in Lamport order, and `lamport` is
then the newest stamp the journal holds, so resuming after it reads only what comes
next. Without it the journal is read forward from `after`, a page at a time. |
|
Try it
The port is whatever the server logged; 8741 is the one it asks for first. The values are the operation's first example.
curl -s "http://127.0.0.1:8741/api/v1/mesh/events?limit=100"
Tested by apps/majordomus-cli/tests/http_serve.rs, which replays the route with these values against a server on a real socket.
Responses
| status | body | meaning |
| 200 |
EventList |
The cooperation journal: the result. |
| 400 |
ErrorBody |
invalid_input: The input does not fit the schema: a parameter of the wrong type, an unknown one, or a value the capability rejects. |
| 404 |
ErrorBody |
not_found: The input names something the repository does not hold. |
| 500 |
ErrorBody |
internal: The capability failed for a reason of its own; the message names it. |
| default |
ErrorBody |
Any other failure the transport reports: 405 method_not_allowed for another method on this path, 400 invalid_input for a body that is not a JSON object. |
The host firewall, as the mesh needs it
What inbound traffic the declaration implies this machine must admit — the multicast group's port, the broadcast port when it is a permitted fallback, every declared hub port whose address is this machine's, and the server's own port when it listens beyond loopback — from the private networks the declaration names; which firewall front this host runs (ufw, nftables, the macOS application firewall); the commands that admit the plan there; what the firewall itself says about the plan now (present, missing, inactive, or unobservable without root); and what the kernel logged it dropping toward those ports in the last five minutes. Fails when a rule is observed missing or a drop was logged. Reads the host; changes nothing.
- query
- experimental
- benchmark waived: external_dependency
- cache disabled
- MCP tool majordomus_mesh_firewall
- $ mesh firewall
Query parameters
| name | type | required | description | examples |
| port |
integer (uint16) |
no |
The port this checkout's server listens on beyond loopback, when it does: a peer
dials it there on the link protocol, so it needs admitting too. Omitted, the plan
holds the discovery ports and the hub ports the declaration gives this machine. |
|
Try it
The port is whatever the server logged; 8741 is the one it asks for first. The values are the operation's first example.
curl -s "http://127.0.0.1:8741/api/v1/mesh/firewall"
Tested by apps/majordomus-cli/tests/http_serve.rs, which replays the route with these values against a server on a real socket.
Responses
| status | body | meaning |
| 200 |
FirewallReport |
The host firewall, as the mesh needs it: the result. |
| 400 |
ErrorBody |
invalid_input: The input does not fit the schema: a parameter of the wrong type, an unknown one, or a value the capability rejects. |
| 404 |
ErrorBody |
not_found: The input names something the repository does not hold. |
| 500 |
ErrorBody |
internal: The capability failed for a reason of its own; the message names it. |
| default |
ErrorBody |
Any other failure the transport reports: 405 method_not_allowed for another method on this path, 400 invalid_input for a body that is not a JSON object. |
Publish a handover to the mesh
Publish a handover record of this checkout — the newest, or the one named under .ai/local/state/handovers/ — to every linked runtime: its task, branch, head and time from its front matter, the issue and milestone given, and its Markdown body, bounded and identified by the body's digest. No path of this machine travels, and no file outside the handovers directory is ever read.
- command
- experimental
- benchmark waived: transient_state
- cache disabled
- MCP tool majordomus_mesh_handover_publish
example latest
Try it
The port is whatever the server logged; 8741 is the one it asks for first. The values are the operation's first example.
curl -s -X POST "http://127.0.0.1:8741/api/v1/mesh/handovers" -H "content-type: application/json" -d '{}'Tested by apps/majordomus-cli/tests/http_serve.rs, which replays the route with these values against a server on a real socket.
Responses
| status | body | meaning |
| 200 |
Written |
Publish a handover to the mesh: the result. |
| 400 |
ErrorBody |
invalid_input: The input does not fit the schema: a parameter of the wrong type, an unknown one, or a value the capability rejects. |
| 404 |
ErrorBody |
not_found: The input names something the repository does not hold. |
| 422 |
ErrorBody |
refused: The command was understood and turned down for the reason the message gives. |
| 500 |
ErrorBody |
internal: The capability failed for a reason of its own; the message names it. |
| default |
ErrorBody |
Any other failure the transport reports: 405 method_not_allowed for another method on this path, 400 invalid_input for a body that is not a JSON object. |
Consume a handover from the mesh
Take a handover another runtime published: record the consumption on the mesh, and write it into this checkout's handovers directory as a record `majordomus handover --resolve` finds on the same branch — once, however often it is consumed.
- command
- experimental
- benchmark waived: transient_state
- cache disabled
- MCP tool majordomus_mesh_handover_consume
example unknown
{"handover":"00000000000000000000000000000000","session":"bench","materialize":false}Try it
The port is whatever the server logged; 8741 is the one it asks for first. The values are the operation's first example.
curl -s -X POST "http://127.0.0.1:8741/api/v1/mesh/handovers/consume" -H "content-type: application/json" -d '{"handover":"00000000000000000000000000000000","session":"bench","materialize":false}'Tested by apps/majordomus-cli/tests/http_serve.rs, which replays the route with these values against a server on a real socket.
Responses
| status | body | meaning |
| 200 |
ConsumeAnswer |
Consume a handover from the mesh: the result. |
| 400 |
ErrorBody |
invalid_input: The input does not fit the schema: a parameter of the wrong type, an unknown one, or a value the capability rejects. |
| 404 |
ErrorBody |
not_found: The input names something the repository does not hold. |
| 422 |
ErrorBody |
refused: The command was understood and turned down for the reason the message gives. |
| 500 |
ErrorBody |
internal: The capability failed for a reason of its own; the message names it. |
| default |
ErrorBody |
Any other failure the transport reports: 405 method_not_allowed for another method on this path, 400 invalid_input for a body that is not a JSON object. |
This machine's node identity
The node identity kept under the user's state directory, public half only: node id, public key, display name. The signing key appears in no projection. Absent is an answer, not an error — the identity is created when a mesh first activates.
- query
- experimental
- benchmark required
- cache disabled
- MCP tool majordomus_mesh_identity
- $ mesh identity
No parameters.
Try it
The port is whatever the server logged; 8741 is the one it asks for first. The values are the operation's first example.
curl -s "http://127.0.0.1:8741/api/v1/mesh/identity"
Tested by apps/majordomus-cli/tests/http_serve.rs, which replays the route with these values against a server on a real socket.
Responses
| status | body | meaning |
| 200 |
MeshIdentityReport |
This machine's node identity: the result. |
| 400 |
ErrorBody |
invalid_input: The input does not fit the schema: a parameter of the wrong type, an unknown one, or a value the capability rejects. |
| 404 |
ErrorBody |
not_found: The input names something the repository does not hold. |
| 500 |
ErrorBody |
internal: The capability failed for a reason of its own; the message names it. |
| default |
ErrorBody |
Any other failure the transport reports: 405 method_not_allowed for another method on this path, 400 invalid_input for a body that is not a JSON object. |
Open a link (the handshake)
The link handshake another runtime posts: a signed hello carrying its runtime card, a fresh nonce and its protocol range. Refused, typed, when malformed, oversized, mis-signed, stale, replayed, of an unsupported protocol, of another repository, untrusted, or this runtime itself; otherwise answered with a signed welcome: this runtime's card, a link id, its marks and the echoed nonce. Changes this process's link table only.
- command
- experimental
- benchmark required
- cache disabled
Request body — Signed, JSON
example malformed
Try it
The port is whatever the server logged; 8741 is the one it asks for first. The values are the operation's first example.
curl -s -X POST "http://127.0.0.1:8741/api/v1/mesh/link/hello" -H "content-type: application/json" -d '{"body":{},"sig":""}'Tested by apps/majordomus-cli/tests/http_serve.rs, which replays the route with these values against a server on a real socket.
Responses
| status | body | meaning |
| 200 |
LinkReply |
Open a link (the handshake): the result. |
| 400 |
ErrorBody |
invalid_input: The input does not fit the schema: a parameter of the wrong type, an unknown one, or a value the capability rejects. |
| 404 |
ErrorBody |
not_found: The input names something the repository does not hold. |
| 422 |
ErrorBody |
refused: The command was understood and turned down for the reason the message gives. |
| 500 |
ErrorBody |
internal: The capability failed for a reason of its own; the message names it. |
| default |
ErrorBody |
Any other failure the transport reports: 405 method_not_allowed for another method on this path, 400 invalid_input for a body that is not a JSON object. |
One sync round of a link
The replication round a linked runtime posts every heartbeat, signed under its link id with a rising counter: its marks and the events this runtime lacks. Ingested — verified end to end, deduplicated, applied in stream order — and answered, signed, with this runtime's marks and the events the peer lacks. An unknown link or a restarted peer is told to say hello again.
- command
- experimental
- benchmark required
- cache disabled
Request body — Signed, JSON
example malformed
Try it
The port is whatever the server logged; 8741 is the one it asks for first. The values are the operation's first example.
curl -s -X POST "http://127.0.0.1:8741/api/v1/mesh/link/sync" -H "content-type: application/json" -d '{"body":{},"sig":""}'Tested by apps/majordomus-cli/tests/http_serve.rs, which replays the route with these values against a server on a real socket.
Responses
| status | body | meaning |
| 200 |
LinkReply |
One sync round of a link: the result. |
| 400 |
ErrorBody |
invalid_input: The input does not fit the schema: a parameter of the wrong type, an unknown one, or a value the capability rejects. |
| 404 |
ErrorBody |
not_found: The input names something the repository does not hold. |
| 422 |
ErrorBody |
refused: The command was understood and turned down for the reason the message gives. |
| 500 |
ErrorBody |
internal: The capability failed for a reason of its own; the message names it. |
| default |
ErrorBody |
Any other failure the transport reports: 405 method_not_allowed for another method on this path, 400 invalid_input for a body that is not a JSON object. |
The discovered nodes
Every runtime this process has observed, one record per node and runtime slot, deduplicated across every discovery source, in node-id order: identity, trust, presence, endpoints, capabilities, repositories, provenance and versions. In memory, gone with the process.
- query
- experimental
- benchmark required
- cache disabled
- MCP tool majordomus_mesh_nodes
No parameters.
Try it
The port is whatever the server logged; 8741 is the one it asks for first. The values are the operation's first example.
curl -s "http://127.0.0.1:8741/api/v1/mesh/nodes"
Tested by apps/majordomus-cli/tests/http_serve.rs, which replays the route with these values against a server on a real socket.
Responses
| status | body | meaning |
| 200 |
NodeList |
The discovered nodes: the result. |
| 400 |
ErrorBody |
invalid_input: The input does not fit the schema: a parameter of the wrong type, an unknown one, or a value the capability rejects. |
| 404 |
ErrorBody |
not_found: The input names something the repository does not hold. |
| 500 |
ErrorBody |
internal: The capability failed for a reason of its own; the message names it. |
| default |
ErrorBody |
Any other failure the transport reports: 405 method_not_allowed for another method on this path, 400 invalid_input for a body that is not a JSON object. |
One runtime
One runtime by its key (`<node>-<runtime>`, or a node id for its first runtime): the machine it runs on, its liveness, its link from here, its sessions and claims, and any refusal recorded against it. Not found is an answer with the reason.
- query
- experimental
- benchmark required
- cache disabled
- MCP tool majordomus_mesh_peer
Query parameters
| name | type | required | description | examples |
| runtime |
string |
yes |
The runtime, `<node>-<runtime>` (a node id alone matches its first runtime). |
unknown: "0000000000000000000000000000000000000000000000000" |
Try it
The port is whatever the server logged; 8741 is the one it asks for first. The values are the operation's first example.
curl -s "http://127.0.0.1:8741/api/v1/mesh/peer?runtime=0000000000000000000000000000000000000000000000000"
Tested by apps/majordomus-cli/tests/http_serve.rs, which replays the route with these values against a server on a real socket.
Responses
| status | body | meaning |
| 200 |
PeerDetail |
One runtime: the result. |
| 400 |
ErrorBody |
invalid_input: The input does not fit the schema: a parameter of the wrong type, an unknown one, or a value the capability rejects. |
| 404 |
ErrorBody |
not_found: The input names something the repository does not hold. |
| 500 |
ErrorBody |
internal: The capability failed for a reason of its own; the message names it. |
| default |
ErrorBody |
Any other failure the transport reports: 405 method_not_allowed for another method on this path, 400 invalid_input for a body that is not a JSON object. |
Who cooperates, machine by machine
Machine → runtime → session → claim: every runtime this one is linked to or has heard through the journal, grouped by machine, local first; each runtime with its liveness, the milliseconds since its beat rose, its link and its newest live claim. The default `summary` view lists only the active sessions, each with its live claims counted and its intent cut; `view=full` lists every session with every claim. `session` and `runtime` narrow either. Remote and local runtimes share one shape, and the whole fold's digest closes the answer: equal digests, equal state.
- query
- experimental
- benchmark required
- cache disabled
- MCP tool majordomus_mesh_peers
Query parameters
| name | type | required | description | examples |
| view |
MeshView | null |
no |
`summary` (the default) or `full`. |
full: "full" |
| claims |
MeshClaimFilter | null |
no |
`all` (the default) or `live`: whether ended claims are kept. |
|
| after |
integer (uint64) |
no |
Only sessions, claims and handovers whose latest Lamport stamp is above this. Reviews
and overlaps carry no stamp of their own and are kept or dropped with their sessions. |
|
| session |
string |
no |
Only what belongs to this session: its key (`<stream>/<session>`) or its bare id. |
|
| runtime |
string |
no |
Only what belongs to this runtime: `<node>-<runtime>`, or a node id for every runtime
of that machine. |
|
| kind |
MeshKind | null |
no |
Only this collection; the others are answered empty. |
|
| limit |
integer (uint) |
no |
At most this many of each collection: the newest by Lamport stamp where it has one,
kept in the fold's own order. |
|
| open |
boolean |
no |
Only what is still open: active sessions, live claims, review requests nobody has
answered and handovers nobody has taken. |
|
| matching |
string |
no |
Only what mentions this text, ignoring case: a path, a branch, an issue, a pull
request, a word of an intent or a subject. |
|
Try it
The port is whatever the server logged; 8741 is the one it asks for first. The values are the operation's first example.
curl -s "http://127.0.0.1:8741/api/v1/mesh/peers?view=full"
Tested by apps/majordomus-cli/tests/http_serve.rs, which replays the route with these values against a server on a real socket.
Responses
| status | body | meaning |
| 200 |
PeerTree |
Who cooperates, machine by machine: the result. |
| 400 |
ErrorBody |
invalid_input: The input does not fit the schema: a parameter of the wrong type, an unknown one, or a value the capability rejects. |
| 404 |
ErrorBody |
not_found: The input names something the repository does not hold. |
| 500 |
ErrorBody |
internal: The capability failed for a reason of its own; the message names it. |
| default |
ErrorBody |
Any other failure the transport reports: 405 method_not_allowed for another method on this path, 400 invalid_input for a body that is not a JSON object. |
Register with this node's mesh
Present one signed envelope; it is verified exactly as a datagram — bounds, staleness, signature, trust policy — and recorded as a rendezvous observation when it holds. The answer carries this node's own envelope and the candidates its registry holds, each verifiable end to end on its own signature. Registration grants nothing: the caller becomes a record, never an authorization. Changes this process's memory only.
- command
- experimental
- benchmark required
- cache disabled
- MCP tool majordomus_mesh_register
example refused
Try it
The port is whatever the server logged; 8741 is the one it asks for first. The values are the operation's first example.
curl -s -X POST "http://127.0.0.1:8741/api/v1/mesh/register" -H "content-type: application/json" -d '{"envelope":{"v":0}}'Tested by apps/majordomus-cli/tests/http_serve.rs, which replays the route with these values against a server on a real socket.
Responses
| status | body | meaning |
| 200 |
RegisterAnswer |
Register with this node's mesh: the result. |
| 400 |
ErrorBody |
invalid_input: The input does not fit the schema: a parameter of the wrong type, an unknown one, or a value the capability rejects. |
| 404 |
ErrorBody |
not_found: The input names something the repository does not hold. |
| 422 |
ErrorBody |
refused: The command was understood and turned down for the reason the message gives. |
| 500 |
ErrorBody |
internal: The capability failed for a reason of its own; the message names it. |
| default |
ErrorBody |
Any other failure the transport reports: 405 method_not_allowed for another method on this path, 400 invalid_input for a body that is not a JSON object. |
Ask the mesh for a review
Ask for a review of a branch, commit or pull request, optionally of one named runtime — which must be linked and carry the `reviews` feature, or the request is refused as `feature_unsupported`. Replicated to every linked runtime. Writes this runtime's journal only.
- command
- experimental
- benchmark waived: transient_state
- cache disabled
- MCP tool majordomus_mesh_review_request
example open
{"session":"bench","subject":"feature/bench","scope":[]}Try it
The port is whatever the server logged; 8741 is the one it asks for first. The values are the operation's first example.
curl -s -X POST "http://127.0.0.1:8741/api/v1/mesh/reviews" -H "content-type: application/json" -d '{"session":"bench","subject":"feature/bench","scope":[]}'Tested by apps/majordomus-cli/tests/http_serve.rs, which replays the route with these values against a server on a real socket.
Responses
| status | body | meaning |
| 200 |
Written |
Ask the mesh for a review: the result. |
| 400 |
ErrorBody |
invalid_input: The input does not fit the schema: a parameter of the wrong type, an unknown one, or a value the capability rejects. |
| 404 |
ErrorBody |
not_found: The input names something the repository does not hold. |
| 422 |
ErrorBody |
refused: The command was understood and turned down for the reason the message gives. |
| 500 |
ErrorBody |
internal: The capability failed for a reason of its own; the message names it. |
| default |
ErrorBody |
Any other failure the transport reports: 405 method_not_allowed for another method on this path, 400 invalid_input for a body that is not a JSON object. |
Answer a review request
Answer a review request from any runtime with approved, changes_requested or commented, and a note. Replicated to every linked runtime. Writes this runtime's journal only.
- command
- experimental
- benchmark waived: transient_state
- cache disabled
- MCP tool majordomus_mesh_review_answer
example unknown
{"request":"none/r-0","session":"bench","verdict":"commented"}Try it
The port is whatever the server logged; 8741 is the one it asks for first. The values are the operation's first example.
curl -s -X POST "http://127.0.0.1:8741/api/v1/mesh/reviews/answer" -H "content-type: application/json" -d '{"request":"none/r-0","session":"bench","verdict":"commented"}'Tested by apps/majordomus-cli/tests/http_serve.rs, which replays the route with these values against a server on a real socket.
Responses
| status | body | meaning |
| 200 |
Written |
Answer a review request: the result. |
| 400 |
ErrorBody |
invalid_input: The input does not fit the schema: a parameter of the wrong type, an unknown one, or a value the capability rejects. |
| 404 |
ErrorBody |
not_found: The input names something the repository does not hold. |
| 422 |
ErrorBody |
refused: The command was understood and turned down for the reason the message gives. |
| 500 |
ErrorBody |
internal: The capability failed for a reason of its own; the message names it. |
| default |
ErrorBody |
Any other failure the transport reports: 405 method_not_allowed for another method on this path, 400 invalid_input for a body that is not a JSON object. |
Withdraw a review request
End a review request this runtime asked for — done, void or superseded — with an optional result note the request then carries. Any session of the asking runtime may (a reconnected asker is another session of it); no other runtime may, and it is never counted as an answer. An unanswered request also reads expired while its runtime has stopped beating. Replicated to every linked runtime. Writes this runtime's journal only.
- command
- experimental
- benchmark waived: transient_state
- cache disabled
- MCP tool majordomus_mesh_review_withdraw
example unknown
{"request":"none/r-0","session":"bench","note":"superseded"}Try it
The port is whatever the server logged; 8741 is the one it asks for first. The values are the operation's first example.
curl -s -X POST "http://127.0.0.1:8741/api/v1/mesh/reviews/withdraw" -H "content-type: application/json" -d '{"request":"none/r-0","session":"bench","note":"superseded"}'Tested by apps/majordomus-cli/tests/http_serve.rs, which replays the route with these values against a server on a real socket.
Responses
| status | body | meaning |
| 200 |
Written |
Withdraw a review request: the result. |
| 400 |
ErrorBody |
invalid_input: The input does not fit the schema: a parameter of the wrong type, an unknown one, or a value the capability rejects. |
| 404 |
ErrorBody |
not_found: The input names something the repository does not hold. |
| 422 |
ErrorBody |
refused: The command was understood and turned down for the reason the message gives. |
| 500 |
ErrorBody |
internal: The capability failed for a reason of its own; the message names it. |
| default |
ErrorBody |
Any other failure the transport reports: 405 method_not_allowed for another method on this path, 400 invalid_input for a body that is not a JSON object. |
Open or update a session on the mesh
Say what a session of this runtime is: its client, worker, intent, task, issue, milestone, branch, head, dirtiness and context revision. Replicated to every linked runtime; a later word replaces an earlier one. Writes this runtime's journal only.
- command
- experimental
- benchmark waived: transient_state
- cache disabled
- MCP tool majordomus_mesh_session_open
example named
{"session":"bench","intent":"benchmark"}Try it
The port is whatever the server logged; 8741 is the one it asks for first. The values are the operation's first example.
curl -s -X POST "http://127.0.0.1:8741/api/v1/mesh/sessions" -H "content-type: application/json" -d '{"session":"bench","intent":"benchmark"}'Tested by apps/majordomus-cli/tests/http_serve.rs, which replays the route with these values against a server on a real socket.
Responses
| status | body | meaning |
| 200 |
Written |
Open or update a session on the mesh: the result. |
| 400 |
ErrorBody |
invalid_input: The input does not fit the schema: a parameter of the wrong type, an unknown one, or a value the capability rejects. |
| 404 |
ErrorBody |
not_found: The input names something the repository does not hold. |
| 422 |
ErrorBody |
refused: The command was understood and turned down for the reason the message gives. |
| 500 |
ErrorBody |
internal: The capability failed for a reason of its own; the message names it. |
| default |
ErrorBody |
Any other failure the transport reports: 405 method_not_allowed for another method on this path, 400 invalid_input for a body that is not a JSON object. |
Close a session on the mesh
End a session of this runtime; every claim it holds ends with it, on every linked runtime. Writes this runtime's journal only.
- command
- experimental
- benchmark waived: transient_state
- cache disabled
- MCP tool majordomus_mesh_session_close
example named
Try it
The port is whatever the server logged; 8741 is the one it asks for first. The values are the operation's first example.
curl -s -X POST "http://127.0.0.1:8741/api/v1/mesh/sessions/close" -H "content-type: application/json" -d '{"session":"bench"}'Tested by apps/majordomus-cli/tests/http_serve.rs, which replays the route with these values against a server on a real socket.
Responses
| status | body | meaning |
| 200 |
Written |
Close a session on the mesh: the result. |
| 400 |
ErrorBody |
invalid_input: The input does not fit the schema: a parameter of the wrong type, an unknown one, or a value the capability rejects. |
| 404 |
ErrorBody |
not_found: The input names something the repository does not hold. |
| 422 |
ErrorBody |
refused: The command was understood and turned down for the reason the message gives. |
| 500 |
ErrorBody |
internal: The capability failed for a reason of its own; the message names it. |
| default |
ErrorBody |
Any other failure the transport reports: 405 method_not_allowed for another method on this path, 400 invalid_input for a body that is not a JSON object. |
The state every linked runtime converges on
The journal folded. The default `summary` view is bounded whatever the history: sessions and claims counted by standing, each runtime with its liveness, its counts and its newest live claim, the open review requests and the handovers nobody has taken, and the digest two runtimes compare. `view=full` is every session, every claim with its standing (held, released, expired, conflicted with its winner), the advisory overlaps, every handover with who consumed it, every review request with its answers, and every stream's liveness on this runtime's clock. `claims=live`, `after` (a Lamport stamp), `session` and `runtime` narrow either view; the digest is always the whole fold's.
- query
- experimental
- benchmark required
- cache disabled
- MCP tool majordomus_mesh_state
Query parameters
| name | type | required | description | examples |
| view |
MeshView | null |
no |
`summary` (the default) or `full`. |
full: "full" |
| claims |
MeshClaimFilter | null |
no |
`all` (the default) or `live`: whether ended claims are kept. |
|
| after |
integer (uint64) |
no |
Only sessions, claims and handovers whose latest Lamport stamp is above this. Reviews
and overlaps carry no stamp of their own and are kept or dropped with their sessions. |
|
| session |
string |
no |
Only what belongs to this session: its key (`<stream>/<session>`) or its bare id. |
|
| runtime |
string |
no |
Only what belongs to this runtime: `<node>-<runtime>`, or a node id for every runtime
of that machine. |
|
| kind |
MeshKind | null |
no |
Only this collection; the others are answered empty. |
|
| limit |
integer (uint) |
no |
At most this many of each collection: the newest by Lamport stamp where it has one,
kept in the fold's own order. |
|
| open |
boolean |
no |
Only what is still open: active sessions, live claims, review requests nobody has
answered and handovers nobody has taken. |
|
| matching |
string |
no |
Only what mentions this text, ignoring case: a path, a branch, an issue, a pull
request, a word of an intent or a subject. |
|
Try it
The port is whatever the server logged; 8741 is the one it asks for first. The values are the operation's first example.
curl -s "http://127.0.0.1:8741/api/v1/mesh/state?view=full"
Tested by apps/majordomus-cli/tests/http_serve.rs, which replays the route with these values against a server on a real socket.
Responses
| status | body | meaning |
| 200 |
MeshStateAnswer |
The state every linked runtime converges on: the result. |
| 400 |
ErrorBody |
invalid_input: The input does not fit the schema: a parameter of the wrong type, an unknown one, or a value the capability rejects. |
| 404 |
ErrorBody |
not_found: The input names something the repository does not hold. |
| 500 |
ErrorBody |
internal: The capability failed for a reason of its own; the message names it. |
| default |
ErrorBody |
Any other failure the transport reports: 405 method_not_allowed for another method on this path, 400 invalid_input for a body that is not a JSON object. |
Prove cooperation now
Run a live verification: cooperation is active, the repository identity resolves, the endpoints are reachable beyond loopback, the heartbeat beats, the journal holds no stuck gap; then one sync round with every peer this runtime dials, timed, and whether both sides now hold the same high-water marks. Each failed check names its impact and remedy. Changes nothing but the journal's replication.
- command
- experimental
- benchmark waived: external_dependency
- cache disabled
- MCP tool majordomus_mesh_verify
Request body — Empty, JSON
example default
Try it
The port is whatever the server logged; 8741 is the one it asks for first. The values are the operation's first example.
curl -s -X POST "http://127.0.0.1:8741/api/v1/mesh/verify" -H "content-type: application/json" -d '{}'Tested by apps/majordomus-cli/tests/http_serve.rs, which replays the route with these values against a server on a real socket.
Responses
| status | body | meaning |
| 200 |
MeshVerifyReport |
Prove cooperation now: the result. |
| 400 |
ErrorBody |
invalid_input: The input does not fit the schema: a parameter of the wrong type, an unknown one, or a value the capability rejects. |
| 404 |
ErrorBody |
not_found: The input names something the repository does not hold. |
| 422 |
ErrorBody |
refused: The command was understood and turned down for the reason the message gives. |
| 500 |
ErrorBody |
internal: The capability failed for a reason of its own; the message names it. |
| default |
ErrorBody |
Any other failure the transport reports: 405 method_not_allowed for another method on this path, 400 invalid_input for a body that is not a JSON object. |
Schemas
The components only this tag's operations use. Each row is the type's own field, its doc comment the description; a type another page owns links there.
AdmitCommand
object
One command that admits a rule: the rule's label, its argument vector exactly as
apply executes it, and the same quoted as one line a person pastes into a root
shell. The two are projections of one vector, so what the report shows is what runs.
use std::net::Ipv4Addr;
use majordomus_cli::mesh::firewall::{plan, render, AdmitCommand, Backend};
use majordomus_cli::mesh::MeshConfig;
let config: MeshConfig = serde_json::from_value(serde_json::json!({
"schema": "mesh/v1", "kind": "mesh-declaration", "id": "doc", "enabled": true,
})).unwrap();
let p = plan(Some(&config), &[Ipv4Addr::new(10, 0, 0, 2)], None);
let commands: Vec<AdmitCommand> = render(Backend::Ufw, &p, None);
assert_eq!(commands[0].argv[0], "ufw");
assert_eq!(commands[0].rule, "udp/7741 to 239.255.77.77 from any");
assert_eq!(commands[0].line, "ufw allow in proto udp to 239.255.77.77 port 7741 comment 'majordomus mesh: multicast discovery'");
| property | type | required | description |
| rule |
string |
yes |
The rule it admits. |
| argv |
array of string |
yes |
The program and its arguments, as executed. |
| line |
string |
yes |
The same, quoted for a shell. |
Used by FirewallReport
Backend
"ufw" | "nftables" | "application_firewall" | "none"
The firewall front this host runs, as far as the executable can tell without privilege:
decided by
detect from the platform and the path, and the thing
render,
observe and
apply are specific to.
use majordomus_cli::mesh::firewall::Backend;
assert_eq!(serde_json::to_value(Backend::ApplicationFirewall).unwrap(), "application_firewall");
assert_eq!(Backend::None.as_str(), "none");
ufwUncomplicated Firewall, the Debian and Ubuntu front: ufw is on the path.nftablesnftables without ufw in front: nft is on the path.application_firewallmacOS: the application firewall (socketfilterfw), which admits executables, not ports.noneNo firewall front this executable knows; nothing is observed and nothing is applied.
Used by FirewallReport
ClaimInput
object
The input of mesh.claim.
| property | type | required | description |
| session |
string | null |
no |
The claiming session; the calling MCP session's own when omitted. Opened when new. |
| client |
string | null |
no |
The client, for a session this opens; cli when omitted. |
| scope |
array of string |
yes |
Repository-relative paths. |
| intent |
string | null |
no |
What the claim is for. |
| mode |
ClaimMode | null → ClaimMode |
no |
exclusive (the default) or advisory. |
| issue |
string | null |
no |
The issue the claim is for. |
| task |
string | null |
no |
The task, for a session this opens. |
Used by mesh.claim
ClaimMode
"exclusive" | "advisory"
Whether a claim excludes others or only tells them. Both are useful and they answer
different questions: an exclusive claim is a worker saying "do not edit this while I
am", an advisory one is saying "I am here". Exclusive is the default because a claim
taken without a thought about the mode is a claim somebody expects to be respected.
use majordomus_cli::mesh::journal::ClaimMode;
assert_eq!(ClaimMode::default(), ClaimMode::Exclusive);
assert_eq!(serde_json::to_value(ClaimMode::Advisory).unwrap(), serde_json::json!("advisory"));
exclusiveExclusive: a second exclusive claim meeting this scope is refused while this one
lives, on every runtime that knows of it.advisoryAdvisory: an announcement, like the peer board's. Overlaps are reported, never
refused.
Used by ClaimInput ClaimView MeshClaimBrief
ClaimOverlap
object
Two live claims of different sessions whose scopes meet, at least one advisory:
reported, never refused.
An overlap is information, not a verdict — it is how two workers find out they are in
the same place without either being stopped. The meeting paths are listed pairwise
rather than summarized, because the useful question is which path of mine meets which
path of theirs.
use std::sync::Arc;
use majordomus_cli::mesh::identity::NodeIdentity;
use majordomus_cli::mesh::journal::{ClaimMode, EventBody, Journal, StreamLiveness};
use majordomus_cli::mesh::state::{fold, ClaimOverlap};
let j = Journal::open(Arc::new(NodeIdentity::ephemeral().unwrap()), "0000000000000001",
"repo".into(), None).unwrap();
for (claim, session, mode) in [
("c1", "s1", ClaimMode::Exclusive),
("c2", "s2", ClaimMode::Advisory),
] {
j.append_own(EventBody::ClaimAcquired { claim: claim.into(), session: session.into(),
scope: vec!["apps".into()], intent: None, mode, issue: None }).unwrap();
}
let state = fold(&j.events(), &|_| StreamLiveness::Own);
let overlap: &ClaimOverlap = &state.overlaps[0];
assert_ne!(overlap.first, overlap.second, "two claims, two sessions");
assert_eq!(overlap.paths, [("apps".to_string(), "apps".to_string())]);
| property | type | required | description |
| first |
string |
yes |
The earlier claim's key. |
| second |
string |
yes |
The later claim's key. |
| paths |
array of array of any |
yes |
The paths that meet, as first ↔ second pairs. |
Used by CooperationState
ClaimState
one of 4 variants
A claim's standing, and — where it ended or lost — what ended it. The two variants that
carry a string exist because the bare verdict is not actionable: a worker told its claim
expired needs to know whether its session closed or its runtime stopped beating, and one
told its claim is conflicted needs the key of the claim that beat it, which is the same
key on every runtime.
use majordomus_cli::mesh::state::ClaimState;
assert!(ClaimState::Held.is_live());
assert!(ClaimState::Conflicted("s/c1".into()).is_live(), "still held, and not the winner");
assert!(!ClaimState::Released.is_live());
// the detail travels with the verdict, so a reader is never left asking who won
let value = serde_json::to_value(ClaimState::Conflicted("s/c1".into())).unwrap();
assert_eq!(value["state"], "conflicted");
assert_eq!(value["detail"], "s/c1");
variant Live; for an exclusive claim, the winner of its scope.
variant Released by its holder.
variant Ended without a release: the holder's session closed or its runtime expired.
| state | "expired" | yes | |
| detail | string | yes | |
variant Live, exclusive, and beaten to its scope by the claim whose key this names.
| state | "conflicted" | yes | |
| detail | string | yes | |
Used by ClaimView
ClaimView
object
One claim anywhere in the mesh: who took what, for what, and where it stands. The
acquisition's Lamport stamp and sequence are carried rather than derived on demand,
because they are what exclusivity is decided by — every runtime ranks two competing
claims by the same triple and so names the same winner, with no message passing and no
"last packet wins".
use std::sync::Arc;
use majordomus_cli::mesh::identity::NodeIdentity;
use majordomus_cli::mesh::journal::{ClaimMode, EventBody, Journal, StreamLiveness};
use majordomus_cli::mesh::state::{fold, ClaimState, ClaimView};
let j = Journal::open(Arc::new(NodeIdentity::ephemeral().unwrap()), "0000000000000001",
"repo".into(), None).unwrap();
j.append_own(EventBody::ClaimAcquired { claim: "c1".into(), session: "s1".into(),
scope: vec!["apps/majordomus-cli".into()], intent: Some("document the mesh".into()),
mode: ClaimMode::Exclusive, issue: Some("#184".into()) }).unwrap();
let state = fold(&j.events(), &|_| StreamLiveness::Own);
let claim: &ClaimView = &state.claims[0];
assert_eq!(claim.scope, ["apps/majordomus-cli"]);
assert_eq!(claim.state, ClaimState::Held);
assert!(claim.acquired_lamport > 0, "the order exclusivity is decided in");
| property | type | required | description |
| key |
string |
yes |
<stream>/<claim>. |
| stream |
StreamId → StreamId |
yes |
The writing stream. |
| runtime |
string |
yes |
<node>-<runtime>. |
| session |
string |
yes |
The holding session's key. |
| scope |
array of string |
yes |
The claimed paths. |
| intent |
string | null |
no |
What it is for. |
| mode |
ClaimMode → ClaimMode |
yes |
Exclusive or advisory. |
| issue |
string | null |
no |
The issue it is for. |
| acquired_lamport |
integer (uint64) |
yes |
The Lamport stamp of the acquisition. |
| acquired_seq |
integer (uint64) |
yes |
The sequence of the acquisition within its stream: with the stamp and the stream,
the total order in which exclusivity is decided. |
| state |
ClaimState → ClaimState |
yes |
Its standing. |
Used by CooperationState SessionNode
ConsumeAnswer
object
The answer of mesh.handover.consume.
| property | type | required | description |
| handover |
HandoverView → HandoverView |
yes |
The handover, as the journal holds it. |
| written |
Written → Written |
yes |
The consumption event. |
| path |
string | null |
no |
The local record written, relative to the repository root, when materialized. |
Used by mesh.handover.consume
CooperationCounters
object
The link and replication counters since start: what this runtime did, not what it
holds. They are monotonic and never reset, so two readings a minute apart are a rate —
which is the only way to tell a mesh that is working from one that is retrying, since
both show the same peers in the same states.
The counts are split by direction throughout (handshakes_out against
handshakes_in, refused_in against refused_out) because a runtime that refuses
everyone and one that everyone refuses need opposite remedies.
#### use std::collections::BTreeMap;
#### use std::sync::{Arc, Mutex, Weak};
#### use majordomus_cli::mesh::config::{CooperationConfig, TrustConfig};
#### use majordomus_cli::mesh::cooperation::*;
#### use majordomus_cli::mesh::identity::NodeIdentity;
#### use majordomus_cli::mesh::link::{LinkReply, LinkTransport, Signed, HELLO_PATH};
#### use majordomus_cli::mesh::registry::MeshRegistry;
#### use majordomus_cli::mesh::repository::{of_root_commits, runtime_id};
#### use majordomus_cli::mesh::trust::TrustPolicy;
#### #[derive(Default)] struct Net(Mutex<BTreeMap<String, Weak<Cooperation>>>);
#### impl LinkTransport for Net {
#### fn post(&self, e: &str, p: &str, m: &Signed) -> Result<LinkReply, String> {
#### let peer = (self.0.lock().unwrap().get(e).and_then(Weak::upgrade))
#### .ok_or_else(|| format!("{e}: nobody there"))?;
#### Ok(if p == HELLO_PATH { peer.accept_hello(m) } else { peer.accept_sync(m) }) } }
#### let net = Arc::new(Net::default());
#### let runtime = |endpoint: &str| { let c = Cooperation::new(CooperationSetup {
#### identity: Arc::new(NodeIdentity::ephemeral().unwrap()), runtime: runtime_id(endpoint),
#### repository: of_root_commits(&["root".into()]), endpoints: vec![endpoint.into()],
#### version: "doc".into(), config: CooperationConfig::default(),
#### trust: TrustConfig { policy: TrustPolicy::Tofu, allow: vec![] }, journal_path: None,
#### registry: Arc::new(MeshRegistry::new()),
#### transport: Arc::clone(&net) as Arc<dyn LinkTransport>, board: None,
#### checkout: CheckoutFacts::default() }).unwrap();
#### net.0.lock().unwrap().insert(endpoint.into(), Arc::downgrade(&c)); c };
#### let (a, b) = (runtime("a:1"), runtime("b:1"));
let key = a.dial(&["b:1".into()]).unwrap();
a.sync_with(&key).unwrap();
let dialer: CooperationCounters = a.status().counters;
let answerer = b.status().counters;
assert_eq!((dialer.handshakes_out, dialer.syncs_out), (1, 1));
assert_eq!((answerer.handshakes_in, answerer.syncs_in), (1, 1), "the other side of it");
assert_eq!(answerer.handshakes_out, 0, "B never dialed anybody");
| property | type | required | description |
| handshakes_out |
integer (uint64) |
yes |
Handshakes this runtime completed as the dialer. |
| handshakes_in |
integer (uint64) |
yes |
Handshakes this runtime accepted. |
| dial_failures |
integer (uint64) |
yes |
Dials that reached no endpoint. |
| syncs_out |
integer (uint64) |
yes |
Sync rounds this runtime completed as the dialer. |
| syncs_failed |
integer (uint64) |
yes |
Sync rounds that failed in transport. |
| syncs_in |
integer (uint64) |
yes |
Sync rounds this runtime answered. |
| events_sent |
integer (uint64) |
yes |
Events pushed in sync requests. |
| events_served |
integer (uint64) |
yes |
Events served in sync answers. |
| reconnects |
integer (uint64) |
yes |
Re-handshakes after an expiry or a dropped link. |
| restarts |
integer (uint64) |
yes |
Peer restarts observed. |
| peers_expired |
integer (uint64) |
yes |
Links expired. |
| claims_refused |
integer (uint64) |
yes |
Claims refused for a conflict. |
| threads |
integer (uint64) |
yes |
Cooperation threads alive now. |
| refused_in |
map of integer (uint64) |
yes |
Hellos and syncs this runtime refused, by code. |
| refused_out |
map of integer (uint64) |
yes |
Refusals this runtime received or applied to a welcome, by code. |
Used by CooperationStatus
CooperationState
object
The whole folded state, every collection in key order, and its digest: two runtimes
that agree print the same digest.
The digest is the point. Convergence is otherwise a claim nobody can check from one
machine; with it, two operators compare one short string and know whether their
runtimes are looking at the same world. opaque_events is the other half of that
honesty: events of kinds this executable does not interpret are counted and relayed
rather than dropped, so a newer peer's traffic is visible instead of silently missing.
use std::sync::Arc;
use majordomus_cli::mesh::identity::NodeIdentity;
use majordomus_cli::mesh::journal::{EventBody, Journal, SessionInfo, StreamLiveness};
use majordomus_cli::mesh::state::{fold, CooperationState};
let j = Journal::open(Arc::new(NodeIdentity::ephemeral().unwrap()), "0000000000000001",
"repo".into(), None).unwrap();
j.append_own(EventBody::SessionOpened { info: SessionInfo::named("s1", "cli") }).unwrap();
let events = j.events();
let state: CooperationState = fold(&events, &|_| StreamLiveness::Own);
assert_eq!(state.digest.len(), 32);
// the same events in another order, and twice over, are the same state
let mut shuffled = events.clone();
shuffled.reverse();
shuffled.extend(events);
assert_eq!(fold(&shuffled, &|_| StreamLiveness::Own).digest, state.digest);
| property | type | required | description |
| sessions |
array of SessionView → SessionView |
yes |
Every session. |
| claims |
array of ClaimView → ClaimView |
yes |
Every claim. |
| overlaps |
array of ClaimOverlap → ClaimOverlap |
yes |
Advisory overlaps between live claims. |
| handovers |
array of HandoverView → HandoverView |
yes |
Every handover. |
| reviews |
array of ReviewView → ReviewView |
yes |
Every review request. |
| opaque_events |
integer (uint) |
yes |
Events of kinds this executable does not interpret. |
| digest |
string |
yes |
32 hex of the SHA-256 of the canonical JSON of everything above. |
Used by MeshStateAnswer
CooperationStatus
object
The whole cooperation runtime, as every surface reports it: the command line, the HTTP
surface and MCP all render this one value, so that a person and an agent asking the same
question of the same runtime are never told different things.
It answers in one reading the three questions a mesh raises — is this runtime
cooperating at all, what does it speak, and who is it linked to — and it stays
answerable when the answer is no: an inactive runtime still reports its protocol range
and the reason, rather than an absence the caller has to interpret.
#### use std::sync::Arc;
#### use majordomus_cli::mesh::config::{CooperationConfig, TrustConfig};
#### use majordomus_cli::mesh::cooperation::{CheckoutFacts, Cooperation, CooperationSetup};
#### use majordomus_cli::mesh::identity::NodeIdentity;
#### use majordomus_cli::mesh::link::HttpTransport;
#### use majordomus_cli::mesh::registry::MeshRegistry;
#### use majordomus_cli::mesh::repository::of_root_commits;
#### let cooperation = Cooperation::new(CooperationSetup {
#### identity: Arc::new(NodeIdentity::ephemeral().unwrap()), runtime: "0000000000000001".into(),
#### repository: of_root_commits(&["root".into()]), endpoints: vec!["127.0.0.1:9".into()],
#### version: "doc".into(), config: CooperationConfig::default(), trust: TrustConfig::default(),
#### journal_path: None, registry: Arc::new(MeshRegistry::new()),
#### transport: Arc::new(HttpTransport), board: None, checkout: CheckoutFacts::default(),
#### }).unwrap();
use majordomus_cli::mesh::cooperation::CooperationStatus;
let running: CooperationStatus = cooperation.status();
assert_eq!(running.runtime.as_deref(), Some(cooperation.runtime_key()));
assert!(running.peers.is_empty(), "nothing is dialed until something dials");
// and where there is no runtime at all, the same shape says why
let none = CooperationStatus::inactive("the mesh declaration is disabled");
assert_eq!(none.protocol, running.protocol, "still answerable about what it speaks");
| property | type | required | description |
| active |
boolean |
yes |
Whether cooperation runs in this process. |
| reason |
string | null |
no |
Why not, when it does not. |
| runtime |
string | null |
no |
This runtime, <node>-<runtime>. |
| stream |
StreamId | null → StreamId |
no |
This runtime's current stream. |
| repository |
MeshRepositoryIdentity | null → MeshRepositoryIdentity |
no |
The repository identity links are matched on. |
| protocol |
array of any |
yes |
The link protocol range spoken. |
| features |
array of string |
yes |
The link features carried. |
| heartbeat_seconds |
integer (uint64) |
yes |
Seconds between heartbeats. |
| expiry_seconds |
integer (uint64) |
yes |
Seconds of silence before expiry. |
| endpoints |
array of string |
yes |
Where this runtime answers. |
| seeds |
array of string |
yes |
The declared seeds. |
| peers |
array of PeerView → PeerView |
yes |
Every linked peer, by runtime key. |
| refused |
array of RefusedView → RefusedView |
yes |
Candidates refused, by endpoint. |
| counters |
CooperationCounters → CooperationCounters |
yes |
Counters. |
| journal |
JournalTallies → JournalTallies |
yes |
The journal's tallies. |
| started_at |
string | null |
no |
When cooperation started, RFC 3339. |
Used by mesh.cooperation
DoctorCheck
object
One check's verdict.
| property | type | required | description |
| check |
string |
yes |
What was checked. |
| ok |
boolean |
yes |
Whether it holds. |
| detail |
string |
yes |
The evidence: a path, an address, an error. |
| impact |
string | null |
no |
What a failure or a limitation means for the mesh. |
| remediation |
string | null |
no |
What to do about it. |
Used by MeshDoctorReport
Durability
"synced" | "memory" | "not_durable" | "unknown"
Whether an event this runtime wrote reached this machine's disk: what an append
acknowledges, and the only thing a caller may say about it.
use majordomus_cli::mesh::journal::Durability;
assert_eq!(serde_json::to_value(Durability::NotDurable).unwrap(), "not_durable");
assert_eq!(Durability::default(), Durability::Unknown, "an answer that did not say");
assert!(Durability::Synced.is_on_disk() && !Durability::NotDurable.is_on_disk());
syncedAppended to the journal file and synced (fsync of its data) before the append
returned.memoryThe journal has no file: it lives in memory and its peers, by configuration.not_durableThe write to the journal file failed. The event is held in memory and replicated to
peers, and is lost to this machine when the runtime restarts unless a peer still holds
it. It is not acknowledged as durable.unknownNot reported: an answer from an executable that predates this field.
Used by Written
Empty
object
No input.
Used by mesh.verify
Envelope
object
The wire envelope: the advertisement plus its signature.
| property | type | required | description |
| v |
integer (uint32) |
yes |
Protocol version. |
| pk |
string |
yes |
The Ed25519 public key, hex. The node id is derived from it, never carried:
a field that could disagree with the key is a field somebody will forge. |
| inst |
InstanceId → InstanceId |
yes |
This run of the node. |
| name |
string |
no |
A human name for the node. Presentation only. |
| seq |
integer (uint64) |
yes |
Monotonic per instance: a replayed datagram carries a counter the reader has
already seen and is dropped. |
| ts |
integer (uint64) |
yes |
Seconds since the Unix epoch at signing time. |
| ep |
array of string |
no |
Where this node answers HTTP, as host:port authorities, at most
MAX_ENDPOINTS. Advisory: an endpoint is where to try, identity is the key. |
| caps |
array of string |
no |
The transports the node serves: http, mcp, ws. Sanitized capability
awareness, never an authorization. |
| repos |
array of string |
no |
The repositories this node serves, as mesh repository ids (32 hex, see
super::repository): derived from the repository's root commits or its declared
identity, never from a path, so that one repository cloned on two machines is
recognised as one. A digest discloses nothing; a reader that serves the same
repository recognises it, anyone else learns only "some repo". |
| ver |
string |
no |
The executable version, informational. |
| rt |
string |
no |
The runtime slot (16 hex): which server of this node — one per checkout. Empty in
version 1. Omitted from the signed bytes when empty, so a version-1 signature
verifies unchanged. |
| sig |
string |
yes |
Ed25519 over Advertisement::signing_bytes, hex. |
Used by RegisterAnswer
EventList
object
The answer of mesh.events.
| property | type | required | description |
| active |
boolean |
yes |
Whether cooperation runs. |
| count |
integer (uint) |
yes |
How many events this answer carries. |
| lamport |
integer (uint64) |
yes |
The after of the next page: the stamp of the last event this page carries, or the
after that was asked when it carries none. A page never ends inside a run of equal
stamps, so resuming after it misses nothing (I2140). |
| events |
array of MeshEvent → MeshEvent |
yes |
The events, in Lamport order. |
Used by mesh.events
FirewallObservation
object
The firewall's own word, judged against the plan: the state, the evidence behind it,
and the labels of the rules not admitted when the state is
missing.
use majordomus_cli::mesh::firewall::{judge_ufw, plan, FirewallObservation, FirewallObservationState};
let o: FirewallObservation = judge_ufw("Status: inactive\n", &plan(None, &[], None));
assert_eq!(o.state, FirewallObservationState::Inactive);
assert!(o.missing.is_empty() && o.detail.contains("inactive"));
| property | type | required | description |
| state |
FirewallObservationState → FirewallObservationState |
yes |
The verdict. |
| detail |
string |
yes |
The evidence: the status line, the rules found, or the refusal. |
| missing |
array of string |
no |
The rules not admitted, by label, when state is missing. |
Used by FirewallReport
FirewallObservationState
"no_backend" | "inactive" | "present" | "missing" | "unobservable"
What the host's firewall says about the plan, as one word. Only
missing is a failure
on its own;
unobservable is an absence that the kernel log may still turn into one.
use majordomus_cli::mesh::firewall::FirewallObservationState;
assert_eq!(serde_json::to_value(FirewallObservationState::Unobservable).unwrap(), "unobservable");
no_backendNo backend to ask.inactiveThe firewall is off: nothing filters, nothing is needed.presentEvery rule of the plan is admitted.missingAt least one rule of the plan is not admitted.unobservableThe firewall could not be asked from this process — it needs root.
Used by FirewallObservation
FirewallPlan
object
What the declaration implies this host must admit: the rules, the networks they admit
from, the hub ports this machine is listed for, and the addresses the plan saw. The
answer of
plan; an empty
rules is "nothing is needed", which is the answer for no
declaration and for a disabled one.
use majordomus_cli::mesh::firewall::{plan, FirewallPlan};
let nothing: FirewallPlan = plan(None, &[], None);
assert!(nothing.rules.is_empty() && nothing.hub_ports.is_empty() && nothing.networks.is_empty());
| property | type | required | description |
| local_addresses |
array of string |
yes |
This machine's addresses, as the plan saw them. |
| networks |
array of string |
yes |
The private networks peers come from: the enclosing ranges of every address the
declaration names, or of this machine's own when it names none. |
| hub_ports |
array of integer (uint16) |
yes |
The ports of the declared hubs this machine is. |
| rules |
array of FirewallRule → FirewallRule |
yes |
The admissions, in a fixed order. |
Used by FirewallReport
FirewallReport
object
The whole report: the plan, the backend, the commands, the firewall's word and the
kernel's, with one verdict sentence and one
ok. What
mesh.firewall answers and
what the doctor's
firewall check reads.
use majordomus_cli::mesh::firewall::{report, FirewallReport, BLOCK_WINDOW_SECONDS};
let r: FirewallReport = report(None, &[], None);
assert!(r.ok && r.commands.is_empty());
assert_eq!(r.window_seconds, BLOCK_WINDOW_SECONDS);
assert_eq!(r.platform, std::env::consts::OS);
| property | type | required | description |
| ok |
boolean |
yes |
Whether the host admits the mesh as far as it can be observed: nothing missing, and
nothing logged as dropped. |
| platform |
string |
yes |
The platform (linux, macos, …). |
| backend |
Backend → Backend |
yes |
The firewall front found. |
| executable |
string | null |
no |
The executable the server runs as, which is what the macOS firewall admits. |
| plan |
FirewallPlan → FirewallPlan |
yes |
What the declaration implies this host must admit. |
| commands |
array of AdmitCommand → AdmitCommand |
yes |
The commands that admit it on this backend; what apply runs. |
| observation |
FirewallObservation → FirewallObservation |
yes |
The firewall's own word about the plan. |
| window_seconds |
integer (uint64) |
yes |
The window blocked_recently covers, in seconds. |
| blocked_recently |
object | null |
no |
Drops the kernel logged toward the plan's ports within the window, per port;
absent when the log could not be read. |
| verdict |
string |
yes |
The sentence a person reads. |
Used by mesh.firewall
FirewallRule
object
One inbound admission the mesh needs on this host: a protocol and a port, the group it is
addressed to when it is multicast, the networks it may come from, and the sentence that
says why. A rule is derived, never written by hand;
FirewallRule::label is how a
verdict names it.
use majordomus_cli::mesh::firewall::{FirewallRule, Protocol, Role};
let rule = FirewallRule {
role: Role::Hub,
protocol: Protocol::Tcp,
port: 8791,
destination: None,
sources: vec!["192.168.7.0/24".into()],
reason: "this machine is a declared rendezvous hub".into(),
};
assert_eq!(rule.label(), "tcp/8791 from 192.168.7.0/24");
| property | type | required | description |
| role |
Role → Role |
yes |
Why. |
| protocol |
Protocol → Protocol |
yes |
The transport protocol. |
| port |
integer (uint16) |
yes |
The destination port. |
| destination |
string | null |
no |
The destination address when it is a multicast group; None for this host's own. |
| sources |
array of string |
yes |
The networks the traffic may come from, as CIDR; empty admits any source, which is
only ever the case for the multicast group, whose TTL keeps it on the segment. |
| reason |
string |
yes |
The sentence a person reads beside the rule. |
Used by FirewallPlan
HandoverBody
object
A handover as the mesh carries it: the record's facts and its body, bounded.
What travels is the writing, plus the few facts that let a receiver file it — the task,
the issue, the branch, the commit. No path of the author's disk is among them, because
the checkout that reads this is somewhere else and a path from another machine is at
best noise. Identity is the digest of the body, so a handover published twice by a
retry or by two runtimes is one handover everywhere.
use majordomus_cli::mesh::journal::HandoverBody;
let body = "# Objective\nship the mesh\n".to_string();
let handover = HandoverBody {
id: HandoverBody::digest_of(&body),
task: Some("t-1".into()),
issue: Some("#184".into()),
milestone: None,
branch: Some("feature/mesh-cooperation".into()),
head: None,
created_at: None,
name: None,
body,
};
assert_eq!(handover.id, HandoverBody::digest_of(&handover.body));
let wire = serde_json::to_value(&handover).unwrap();
assert!(wire.get("milestone").is_none(), "what it does not say is omitted");
| property | type | required | description |
| id |
string |
yes |
The content digest (32 hex): the same handover published twice is one handover. |
| task |
string | null |
no |
The task the handover closes or continues. |
| issue |
string | null |
no |
The issue it belongs to. |
| milestone |
string | null |
no |
The milestone it belongs to. |
| branch |
string | null |
no |
The branch it was written on. |
| head |
string | null |
no |
The commit it was written at. |
| created_at |
string | null |
no |
When it was written, RFC 3339, as its author's clock said. |
| name |
string | null |
no |
The record's file name at its origin, informational. |
| body |
string |
yes |
The Markdown body (# Objective, # Current State, # Next Action, ...). |
Used by HandoverView
HandoverConsumeInput
object
The input of mesh.handover.consume.
| property | type | required | description |
| handover |
string |
yes |
The handover's id (its content digest). |
| session |
string | null |
no |
The consuming session; the calling MCP session's own when omitted. |
| materialize |
boolean | null |
no |
Whether to write it as a local handover record majordomus handover --resolve
finds (default true). |
Used by mesh.handover.consume
HandoverPublishInput
object
The input of mesh.handover.publish.
| property | type | required | description |
| path |
string | null |
no |
The record, relative to the repository root and inside
.ai/local/state/handovers/; the newest record when omitted. Nothing outside that
directory is ever read by this command. |
| issue |
string | null |
no |
The issue the handover belongs to. |
| milestone |
string | null |
no |
The milestone it belongs to. |
Used by mesh.handover.publish
HandoverView
object
A handover anywhere in the mesh, with everyone who has taken it. The id is the digest
of the body, so the same handover published twice — by a retry, or by two runtimes that
both saw it — is one handover here; and
consumed_by is what keeps two workers from
silently picking up the same continuity, since the list is folded from events every
runtime holds.
use std::sync::Arc;
use majordomus_cli::mesh::identity::NodeIdentity;
use majordomus_cli::mesh::journal::{EventBody, HandoverBody, Journal, StreamLiveness};
use majordomus_cli::mesh::state::{fold, HandoverView};
let j = Journal::open(Arc::new(NodeIdentity::ephemeral().unwrap()), "0000000000000001",
"repo".into(), None).unwrap();
let body = "# Objective\nship\n".to_string();
let handover = HandoverBody { id: HandoverBody::digest_of(&body), task: None, issue: None,
milestone: None, branch: Some("feature/x".into()), head: None, created_at: None,
name: None, body };
let id = handover.id.clone();
j.append_own(EventBody::HandoverPublished { handover }).unwrap();
j.append_own(EventBody::HandoverConsumed { handover: id.clone(), session: "s1".into() })
.unwrap();
let state = fold(&j.events(), &|_| StreamLiveness::Own);
let view: &HandoverView = &state.handovers[0];
assert_eq!(view.id, id, "identified by what it says, not by who said it");
assert_eq!(view.consumed_by.len(), 1, "somebody has picked this one up");
| property | type | required | description |
| id |
string |
yes |
The content digest. |
| stream |
StreamId → StreamId |
yes |
The stream that first published it. |
| runtime |
string |
yes |
<node>-<runtime> of the publisher. |
| published_lamport |
integer (uint64) |
yes |
The Lamport stamp of the first publication. |
| handover |
HandoverBody → HandoverBody |
yes |
The record's facts and body. |
| consumed_by |
array of string |
yes |
Every session that consumed it, by key, in Lamport order. |
Used by ConsumeAnswer CooperationState
InstanceId
string
One run of a node: 16 hex characters of OS entropy, new at every process start. A
restart keeps the node id and changes this, which is how a reader tells "the same
node came back" from "the same process is still there".
Used by Envelope NodeRecord PublicIdentity
JournalTallies
object
The journal's counters since start: how much it holds, how much it has written,
received, absorbed, refused, held back and compacted away. They are what makes a
replication problem diagnosable from one machine — a runtime whose
received never
rises is not linked, one whose
rejected rises is being sent something it will not
take, and one whose
pending stays high is missing an event ahead of what it has.
use std::sync::Arc;
use majordomus_cli::mesh::identity::NodeIdentity;
use majordomus_cli::mesh::journal::{EventBody, Journal, JournalTallies};
let j = Journal::open(Arc::new(NodeIdentity::ephemeral().unwrap()), "0000000000000001",
"repo".into(), None).unwrap();
let before: JournalTallies = j.tallies();
assert_eq!((before.written, before.events), (0, 0));
j.append_own(EventBody::SessionClosed { session: "s1".into() }).unwrap();
let after = j.tallies();
assert_eq!((after.written, after.events, after.streams), (1, 1, 1));
assert!(after.lamport > before.lamport);
| property | type | required | description |
| streams |
integer (uint) |
yes |
Streams tracked. |
| events |
integer (uint) |
yes |
Events held. |
| written |
integer (uint64) |
yes |
Events written by this runtime since start. |
| received |
integer (uint64) |
yes |
Events received and stored. |
| duplicates |
integer (uint64) |
yes |
Duplicate deliveries absorbed. |
| rejected |
integer (uint64) |
yes |
Events refused. |
| pending |
integer (uint) |
yes |
Events held for a gap right now. |
| compacted |
integer (uint64) |
yes |
Events dropped by compaction since start. |
| opaque |
integer (uint) |
yes |
Events of a kind this executable does not interpret, held and relayed. |
| lamport |
integer (uint64) |
yes |
The highest Lamport stamp seen. |
| write_failures |
integer (uint64) |
no |
Appends to the journal file that failed since start: the events were held in memory
and replicated, and are not on this machine's disk. Nonzero fails mesh doctor. |
| unreadable |
integer (uint64) |
no |
Lines of the journal file the last reload could not read: torn, corrupt, or written
by something that is not this journal. Counted, never silently dropped. |
| unread_bytes |
integer (uint64) |
no |
Bytes of the journal file the last reload left unread because they lie beyond
MAX_RELOAD_BYTES: on disk and kept by compaction, and not in memory. Nonzero fails
mesh doctor. |
Used by CooperationStatus
LinkRefusal
object
A refusal: the code, the reason in words, and the protocol range of the refuser so a
version mismatch is diagnosable from either end.
Carrying the refuser's protocol range in every refusal, not only in the one about
protocols, is what makes a version problem visible from the side that has the log: the
dialer sees what the answerer speaks without anyone having to read the other machine.
use majordomus_cli::mesh::link::{LinkRefusal, RefusalCode, LINK_PROTOCOL_MAX};
let refusal = LinkRefusal::new(RefusalCode::Untrusted, "the key is not in the allowlist");
assert_eq!(refusal.code, RefusalCode::Untrusted);
assert_eq!(refusal.proto_max, LINK_PROTOCOL_MAX);
assert_eq!(refusal.to_string(), "untrusted: the key is not in the allowlist");
| property | type | required | description |
| code |
RefusalCode → RefusalCode |
yes |
The rule that refused. |
| detail |
string |
yes |
Why, in words. |
| proto_min |
integer (uint32) |
yes |
The refuser's oldest protocol. |
| proto_max |
integer (uint32) |
yes |
The refuser's newest protocol. |
Used by LinkReply RefusedView
LinkReply
object
What a hello or a sync answers over the wire: a signed message or a refusal. Both are
successful replies — the transport carries them the same way and neither is an error —
because a refusal is a decision this protocol wants recorded and shown, and an answer
that arrived as an HTTP failure would be indistinguishable from a peer that is down.
use majordomus_cli::mesh::identity::NodeIdentity;
use majordomus_cli::mesh::link::{sign, Domain, LinkReply, RefusalCode};
let node = NodeIdentity::ephemeral().unwrap();
let accepted = LinkReply::accepted(sign(&node, Domain::Welcome, serde_json::json!({})));
assert!(accepted.signed.is_some() && accepted.refusal.is_none());
let refused = LinkReply::refused(RefusalCode::SelfLink, "this runtime dialled itself");
assert!(refused.signed.is_none());
assert_eq!(refused.refusal.unwrap().code, RefusalCode::SelfLink);
| property | type | required | description |
| signed |
Signed | null → Signed |
no |
The signed welcome or sync answer, when accepted. |
| refusal |
LinkRefusal | null → LinkRefusal |
no |
The refusal, when not. |
Used by mesh.link.hello mesh.link.sync
LinkState
"connecting" | "connected" | "degraded" | "unreachable" | "expired"
A link's standing, from this runtime's side. It is a statement about a connection and
never about the peer: a runtime reachable only through a third party is
Expired here
while its streams beat on everywhere, which is why claims follow streams and not this.
The variants are ordered by how far the link has fallen, so the worst link in a table
is the maximum and a reader never has to rank them by hand.
use majordomus_cli::mesh::cooperation::LinkState;
let table = [LinkState::Connected, LinkState::Unreachable, LinkState::Degraded];
assert_eq!(table.iter().max(), Some(&LinkState::Unreachable), "the worst link shows");
assert_eq!(serde_json::to_value(LinkState::Connected).unwrap(), "connected");
connectingHandshaking, or never yet exchanged.connectedAn exchange within two heartbeats.degradedRounds are failing; the last exchange is under half the expiry old.unreachableThe last exchange is over half the expiry old; the link is about to expire.expiredNo exchange within the expiry: the link is dropped and will be re-dialed.
Used by PeerVerdict PeerView
MachineNode
object
One machine in the peer tree.
| property | type | required | description |
| node |
string |
yes |
The node id. |
| name |
string |
yes |
Its display name, when a card or this node said. |
| local |
boolean |
yes |
Whether it is this machine. |
| runtimes |
array of RuntimeNode → RuntimeNode |
yes |
Its runtimes. |
Used by PeerTree
MeshClaimBrief
object
One live claim, told briefly: whose it is, what it is for and how many paths it holds,
with its intent cut to
SUMMARY_TEXT characters — enough to know whom to ask.
use std::sync::Arc;
use majordomus_cli::mesh::identity::NodeIdentity;
use majordomus_cli::mesh::journal::{ClaimMode, EventBody, Journal, SessionInfo, StreamLiveness};
use majordomus_cli::mesh::state::fold;
let j = Journal::open(Arc::new(NodeIdentity::ephemeral().unwrap()), "0000000000000001",
"repo".into(), None).unwrap();
j.append_own(EventBody::SessionOpened { info: SessionInfo::named("s1", "claude-code") })
.unwrap();
for (claim, path) in [("c1", "apps/a"), ("c2", "apps/b")] {
j.append_own(EventBody::ClaimAcquired { claim: claim.into(), session: "s1".into(),
scope: vec![path.into()], intent: Some("work".into()), mode: ClaimMode::Exclusive,
issue: Some("#1".into()) }).unwrap();
}
j.append_own(EventBody::ClaimReleased { claim: "c1".into() }).unwrap();
let state = fold(&j.events(), &|_| StreamLiveness::Own);
use majordomus_cli::mesh::page::{summarize, ClaimBrief};
let summary = summarize(&state, &[]);
let newest: &ClaimBrief = summary.runtimes[0].newest_claim.as_ref().unwrap();
assert_eq!((newest.issue.as_deref(), newest.paths), (Some("#1"), 1));
| property | type | required | description |
| key |
string |
yes |
<stream>/<claim>. |
| session |
string |
yes |
The holding session's key. |
| issue |
string | null |
no |
The issue it is for. |
| intent |
string | null |
no |
What it is for, cut to SUMMARY_TEXT characters. |
| paths |
integer (uint) |
yes |
How many paths it claims. |
| mode |
ClaimMode → ClaimMode |
yes |
Exclusive or advisory. |
| acquired_lamport |
integer (uint64) |
yes |
The Lamport stamp of the acquisition. |
Used by MeshRuntimeSummary RuntimeNode
MeshClaimCounts
object
Claims by standing: held, conflicted, released and expired. Held and conflicted are
live; the other two are history, counted rather than listed.
use majordomus_cli::mesh::page::ClaimCounts;
let counts = ClaimCounts { held: 2, released: 5, ..ClaimCounts::default() };
assert_eq!(serde_json::to_value(&counts).unwrap()["released"], 5);
| property | type | required | description |
| held |
integer (uint) |
yes |
Live and, for an exclusive claim, the winner of its scope. |
| conflicted |
integer (uint) |
yes |
Live, exclusive, and beaten to its scope. |
| released |
integer (uint) |
yes |
Released by the holder. |
| expired |
integer (uint) |
yes |
Ended without a release. |
Used by MeshRuntimeSummary MeshSummary
MeshClaimFilter
"all" | "live"
Which claims a full answer keeps: all of them, the ended ones included, or only those
still live — the question an executor asks before it takes a lease.
use majordomus_cli::mesh::page::ClaimFilter;
assert_eq!(ClaimFilter::default(), ClaimFilter::All);
assert_eq!(serde_json::from_str::<ClaimFilter>(r#""live""#).unwrap(), ClaimFilter::Live);
allEvery claim, ended ones included.liveOnly claims that are still live: held, or conflicted and still held.
Used by mesh.peers mesh.state
MeshDoctorReport
object
The whole self-check.
| property | type | required | description |
| ok |
boolean |
yes |
Whether every check holds. |
| checks |
array of DoctorCheck → DoctorCheck |
yes |
The checks, in the order they ran. |
Used by mesh.doctor
MeshEvent
object
One event as the wire and the store carry it. The body is kept as JSON so that an
event of an unknown kind survives storage and relay byte-for-byte;
MeshEvent::body
interprets it.
Every field above the signature is covered by it, which is what makes a relay harmless:
a runtime that forwards an event cannot alter its stream, its sequence, its repository
or its body without the next reader noticing, and so vouches for nothing. The wall
clock is the one field nothing is decided by — freshness is measured with beats and
monotonic clocks, never by comparing two machines' idea of the time.
use std::sync::Arc;
use majordomus_cli::mesh::identity::NodeIdentity;
use majordomus_cli::mesh::journal::{EventBody, Journal, MeshEvent};
let j = Journal::open(Arc::new(NodeIdentity::ephemeral().unwrap()), "0000000000000001",
"repo".into(), None).unwrap();
j.append_own(EventBody::SessionClosed { session: "s1".into() }).unwrap();
let event: &MeshEvent = &j.events()[0];
assert_eq!(event.seq, 1, "dense from 1 within its stream");
assert_eq!(event.kind(), "session_closed");
assert_eq!(event.id(), format!("{}/1", event.stream));
// a relayed byte changed anywhere under the signature is a different event
let mut tampered = event.clone();
tampered.repo = "another-repository".into();
assert_ne!(tampered.signing_bytes(), event.signing_bytes());
| property | type | required | description |
| v |
integer (uint32) |
yes |
The event format version. |
| stream |
StreamId → StreamId |
yes |
The stream that wrote it. |
| seq |
integer (uint64) |
yes |
Dense from 1 within the stream. |
| lamport |
integer (uint64) |
yes |
The Lamport stamp: above every stamp its writer had seen. |
| at |
integer (uint64) |
yes |
The writer's wall clock, seconds since the Unix epoch. Informational only: nothing
is decided by comparing it with another machine's clock. |
| repo |
string |
yes |
The mesh repository id the event belongs to. |
| pk |
string |
yes |
The writer's Ed25519 public key, hex; its digest is the stream's node. |
| body |
any |
yes |
The body, kind tagged. |
| sig |
string |
yes |
Ed25519 over the canonical bytes of everything above, hex. |
Used by EventList
MeshHandoverBrief
object
A handover nobody has taken, told briefly: the id
mesh.handover.consume takes, who
published it and what it continues. Its body stays in the full view.
use majordomus_cli::mesh::page::HandoverBrief;
let brief = HandoverBrief { id: "ab12".into(), runtime: "n-r".into(),
branch: Some("feature/x".into()), issue: None, published_lamport: 7 };
assert_eq!(serde_json::to_value(&brief).unwrap()["branch"], "feature/x");
| property | type | required | description |
| id |
string |
yes |
The content digest: what mesh.handover.consume takes. |
| runtime |
string |
yes |
<node>-<runtime> of the publisher. |
| branch |
string | null |
no |
The branch it continues. |
| issue |
string | null |
no |
The issue it continues. |
| published_lamport |
integer (uint64) |
yes |
The Lamport stamp of the first publication. |
Used by MeshSummary
MeshIdentityReport
object
The answer of mesh.identity: this machine's node identity, public half only.
| property | type | required | description |
| path |
string | null |
no |
Where the identity file lives (or would). |
| present |
boolean |
yes |
Whether the file exists. |
| identity |
PublicIdentity | null → PublicIdentity |
no |
The public identity, when the file exists and loads. The signing key is not
here, not in any projection, and not in any log. |
| error |
string | null |
no |
Why the identity did not load, when it did not. |
Used by mesh.identity
MeshKind
"sessions" | "claims" | "overlaps" | "handovers" | "reviews"
One collection of the folded state, as
kind names it: a reader that wants only the
reviews is not sent the claims.
use majordomus_cli::mesh::page::Kind;
assert_eq!(serde_json::from_str::<Kind>(r#""reviews""#).unwrap(), Kind::Reviews);
assert!(serde_json::from_str::<Kind>(r#""everything""#).is_err());
sessionsThe sessions.claimsThe claims.overlapsThe advisory overlaps.handoversThe handovers.reviewsThe review requests.
Used by mesh.peers mesh.state
MeshProviderState
"running" | "failed" | "stopped"
The provider's health, as every surface reports it.
runningThreads up, socket bound (where the provider has one).failedThe provider could not start or died; ProviderStatus::detail says why. One
provider failing never stops another — failure isolation is the contract.stoppedStopped by the manager.
Used by ProviderStatus
MeshRepositoryBasis
"declared" | "root_commits"
What the repository identity was derived from, which a reader needs in order to judge
it: an identity taken from the root commits is a fact about the history two clones
share, while a declared one is a decision somebody made, and only the second can be
changed by editing a file.
use majordomus_cli::mesh::repository::{declared, of_root_commits, MeshRepositoryBasis};
assert_eq!(declared("majordomus").unwrap().basis, MeshRepositoryBasis::Declared);
assert_eq!(
of_root_commits(&["a".repeat(40)]).basis,
MeshRepositoryBasis::RootCommits,
);
declaredcooperation.repository in the mesh declaration.root_commitsThe repository's root commits.
Used by MeshRepositoryIdentity
MeshRepositoryIdentity
object
A repository's mesh identity, carried by every advertisement, link and event, together
with the account of where it came from. The
detail exists so that a person told two
checkouts are not the same repository can see what each one was derived from instead of
comparing two opaque digests.
use majordomus_cli::mesh::repository::MeshRepositoryIdentity;
use majordomus_cli::mesh::repository::of_root_commits;
let identity: MeshRepositoryIdentity = of_root_commits(&["a".repeat(40)]);
assert_eq!(identity.id.len(), 32, "32 hex characters, on every machine");
assert_eq!(identity.detail, "aaaaaaaaaaaa", "the evidence, short enough to read");
| property | type | required | description |
| id |
string |
yes |
32 hex: what advertisements, links and events carry. |
| basis |
MeshRepositoryBasis → MeshRepositoryBasis |
yes |
What it was derived from. |
| detail |
string |
yes |
The evidence, human-readable: the declared value, or the root commits. |
Used by CooperationStatus
MeshReviewBrief
object
An open review request, told briefly: its key, what is to be reviewed and whom it asks.
Only requests nobody has answered are listed in the summary.
use majordomus_cli::mesh::page::ReviewBrief;
let brief = ReviewBrief { key: "s/r1".into(), subject: "feature/x".into(), issue: None,
reviewer: None };
assert!(serde_json::to_value(&brief).unwrap().get("issue").is_none(), "absent, not null");
| property | type | required | description |
| key |
string |
yes |
<stream>/<review>. |
| subject |
string |
yes |
What is to be reviewed, cut to SUMMARY_TEXT characters. |
| issue |
string | null |
no |
The issue. |
| reviewer |
string | null |
no |
The runtime asked, when one was. |
Used by MeshSummary
MeshRuntimeSummary
object
One runtime, counted: its liveness and beat, its sessions and claims by standing, and its
newest live claim. The summary lists these, the living runtimes first.
use std::sync::Arc;
use majordomus_cli::mesh::identity::NodeIdentity;
use majordomus_cli::mesh::journal::{ClaimMode, EventBody, Journal, SessionInfo, StreamLiveness};
use majordomus_cli::mesh::state::fold;
let j = Journal::open(Arc::new(NodeIdentity::ephemeral().unwrap()), "0000000000000001",
"repo".into(), None).unwrap();
j.append_own(EventBody::SessionOpened { info: SessionInfo::named("s1", "claude-code") })
.unwrap();
for (claim, path) in [("c1", "apps/a"), ("c2", "apps/b")] {
j.append_own(EventBody::ClaimAcquired { claim: claim.into(), session: "s1".into(),
scope: vec![path.into()], intent: Some("work".into()), mode: ClaimMode::Exclusive,
issue: Some("#1".into()) }).unwrap();
}
j.append_own(EventBody::ClaimReleased { claim: "c1".into() }).unwrap();
let state = fold(&j.events(), &|_| StreamLiveness::Own);
use majordomus_cli::mesh::page::{summarize, RuntimeSummary};
let summary = summarize(&state, &[]);
let runtime: &RuntimeSummary = &summary.runtimes[0];
assert_eq!((runtime.sessions.active, runtime.claims.held, runtime.claims.released), (1, 1, 1));
| property | type | required | description |
| runtime |
string |
yes |
<node>-<runtime>. |
| liveness |
StreamLiveness → StreamLiveness |
yes |
Own, live or expired. |
| last_beat_ms |
integer | null |
no |
Milliseconds since its beat last rose, on this runtime's clock. |
| sessions |
MeshSessionCounts → MeshSessionCounts |
yes |
Its sessions by standing. |
| claims |
MeshClaimCounts → MeshClaimCounts |
yes |
Its claims by standing. |
| newest_claim |
MeshClaimBrief | null → MeshClaimBrief |
no |
Its newest live claim. |
Used by MeshSummary
MeshSessionCounts
object
Sessions by standing: how many are active, closed and expired. A count stays the same
size however many sessions the mesh has seen, which is why the summary carries counts.
use majordomus_cli::mesh::page::SessionCounts;
let none = SessionCounts::default();
assert_eq!(serde_json::to_value(&none).unwrap(),
serde_json::json!({ "active": 0, "closed": 0, "expired": 0 }));
| property | type | required | description |
| active |
integer (uint) |
yes |
Open, on a runtime that beats. |
| closed |
integer (uint) |
yes |
Closed by their own runtime. |
| expired |
integer (uint) |
yes |
Never closed, on a runtime that stopped beating. |
Used by MeshRuntimeSummary MeshSummary
MeshSource
"udp_multicast" | "udp_broadcast" | "rendezvous" | "synthetic"
Where an observation came from. A record accumulates these; provenance is the answer
to "why do I see this node".
udp_multicastA multicast datagram.udp_broadcastA broadcast datagram.rendezvousA rendezvous answer or registration.syntheticA synthetic provider (tests, self-check).
Used by Sighting
MeshStateAnswer
object
The answer of mesh.state.
| property | type | required | description |
| active |
boolean |
yes |
Whether cooperation runs. |
| view |
MeshView → MeshView |
yes |
What the answer carries: summary or full. |
| reason |
string | null |
no |
Why not. |
| summary |
MeshSummary | null → MeshSummary |
no |
The summary, in the summary view: counts, each runtime's newest live claim, the
open reviews and the untaken handovers. |
| state |
CooperationState | null → CooperationState |
no |
The folded state narrowed by the filters, in the full view; its digest is the whole
fold's. |
| streams |
array of StreamView → StreamView |
yes |
Every stream's liveness. |
Used by mesh.state
MeshStatus
object
The whole mesh, as every surface reports it.
| property | type | required | description |
| active |
boolean |
yes |
Whether the mesh is running in this process. |
| reason |
string | null |
no |
Why not, when it is not: no declaration, disabled, no identity, stopped. |
| identity |
PublicIdentity | null → PublicIdentity |
no |
This node, when the mesh has an identity. |
| trust_policy |
string | null |
no |
The trust policy in force. |
| providers |
array of ProviderStatus → ProviderStatus |
yes |
Every provider, canonical order (by id). |
| tallies |
Tallies → Tallies |
yes |
The registry's tallies. |
| refusals |
Refusals → Refusals |
yes |
Refused datagrams, by reason. |
| started_at |
string | null |
no |
When the mesh started, RFC 3339. |
Used by mesh.status
MeshSummary
object
The mesh in an answer a worker reads whole: counts, each runtime, the open reviews and
the untaken handovers, every list bounded by
SUMMARY_LIST with the rest counted.
use std::sync::Arc;
use majordomus_cli::mesh::identity::NodeIdentity;
use majordomus_cli::mesh::journal::{ClaimMode, EventBody, Journal, SessionInfo, StreamLiveness};
use majordomus_cli::mesh::state::fold;
let j = Journal::open(Arc::new(NodeIdentity::ephemeral().unwrap()), "0000000000000001",
"repo".into(), None).unwrap();
j.append_own(EventBody::SessionOpened { info: SessionInfo::named("s1", "claude-code") })
.unwrap();
for (claim, path) in [("c1", "apps/a"), ("c2", "apps/b")] {
j.append_own(EventBody::ClaimAcquired { claim: claim.into(), session: "s1".into(),
scope: vec![path.into()], intent: Some("work".into()), mode: ClaimMode::Exclusive,
issue: Some("#1".into()) }).unwrap();
}
j.append_own(EventBody::ClaimReleased { claim: "c1".into() }).unwrap();
let state = fold(&j.events(), &|_| StreamLiveness::Own);
use majordomus_cli::mesh::page::{summarize, Summary};
let summary: Summary = summarize(&state, &[]);
assert_eq!((summary.claims.held, summary.claims.released), (1, 1));
assert_eq!(summary.digest, state.digest);
| property | type | required | description |
| digest |
string |
yes |
The whole fold's digest: equal digests, equal state. |
| sessions |
MeshSessionCounts → MeshSessionCounts |
yes |
Every session by standing. |
| claims |
MeshClaimCounts → MeshClaimCounts |
yes |
Every claim by standing. |
| overlaps |
integer (uint) |
yes |
Advisory overlaps between live claims. |
| runtimes |
array of MeshRuntimeSummary → MeshRuntimeSummary |
yes |
Each runtime counted, the living first; at most SUMMARY_LIST. |
| runtimes_not_listed |
integer (uint) |
yes |
Runtimes past the list, counted. |
| open_reviews |
array of MeshReviewBrief → MeshReviewBrief |
yes |
Review requests nobody has answered; at most SUMMARY_LIST. |
| open_reviews_not_listed |
integer (uint) |
yes |
Open reviews past the list, counted. |
| untaken_handovers |
array of MeshHandoverBrief → MeshHandoverBrief |
yes |
Handovers nobody has consumed; at most SUMMARY_LIST. |
| untaken_handovers_not_listed |
integer (uint) |
yes |
Untaken handovers past the list, counted. |
Used by MeshStateAnswer
MeshVerifyCheck
object
One verification check: what was checked, whether it holds, the evidence, and — when
something is wrong or limited — the impact and what to do about it.
The impact and the remedy are carried with the verdict rather than left to whoever
renders it, so that a failing check is actionable wherever it is read — in a terminal,
in a JSON answer, or by an agent that has never seen the mesh before. A check that
holds carries neither, because there is nothing to do.
#### use std::sync::Arc;
#### use majordomus_cli::mesh::config::{CooperationConfig, TrustConfig};
#### use majordomus_cli::mesh::cooperation::{CheckoutFacts, Cooperation, CooperationSetup};
#### use majordomus_cli::mesh::identity::NodeIdentity;
#### use majordomus_cli::mesh::link::HttpTransport;
#### use majordomus_cli::mesh::registry::MeshRegistry;
#### use majordomus_cli::mesh::repository::of_root_commits;
#### let cooperation = Cooperation::new(CooperationSetup {
#### identity: Arc::new(NodeIdentity::ephemeral().unwrap()), runtime: "0000000000000001".into(),
#### repository: of_root_commits(&["root".into()]), endpoints: vec!["127.0.0.1:9".into()],
#### version: "doc".into(), config: CooperationConfig::default(), trust: TrustConfig::default(),
#### journal_path: None, registry: Arc::new(MeshRegistry::new()),
#### transport: Arc::new(HttpTransport), board: None, checkout: CheckoutFacts::default(),
#### }).unwrap();
use majordomus_cli::mesh::cooperation::MeshVerifyCheck;
let report = cooperation.verify();
let first: &MeshVerifyCheck = &report.checks[0];
assert_eq!(first.check, "cooperation");
assert!(first.detail.contains(cooperation.runtime_key()), "the evidence, not a verdict");
assert!(first.remediation.is_none(), "a check that holds asks for nothing");
| property | type | required | description |
| check |
string |
yes |
What was checked. |
| ok |
boolean |
yes |
Whether it holds. |
| detail |
string |
yes |
The evidence. |
| impact |
string | null |
no |
What the failure or limitation means for cooperation. |
| remediation |
string | null |
no |
What to do. |
Used by MeshVerifyReport
MeshVerifyReport
object
What
mesh verify found: the local checks, every peer's round, and the state digest
afterwards. The digest is what makes the report worth running on two machines — two
runtimes that print the same digest have the same sessions, claims, handovers and
reviews, and two that do not have something to reconcile, whatever their links say.
ok is the conjunction of everything below it, so a caller may act on the one field
and a person may read the rest to find out which part of it was false.
#### use std::sync::Arc;
#### use majordomus_cli::mesh::config::{CooperationConfig, TrustConfig};
#### use majordomus_cli::mesh::cooperation::{CheckoutFacts, Cooperation, CooperationSetup};
#### use majordomus_cli::mesh::identity::NodeIdentity;
#### use majordomus_cli::mesh::link::HttpTransport;
#### use majordomus_cli::mesh::registry::MeshRegistry;
#### use majordomus_cli::mesh::repository::of_root_commits;
#### let cooperation = Cooperation::new(CooperationSetup {
#### identity: Arc::new(NodeIdentity::ephemeral().unwrap()), runtime: "0000000000000001".into(),
#### repository: of_root_commits(&["root".into()]), endpoints: vec!["127.0.0.1:9".into()],
#### version: "doc".into(), config: CooperationConfig::default(), trust: TrustConfig::default(),
#### journal_path: None, registry: Arc::new(MeshRegistry::new()),
#### transport: Arc::new(HttpTransport), board: None, checkout: CheckoutFacts::default(),
#### }).unwrap();
use majordomus_cli::mesh::cooperation::MeshVerifyReport;
let report: MeshVerifyReport = cooperation.verify();
assert_eq!(report.runtime.as_deref(), Some(cooperation.runtime_key()));
assert_eq!(report.digest, Some(cooperation.state().digest), "what a peer is compared to");
assert_eq!(report.ok, report.checks.iter().all(|c| c.ok), "one field for every check");
| property | type | required | description |
| ok |
boolean |
yes |
Whether every check holds and every round succeeded. |
| runtime |
string | null |
no |
This runtime. |
| checks |
array of MeshVerifyCheck → MeshVerifyCheck |
yes |
The checks, in the order they ran. |
| peers |
array of PeerVerdict → PeerVerdict |
yes |
Every linked peer's verdict. |
| digest |
string | null |
no |
The state digest after the rounds. |
Used by mesh.verify
MeshView
"summary" | "full"
How much an answer about the mesh carries. The summary is the default because it is the
answer a worker can read whole whatever the mesh's history; the full fold is one word
away when a reader needs every record.
use majordomus_cli::mesh::page::View;
assert_eq!(View::default(), View::Summary);
assert_eq!(serde_json::to_value(View::Full).unwrap(), "full");
summaryCounts, each runtime's newest live claim, the open reviews and the untaken handovers:
bounded, whatever the history.fullEverything, narrowed only by the filters.
Used by mesh.peers mesh.state MeshStateAnswer PeerTree
NodeId
string
The stable identity of a node: 32 hex characters of the SHA-256 of its Ed25519 public
key. Derived, never chosen: two nodes with one id hold one key.
Used by NodeRecord PublicIdentity
NodeList
object
The answer of mesh.nodes: every node this process has observed.
| property | type | required | description |
| count |
integer (uint) |
yes |
How many nodes the registry holds. |
| nodes |
array of NodeRecord → NodeRecord |
yes |
The nodes, in node-id order — the one canonical order every surface shows. |
Used by mesh.nodes
NodeRecord
object
One discovered node: the canonical record every surface projects.
| property | type | required | description |
| node_id |
NodeId → NodeId |
yes |
The node id: the digest of the key below. |
| runtime |
string |
no |
The runtime slot: which server of the node (one per checkout), 16 hex; empty for a
version-1 advertisement. A record is one runtime: two worktrees of one machine are
two records under one node id and one key. |
| public_key |
string |
yes |
The Ed25519 public key that proved this record's advertisements. |
| display_name |
string |
yes |
The node's own display name. Presentation only. |
| instance_id |
InstanceId → InstanceId |
yes |
The node's current run. |
| trust |
TrustState → TrustState |
yes |
What the policy decided. |
| presence |
Presence → Presence |
yes |
Whether the node is answering now. |
| endpoints |
array of string |
yes |
Advertised host:port authorities. |
| capabilities |
array of string |
yes |
Advertised transports (http, mcp, ws). |
| repositories |
array of string |
yes |
Advertised repository digests. |
| protocol_version |
integer (uint32) |
yes |
The advertised protocol version. |
| version |
string |
yes |
The advertised executable version. |
| sources |
array of Sighting → Sighting |
yes |
Every source this node was seen through, most recent sighting per source. |
| first_seen |
string |
yes |
First seen, RFC 3339. |
| last_seen |
string |
yes |
Last seen, RFC 3339. |
| restarts |
integer (uint64) |
yes |
How many instances this node id has shown (a restart increments it). |
Used by NodeList
PeerDetail
object
The answer of mesh.peer.
| property | type | required | description |
| found |
boolean |
yes |
Whether the runtime is known here. |
| reason |
string | null |
no |
Why not, when it is not. |
| machine |
string | null |
no |
The machine it runs on. |
| runtime |
RuntimeNode | null → RuntimeNode |
no |
The runtime, its link and its sessions. |
| refused |
array of RefusedView → RefusedView |
yes |
Refusals recorded against it. |
Used by mesh.peer
PeerTree
object
The answer of mesh.peers: machine → runtime → session → claim, every level from the
one journal and the one link table.
| property | type | required | description |
| active |
boolean |
yes |
Whether cooperation runs. |
| view |
MeshView → MeshView |
yes |
What the answer carries: summary or full. |
| reason |
string | null |
no |
Why not. |
| runtime |
string | null |
no |
This runtime. |
| repository |
string | null |
no |
The mesh repository id. |
| machines |
array of MachineNode → MachineNode |
yes |
The machines, local first. |
| refused |
array of RefusedView → RefusedView |
yes |
Candidates refused, with the rule. |
| digest |
string | null |
no |
The digest of the cooperation state: equal digests, equal state. |
Used by mesh.peers
PeerVerdict
object
What one sync round, run now rather than remembered, said about one peer. Verification
does not report the link's standing and stop there: it exchanges with the peer and says
whether the exchange worked and whether both sides ended holding the same marks, which
is the difference between a peer that answers and a peer that agrees.
round_trip and converged are optional because a peer this runtime does not dial is
not asked — an absent answer is not a failed one, and reporting false there would
make an inbound-only link look broken every time.
#### use std::collections::BTreeMap;
#### use std::sync::{Arc, Mutex, Weak};
#### use majordomus_cli::mesh::config::{CooperationConfig, TrustConfig};
#### use majordomus_cli::mesh::cooperation::*;
#### use majordomus_cli::mesh::identity::NodeIdentity;
#### use majordomus_cli::mesh::link::{LinkReply, LinkTransport, Signed, HELLO_PATH};
#### use majordomus_cli::mesh::registry::MeshRegistry;
#### use majordomus_cli::mesh::repository::{of_root_commits, runtime_id};
#### use majordomus_cli::mesh::trust::TrustPolicy;
#### #[derive(Default)] struct Net(Mutex<BTreeMap<String, Weak<Cooperation>>>);
#### impl LinkTransport for Net {
#### fn post(&self, e: &str, p: &str, m: &Signed) -> Result<LinkReply, String> {
#### let peer = (self.0.lock().unwrap().get(e).and_then(Weak::upgrade))
#### .ok_or_else(|| format!("{e}: nobody there"))?;
#### Ok(if p == HELLO_PATH { peer.accept_hello(m) } else { peer.accept_sync(m) }) } }
#### let net = Arc::new(Net::default());
#### let runtime = |endpoint: &str| { let c = Cooperation::new(CooperationSetup {
#### identity: Arc::new(NodeIdentity::ephemeral().unwrap()), runtime: runtime_id(endpoint),
#### repository: of_root_commits(&["root".into()]), endpoints: vec![endpoint.into()],
#### version: "doc".into(), config: CooperationConfig::default(),
#### trust: TrustConfig { policy: TrustPolicy::Tofu, allow: vec![] }, journal_path: None,
#### registry: Arc::new(MeshRegistry::new()),
#### transport: Arc::clone(&net) as Arc<dyn LinkTransport>, board: None,
#### checkout: CheckoutFacts::default() }).unwrap();
#### net.0.lock().unwrap().insert(endpoint.into(), Arc::downgrade(&c)); c };
#### let (a, b) = (runtime("a:1"), runtime("b:1"));
a.dial(&["b:1".into()]).unwrap();
let dialer: &PeerVerdict = &a.verify().peers[0];
assert_eq!(dialer.round_trip, Some(true), "asked, and answered");
let answerer = &b.verify().peers[0];
assert!(!answerer.dialed, "B does not dial A, so B asks nothing of it");
assert_eq!(answerer.round_trip, None, "and reports no verdict it did not earn");
| property | type | required | description |
| runtime |
string |
yes |
The peer's runtime key. |
| state |
LinkState → LinkState |
yes |
The link's standing after the round. |
| dialed |
boolean |
yes |
Whether this runtime dials the peer (and so ran a round with it now). |
| round_trip |
boolean | null |
no |
Whether the round succeeded; None when the peer dials this runtime instead. |
| rtt_ms |
integer | null |
no |
The round trip, milliseconds. |
| converged |
boolean | null |
no |
Whether both sides hold the same high-water mark of every stream after the round. |
| detail |
string |
yes |
What happened. |
Used by MeshVerifyReport
PeerView
object
One peer as every surface shows it: who it is, how this runtime reaches it, and what
the link has been through. The counters are carried per peer rather than only in the
totals because the question a person asks of a flapping mesh is which peer is flapping,
and
restarts answers a different one from
reconnects — a peer whose process keeps
dying, against a link that keeps dropping under a process that never did.
#### use std::collections::BTreeMap;
#### use std::sync::{Arc, Mutex, Weak};
#### use majordomus_cli::mesh::config::{CooperationConfig, TrustConfig};
#### use majordomus_cli::mesh::cooperation::*;
#### use majordomus_cli::mesh::identity::NodeIdentity;
#### use majordomus_cli::mesh::link::{LinkReply, LinkTransport, Signed, HELLO_PATH};
#### use majordomus_cli::mesh::registry::MeshRegistry;
#### use majordomus_cli::mesh::repository::{of_root_commits, runtime_id};
#### use majordomus_cli::mesh::trust::TrustPolicy;
#### #[derive(Default)] struct Net(Mutex<BTreeMap<String, Weak<Cooperation>>>);
#### impl LinkTransport for Net {
#### fn post(&self, e: &str, p: &str, m: &Signed) -> Result<LinkReply, String> {
#### let peer = (self.0.lock().unwrap().get(e).and_then(Weak::upgrade))
#### .ok_or_else(|| format!("{e}: nobody there"))?;
#### Ok(if p == HELLO_PATH { peer.accept_hello(m) } else { peer.accept_sync(m) }) } }
#### let net = Arc::new(Net::default());
#### let runtime = |endpoint: &str| { let c = Cooperation::new(CooperationSetup {
#### identity: Arc::new(NodeIdentity::ephemeral().unwrap()), runtime: runtime_id(endpoint),
#### repository: of_root_commits(&["root".into()]), endpoints: vec![endpoint.into()],
#### version: "doc".into(), config: CooperationConfig::default(),
#### trust: TrustConfig { policy: TrustPolicy::Tofu, allow: vec![] }, journal_path: None,
#### registry: Arc::new(MeshRegistry::new()),
#### transport: Arc::clone(&net) as Arc<dyn LinkTransport>, board: None,
#### checkout: CheckoutFacts::default() }).unwrap();
#### net.0.lock().unwrap().insert(endpoint.into(), Arc::downgrade(&c)); c };
#### let (a, b) = (runtime("a:1"), runtime("b:1"));
a.dial(&["b:1".into()]).unwrap();
let peer: &PeerView = &a.peers()[0];
assert_eq!(peer.runtime, b.runtime_key(), "the durable identity, not the endpoint");
assert!(peer.outbound, "this runtime dials it");
assert_eq!(peer.handshakes, 1);
assert_eq!(peer.restarts, 0, "the peer's process has not died under the link");
| property | type | required | description |
| runtime |
string |
yes |
<node>-<runtime>: the peer's durable identity. |
| node |
string |
yes |
The node (machine key digest). |
| instance |
string |
yes |
The current instance. |
| name |
string |
yes |
The node's display name. |
| version |
string |
yes |
The executable version. |
| local |
boolean |
yes |
Whether the peer runs on this machine (the same node key). |
| trust |
TrustState → TrustState |
yes |
The trust verdict the link was admitted under. |
| protocol |
integer (uint32) |
yes |
The negotiated link protocol. |
| features |
array of string |
yes |
The link features the peer carries. |
| endpoints |
array of string |
yes |
Where the peer answers. |
| state |
LinkState → LinkState |
yes |
The link's standing. |
| outbound |
boolean |
yes |
Whether this runtime dials the peer. |
| inbound |
boolean |
yes |
Whether the peer dials this runtime. |
| endpoint |
string | null |
no |
The endpoint this runtime dials, when it does. |
| last_exchange_ms |
integer | null |
no |
Milliseconds since the last successful exchange in either direction. |
| rtt_ms |
integer | null |
no |
The last round trip, milliseconds. |
| failures |
integer (uint32) |
yes |
Consecutive failed rounds. |
| last_error |
string | null |
no |
The last failure, in words. |
| handshakes |
integer (uint64) |
yes |
Handshakes completed with this peer. |
| reconnects |
integer (uint64) |
yes |
Handshakes after an expiry or a dropped link. |
| restarts |
integer (uint64) |
yes |
New instances of the peer seen (its process restarted). |
| linked_at |
string |
yes |
When the current link was established, RFC 3339. |
Used by CooperationStatus RuntimeNode
Presence
"present" | "absent"
Whether the node is answering now, by its own announcements.
presentAn advertisement arrived within PRESENCE_TTL.absentNothing within the TTL; the node may be gone or unreachable. It stays listed
until RETENTION passes, then expires out entirely.
Used by NodeRecord
Protocol
"udp" | "tcp"
The transport protocol of a rule: discovery is datagrams, registration and the link
protocol are connections. Serialised as the word a firewall front takes.
use majordomus_cli::mesh::firewall::Protocol;
assert_eq!(serde_json::to_value(Protocol::Udp).unwrap(), "udp");
assert_eq!(serde_json::to_value(Protocol::Tcp).unwrap(), "tcp");
udpDatagrams: discovery.tcpConnections: the hub's registrations and the link protocol.
Used by FirewallRule
ProviderStatus
object
One provider's status: state, why, and its counters.
| property | type | required | description |
| id |
string |
yes |
The provider id: udp_multicast, udp_broadcast, rendezvous, synthetic. |
| state |
MeshProviderState → MeshProviderState |
yes |
The state. |
| detail |
string | null |
no |
Why, when Failed; what it watches, when Running. |
| sent |
integer (uint64) |
yes |
Advertisements transmitted. |
| received |
integer (uint64) |
yes |
Datagrams heard and handed to the manager. |
Used by MeshStatus
PublicIdentity
object
What everyone may know about this node: the id, the public key, a display name and
this run's instance. Everything an advertisement carries; nothing that signs.
| property | type | required | description |
| node_id |
NodeId → NodeId |
yes |
The node id: a digest of the public key. |
| public_key |
string |
yes |
The Ed25519 public key, 64 hex characters. Whoever holds the matching secret is
this node; there is no other proof. |
| display_name |
string |
yes |
A human name for the node, from the identity file; defaults to the first eight
characters of the node id. Presentation only — never used for trust. |
| instance_id |
InstanceId → InstanceId |
yes |
This run of the node. |
Used by MeshIdentityReport MeshStatus
RefusalCode
"malformed" | "oversized" | "protocol_unsupported" | "signature" | "stale" | "replay" | "repository_mismatch" | "untrusted" | "self_link" | "not_active" | "unknown_link" | "capacity" | "feature_unsupported" | "misaddressed"
Why a link message was turned down. Every refusal is typed, counted and shown: a mesh
that refuses a peer says which rule did, instead of dropping a socket.
The codes are the diagnosis a person needs when two runtimes will not link, and they
are deliberately distinct where the remedies differ: Untrusted is a key to add,
RepositoryMismatch is two different repositories, SelfLink is a configuration that
dials its own endpoint, and UnknownLink is not a failure at all but an instruction to
say hello again.
use majordomus_cli::mesh::link::RefusalCode;
// the wire word is the code's identity across surfaces, and it is stable
assert_eq!(RefusalCode::RepositoryMismatch.as_str(), "repository_mismatch");
assert_eq!(
serde_json::to_value(RefusalCode::Untrusted).unwrap(),
serde_json::json!("untrusted"),
);
assert_ne!(RefusalCode::Untrusted, RefusalCode::RepositoryMismatch);
malformedNot the shape of the message.oversizedOver MAX_LINK_MESSAGE.protocol_unsupportedThe protocol ranges do not meet.signatureThe signature does not verify.staleThe timestamp is outside the skew window.replayA nonce or a counter already seen.repository_mismatchThe peer serves another repository: the isolation rule.untrustedThe peer's key is not trusted by this runtime's policy: discovery is not trust.self_linkThe peer is this very runtime.not_activeThis runtime's cooperation is not active.unknown_linkThe link id is unknown here: the answerer restarted or expired the link; say hello.capacityA bound of this runtime is full.feature_unsupportedThe peer lacks a feature the operation needs.misaddressedA hello addressed to another runtime: captured on its way there and replayed here.
Used by LinkRefusal
Refusals
object
Refused datagrams, by reason. The diagnosis of a hostile or misconfigured network:
a mesh that hears garbage says so, with numbers, instead of being quietly empty.
| property | type | required | description |
| oversized |
integer (uint64) |
yes |
Longer than the datagram bound. |
| malformed |
integer (uint64) |
yes |
Not an envelope. |
| version |
integer (uint64) |
yes |
A protocol version this executable does not read. |
| bounds |
integer (uint64) |
yes |
Out-of-bounds fields. |
| stale |
integer (uint64) |
yes |
Outside the clock-skew window. |
| signature |
integer (uint64) |
yes |
A signature that does not verify. |
| self_heard |
integer (uint64) |
yes |
This node's own datagrams, heard back and skipped. |
| overflow |
integer (uint64) |
no |
Observations dropped because the queue to the registry was full: a flood, counted
rather than held (I2133). |
Used by MeshStatus
RefusedView
object
A candidate that was refused, with the rule that refused it. A refusal is kept and
shown rather than logged and dropped, because the commonest mesh failure is a link that
never forms: a person who declared a seed and sees no peer needs the decision that was
made about it — the wrong repository, a key nobody allowed, a clock too far out — and
the direction says which side made it.
#### use std::collections::BTreeMap;
#### use std::sync::{Arc, Mutex, Weak};
#### use majordomus_cli::mesh::config::{CooperationConfig, TrustConfig};
#### use majordomus_cli::mesh::cooperation::*;
#### use majordomus_cli::mesh::identity::NodeIdentity;
#### use majordomus_cli::mesh::link::{LinkReply, LinkTransport, RefusalCode, Signed, HELLO_PATH};
#### use majordomus_cli::mesh::registry::MeshRegistry;
#### use majordomus_cli::mesh::repository::{of_root_commits, runtime_id};
#### use majordomus_cli::mesh::trust::TrustPolicy;
#### #[derive(Default)] struct Net(Mutex<BTreeMap<String, Weak<Cooperation>>>);
#### impl LinkTransport for Net {
#### fn post(&self, e: &str, p: &str, m: &Signed) -> Result<LinkReply, String> {
#### let peer = (self.0.lock().unwrap().get(e).and_then(Weak::upgrade))
#### .ok_or_else(|| format!("{e}: nobody there"))?;
#### Ok(if p == HELLO_PATH { peer.accept_hello(m) } else { peer.accept_sync(m) }) } }
#### let net = Arc::new(Net::default());
#### let runtime = |endpoint: &str, policy: TrustPolicy| {
#### let c = Cooperation::new(CooperationSetup {
#### identity: Arc::new(NodeIdentity::ephemeral().unwrap()), runtime: runtime_id(endpoint),
#### repository: of_root_commits(&["root".into()]), endpoints: vec![endpoint.into()],
#### version: "doc".into(), config: CooperationConfig::default(),
#### trust: TrustConfig { policy, allow: vec![] }, journal_path: None,
#### registry: Arc::new(MeshRegistry::new()),
#### transport: Arc::clone(&net) as Arc<dyn LinkTransport>, board: None,
#### checkout: CheckoutFacts::default() }).unwrap();
#### net.0.lock().unwrap().insert(endpoint.into(), Arc::downgrade(&c)); c };
#### let a = runtime("a:1", TrustPolicy::Tofu);
#### let strict = runtime("s:1", TrustPolicy::DenyUnknown);
a.dial(&["s:1".into()]).expect_err("reachable, and not trusted");
let refused: &RefusedView = &strict.status().refused[0];
assert_eq!(refused.refusal.code, RefusalCode::Untrusted);
assert_eq!(refused.direction, "inbound", "this runtime refused the hello");
assert_eq!(refused.runtime.as_deref(), Some(a.runtime_key()));
| property | type | required | description |
| endpoint |
string |
yes |
The endpoint dialed, or inbound:<runtime> for a refused hello. |
| direction |
string |
yes |
outbound (this runtime refused the peer's welcome, or the peer refused this
hello) or inbound (this runtime refused the peer's hello). |
| runtime |
string | null |
no |
The peer's runtime key, when its card said. |
| refusal |
LinkRefusal → LinkRefusal |
yes |
The refusal. |
| at |
string |
yes |
When, RFC 3339. |
Used by CooperationStatus PeerDetail PeerTree
RegisterAnswer
object
What a registration answers: whether the envelope was accepted, and the candidates
the rendezvous holds. Defined here beside the client; the capability re-exports it.
| property | type | required | description |
| accepted |
boolean |
yes |
Whether the presented envelope was accepted into the local registry. |
| refusal |
string | null |
no |
Why not, when it was not. |
| candidates |
array of Envelope → Envelope |
yes |
The signed envelopes of the nodes this rendezvous currently holds, the answering
node's own included. Each verifies on its own signature; the rendezvous vouches
for nothing. |
Used by mesh.register
RegisterInput
object
The input of mesh.register: one signed envelope, exactly as the wire carries it.
| property | type | required | description |
| envelope |
any |
yes |
The envelope. Verified here exactly as a datagram would be — bounds, staleness,
signature — before anything is recorded. |
Used by mesh.register
ReleaseInput
object
The input of mesh.release.
| property | type | required | description |
| claim |
string |
yes |
The claim's key, <stream>/<claim>, as mesh.claim answered it. |
Used by mesh.release
ReviewAnswer
object
One answer to a review request: who answered, what they decided, and why. The verdict
is one of the three declared words, so a requester can act on it without reading the
note; the note is where the reasoning goes and is optional, because "approved" often
needs none.
use majordomus_cli::mesh::state::ReviewAnswer;
let answer = ReviewAnswer {
session: "a1b2c3d4-r1-0f/s2".into(),
verdict: "changes_requested".into(),
note: Some("the fold is not deterministic".into()),
};
assert_eq!(answer.verdict, "changes_requested");
// the note is omitted rather than carried empty when there is nothing to say
let bare = ReviewAnswer { note: None, ..answer };
let value = serde_json::to_value(&bare).unwrap();
assert!(value.get("note").is_none());
| property | type | required | description |
| session |
string |
yes |
The answering session's key. |
| verdict |
string |
yes |
The verdict. |
| note |
string | null |
no |
The note. |
Used by ReviewView
ReviewAnswerInput
object
The input of mesh.review.answer.
| property | type | required | description |
| request |
string |
yes |
The request's key, <stream>/<review>. |
| session |
string | null |
no |
The answering session; the calling MCP session's own when omitted. |
| verdict |
string |
yes |
approved, changes_requested or commented. |
| note |
string | null |
no |
The note. |
Used by mesh.review.answer
ReviewRequestInput
object
The input of mesh.review.request.
| property | type | required | description |
| session |
string | null |
no |
The requesting session; the calling MCP session's own when omitted. |
| subject |
string |
yes |
What to review: a branch, a commit, a pull request. |
| scope |
array of string |
no |
The paths it covers. |
| issue |
string | null |
no |
The issue. |
| reviewer |
string | null |
no |
The runtime asked (<node>-<runtime>); anyone when omitted. |
| body |
string | null |
no |
What the reviewer needs beyond the subject: evidence, commands, the question. Up to
8 KiB, several lines. |
Used by mesh.review.request
ReviewState
"open" | "answered" | "withdrawn" | "expired"
A review request's standing: whether anybody has answered it yet. There is deliberately
no
Approved or
Rejected here — a request may be answered by several runtimes and the
verdicts are the answers' own, so collapsing them into one state would throw away the
disagreement that makes a second reviewer worth asking.
use std::sync::Arc;
use majordomus_cli::mesh::identity::NodeIdentity;
use majordomus_cli::mesh::journal::{EventBody, Journal, StreamLiveness};
use majordomus_cli::mesh::state::{fold, ReviewState};
let j = Journal::open(Arc::new(NodeIdentity::ephemeral().unwrap()), "0000000000000001",
"repo".into(), None).unwrap();
j.append_own(EventBody::ReviewRequested { review: "r1".into(), session: "s1".into(),
subject: "feature/x".into(), scope: vec![], issue: None, reviewer: None, body: None }).unwrap();
let asked = fold(&j.events(), &|_| StreamLiveness::Own);
assert_eq!(asked.reviews[0].state, ReviewState::Open);
let request = asked.reviews[0].key.clone();
j.append_own(EventBody::ReviewAnswered { request, session: "s2".into(),
verdict: "approved".into(), note: None }).unwrap();
assert_eq!(fold(&j.events(), &|_| StreamLiveness::Own).reviews[0].state, ReviewState::Answered);
openNobody has answered.answeredAt least one answer.withdrawnThe runtime that asked ended the request: done, void or superseded. Whatever was
answered before stays with it.expiredNobody answered and the runtime that asked stopped beating. A reading of liveness,
like a claim's expiry, decided from the journal and the streams' beats so that no
runtime needs another's clock — a runtime that beats again reads open again.
Used by ReviewView
ReviewView
object
One review request anywhere in the mesh, with every answer it has drawn. A request with
no
reviewer is an offer to the whole mesh rather than to nobody, which is why the
answers are a list: any linked runtime carrying the
reviews feature may add one, and
they are kept in Lamport order so that every runtime reads them in the same sequence.
use std::sync::Arc;
use majordomus_cli::mesh::identity::NodeIdentity;
use majordomus_cli::mesh::journal::{EventBody, Journal, StreamLiveness};
use majordomus_cli::mesh::state::{fold, ReviewView};
let j = Journal::open(Arc::new(NodeIdentity::ephemeral().unwrap()), "0000000000000001",
"repo".into(), None).unwrap();
j.append_own(EventBody::ReviewRequested { review: "r1".into(), session: "s1".into(),
subject: "feature/mesh-cooperation".into(), scope: vec!["src/mesh".into()],
issue: Some("#184".into()), reviewer: None, body: None }).unwrap();
let request = fold(&j.events(), &|_| StreamLiveness::Own).reviews[0].key.clone();
j.append_own(EventBody::ReviewAnswered { request, session: "s2".into(),
verdict: "approved".into(), note: Some("reads well".into()) }).unwrap();
let state = fold(&j.events(), &|_| StreamLiveness::Own);
let review: &ReviewView = &state.reviews[0];
assert_eq!(review.subject, "feature/mesh-cooperation");
assert_eq!(review.reviewer, None, "offered to the mesh, not addressed");
assert_eq!(review.answers[0].verdict, "approved");
| property | type | required | description |
| key |
string |
yes |
<stream>/<review>. |
| session |
string |
yes |
The requesting session's key. |
| subject |
string |
yes |
What is to be reviewed. |
| scope |
array of string |
yes |
Paths it covers. |
| issue |
string | null |
no |
The issue. |
| reviewer |
string | null |
no |
The runtime asked, when one was. |
| body |
string | null |
no |
What the reviewer needs beyond the subject, when the requester wrote it. |
| state |
ReviewState → ReviewState |
yes |
Its standing. |
| answers |
array of ReviewAnswer → ReviewAnswer |
yes |
The answers, in Lamport order. |
| result |
string | null |
no |
The requester's own result or reason, written when it withdrew the request. Never an
answer: a review is another session's verdict. |
Used by CooperationState
ReviewWithdrawInput
object
The input of mesh.review.withdraw.
| property | type | required | description |
| request |
string |
yes |
The request's key, <stream>/<review>. |
| session |
string | null |
no |
The withdrawing session of the asking runtime; the calling MCP session's own when
omitted. |
| note |
string | null |
no |
The requester's result or reason, up to 8 KiB: what came of it, or why it is void. |
Used by mesh.review.withdraw
Role
"multicast" | "broadcast" | "hub" | "link"
Why a rule exists: which part of the declaration implied it. The role is what a person
reads beside the rule and what a verdict names, and it is serialised in snake case.
use majordomus_cli::mesh::firewall::Role;
assert_eq!(serde_json::to_value(Role::Hub).unwrap(), "hub");
let back: Role = serde_json::from_value(serde_json::json!("multicast")).unwrap();
assert_eq!(back, Role::Multicast);
multicastThe declared multicast group: every runtime of the segment advertises to it.broadcastThe declared broadcast port, when broadcast is a permitted fallback.hubThis machine is a declared rendezvous hub: the fleet registers here.linkThis machine's server is dialed by peers on the link protocol.
Used by FirewallRule
RuntimeNode
object
One runtime in the peer tree.
| property | type | required | description |
| runtime |
string |
yes |
<node>-<runtime>. |
| this_runtime |
boolean |
yes |
Whether this is the runtime answering. |
| liveness |
StreamLiveness → StreamLiveness |
yes |
Whether its stream beats: own, live, or expired. |
| last_beat_ms |
integer | null |
no |
Milliseconds since its beat last rose, on this runtime's clock. |
| link |
PeerView | null → PeerView |
no |
This runtime's link to it, when there is one. |
| newest_claim |
MeshClaimBrief | null → MeshClaimBrief |
no |
Its newest live claim, told briefly. |
| sessions |
array of SessionNode → SessionNode |
yes |
Its sessions; in the summary view only the active ones. |
Used by MachineNode PeerDetail
SessionCloseInput
object
The input of mesh.session.close.
| property | type | required | description |
| session |
string | null |
no |
The session; the calling MCP session's own when omitted. |
Used by mesh.session.close
SessionInfo
object
What a session tells the mesh about itself. Every field is a public fact about work in
progress; none is a path on the author's disk, a secret or a credential.
Almost everything here is optional, and that is the design: a session says what it
knows, and a field it leaves out is omitted rather than carried empty, so a reader can
tell "no task" from "a task called nothing". Opening the same session id again replaces
this record, which is how a session that learns its branch later tells the mesh.
use majordomus_cli::mesh::journal::SessionInfo;
let mut info = SessionInfo::named("s1", "claude-code");
assert_eq!(info.task, None);
assert!(serde_json::to_value(&info).unwrap().get("task").is_none(), "unsaid, not empty");
info.branch = Some("feature/mesh-cooperation".into());
assert_eq!(serde_json::to_value(&info).unwrap()["branch"], "feature/mesh-cooperation");
| property | type | required | description |
| session |
string |
yes |
The session's id within its stream. |
| client |
string |
yes |
The client: claude-code, codex, cli, ... |
| worker |
string | null |
no |
The worker's own name for itself, when it gave one. |
| intent |
string | null |
no |
What the session says it is doing. |
| task |
string | null |
no |
The task id (t-...) the session works under. |
| issue |
string | null |
no |
The issue (I0001, #184) the work belongs to. |
| milestone |
string | null |
no |
The milestone the work belongs to. |
| checkout |
string | null |
no |
The checkout id (a digest, never a path). |
| branch |
string | null |
no |
The branch checked out. |
| head |
string | null |
no |
The commit checked out. |
| dirty |
boolean | null |
no |
Whether the working tree had uncommitted changes when this was said. |
| context |
string | null |
no |
The context revision the session works from (a digest the context compiler names). |
Used by SessionNode SessionView
SessionInput
object
The input of mesh.session.open: what a session says about itself.
| property | type | required | description |
| session |
string | null |
no |
The session id within this runtime; the calling MCP session's own when omitted. |
| client |
string | null |
no |
The client (claude-code, codex, cli); cli when omitted. |
| worker |
string | null |
no |
The worker's name for itself. |
| intent |
string | null |
no |
What it is doing. |
| task |
string | null |
no |
The task id. |
| issue |
string | null |
no |
The issue. |
| milestone |
string | null |
no |
The milestone. |
| branch |
string | null |
no |
The branch. |
| head |
string | null |
no |
The head. |
| dirty |
boolean | null |
no |
Whether the working tree is dirty. |
| context |
string | null |
no |
The context revision it works from. |
Used by mesh.session.open
SessionNode
object
One session in the peer tree.
| property | type | required | description |
| key |
string |
yes |
<stream>/<session>. |
| info |
SessionInfo → SessionInfo |
yes |
What the session last said about itself. |
| state |
SessionState → SessionState |
yes |
Its standing. |
| claims |
array of ClaimView → ClaimView |
yes |
The claims it holds or held; empty in the summary view, which counts them instead. |
| claims_held |
integer (uint) |
yes |
How many of its claims are live. |
| reviews |
array of string |
yes |
The review requests it opened, by key. |
Used by RuntimeNode
SessionState
"active" | "closed" | "expired"
A session's standing, as every runtime of the mesh reads it. The distinction that
matters is between
Closed and
Expired: the first is a session that said it was
finished, the second one whose runtime stopped beating and can no longer say anything.
Both end the session's claims, but only the second is a machine nobody should expect an
answer from.
use std::sync::Arc;
use majordomus_cli::mesh::identity::NodeIdentity;
use majordomus_cli::mesh::journal::{EventBody, Journal, SessionInfo, StreamLiveness};
use majordomus_cli::mesh::state::{fold, SessionState};
let j = Journal::open(Arc::new(NodeIdentity::ephemeral().unwrap()), "0000000000000001",
"repo".into(), None).unwrap();
j.append_own(EventBody::SessionOpened { info: SessionInfo::named("s1", "cli") }).unwrap();
assert_eq!(fold(&j.events(), &|_| StreamLiveness::Own).sessions[0].state, SessionState::Active);
// the same events, read on a runtime that no longer hears the writer
let gone = fold(&j.events(), &|_| StreamLiveness::Expired);
assert_eq!(gone.sessions[0].state, SessionState::Expired);
activeOpen, and its runtime is alive.closedClosed by its own runtime.expiredNever closed, but its runtime stopped beating: gone, as far as anyone can tell.
Used by SessionNode SessionView
SessionView
object
One session anywhere in the mesh, as the fold sees it: what the session said about
itself, where it is writing from, and whether it still counts. The key is
<stream>/<session> rather than the session's own id, because two runtimes may both
call a session
s1 and the mesh has to hold them apart.
use std::sync::Arc;
use majordomus_cli::mesh::identity::NodeIdentity;
use majordomus_cli::mesh::journal::{EventBody, Journal, SessionInfo, StreamLiveness};
use majordomus_cli::mesh::state::{fold, SessionView};
let j = Journal::open(Arc::new(NodeIdentity::ephemeral().unwrap()), "0000000000000001",
"repo".into(), None).unwrap();
j.append_own(EventBody::SessionOpened { info: SessionInfo::named("s1", "claude-code") })
.unwrap();
let state = fold(&j.events(), &|_| StreamLiveness::Own);
let view: &SessionView = &state.sessions[0];
assert!(view.key.ends_with("/s1"), "qualified by the stream that wrote it: {}", view.key);
assert_eq!(view.info.client, "claude-code");
assert_eq!(view.runtime, view.stream.runtime_key());
| property | type | required | description |
| key |
string |
yes |
<stream>/<session>: unique across the mesh. |
| stream |
StreamId → StreamId |
yes |
The writing stream. |
| runtime |
string |
yes |
<node>-<runtime>. |
| node |
string |
yes |
The node (machine). |
| info |
SessionInfo → SessionInfo |
yes |
What the session last said about itself. |
| state |
SessionState → SessionState |
yes |
Its standing. |
| updated_lamport |
integer (uint64) |
yes |
The Lamport stamp of its latest word. |
Used by CooperationState
Sighting
object
One provenance entry: a source, and where the observation physically came from.
| property | type | required | description |
| source |
MeshSource → MeshSource |
yes |
The provider kind that saw it. |
| path |
string |
yes |
The network path, as text: a sender address, an interface, a rendezvous URL. |
| at |
string |
yes |
When, RFC 3339. |
Used by NodeRecord
Signed
object
A signed message: a JSON body and the Ed25519 signature over its canonical bytes under
the message's domain. The body stays JSON so that fields a newer peer adds survive
verification here unchanged.
Keeping the body as JSON rather than as the bytes that were signed is what makes the
protocol extensible: a field a newer peer added is carried through verification
untouched, because the signature is checked over the canonical form of whatever is
there rather than over a shape this version knows.
use majordomus_cli::mesh::identity::NodeIdentity;
use majordomus_cli::mesh::link::{sign, verify_signed, Domain, Signed};
let node = NodeIdentity::ephemeral().unwrap();
let signed: Signed = sign(&node, Domain::SyncRequest, serde_json::json!({ "b": 2, "a": 1 }));
assert_eq!(signed.sig.len(), 128, "hex Ed25519");
// key order is not content, so a round trip through JSON still verifies
let wire = serde_json::to_string(&signed).unwrap();
let back: Signed = serde_json::from_str(&wire).unwrap();
assert!(verify_signed(&node.public.public_key, Domain::SyncRequest, &back));
| property | type | required | description |
| body |
any |
yes |
The message. |
| sig |
string |
yes |
Hex Ed25519 over the domain and the canonical JSON of body. |
Used by mesh.link.hello mesh.link.sync LinkReply
StreamId
string
One stream: a run of one runtime of one node.
<node 32 hex>-<runtime 16 hex>-<instance 16 hex>. The node is the machine's key, the runtime is one checkout's server on that
machine, the instance is one process run of that server — so two worktrees on one
machine are two streams, and a restart is a new stream of the same runtime.
Numbering events within a stream rather than globally is what makes replication cheap
and unambiguous: a gap in one stream is visible without coordinating with anyone, and
(stream, seq) identifies an event on every machine that holds it.
use majordomus_cli::mesh::journal::StreamId;
let id = StreamId::new(&"a".repeat(32), &"b".repeat(16), &"c".repeat(16)).unwrap();
assert_eq!(id.node(), "a".repeat(32));
assert_eq!(id.runtime(), "b".repeat(16));
// a restart is a new stream, and the same runtime
let after = StreamId::new(&"a".repeat(32), &"b".repeat(16), &"d".repeat(16)).unwrap();
assert_ne!(after, id);
assert_eq!(after.runtime_key(), id.runtime_key());
Used by ClaimView CooperationStatus HandoverView MeshEvent SessionView StreamView
StreamLiveness
"own" | "live" | "expired"
Whether a stream is speaking now, on this runtime's clock. It is a verdict about a
stream and not about a link: a runtime reachable only through a relay is alive, and a
runtime that crashed stops beating for everyone at once.
Own is kept apart from
Live
because a runtime's own stream needs no evidence — it is the thing doing the beating.
use majordomus_cli::mesh::journal::StreamLiveness;
assert!(StreamLiveness::Own.is_alive() && StreamLiveness::Live.is_alive());
assert!(!StreamLiveness::Expired.is_alive());
assert_eq!(
serde_json::to_value(StreamLiveness::Expired).unwrap(),
serde_json::json!("expired"),
);
ownThis runtime's own current stream.liveIts beat rose within the expiry.expiredIts beat has not risen within the expiry, or was never heard.
Used by MeshRuntimeSummary RuntimeNode StreamView
StreamView
object
One stream's liveness.
| property | type | required | description |
| stream |
StreamId → StreamId |
yes |
The stream. |
| liveness |
StreamLiveness → StreamLiveness |
yes |
Own, live or expired. |
| last_beat_ms |
integer | null |
no |
Milliseconds since its beat rose. |
Used by MeshStateAnswer
Tallies
object
The registry's tallies, for status and health.
| property | type | required | description |
| nodes |
integer (uint) |
yes |
Records held. |
| trusted |
integer (uint) |
yes |
Of which trusted. |
| present |
integer (uint) |
yes |
Of which present. |
| accepted |
integer (uint64) |
yes |
Observations accepted since start. |
| replayed |
integer (uint64) |
yes |
Replayed datagrams dropped since start. |
| expired |
integer (uint64) |
yes |
Records expired out since start. |
| evicted |
integer (uint64) |
yes |
Records evicted for space since start. |
Used by MeshStatus
TrustState
one of 3 variants
What the policy decided about one node.
variant Valid, recorded, trusted for nothing. The resting state of an unknown node.
variant The policy trusts this key; detail says which rule did (allowlist, tofu).
| state | "trusted" | yes | |
| detail | string | yes | |
variant The policy rejected this node; detail says why (for instance a TOFU key
change). A rejected node stays listed — an operator debugging a mesh needs to
see what was refused, not wonder about silence.
| state | "rejected" | yes | |
| detail | string | yes | |
Used by NodeRecord PeerView
Written
object
What an operation wrote, as its answer to the caller. Every mesh operation is an event
appended to this runtime's journal, and the caller is handed the three facts it cannot
work out for itself: the mesh-wide key of the thing it created, the event that says so,
and the Lamport stamp that places it against everyone else's events.
The key is what a later operation is addressed by — a claim is released by the key its
acquisition returned, never by the local name it was given — and the stamp is what a
caller waiting for a peer to catch up compares against.
#### use std::sync::Arc;
#### use majordomus_cli::mesh::config::{CooperationConfig, TrustConfig};
#### use majordomus_cli::mesh::cooperation::{CheckoutFacts, Cooperation, CooperationSetup};
#### use majordomus_cli::mesh::identity::NodeIdentity;
#### use majordomus_cli::mesh::link::HttpTransport;
#### use majordomus_cli::mesh::registry::MeshRegistry;
#### use majordomus_cli::mesh::repository::of_root_commits;
#### let cooperation = Cooperation::new(CooperationSetup {
#### identity: Arc::new(NodeIdentity::ephemeral().unwrap()), runtime: "0000000000000001".into(),
#### repository: of_root_commits(&["root".into()]), endpoints: vec!["127.0.0.1:9".into()],
#### version: "doc".into(), config: CooperationConfig::default(), trust: TrustConfig::default(),
#### journal_path: None, registry: Arc::new(MeshRegistry::new()),
#### transport: Arc::new(HttpTransport), board: None, checkout: CheckoutFacts::default(),
#### }).unwrap();
use majordomus_cli::mesh::cooperation::Written;
use majordomus_cli::mesh::journal::{ClaimMode, Durability, SessionInfo};
let session = SessionInfo::named("s1", "cli");
let written: Written = cooperation
.claim(&session, vec!["docs".into()], None, ClaimMode::Advisory, None)
.unwrap();
assert!(written.lamport > 0, "placed against every other runtime's events");
assert_eq!(written.durability, Durability::Memory, "this journal has no file");
// and the key it returned is how the claim is addressed from here on
cooperation.release(&written.key).unwrap();
| property | type | required | description |
| key |
string |
yes |
<stream>/<local id> of what was created or changed. |
| event |
string |
yes |
<stream>/<seq> of the event that says so. |
| lamport |
integer (uint64) |
yes |
The event's Lamport stamp. |
| durability |
Durability → Durability |
no |
Whether the event reached this machine's disk: synced, memory (a journal with no
file, by configuration), or not_durable — held and replicated to peers, not on disk
here, and not acknowledged as durable. unknown from an executable that predates it. |
Used by mesh.claim mesh.release mesh.handover.publish mesh.review.request mesh.review.answer mesh.review.withdraw mesh.session.open mesh.session.close ConsumeAnswer