mesh.link.hello
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.
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
- module mesh
- #mesh
- #cooperation
- #link
Exposure
Where this one definition is reachable. Absence is explicit: a surface not listed does not carry it.
| surface | as |
|---|---|
| MCP | — |
| HTTP | POST /api/v1/mesh/link/hello operationId mesh.link.hello |
| command line | — |
Schemas
The canonical input and output, from the Rust types; the MCP tool schema and the OpenAPI parameters and responses are derived from these.
input · Signed
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 | object | yes | The message. |
| sig | string | yes | Hex Ed25519 over the domain and the canonical JSON of body. |
JSON Schema
{
"description": "A signed message: a JSON body and the Ed25519 signature over its canonical bytes under\nthe message's domain. The body stays JSON so that fields a newer peer adds survive\nverification here unchanged.\n\nKeeping the body as JSON rather than as the bytes that were signed is what makes the\nprotocol extensible: a field a newer peer added is carried through verification\nuntouched, because the signature is checked over the canonical form of whatever is\nthere rather than over a shape this version knows.\n\n```\nuse majordomus_cli::mesh::identity::NodeIdentity;\nuse majordomus_cli::mesh::link::{sign, verify_signed, Domain, Signed};\n\nlet node = NodeIdentity::ephemeral().unwrap();\nlet signed: Signed = sign(&node, Domain::SyncRequest, serde_json::json!({ \"b\": 2, \"a\": 1 }));\nassert_eq!(signed.sig.len(), 128, \"hex Ed25519\");\n\n// key order is not content, so a round trip through JSON still verifies\nlet wire = serde_json::to_string(&signed).unwrap();\nlet back: Signed = serde_json::from_str(&wire).unwrap();\nassert!(verify_signed(&node.public.public_key, Domain::SyncRequest, &back));\n```",
"properties": {
"body": {
"description": "The message."
},
"sig": {
"description": "Hex Ed25519 over the domain and the canonical JSON of `body`.",
"type": "string"
}
},
"required": [
"body",
"sig"
],
"title": "Signed",
"type": "object"
}output · LinkReply
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 |
|---|---|---|---|
| refusal | one of 2 | no | The refusal, when not. |
| signed | one of 2 | no | The signed welcome or sync answer, when accepted. |
JSON Schema
{
"$defs": {
"LinkRefusal": {
"description": "A refusal: the code, the reason in words, and the protocol range of the refuser so a\nversion mismatch is diagnosable from either end.\n\nCarrying the refuser's protocol range in every refusal, not only in the one about\nprotocols, is what makes a version problem visible from the side that has the log: the\ndialer sees what the answerer speaks without anyone having to read the other machine.\n\n```\nuse majordomus_cli::mesh::link::{LinkRefusal, RefusalCode, LINK_PROTOCOL_MAX};\n\nlet refusal = LinkRefusal::new(RefusalCode::Untrusted, \"the key is not in the allowlist\");\nassert_eq!(refusal.code, RefusalCode::Untrusted);\nassert_eq!(refusal.proto_max, LINK_PROTOCOL_MAX);\nassert_eq!(refusal.to_string(), \"untrusted: the key is not in the allowlist\");\n```",
"properties": {
"code": {
"$ref": "#/$defs/RefusalCode",
"description": "The rule that refused."
},
"detail": {
"description": "Why, in words.",
"type": "string"
},
"proto_max": {
"description": "The refuser's newest protocol.",
"format": "uint32",
"minimum": 0,
"type": "integer"
},
"proto_min": {
"description": "The refuser's oldest protocol.",
"format": "uint32",
"minimum": 0,
"type": "integer"
}
},
"required": [
"code",
"detail",
"proto_min",
"proto_max"
],
"type": "object"
},
"RefusalCode": {
"description": "Why a link message was turned down. Every refusal is typed, counted and shown: a mesh\nthat refuses a peer says which rule did, instead of dropping a socket.\n\nThe codes are the diagnosis a person needs when two runtimes will not link, and they\nare deliberately distinct where the remedies differ: `Untrusted` is a key to add,\n`RepositoryMismatch` is two different repositories, `SelfLink` is a configuration that\ndials its own endpoint, and `UnknownLink` is not a failure at all but an instruction to\nsay hello again.\n\n```\nuse majordomus_cli::mesh::link::RefusalCode;\n\n// the wire word is the code's identity across surfaces, and it is stable\nassert_eq!(RefusalCode::RepositoryMismatch.as_str(), \"repository_mismatch\");\nassert_eq!(\n serde_json::to_value(RefusalCode::Untrusted).unwrap(),\n serde_json::json!(\"untrusted\"),\n);\nassert_ne!(RefusalCode::Untrusted, RefusalCode::RepositoryMismatch);\n```",
"oneOf": [
{
"const": "malformed",
"description": "Not the shape of the message.",
"type": "string"
},
{
"const": "oversized",
"description": "Over `MAX_LINK_MESSAGE`.",
"type": "string"
},
{
"const": "protocol_unsupported",
"description": "The protocol ranges do not meet.",
"type": "string"
},
{
"const": "signature",
"description": "The signature does not verify.",
"type": "string"
},
{
"const": "stale",
"description": "The timestamp is outside the skew window.",
"type": "string"
},
{
"const": "replay",
"description": "A nonce or a counter already seen.",
"type": "string"
},
{
"const": "repository_mismatch",
"description": "The peer serves another repository: the isolation rule.",
"type": "string"
},
{
"const": "untrusted",
"description": "The peer's key is not trusted by this runtime's policy: discovery is not trust.",
"type": "string"
},
{
"const": "self_link",
"description": "The peer is this very runtime.",
"type": "string"
},
{
"const": "not_active",
"description": "This runtime's cooperation is not active.",
"type": "string"
},
{
"const": "unknown_link",
"description": "The link id is unknown here: the answerer restarted or expired the link; say hello.",
"type": "string"
},
{
"const": "capacity",
"description": "A bound of this runtime is full.",
"type": "string"
},
{
"const": "feature_unsupported",
"description": "The peer lacks a feature the operation needs.",
"type": "string"
},
{
"const": "misaddressed",
"description": "A hello addressed to another runtime: captured on its way there and replayed here.",
"type": "string"
}
]
},
"Signed": {
"description": "A signed message: a JSON body and the Ed25519 signature over its canonical bytes under\nthe message's domain. The body stays JSON so that fields a newer peer adds survive\nverification here unchanged.\n\nKeeping the body as JSON rather than as the bytes that were signed is what makes the\nprotocol extensible: a field a newer peer added is carried through verification\nuntouched, because the signature is checked over the canonical form of whatever is\nthere rather than over a shape this version knows.\n\n```\nuse majordomus_cli::mesh::identity::NodeIdentity;\nuse majordomus_cli::mesh::link::{sign, verify_signed, Domain, Signed};\n\nlet node = NodeIdentity::ephemeral().unwrap();\nlet signed: Signed = sign(&node, Domain::SyncRequest, serde_json::json!({ \"b\": 2, \"a\": 1 }));\nassert_eq!(signed.sig.len(), 128, \"hex Ed25519\");\n\n// key order is not content, so a round trip through JSON still verifies\nlet wire = serde_json::to_string(&signed).unwrap();\nlet back: Signed = serde_json::from_str(&wire).unwrap();\nassert!(verify_signed(&node.public.public_key, Domain::SyncRequest, &back));\n```",
"properties": {
"body": {
"description": "The message."
},
"sig": {
"description": "Hex Ed25519 over the domain and the canonical JSON of `body`.",
"type": "string"
}
},
"required": [
"body",
"sig"
],
"type": "object"
}
},
"description": "What a hello or a sync answers over the wire: a signed message or a refusal. Both are\nsuccessful replies — the transport carries them the same way and neither is an error —\nbecause a refusal is a decision this protocol wants recorded and shown, and an answer\nthat arrived as an HTTP failure would be indistinguishable from a peer that is down.\n\n```\nuse majordomus_cli::mesh::identity::NodeIdentity;\nuse majordomus_cli::mesh::link::{sign, Domain, LinkReply, RefusalCode};\n\nlet node = NodeIdentity::ephemeral().unwrap();\nlet accepted = LinkReply::accepted(sign(&node, Domain::Welcome, serde_json::json!({})));\nassert!(accepted.signed.is_some() && accepted.refusal.is_none());\n\nlet refused = LinkReply::refused(RefusalCode::SelfLink, \"this runtime dialled itself\");\nassert!(refused.signed.is_none());\nassert_eq!(refused.refusal.unwrap().code, RefusalCode::SelfLink);\n```",
"properties": {
"refusal": {
"anyOf": [
{
"$ref": "#/$defs/LinkRefusal"
},
{
"type": "null"
}
],
"description": "The refusal, when not."
},
"signed": {
"anyOf": [
{
"$ref": "#/$defs/Signed"
},
{
"type": "null"
}
],
"description": "The signed welcome or sync answer, when accepted."
}
},
"title": "LinkReply",
"type": "object"
}Policies
- benchmark
- required — a target on every transport the exposure declares; the cases are the input type's
- cache
- disabled — every call runs the handler
Benchmark targets
Derived from the registry for this repository: one requirement per transport, and the cases the input type provides. The whole matrix is on the benchmarks page.