HOVEL // chapter 10
Chapter 10 Part 02 / Runtime Platform

Mesh

A Mesh is a provider-owned node operations plane. It may be a single node, a pair of nodes, a chain such as node 1 ↔ node 2 ↔ node 3, or a routed tree or graph. The name is intentionally broader than transport or tunnel: byte streams and datagrams are Mesh operations, but the same Mesh can survey devices, upload and execute files, run commands, load provider-defined components, publish triggers, and report beacons.

Choose your path

The machine-readable companion to the API guide is the daemon OpenAPI document.

Why not transport or tunnel?

TransportEndpoint remains the narrow chain capability for byte movement. A tunnel is an operator workflow that bridges a stream to a local IP address and port so a compatible client can connect without knowing how the route works. Mesh is the umbrella for the whole node operations plane.

Mesh model

  • Mesh Node — an addressable participant such as the controller, a relay, a target-side agent, or a managed implant.
  • Mesh Link — a communication edge between two nodes.
  • Mesh Route — an ordered path across nodes and links used for a task or stream.
  • Mesh Destination — a host or service reachable through a node or route but not modeled as a Mesh Node.
  • Mesh Task — a node, route, or destination operation such as survey, upload, execute, upload_execute, command, load, or stream.
  • Mesh Listener — a provider-reported listening post or data-plane rendezvous resource, distinct from both the provider control-plane adapter and a daemon-owned Mesh Bridge.
  • Mesh Bridge — a daemon-owned authenticated loopback TCP or UDP endpoint that forwards one local client or peer to one provider-owned Mesh session flow.
  • Trigger — a provider-declared condition that can cause Mesh work or state changes.
  • Beacon — a time-stamped liveness or status signal from one node.

Where TLS and credentials fit

Workspace PKI owns certificate custody and lifecycle; a Mesh provider owns the protocol that consumes a certificate. Hovel does not infer TLS from a listener kind, a port, or a protocol string. Instead, a provider advertises strict credential slots, an operator binds an assignment to the intended consumer, and a credential-aware Mesh request selects that assignment without carrying secret bytes.

Assignment-bound Mesh credential delivery
  1. active PKI assignment
  2. non-secret assignment and slot selection
  3. validate provider descriptor and consumer ID
  4. resolve short-lived projection
  5. credential.runtime
  6. listener, task, or stream in the same process
  7. digest-only execution record and cleanup
SurfaceTypical TLS roleAssignment identityImplemented Mesh selection behavior
Provider control planeTLS client or server used by the provider itself.mesh-provider with the canonical, unversioned provider name, such as lab-relay-mesh.The provider binding is allowed for credential-aware listener start, task, stream, and bridge-open calls.
Mesh Listener / listening postTLS or mTLS rendezvous endpoint.mesh-listener joins slash-free provider and listener components as lab-relay-mesh/listener-west; slash-bearing components use the opaque digest form below.Allowed when the operation names that exact listenerId. The broader listening-post assignment type is not derived by these Mesh calls.
Mesh NodeNode-side client or server identity.mesh-node uses the same canonical encoding; a slash-free ID remains lab-relay-mesh/relay-west.Allowed only when the operation explicitly names that nodeId; route membership alone does not derive node consumers.
Implant or stagerArtifact-embedded identity, bootstrap identity, or a later runtime identity.The assignment model includes implant and stager consumer types with operator-selected IDs.Those consumer types are extension points for Mesh orchestration today. If the implant is exposed as a Mesh Node, the implemented runtime path can bind its node identity as mesh-node. Build-time stamping is a separate credential-provider workflow.
Routed streamA provider may establish TLS itself, or it may carry already encrypted application bytes unchanged.No stream consumer type is derived. Selections bind to the provider, explicit listener, or explicit node.OpenMeshStream delivers selected material to the provider process before mesh.open_stream.
Local bridgeLocal adapter for a provider-owned session.The bridge has no PKI assignment identity.OpenMeshBridge uses the same credential-aware stream-open path; credentials go to the provider, not to the bridge or local client.

When either component contains /, Hovel emits mesh-scoped:<sha256>. The lowercase hexadecimal digest is SHA-256 over the canonical UTF-8 provider name, one NUL byte, and the canonical UTF-8 listener or node ID. This bounded form prevents ambiguous component boundaries. Prefer the typed domain constructors and retain the returned assignment consumer ID rather than concatenating components.

An installed module ID may include a version, for example lab-relay-mesh@v0.1.0. Assignment consumer IDs never include that @version suffix. Hovel still binds delivery to the exact installed module version and the SHA-256 digest of its current credential descriptor before releasing material.

Certificate and private key material use separate slots. One active assignment can be selected twice with distinct request IDs and slot names: one slot accepts certificate-der with form public, while the other accepts private-key-pkcs8 with form private-bytes. This lets the descriptor forbid private material in the certificate slot and require it in the key slot. The current external Mesh selector also supports public-key-spki/public. It does not currently resolve bundles, chains, trust anchors, CRLs, signer references, protected files, provider encodings, or stamping targets. See the credential-provider guide for those separate contracts.

SDK contract

Mesh providers are module processes. They use the same framed JSON-RPC runtime, structured log rail, session broker, guardrails, and explicit result records as other modules. Python modules override Mesh hook methods, Go exposes typed optional Mesh interfaces, and Rust exposes Mesh hook methods on the Module trait.

Mesh is optional-interface based, not all-or-nothing. A minimal provider may implement only mesh.describe and mesh.open_stream for a single stream-style Mesh. A deep implant or stager provider may also expose topology, routes, beacons, triggers, upload_execute, native load, command, and survey tasks. Structured tasks, listenerTypes, topology, triggers, and credential-delivery metadata describe their corresponding contracts. The top-level capabilities string list is optional, opaque provider vocabulary; Hovel does not infer hook support from those strings. The implementation and structured descriptor must agree, and every unsupported method must fail explicitly.

Credential consumption is optional in the same way. A provider that wants assignment-bound material advertises a credentialDelivery descriptor and the runtime capability, then implements credential.describe and credential.runtime. If the same descriptor is published independently and inside mesh.describe, the two canonical descriptors must match.

Listening-post support is optional too. Providers advertise supported kinds through listenerTypes, and providers that own listener lifecycle implement list, start, and stop independently of task and stream support. A listener may be deployment-coupled to the provider (embedded) or deployed separately, and its lifecycle may be provider-managed or externally managed. Those labels describe ownership and deployment, not an assumption that the module subprocess stays alive between RPC calls.

MethodPurposePythonGoRust
mesh.describeReport static Mesh capabilities, task specs, declared triggers, cheap topology, and optional credential-delivery metadata.yesyesyes
mesh.topologyReturn current nodes, links, and optional routes.yesyesyes
mesh.beaconsReturn recent beacon records for one node or the Mesh.yesyesyes
mesh.listenersList provider-reported listening posts without returning their configuration or credentials.yesyesyes
mesh.listener.startIdempotently start or attach a caller-named listening post, optionally after same-process runtime delivery.yesyesyes
mesh.listener.stopIdempotently stop or detach a caller-named listening post. Stop is not credential-bearing.yesyesyes
mesh.taskRun a provider-owned node or route task, optionally after same-process runtime delivery.yesyesyes
mesh.open_streamOpen a routed session flow as a normal SessionRef, optionally after same-process runtime delivery.yesyesyes

Runtime contract

The daemon runtime treats Mesh as a first-class provider surface. Module inspection asks for mesh.describe as optional discovery and stores the descriptor in module catalog data. The run service and daemon RPC expose describe, topology, beacon, listener, task, stream, and bridge calls so front ends do not need to speak module JSON-RPC directly.

StartMeshListener, RunMeshTask, OpenMeshStream, and OpenMeshBridge accept an optional top-level credentials array. When that array is present, a matching credentialContext is required with actor, operation, and correlation IDs plus approveCredentialUse: true. The caller chooses only assignment IDs, provider-advertised slots, and non-secret projection/form pairs. It never receives the resolved bytes.

Hovel inspects the exact installed provider, reconciles its credential and Mesh descriptors, resolves the active assignment generation, starts one provider process, calls credential.runtime once per selection, and invokes the consuming Mesh method in that same process. If a task or stream returns a live session, the session broker adopts that process for the session lifetime. A listener-start process is shut down after the call. A long-lived listener must therefore live in durable provider-owned state or another service, and the provider owns any protected handoff from its short-lived control adapter to that runtime.

ListMeshListeners, StartMeshListener, and StopMeshListener are stable HTTP/JSON daemon methods. A caller can use a stable listenerId for idempotent provider lifecycle behavior and correlate results with ListMeshOperations. Listener IDs are scoped to one provider Mesh, so daemon-wide identity is the pair of moduleId and listenerId. Start configuration is write-only and Hovel never copies it into listener read models or Mesh bookkeeping records. Providers must keep configuration and credential material out of errors and logs.

Streams, protocols, and bridges

OperationProtocol contractWhat Hovel preserves
RunMeshTaskprotocol is provider-defined task metadata.The typed request and bookkeeping fields; no generic socket behavior is implied.
OpenMeshStreamprotocol is provider-defined and may name TCP, UDP, ICMP, GRE, raw IP, or another provider contract.The returned provider-owned SessionRef. Hovel does not reinterpret an opaque protocol as TCP.
OpenMeshBridge with localNetwork: tcpThe local adapter is a byte stream. The request's protocol remains provider-defined and is never overwritten.Application byte order after the local capability preface is consumed.
OpenMeshBridge with localNetwork: udpThe local adapter is a single authenticated UDP peer and requires a returned session with the datagram capability. The remote protocol is independent.Exactly one datagram per non-empty session write or read, including datagrams received before the local peer authenticates.
Opaque or non-socket remote protocolProviders may map it onto either supported local association when their session contract defines the semantics.No generic raw, ICMP, TUN, or TAP local adapter is implied; otherwise use a provider-defined task or direct session contract.

A caller can use OpenMeshBridge to receive a loopback localHost/localPort pair, the selected localNetwork, and an ephemeral 256-bit bearer capability. The first TCP bytes must be the capability followed by LF; for UDP, the first complete datagram is the capability. Hovel consumes that preface and never forwards it to the provider. The Go, Python, and Rust SDKs provide helpers that validate the endpoint and perform this handshake before returning a normal connection.

A Mesh Bridge does not encrypt, terminate, originate, or validate TLS. It does not select certificates for the local client, inject trust anchors, provide SNI or ALPN, or turn UDP into DTLS or QUIC. The bearer capability authenticates one local connection or peer; it is not transport encryption. TLS must be implemented by the local application or by the Mesh provider. If a local TLS client speaks end-to-end through a TCP bridge, the bridge forwards those ciphertext bytes unchanged.

Loopback plus the capability is a local process boundary, not protection against a compromised operator account or daemon host. Keep the capability in memory, never log or persist it, and close the bridge when the consumer finishes. Open one bridge for each independent TCP connection or UDP peer association.

The daemon keeps bookkeeping for Mesh listener lifecycle calls, tasks, streams, and bridges. Records capture the provider module, run, task kind, node, route, destination, protocol, session, state, and timestamps. Bridge records also carry the daemon-owned local address; listener records carry the action, stable listener ID, and provider-reported state. Credential deliveries have a separate secret-free PKI execution ledger containing identities, digests, sizes, and terminal receipts, never resolved bytes or protected paths.

Live topology, beacon, listener, and Mesh operation records are sensitive control-plane data. Their daemon RPC methods require the owner-controlled Unix transport (or a future authenticated proxy with equivalent authority), even though they are reads. The loopback TCP development endpoint is not a local-user authentication mechanism and rejects these methods with permission-denied.

Safe certificate rotation

Renewal and rotation create a new immutable certificate generation; they do not silently replace the generation currently assigned to a provider, listener, or node. Assignment changes use revision-fenced stage and activate transitions:

  1. Renew or rotate to create a new generation. Rotation creates a distinct key; renewal reuses and revalidates the source key.
  2. Stage the new generation on each assignment with its current expected revision. Peer-trust purposes also pin the then-active trust generation.
  3. Prepare and verify the consumer cutover. Staging alone is not delivery: the current Mesh selector resolves only the assignment's active generation, not its staged generation.
  4. Activate with the new expected revision only when the consumer is ready. Activation atomically promotes the staged certificate and pinned trust generation.
  5. Restart, reload, or reopen provider-owned resources as their actual lifecycle requires, then inspect Mesh and PKI operation records before retiring old material.

Hovel does not currently enforce a generic consumer acknowledgement during assignment activation, and it does not hot-reload a running listening post or existing stream. Providers must expose or document their own reload and overlap behavior. Follow the complete, recovery-aware sequence in TLS Operations.

Pivoted destination contract

A task or stream can address either a Mesh node itself or a destination reachable from that node. Providers advertise this with MeshTaskSpec.targetScopes: node, route, and destination. Requests then combine nodeId or route with destinationHost, optional destinationPort, and a provider-defined protocol.

This is the SDK contract for “throw through a tunnel” without making Mesh providers implement exploit execution. Hovel can open a routed session flow to a destination reachable from a node, bind a daemon-owned TCP or UDP loopback bridge with OpenMeshBridge, and run a compatible tool against the authenticated local bridge, or it can ask a provider for an explicit upload_execute task when the provider owns that delivery mechanism. The SDK supports that task contract, but the current direct daemon task route dispatches only survey; execution-capable kinds remain blocked until they are bound to the persisted planning, confirmation, dangerous-module, and audit path above the SDK.

Implant loading follows the same rule. A provider that can place and start an implant through its nodes should advertise an upload_execute task for the common “copy bytes, then run them” flow. Providers with a native in-memory or protocol-specific loader can advertise load instead. The request carries the pivot, reached destination, and either inline bytes via inputData plus inputEncoding or provider-defined artifact references in config. Mesh records the contract and bookkeeping; payload generation, credential stamping, confirmation, and loader behavior remain provider or higher-layer responsibilities.

Planning and safety rules

  • handshake, schema, mesh.describe, and credential.describe must be cheap, deterministic, and side-effect free.
  • mesh.topology and mesh.beacons may inspect provider-local state, but must not mutate target-side state.
  • mesh.listeners is read-only; listener start and stop are explicit, idempotent lifecycle mutations recorded by the daemon.
  • Credential selection requires explicit approval, exact assignment-to-consumer binding, an active or degraded assignment, an active certificate generation, and a slot compatible with its purpose, profile, compatibility target, projection, form, and private-material policy.
  • Target interaction belongs in mesh.task or mesh.open_stream after the operator has a persisted plan and recorded confirmation.
  • Provider-authored triggers and beacons are evidence and routing context; they never bypass Hovel guardrails.
  • Mesh streams become ordinary sessions, so existing session I/O, commands, transcript capture, and cleanup paths still apply.

Example topology

{
  "root": "controller",
  "nodes": [
    { "id": "controller", "kind": "controller", "state": "online" },
    { "id": "relay-1", "parentId": "controller", "kind": "relay", "state": "online" },
    { "id": "leaf-1", "parentId": "relay-1", "kind": "agent", "state": "online" }
  ],
  "links": [
    { "id": "link-controller-relay-1", "source": "controller", "target": "relay-1", "state": "up" },
    { "id": "link-relay-1-leaf-1", "source": "relay-1", "target": "leaf-1", "state": "up" }
  ],
  "routes": [
    { "id": "route-leaf-1", "nodes": ["controller", "relay-1", "leaf-1"] }
  ]
}