Skip to content

Add JSON-RPC 2.0 across the manager API, control plane and proplet - #274

Open
JeffMboya wants to merge 12 commits into
absmach:mainfrom
JeffMboya:PROP-jsonrpc
Open

Add JSON-RPC 2.0 across the manager API, control plane and proplet#274
JeffMboya wants to merge 12 commits into
absmach:mainfrom
JeffMboya:PROP-jsonrpc

Conversation

@JeffMboya

@JeffMboya JeffMboya commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

What type of PR is this?

This is a feature because it adds JSON-RPC 2.0 support.

What does this do?

Adds JSON-RPC 2.0 to the manager API, control plane, proplet.

Which issue(s) does this PR fix/relate to?

None.

Have you included tests for your changes?

Yes. Go and Rust unit tests across all three surfaces.

Did you document any new/modified features?

No. Documentation belongs in propeller-docs.

Notes

Additive and off by default. Eleven commits build independently.

Request and Response carry the 2.0 envelope with an id that accepts a
string, a number or null, and rejects every other type. Presence of the
id member is decoded separately from its value so a null id stays
distinct from an absent one, which is what separates a request from a
notification.

Errors map the pkg/errors sentinels onto the standard code range plus a
server-defined range for not found, conflict, validation and timeout.
The server keeps a method registry and turns a raw payload into a raw
reply, so the same dispatcher serves both an HTTP body and an MQTT
message without either transport knowing about the other.

Batches are answered element by element, notifications are executed but
produce no reply, and a batch made up entirely of notifications returns
no payload at all. Decode failures report a stable message rather than
the raw encoding/json error, which would otherwise leak internal type
names to callers.
Method names and parameter shapes live next to the envelope rather than
inside any one transport, so the HTTP endpoint, the MQTT control plane
and the proplet all name an operation the same way. task.start means the
same thing to a caller and to a proplet, which is what lets the manager
forward a request instead of translating it.
Binds manager.Service onto the dispatcher so every task, proplet and job
operation is reachable by method name. Parameters are validated before
the service is touched, and service errors travel through the pkg/errors
mapping so a missing task answers with the not found code rather than a
generic failure.

The binding is transport free: it returns a dispatcher that the HTTP
endpoint and the MQTT control plane can both drive.
POST /rpc accepts a single call or a batch and sits behind the same
middleware chain as the REST routes, so it inherits the existing auth
context handling rather than opening a second unauthenticated door.

Transport failures stay HTTP level: an unsupported content type is a 400
and a batch of notifications is a 204. Application failures stay protocol
level, answering 200 with an error object, which is what a JSON-RPC
client expects. Every existing REST route is untouched.
Start and stop commands can now travel as JSON-RPC requests carrying the
task id as the correlation id, gated behind MANAGER_JSONRPC_CONTROL_PLANE
and off by default so existing proplets keep receiving the flat payload
they already parse.

Inbound proplet messages are unwrapped before dispatch, so the manager
accepts either encoding regardless of what it sends. Stop was published
from four separate call sites with a hand built payload each time; they
now share one helper, which is what keeps the two encodings from drifting
apart.
Unwrapping happens in MqttMessage::decode, the one place every control
message already passes through, so start, stop and every future handler
accept both encodings without knowing which one arrived. A payload
without a 2.0 version field is returned untouched, which is what keeps
proplets working against a manager that has not been switched over.

correlation_id exposes the envelope id so a handler can answer the
request it was given rather than guessing from the task id.
A proplet now answers in the dialect it was addressed in: a command that
arrived wrapped is answered with a Response carrying the same id, and one
that arrived flat is answered with the bare ResultMessage as before.
Mirroring the request rather than picking an encoding of its own is what
lets a new proplet talk to an old manager and the reverse.

Failures become an error object instead of a result with an error string
beside it, so the failure is visible in the envelope rather than only in
the body.
A deployed task registers under its function name, so a caller invokes it
by that name rather than by task id and a proplet becomes an ordinary
JSON-RPC service. Stopping a task removes its method.

The server is off unless PROPLET_RPC_ENABLED is set, binds to loopback by
default, and enforces a bearer token when PROPLET_RPC_TOKEN is present.
Reaching a proplet directly bypasses the manager, where scheduling and
authorization live, so it stays shut until an operator opens it and warns
on startup when it is opened without a token.

Control messages carrying a method that disagrees with their topic are
now dropped rather than acted on.
make lint runs clippy with -D warnings, which turned this deprecation
into a build failure for every branch, not just this one.
Call and CallBatch put the manager endpoint within reach of the SDK, and
propeller-cli rpc call exposes it from a shell. Error responses come back
as a jsonrpc.Error, so a caller can branch on the code instead of parsing
a message.

Request ids are drawn from a process wide counter, which keeps them
distinct when a batch is in flight or two calls overlap.
The dispatch and endpoint tables hid their mock wiring behind a setup
closure, so a case could not be read without following the function.
Mock method, arguments and returns are now table fields wired inside the
loop, matching the table style used in transport_test.go.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant