Skip to content

feat(fleetnode): pairing foundation (proto, node pairer, SQL guards)#388

Draft
ankitgoswami wants to merge 3 commits into
mainfrom
fleetnode-pairing
Draft

feat(fleetnode): pairing foundation (proto, node pairer, SQL guards)#388
ankitgoswami wants to merge 3 commits into
mainfrom
fleetnode-pairing

Conversation

@ankitgoswami
Copy link
Copy Markdown
Contributor

@ankitgoswami ankitgoswami commented Jun 4, 2026

Summary

Foundation for pairing miners discovered via fleet nodes. A fleet node can already discover miners (#235), but those discovered_device rows are a dead end: the cloud Pair path refuses fleet-node-reported devices (it has no route to a LAN behind a node), and PairDeviceToFleetNode needs an already-existing device row that these never get. Per RFC 0001 the node owns miner I/O and holds the per-node miner-signing key, so pairing must happen on the node, orchestrated over the existing ControlStream.

This PR lands the safe, tested scaffolding. It is behavior-preserving and inert: discovery keeps working, and nothing new is reachable until the operator RPC lands.

What is in here

  • Proto + codegen. An AgentCommand oneof carried in ControlCommand.payload (discover today, pair next), FleetNodePairRequest/FleetNodePairTarget, the ReportPairedDevices gateway RPC with per-device FleetNodePairResult/PairOutcome, and the operator-facing ListFleetNodeDiscoveredDevices and PairDiscoveredDevicesOnFleetNode admin RPCs. Existing handlers return Unimplemented for the new RPCs.
  • Node batch pairer. handleCommand decodes AgentCommand and dispatches discover or pair. A new pluginPairer authenticates a batch of targets on the node's local plugins: asymmetric drivers (Proto) use the node's own miner-signing key, basic-auth drivers (Antminer) use operator-supplied or plugin-default credentials, and per-device outcomes stream back via ReportPairedDevices with bounded fan-out and PARTIAL semantics, mirroring discovery.
  • Server SQL. ListFleetNodeDiscoveredDevices (inverse of the discovery listing, which excludes fleet-node rows; surfaces AUTHENTICATION_NEEDED for retry) and the cloud-dial guard refinements (see below).

Two things reviewers should know

  1. Discovery wire change (contained). Discovery moves from a bare DiscoverRequest to an AgentCommand envelope inside ControlCommand.payload. The server-wrap (DiscoverOnFleetNode) and node-unwrap ship together here, so there is no skew within a deploy; node and server are the same binary and this is pre-release.
  2. device_pairing model. A fleet-node-paired device will be device_pairing.pairing_status = PAIRED (so it reads as paired in the UI and is command-targetable), with the transport dimension expressed by fleet_node_device membership. "Cloud-dialed" is therefore refined to PAIRED AND NOT EXISTS(fleet_node_device): DeviceHasActiveCloudPairing, the discovery promotion guard (same-node carve-out so a node can still refresh its own devices), and the miner_service dial/credential fetch all exclude node-owned devices. Display and command-selection queries are unchanged. These refinements are dormant until PR4 creates node-paired data.

Coordination

Aligned with the sibling effort "send commands to miners paired via fleet nodes": the AgentCommand envelope is shared (it adds a miner_command arm), and the device_pairing = PAIRED decision resolves that effort's open question about whether AllDevices includes node devices (it does; transport routes via fleet_node_device). The miner_service dial exclusion is the seam that effort fills with node routing.

Testing

Node tests pass with -race; control-registry, admin-handler (including discovery dispatch through the new envelope), and fleetnode domain tests pass against the live test DB. New tests cover the node pair dispatch and outcomes, the miner-signing key cross-check, the listing query, and the refined guards under the new PAIRED-and-node-bound model.

Still to come

Per-device persistence + registry pair-result routing, the streaming operator RPC that makes pairing reachable, and an end-to-end CLI demo plus fake-rig fixtures.

🤖 Generated with Claude Code

ankitgoswami and others added 3 commits June 3, 2026 17:10
Wire shapes for pairing fleet-node-discovered miners: an AgentCommand oneof carried in ControlCommand.payload (discover today, pair next), FleetNodePairRequest/Target, the ReportPairedDevices gateway RPC with per-device FleetNodePairResult/PairOutcome, and the operator-facing ListFleetNodeDiscoveredDevices and PairDiscoveredDevicesOnFleetNode admin RPCs. Additive only; existing handlers return Unimplemented for the new RPCs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The node now decodes an AgentCommand from ControlCommand.payload and dispatches discover or pair. Discovery migrates into the envelope (server wraps in DiscoverOnFleetNode, node unwraps) in this commit. A new pluginPairer authenticates a batch of targets on the node's local plugins: asymmetric drivers use the node's own miner-signing key, basic-auth drivers use operator-supplied or plugin-default credentials, and per-device outcomes (PAIRED / AUTH_NEEDED / AUTH_FAILED / ERROR) stream back via ReportPairedDevices with PARTIAL semantics. A cross-check test pins the node-derived public key to token.Service.ExtractPublicKeyFromPrivateKey.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… non-node devices

Adds ListFleetNodeDiscoveredDevices (the inverse of GetActiveUnpairedDiscoveredDevices, which excludes fleet-node rows) so operators can enumerate pair candidates, including AUTHENTICATION_NEEDED rows for retry. Refines the cloud-dial guards to mean PAIRED AND NOT EXISTS(fleet_node_device): a fleet-node-paired device will also be device_pairing=PAIRED (so it reads as paired in the UI) but is node-dialed, so DeviceHasActiveCloudPairing, the discovery promotion guard (same-node carve-out), and the miner_service dial/credential fetch all exclude node-owned devices. Display and command-selection queries are unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added javascript Pull requests that update javascript code client server shared labels Jun 4, 2026
Comment on lines +3 to +5
import (
"context"
"crypto/ed25519"
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 4, 2026

🔐 Codex Security Review

Note: This is an automated security-focused code review generated by Codex.
It should be used as a supplementary check alongside human review.
False positives are possible - use your judgment.

Scope summary

  • Reviewed pull request diff only (8e49c1617ac0e3702f635762250bf8f414a3d9fa...f2530f67dc36d235b55b022286fd14b053be9fa7, exact PR three-dot diff)
  • Model: gpt-5.5

💡 Click "edited" above to see previous reviews for this PR.


Review Summary

Overall Risk: HIGH

Findings

[HIGH] Pairing Credentials Are Not Redacted From Request Logging

  • Category: Auth
  • Location: proto/fleetnodeadmin/v1/fleetnodeadmin.proto:147
  • Description: PairDiscoveredDevicesOnFleetNodeRequest adds pairing.v1.Credentials, but the new procedure is not added to the existing redaction/sensitive-body lists. For server-streaming RPCs, the request body can be logged at debug level unless the procedure is in SensitiveBodyProcedures.
  • Impact: Miner usernames/passwords supplied for pairing can be written to application logs.
  • Recommendation: Add FleetNodeAdminServicePairDiscoveredDevicesOnFleetNodeProcedure to RedactedRequestProcedures and SensitiveBodyProcedures, and add a regression test covering this credential-bearing RPC.

[HIGH] New RPCs Are Mounted Via Generated Stubs But Not Implemented

  • Category: Reliability
  • Location: server/generated/grpc/fleetnodeadmin/v1/fleetnodeadminv1connect/fleetnodeadmin.connect.go:349
  • Description: The new admin RPCs and gateway ReportPairedDevices RPC are present in generated interfaces, but the concrete admin/gateway handlers do not implement them; the embedded Unimplemented...Handler will handle these methods.
  • Impact: ListFleetNodeDiscoveredDevices, PairDiscoveredDevicesOnFleetNode, and ReportPairedDevices return CodeUnimplemented, so the new batch-pairing flow cannot work end to end.
  • Recommendation: Implement the concrete admin and gateway handler methods before exposing the proto surface, including permission checks, fleet-node subject checks, result routing, and tests.

[HIGH] New Procedures Are Missing From Auth/RBAC Classification

  • Category: Auth
  • Location: server/generated/grpc/fleetnodegateway/v1/fleetnodegatewayv1connect/fleetnodegateway.connect.go:213
  • Description: The generated service interfaces now include new procedures, but the auth classification maps are not updated. The gateway report RPC is not in FleetNodeAuthenticatedProcedures, and the new admin RPCs are not in the session-only/RBAC procedure lists.
  • Impact: The RPC contract test should fail for unclassified procedures. At runtime, fleet nodes using session tokens will not authenticate correctly to ReportPairedDevices; admin pairing endpoints also miss the existing FleetNodeAdmin session-only policy.
  • Recommendation: Add gateway ReportPairedDevices to FleetNodeAuthenticatedProcedures; add the admin RPCs to ProcedurePermissions and SessionOnlyProcedures with fleetnode:read/fleetnode:manage as appropriate.

[MEDIUM] ControlCommand Payload Format Is Changed Without Compatibility Fallback

  • Category: Protobuf
  • Location: server/internal/handlers/fleetnode/admin/handler.go:204
  • Description: Discovery commands are now marshaled inside pairing.v1.AgentCommand, while agents now unmarshal ControlCommand.payload as that envelope. Previously the payload was a raw DiscoverRequest.
  • Impact: Mixed-version server/fleet-node deployments can break discovery during rolling upgrades because old agents and new agents decode different payload shapes.
  • Recommendation: Add a version/capability handshake or dual-decode fallback for raw DiscoverRequest while rolling out the envelope.

[MEDIUM] Pair Targets Are Not Validated Before Plugin Execution

  • Category: gRPC
  • Location: server/cmd/fleetnode/pair.go:65
  • Description: The agent validates only the outer ControlCommand; the unmarshaled AgentCommand/FleetNodePairRequest is not protovalidated. sdk.ParsePort errors are ignored, so malformed ports can become zero-value ports before reaching plugins.
  • Impact: Malformed pair commands can bypass proto caps and field validation, wasting agent/plugin resources or causing plugins to dial unintended invalid endpoints.
  • Recommendation: Run protovalidate.Validate(&agentCmd) or validate body.Pair before fan-out, enforce the targets max, and reject ParsePort errors with ACK_CODE_BAD_REQUEST.

Notes

No SQL injection, command injection, cryptostealing/pool hijack, or hardcoded wallet/pool redirection was evident in the changed hunks I reviewed.


Generated by Codex Security Review |
Triggered by: @ankitgoswami |
Review workflow run

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

client javascript Pull requests that update javascript code server shared

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants