fix(gateway): reject invoke for unadvertised node commands#98051
fix(gateway): reject invoke for unadvertised node commands#98051ly-wang19 wants to merge 2 commits into
Conversation
|
Codex review: stale review; fresh review needed. Summary Next step |
When a node explicitly advertises a commands list but omits a command the gateway tries to invoke (e.g. system.execApprovals.get on a node that only advertises system.run* and system.which), the gateway now returns a structured UNSUPPORTED_CAPABILITY error instead of sending the invoke and surfacing whatever unstructured error bubbles back. Nodes that did not declare commands keep legacy behavior so the capability gate only fires when the node opted into command advertisement. Closes openclaw#97578.
The new branch returns the minimal { ok: false, error } shape (matching
the sibling NOT_CONNECTED failure above). The test was over-asserting
payload/payloadJSON fields the code never sets on this failure path.
f862648 to
b6f9204
Compare
|
Superseded by #100505 / fccb888. The landed fix enforces this invariant at the dedicated exec-approvals relay, where the admin-scoped exception is owned, instead of changing Thank you @ly-wang19 for the candidate and tests. |
What Problem This Solves
openclaw approvals get --node <id> --jsonagainst a connected node that does not advertisesystem.execApprovals.get/setcurrently surfaces an unstructured error (Cannot read properties of undefined (reading 'socket')) instead of a clear unsupported-capability message.Closes #97578.
Why This Change Was Made
nodeRegistry.invokechecks only whether the node is connected. It never consults the per-session command list, so a request for a command the node never advertised is sent over the wire and the gateway surfaces whatever the node-side dispatch produces (in practice, an internal TypeError that varies by node implementation). Adding a capability gate insideinvokeitself — returning a structuredUNSUPPORTED_CAPABILITYerror when the node has explicitly advertised a commands list that does not include the requested command — lets every caller return an actionable error instead of relying on each handler to duplicate the check. The gate mirrors the existing semantics inisNodeCommandAllowed(src/gateway/node-command-policy.ts): an empty declared-commands list keeps legacy behavior, so the gate only fires when the node has opted into command advertisement.User Impact
openclaw approvals get/set --node <id>against a node that does not support exec approvals management now returns a structured error (UNSUPPORTED_CAPABILITY: node does not support command "system.execApprovals.get") instead of a confusing TypeError.nodeRegistry.invoke(e.g.system.run, canvas commands) keep their existing behavior when the node already advertises those commands.Evidence
src/gateway/node-registry.test.ts:rejects invoke for commands the node did not advertised— verifies a node that advertisedsystem.run*andsystem.whichreturnsUNSUPPORTED_CAPABILITYforsystem.execApprovals.get.still invokes when the node did not declare a commands list— verifies the legacy empty-commands-list path still sends the invoke frame.node scripts/run-vitest.mjs node-registry.testexits 0.