Skip to content

node-api: remove WorkerApi and BuilderApi RPCs that have no caller - #1213

Merged
ac-pplx merged 4 commits into
mainfrom
ac/node-api-delete-unused-workerapi-rpcs
Oct 31, 2026
Merged

ac-pplx merged 4 commits into
mainfrom
ac/node-api-delete-unused-workerapi-rpcs

Conversation

@ac-pplx

@ac-pplx ac-pplx commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

Summary

space.node.api.v1.WorkerApi becomes a 1.x-stable contract at 1.0, and docs/versioning.md consolidates breaking changes at 0.9.0. This removes 29 node-api relays that nothing in this repository calls, so they don't get frozen into that contract: 17 on WorkerApi and 12 on BuilderApi. No code, script, chart or doc in this repository calls any of them. #1156 already deleted the unreachable api-crate wrappers that reached some of them.

Four commits:

  1. api: send the untracked wake to WorkerApiInternal.WakeSandbox. An explicit or implicit wake for a sandbox with no loaded row (untracked_action in crates/libs/api/src/controllers/sandbox/lifecycle.rs) was the only caller of the public WorkerApi.WakeSandbox. It now uses WorkerApiInternal.WakeSandbox, as tracked Direct and Kubernetes wakes already do. No production caller reaches this path: every caller of SandboxController::action passes the loaded row, so only tests send the untracked wake. The commit keeps that path working against the internal service.
    • The request is the same: blocking, no start context, no runtime upgrade, and the implicit wake source for an implicit wake.
    • The internal handler requires control-plane policy authority only for a runtime upgrade or an activation auto-lifecycle policy, so this request needs none.
    • The api already holds the internal-surface credential.
  2. node-api: remove the WorkerApi RPCs listed below.
  3. node-api: remove the BuilderApi RPCs listed below. BuilderApi stays a separate service.
  4. node-api: correct comments and docs the RPC removal left stale. Only comment, error-message, proto-comment and docs updates; behaviour does not change.
    • The doc of require_policy_mutation_authority now lists every request it gates, and the function is now private.
    • The trusted-policy-allowlist comments and the TrustedPolicyServiceAccountsMissing startup message no longer describe the builder as serving policy RPCs.
    • The WorkerApi and WorkerApiInternal service descriptions in worker.proto match what each service now serves.
    • Simulator comments no longer mention a public Wake or ping, and SimState::list is test-only.
    • The pool-target capacity note moves from docs/configuration.md to docs/warm-pools.md.
    • The new characterization test writes its own token files, so its bearer check tells the two credentials apart on any host.

Protobuf services cannot reserve names, and the repository has no proto lint or retired-RPC convention. Each service in worker.proto and builder.proto therefore lists its retired names in a comment, marked as retired in 0.9.0. No message type was deleted: every request and response is imported from an internal package or shared with another service (RefreshFacetState* is still served by NodeCtl). builder.proto also drops its now-unused sandbox-manager import, along with a node/v1 import nothing in it used.

Each removal deletes the node-api handler, the simulator handler (WorkerApi only; the simulator has no BuilderApi), and the pplx-space-node-api-client wrapper. It also removes:

  • SpacedPingGuest from the guest-bridge allowlist (auth/k8s.rs).
  • The public wake's start-context and runtime-upgrade guards, which only the removed handler used.
  • The builder's restart-handoff macro, and node-api's unused node-ctl refresh_facet_state helper.

Docs (docs/configuration.md, docs/execution-environments.md), the Kind README auth table and the node-api chart comment no longer describe the removed authority or a public Wake. The contract list in docs/versioning.md is unchanged.

Removed from WorkerApi, and where each function is served

RPC Served instead
WakeSandbox (public twin) WorkerApiInternal.WakeSandbox (unchanged), used by every control-plane wake
GetSandboxHealth, ListSandboxes space-sbxmgr on the node; control-plane state comes from node-ctl's published sandbox states and SandboxState (kept)
SpacedPingGuest SpacedGetStatus (kept; an operator script uses it) and SpacedWaitReady
EnableSnapshots, DisableSnapshots node-ctl's sandbox desired-state facet calls sandbox-manager locally; space-sbxmgr for operators
DeleteSnapshots space-sbxmgr snapshots on the node (no control-plane path)
ListTemplates, GetTemplate, UpdateTemplateSettings, UpdateTemplateSnapshotPolicy, CreatePool, DeletePool, PoolStatus node-ctl's template desired-state facet calls template-manager locally; space-tmplmgr for operators
UnexposePort, DenyPort node-ctl's port-exposure facet calls node-manager locally; space-nodemgr unexpose / deny
RefreshFacetState node-ctl serves it on its own socket; space-nodectl

Removed from BuilderApi, and where each function is served

RPC Served instead
SandboxState, ListSandboxes, StopSandbox, DeleteSandbox space-sbxmgr on the builder; build sandboxes are internal to template-manager and the builder
ListTemplates, GetTemplate, UpdateTemplateSettings, UpdateTemplateSnapshotPolicy, CreatePool, DeletePool, PoolStatus node-ctl's template facet calls template-manager locally; space-tmplmgr. The api reached these only through the TemplateController wrappers #1156 deleted
RefreshFacetState node-ctl; space-nodectl

BuilderApi keeps BuildTemplate, GetBuildTemplateStatus, UploadBuildBlob, TriggerStateReconciliation, DeleteTemplate, DeleteTemplateBuild, PullTemplate, NodeExportTemplate and ListNodeTemplates. Nothing on WorkerApiInternal, NodeInfo, or the internal services (SandboxManager, TemplateManager, NodeManager, NodeCtl) changes.

Migration for 0.9

A custom control plane calling node-api directly should:

  • Wake sandboxes through WorkerApiInternal.WakeSandbox. It is served on the same worker listener as WorkerApi but takes the worker's internal credential, not the one WorkerApi takes. With Kubernetes token auth, use a token for audience space-node-api-internal from a service account listed in SPACE_NODE_API_INTERNAL_AUTH_ALLOWED_SERVICE_ACCOUNTS (SPACE's own clients read it from SPACE_NODE_API_INTERNAL_TOKEN_PATH). With static tokens, use SPACE_INTERNAL_CONTROL_TOKEN; this surface refuses SPACE_INTERNAL_TOKEN. A runtime upgrade, or a start context that carries an activation auto-lifecycle policy, also needs control-plane policy authority: with Kubernetes token auth, an account in SPACE_NODE_API_AUTH_TRUSTED_POLICY_SERVICE_ACCOUNTS; static tokens carry it only when Kubernetes auth is off.
  • List a worker's sandboxes from the sandbox inventory node-ctl publishes to the control plane (ClusterApi.PublishNodeState), or with space-sbxmgr list on the node, instead of ListSandboxes. SandboxState reads sandboxes whose IDs you already have. Use space-sbxmgr health on the node instead of GetSandboxHealth, and SpacedGetStatus or SpacedWaitReady instead of SpacedPingGuest.
  • Drive template, pool, snapshot-policy, sandbox snapshot enablement and port state through the control plane's desired state, which node-ctl reconciles on each node, rather than through node-api relays. Delete a sandbox's named snapshots with space-sbxmgr snapshots on the node; no control-plane path replaces DeleteSnapshots.

A caller that node-api admits gets UNIMPLEMENTED from a removed method. The auth layer answers first, so accounts restricted by method (the guest-bridge and peer-node accounts) get PERMISSION_DENIED, as for any other method outside their allowlist; for example, the guest-bridge account now gets PERMISSION_DENIED for SpacedPingGuest.

Validation

At this head (3451e73a, on main at c83b7b9e):

  • cargo check --workspace --all-features --all-targets: exit 0, no warnings. The build regenerated no tracked file; nothing generated from worker.proto is checked in.
  • bash ci/scripts/run-rust-checks.sh clippy: exit 0.
  • cargo arch: exit 0.
  • cargo nextest run --all-features --no-fail-fast -p pplx-space-db-test-fixtures -p pplx-space-common-svc -p pplx-space-node-api-server -p pplx-space-node-api-sim -p pplx-space-node-api-client -p pplx-space-api, with the nextest Postgres and Redis setup scripts: 2461 passed, 0 failed, 1 skipped (a default-ignored test), no dependency skips.
    • This covers worker- and builder-role node-api tests: template-mutation authorization over the wire for both roles, and policy authorization on the worker (the builder serves no policy-bearing RPC after this PR); the internal Wake's runtime-upgrade authority; template transfer; and builder peer-node confinement.
  • New test controllers::sandbox::lifecycle::characterization_tests::untracked_wake_uses_the_internal_worker_wake.
    • It serves only WorkerApiInternal on a placed Direct worker and checks that an explicit and an implicit untracked wake arrive there with the same fields under the internal bearer. It writes its own token files, so the bearer check tells the two credentials apart on any host.
    • Added alone on the parent of commit 1, it fails with HTTP 501 (UNIMPLEMENTED); with commit 1 it passes. Run by name at this head, its child test process ran against Postgres, so it did not skip.
  • Proto lint / breaking-change tooling: the repository has none (no buf config, no proto lint in CI). This is a breaking wire change by design.
  • Kind: CI's path-selected Kind / Firecracker smoke, all four shards, passed in this head's pull-request run. None of the Kind scenarios reaches the untracked wake; only the new characterization test does.

Severity and risk

Severity: low. This removes unused API surface before the contract freezes; no in-repo behaviour changes except where the untracked wake is sent.

Risk: a custom control plane outside this repository that calls a removed RPC gets UNIMPLEMENTED; nothing in this repository does. The only changed code path is the untracked wake, and only tests reach it. Rollback is reverting this PR.

Rollout

Any deploy order works:

  • Old control plane, new node-api: the old api's only call to a removed RPC is the untracked wake's public WakeSandbox, and no production request reaches it.
  • New control plane, old node-api: the untracked wake reaches WorkerApiInternal.WakeSandbox, which old node-apis already serve.

AI assistance: Claude Code wrote the code changes, the tests and this description.

@github-actions github-actions Bot added the triage:pending Waiting for a maintainer to triage label Oct 31, 2026
@github-actions

Copy link
Copy Markdown

Thanks for opening this pull request! A SPACE maintainer will take a look.

CONTRIBUTING.md explains how we review outside contributions.

@ac-pplx
ac-pplx added this pull request to stack #1214 October 31, 2026 00:57
Base automatically changed from ac/api-delete-dead-node-api-wrappers to main October 31, 2026 01:08
An explicit or implicit wake for a sandbox with no loaded row called the
public WorkerApi.WakeSandbox, the only caller of that RPC. Send it to
WorkerApiInternal.WakeSandbox instead, which tracked Direct and Kubernetes
wakes already use, so the public twin can be removed.

The request is unchanged: no start context, no runtime upgrade, blocking,
and the implicit wake source for an implicit wake. The internal handler
requires control-plane policy authority only for a runtime upgrade or an
activation auto-lifecycle policy, so this request needs none, and the api
already holds the internal-surface credential its tracked wakes use.

A characterization test serves only WorkerApiInternal on the placed
worker and checks that both wakes arrive there under the internal bearer
with the same fields. It failed with HTTP 501 (UNIMPLEMENTED) before the
change.
WorkerApi becomes a 1.x-stable contract at 1.0, and 0.9 is where breaking
changes consolidate. Remove 17 relays nothing calls:

- GetSandboxHealth, ListSandboxes, SpacedPingGuest
- WakeSandbox (the public twin; WorkerApiInternal.WakeSandbox stays)
- EnableSnapshots, DisableSnapshots, DeleteSnapshots
- ListTemplates, GetTemplate, UpdateTemplateSettings,
  UpdateTemplateSnapshotPolicy, CreatePool, DeletePool, PoolStatus
- UnexposePort, DenyPort
- RefreshFacetState

Their functions are served elsewhere: template, snapshot, port and facet
state arrive through node-ctl facets that call the node services over
local sockets, the node-local CLIs cover operator use, and control-plane
wakes use WorkerApiInternal.WakeSandbox.

Protobuf services cannot reserve names, so worker.proto lists the
retired names in a comment. Every message stays: each is imported from
an internal package or shared with another service.

Delete the node-api handlers, the simulator's handlers, the client-crate
wrappers, SpacedPingGuest from the guest-bridge allowlist, and the public
wake guards only the removed handler used. Tests that named a removed
method now use one that remains, the runtime-upgrade wake test covers
the internal service only, and the template and policy authorization
tests cover the pool and settings RPCs on the builder only. Docs and the
Kind chart comment no longer list worker pool mutation or a public Wake.

A caller outside this repository that calls a removed RPC now gets
UNIMPLEMENTED.
Remove 12 BuilderApi relays nothing calls:

- SandboxState, ListSandboxes, StopSandbox, DeleteSandbox
- ListTemplates, GetTemplate, UpdateTemplateSettings,
  UpdateTemplateSnapshotPolicy, CreatePool, DeletePool, PoolStatus
- RefreshFacetState

The api's sandbox calls use the worker runtime client, and the template
calls were reached only through the TemplateController wrappers the
previous change deleted. On a builder node, template state arrives
through node-ctl's template facet, which calls template-manager over its
local socket, the node-local CLIs cover operator use, and node-ctl serves
RefreshFacetState itself.

BuilderApi keeps BuildTemplate, GetBuildTemplateStatus, UploadBuildBlob,
TriggerStateReconciliation, DeleteTemplate, DeleteTemplateBuild,
PullTemplate, NodeExportTemplate and ListNodeTemplates, and stays a
separate service. builder.proto lists the retired names in a comment,
since services cannot reserve them, and drops the sandbox-manager import
it no longer uses along with the node/v1 import nothing in it used.

Delete the node-api handlers, their restart-handoff forwarding macro, the
client-crate wrappers and node-api's now-unused node-ctl
refresh_facet_state helper. The policy authorization test loses its
builder settings case, the template authorization test its pool cases,
and the peer-node test names a builder method that remains. Docs no
longer list builder pool or settings authority.

A caller outside this repository that calls a removed RPC now gets
UNIMPLEMENTED.
@ac-pplx
ac-pplx force-pushed the ac/node-api-delete-unused-workerapi-rpcs branch from 6a53667 to 746b5f6 Compare October 31, 2026 01:08
Removing the unused WorkerApi and BuilderApi RPCs left text that still
described them. This updates comments, one error message, proto comments
and docs. Behaviour does not change.

- worker.rs: the require_policy_mutation_authority doc now lists the
  requests it gates: the two policy setters; CreateSandbox and
  AdoptPoolSandbox with a policy override or nonzero generation; an
  activation auto-lifecycle policy on StartSandbox, ResumeSandbox or the
  internal Wake; and the internal Wake's runtime upgrade. The function
  is now private, since only worker.rs calls it. The empty template-pools
  section header is gone.
- auth: the k8s.rs comments say a worker serves the policy RPCs and both
  roles gate template deletion on the trusted policy allowlist. The
  TrustedPolicyServiceAccountsMissing message no longer calls the
  builder policy-serving.
- worker.proto: the WorkerApi and WorkerApiInternal descriptions match
  what each service now serves. Rust stubs are generated at build time,
  so no generated file changes.
- simulator: the sim_wake doc names WorkerApiInternal only, "ping" is
  gone from two comments, and SimState::list is test-only.
- docs: the pool-target capacity note moves from the template mutation
  authority section of configuration.md to warm-pools.md, where targets
  are set.
- untracked_wake_uses_the_internal_worker_wake writes its own token
  files, so its bearer check can tell the two credentials apart even on
  a host that has the default token files.
@ac-pplx
ac-pplx marked this pull request as ready for review October 31, 2026 03:37
@the-gigi-pplx

Copy link
Copy Markdown
Collaborator

Review — 4 findings

Medium

  1. proto/node/api/v1/worker.proto:15 — The 0.9 migration guidance lives only in this PR's description.
    docs/versioning.md:117-118 says that before 0.9 we "consolidate planned breaking changes at 0.9.0, and publish migration guidance", and docs/versioning.md:28 lists the worker node-api as a service contract. This PR removes 17 WorkerApi RPCs from that contract. The proto comment names the release ("Retired in 0.9.0"), but nothing in the tree tells an external cluster manager what replaces ListSandboxes, GetSandboxHealth, UnexposePort/DenyPort, or the public WakeSandbox. The repo has no changelog or release-notes file, and a PR body is hard to find once the PR merges.
    Suggest landing the "Migration for 0.9" section as a doc in this PR, for example a 0.9 section in docs/versioning.md or a docs/migrations/0.9.md linked from it. Later 0.9 breaks can then add to the same place.

Low

  1. crates/libs/common/svc/src/auth/k8s/tests.rs:486, :556 — Two test comments still say the builder serves the policy RPCs: "Both worker and builder serve the policy RPCs, so both enforce it" and "(both serve the policy RPCs)". After this PR the builder checks the trusted-policy set only through require_template_mutation_authority (builder.rs:78, :87, for DeleteTemplate/DeleteTemplateBuild). Commit 4 already rewrote that rationale in k8s.rs:614, so these two now say something different.
  2. crates/libs/common/svc/src/auth/k8s.rs:812 — The same leftover is in network_policy_mutation_authority. The comment calls the gated surfaces "policy-serving" and describes None as "a surface that does not serve the policy RPCs". (Builder, Normal) still gets Some(..) and now serves no policy RPC, only template deletion. The sibling comment at k8s.rs:660 was updated to "policy or template-mutation RPCs", and this one could say the same.
  3. crates/libs/node/mgr/server/src/service/control.rs:421, :586; crates/libs/template/mgr/src/service/pools.rs:8, :132 — The backends still let node-api's peer identity call methods node-api no longer forwards. node-mgr's UnexposePort and DenyPort use ALLOW_NODE_ADMIN_OR_NODEAPI (service.rs:95, which includes SPACE_NODEAPI). template-mgr's CreatePool and DeletePool use ALLOW_TEMPLATE_POOL_OPERATOR (service.rs:333, also SPACE_NODEAPI). After this PR, node-api's server crate has no call to unexpose_port, deny_port, create_pool or delete_pool. This doesn't regress anything: node-api had these grants before. Dropping SPACE_NODEAPI from those lists would finish the least-privilege half of the removal, here or in a follow-up. That needs a node-mgr allowlist split, because ExposePort/AllowPort share the constant and still need node-api.

Verified against 3451e73a4a09aa552becc15ca8012ba6969a1720 (merge base c83b7b9e). What I checked:

  • Callers: no caller of a removed RPC remains anywhere in the tree. I searched the fully qualified WorkerApi/<Method> and BuilderApi/<Method> paths and the bare method names across Rust, Go, Python, shell, charts and docs. The Go shim's matches are SandboxManager methods, which this PR doesn't touch.
  • Untracked wake: every production caller of SandboxController::action passes Some(&sandbox) (ingress.rs:193, public_sandbox_operations.rs:811, routes/e2b/lifecycle.rs:928, :995, :1315). So only tests reach the untracked wake, as the Rollout section says.
  • Internal Wake gate: WorkerApiInternal.wake_sandbox (worker.rs:1286) requires policy authority only for a runtime upgrade or an activation auto-lifecycle policy, so the untracked wake's request needs none.
  • Doc comment: the new require_policy_mutation_authority doc names every one of its call sites.

I relied on CI for compile, clippy, tests and the Kind shards (all green at this head) and didn't rebuild locally.

@the-gigi-pplx the-gigi-pplx left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved. 4 non-blocking findings in the review comment above.

@ac-pplx
ac-pplx added this pull request to the merge queue Oct 31, 2026
Merged via the queue into main with commit 52e82f8 Oct 31, 2026
28 checks passed
@ac-pplx
ac-pplx deleted the ac/node-api-delete-unused-workerapi-rpcs branch October 31, 2026 04:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

triage:pending Waiting for a maintainer to triage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants