Skip to content

api: delete the unused sandbox_ip / access_sandbox_ip substrate surface - #1159

Merged
ac-pplx merged 1 commit into
mainfrom
ac/substrate-delete-sandbox-ip
Oct 31, 2026
Merged

ac-pplx merged 1 commit into
mainfrom
ac/substrate-delete-sandbox-ip

Conversation

@ac-pplx

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

Copy link
Copy Markdown
Contributor

Summary

Deletes sandbox_ip and access_sandbox_ip from SubstrateAdapter, plus the code that existed only to support them (the leftovers are listed under "Not covered"). Neither method had a production caller. Every data-plane caller takes a node-api RuntimeClient from the substrate (access_runtime_client, runtime_client and their siblings), and nothing asks it for a bare sandbox IP. Every reader of the trait and every dispatch change was paying for dead surface.

Removed:

  • Both trait methods, their two forwarding arms in impl SubstrateAdapter for Substrate, and the Kubernetes and Direct implementations.

  • The SandboxController::sandbox_ip / access_sandbox_ip wrappers.

  • The panicking stubs in the two test-double substrates in create_postgres_tests.rs.

  • initial_lifecycle_may_need_wake and successful_access_reconnect_outcome, which only the wrapper used, and the unit test of successful_access_reconnect_outcome.

  • SandboxAction::Access. The wrapper was the only production code that built it. The drain-gate test that passed it now passes SandboxAction::Spaced; the gate uses the action only to label its 409, and the test asserts only the status. The characterization test's internal-label list drops it.

  • The Kubernetes test that drove dormant access through access_sandbox_ip (dormant_access_preserves_operation_after_dispatched_wake_failure). Two tests still pin its invariant:

    • legacy_paused_restore_preserves_handoff_without_route_identity: a dispatched Wake does not publish the route and keeps its operation owner, through restore_evacuated_execution.
    • only_a_proven_no_send_wake_failure_may_release_the_operation: the release predicate.

    adapter_characterization_tests/restoring_access.rs covers restoring, which is_dormant_lifecycle excludes, so it does not stand in for the deleted test.

Updated the comments and the Direct module doc that named the deleted items. That includes the lock-contention arm of reconnect_controller_error_outcome, which is now described as pre-emptive: its only production caller, the Wake / ImplicitWake admission check in SandboxController::action, passes cluster-admission 429s. Helpers the deleted code shared still have other callers and stay: resolve_pod_ip, selected_wake_pod_ip, suspend_restore_plan, charge_implicit_access_activation, ensure_access_path_not_blocked_by_drain, and others.

File-size caps in architecture-baseline.toml, lowered to the new counts:

  • controllers/sandbox/lifecycle.rs: 1825 → 1815
  • controllers/sandbox/mod.rs: 1193 → 1086
  • controllers/substrate/kubernetes/adapter/substrate_adapter.rs: 2467 → 2344
  • controllers/substrate/mod.rs: 2771 → 2089. This drop includes 646 lines of slack that predate this change.

controllers/substrate/direct.rs shrank too, but its cap (3907) is already below its count, so it is unchanged.

Validation

Before deleting, rg -n "sandbox_ip|access_sandbox_ip" across crates/, space_smoke/, scripts/, examples/ and docs/ found only the definitions, forwarding arms, impls, the wrapper, test-double stubs, one Kubernetes test and comments. The only other hit, in space_smoke, is an unrelated local variable named sandbox_ips. After removing SandboxAction::Access, a grep of docs, Python and Go found nothing that names the access action label or its drain message. The access hits there are the auto-lifecycle wake source, a separate vocabulary.

At this head, rebased on current main (the rebase resolved #1153's direct.rs gap table, kept as merged, and a runtime_upgrade: None line #1015 added inside the deleted access_sandbox_ip; caps recounted with cargo arch):

  • cargo fmt --all -- --check: clean.
  • cargo check -p pplx-space-api (no kubernetes feature) and cargo check --workspace --all-features --all-targets: clean.
  • cargo clippy --workspace --locked --all-targets --all-features -- -D warnings: clean.
  • cargo arch: 0 required findings; against main's baseline, no raised caps or new entries.
  • cargo nextest run -p pplx-space-api --all-features --no-fail-fast against real Postgres and Redis, started by the nextest setup scripts: 2126 passed, 1 skipped by nextest's config, 0 failed, no test-dependency skips. The run includes non_lifecycle_actions_are_bad_requests_and_write_nothing, drain_gate_blocks_dormant_sandbox_on_draining_target_node, reconnect_tags_classify_cluster_admission_rejection, legacy_paused_restore_preserves_handoff_without_route_identity and only_a_proven_no_send_wake_failure_may_release_the_operation.

No new tests: nothing behavioural changes, and the compiler is the oracle for the deletion. Not run: Kind or smoke tests, since there is no wire, schema or config change.

Severity and risk

Severity: does not apply (dead-code removal). Risk: low. The surface is in-process only, and the compiler proves nothing else in the workspace referenced it. Roll back by reverting the commit.

Rollout

No wire, schema or config change. Trial merges against the current open-PR heads give:

Not covered

  • The (_, "access") => ReconnectEntrypoint::Dataplane reconnect-metric mapping and its "dataplane" tag. The wrapper was their only production producer; existing tests still exercise them. Left for a follow-up.
  • The suspend_restore_storage_uri parameter of ensure_execution_ready_for_access, now None at every call site. Left because an open PR edits that line.
  • The arms of reconnect_controller_error_outcome that only tests reach now: its one production caller passes admission 429s. Whether to simplify them is a design call for the follow-up; the code and its tests stay.

AI assistance: Claude Code wrote the code change, the commit message and this description.

@github-actions github-actions Bot added the triage:pending Waiting for a maintainer to triage label Oct 30, 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 force-pushed the ac/substrate-delete-sandbox-ip branch from 42921bc to 6f799c4 Compare October 30, 2026 17:32
@ac-pplx

ac-pplx commented Oct 30, 2026

Copy link
Copy Markdown
Contributor Author

Merge note for open PRs that touch code this PR deletes. @the-gigi-pplx, these are yours: #1068, #1131, #1172, #1175 and #1015 edit bodies of sandbox_ip/access_sandbox_ip (or helpers only they used), which this PR removes. None of them adds a new caller, so whichever lands second resolves by keeping the deletion. #1061 and #1153 also touch adjacent single lines (the lifecycle.rs file-size cap and a direct.rs doc bullet); recount the cap on rebase. After this lands, substrate/mod.rs's file-size cap no longer has slack, so PRs that add trait methods will see an advisory file-size finding, not a failure.

@the-gigi-pplx

Copy link
Copy Markdown
Collaborator

Review — 1 finding

Low

  1. crates/libs/api/src/controllers/substrate/kubernetes/adapter/substrate_adapter.rs — this needs a rebase now that PR [api] decide the runtime upgrade at resume and start, and stage pod-less sandboxes on the node #1015 is on main.
    PR [api] decide the runtime upgrade at resume and start, and stage pod-less sandboxes on the node #1015 edited the body of the Kubernetes access_sandbox_ip that this PR deletes, so applying this diff to current main stops on a modify/delete conflict in that impl. As the description says, the resolution keeps the deletion. I checked that current main adds no new caller of sandbox_ip, access_sandbox_ip, SandboxAction::Access, initial_lifecycle_may_need_wake or successful_access_reconnect_outcome: every remaining hit is one this PR removes. On the direct.rs module-doc bullet, PR substrate: make Kubernetes and Direct first-class in the substrate contract #1153 also conflicts as described. Whichever lands second should keep PR substrate: make Kubernetes and Direct first-class in the substrate contract #1153's surrounding text and drop the two method names.

Checked and holds:

  • SandboxAction::Access was never reachable from a request. FromStr never parsed "access", and TryFrom<SandboxLifecycleAction> has no Access arm, so the deletion changes no public error.
  • The drain-gate test's switch from Access to Spaced keeps its meaning. ensure_resolved_node_not_draining uses the action only to label the 409 (drain_block_conflict).
  • The deleted dormant_access_preserves_operation_after_dispatched_wake_failure drove only the deleted inline body. That body ran its own activation-attempt and wake sequence rather than a shared helper, so no surviving production path loses coverage with it. ensure_execution_ready_for_access keeps its two production callers and its Kubernetes tests.
  • The rewritten reconnect_controller_error_outcome comment is accurate. The only production caller left is the admission check in lifecycle.rs that passes admit_action_cluster_activation's error. The neighbouring comment in controllers/mod.rs that names the function as a retry_after() reader still holds.
  • No stale mention of any deleted name remains in the tree at this head.

Not checked: I didn't run cargo, and relied on this head's green CI for compilation and the tests the description lists.

Verified against 6f799c4d0e9d79bf7507a11c43bf6f4c3dc75a9d. Reproduced locally: a git apply --3way trial of this diff onto current main, alone and after PR #1153 and after PR #1156. Only the two conflicts above appeared, and PR #1156 doesn't conflict with this one. I also ran tree-wide searches for the deleted names at this head and on current main.

@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. 1 non-blocking finding in the review comment above.

SubstrateAdapter carried two required methods, `sandbox_ip` and
`access_sandbox_ip`, with a forwarding arm each, a Kubernetes
implementation, no-op Direct implementations, and SandboxController
wrappers. Nothing outside tests called the wrappers: every data-plane
caller takes a node-api `RuntimeClient` from the substrate
(`access_runtime_client`, `runtime_client` and their siblings), and
nothing asks it for a bare sandbox IP. Every reader of the trait and
every future dispatch change paid for the dead surface.

Remove both trait methods, their forwarding arms, the Kubernetes and
Direct implementations, the controller wrappers, and the test doubles'
panicking stubs. Also remove what existed only for them: the
reconnect-outcome helpers the wrapper used and their unit test; the
Kubernetes test that drove dormant access through `access_sandbox_ip`;
and `SandboxAction::Access`, whose only production constructor was the
wrapper. The drain-gate test that passed it now passes
`SandboxAction::Spaced`, since the gate uses the action only to label
its 409, and the characterization test's internal-label list drops it.

Of the deleted Kubernetes test's invariant, two tests still pin parts:
`legacy_paused_restore_preserves_handoff_without_route_identity` (a
dispatched Wake keeps its operation owner and does not publish the
route, through `restore_evacuated_execution`) and
`only_a_proven_no_send_wake_failure_may_release_the_operation` (the
release predicate).

Update comments and the Direct module doc that named the deleted items,
including the lock-contention arm of
`reconnect_controller_error_outcome`. That arm is now pre-emptive: the
function's only production caller, the Wake admission check, passes
cluster-admission 429s.

Left in place: the `(_, "access") => ReconnectEntrypoint::Dataplane`
reconnect-metric mapping, which no production path produces now;
the `suspend_restore_storage_uri` parameter of
`ensure_execution_ready_for_access`, now `None` at every call site; and
the arms of `reconnect_controller_error_outcome` that only tests reach.

Lower four file-size caps in architecture-baseline.toml to the counts
`cargo arch` reports for the changed files: `sandbox/lifecycle.rs`
1825 -> 1815, `sandbox/mod.rs` 1193 -> 1086, `substrate_adapter.rs`
2467 -> 2344, and `substrate/mod.rs` 2771 -> 2089. The `substrate/mod.rs`
drop includes 646 lines of slack that predate this change. `direct.rs`
shrank too, but its cap is already below its count and stays.

In-process only; no wire, schema or config change. Revert to roll back.
@ac-pplx
ac-pplx force-pushed the ac/substrate-delete-sandbox-ip branch from 6f799c4 to b7631d0 Compare October 30, 2026 23:28
@ac-pplx
ac-pplx added this pull request to the merge queue Oct 31, 2026
Merged via the queue into main with commit 0a8bd3d Oct 31, 2026
23 checks passed
@ac-pplx
ac-pplx deleted the ac/substrate-delete-sandbox-ip branch October 31, 2026 01:11
ac-pplx added a commit that referenced this pull request Nov 2, 2026
Every archcheck rule is to be required from the day it lands, with
exceptions that only shrink: no new baseline entries, no raised caps, and
stale entries removed. A rule that can't be enforced that way is deleted.
The README now states this policy under "Configuration and modes".
pattern_ratchet.direct_tables, also advisory, stays because open pull
requests shrink it (#1202).

The advisory file_size rule fails that test. Its baseline was seeded with
99 entries on 2026-10-13, reached 102, and lists 98 production files over
1,000 code lines today; #1156 and #1159 lowered caps. What can't be made
required is listed_no_growth: in the 14 days to 2026-10-31, 149 of main's
371 first-parent commits (40%) added more lines than they deleted to one
of the 98 listed files (git log --numstat, raw lines). Requiring it would
block about that share of pull requests.

The change removes the file_size module, its configuration code and its
[rules.file_size] tables in ARCHITECTURE.toml, its [file_size] baseline
table and base-copy read, its part of the generated header, and
--write-baseline=file_size. ProductionFile's code_lines field and
count_code_lines go too, since only file_size read them. The baseline was
edited by hand, so the other tables keep their comments, and its header
matches what --write-baseline now generates.

Kept: the shared count ratchet (rules/ratchet.rs), which pattern_ratchet
uses, and the code-line definition (context::code_lines). Two small
changes follow from [file_size] no longer always being present:

- Baseline::write puts one blank line between whichever parts it writes.
  Without this, a baseline with no table of caps gets a doubled blank line.
- When the base baseline is not TOML and no rule compares tables with it,
  skipped() falls back to each rule's own lines. Without this, the warning
  ends in ";  skipped". crate_names then reports the unreadable copy
  itself.

Tests that used file_size for behaviour the pattern ratchets share now
use a pattern_ratchet fixture. These cover renames (merged into
pattern_ratchet_renames), --renames from git, the human report, rdjson
(document, exact paths, stdout/stderr), base-baseline problems, the
skip-line and no-base checks, implicit members, scoped and unscoped
--write-baseline, and write errors. The rows of lenient_base_baseline and
behaviours not covered elsewhere moved into the pattern_ratchet tables.
Tests that only exercised file_size are deleted: its behaviours table,
its modes test, its limit digit grouping, write_baseline_output's limit
cases, and configuration rows for its keys. New assertions pin
Baseline::write's layout, with and without a table of caps, and the one
warning that names every pattern_ratchet instance's comparisons for a
base copy that is not TOML. The layers tests change only their fixtures
and the expectations that follow from them: their [file_size] lines are
gone from configurations and baselines, so expected baseline line numbers
move up by one, the warning for a base copy that is not TOML no longer
names file_size's comparisons, and a scoped write keeps a stale
[crate_names] entry where it kept a stale [file_size] one.

The CI ordering script's comment no longer says the report lists
file_size findings first. Its test fixtures use a pattern_ratchet code
and table, with each case's meaning unchanged. Five api test modules'
doc comments no longer cite the file-size guidance.
meefs pushed a commit to meefs/SPACE that referenced this pull request Nov 4, 2026
…rplexityai#1222)

Every archcheck rule is to be required from the day it lands, with
exceptions that only shrink: no new baseline entries, no raised caps, and
stale entries removed. A rule that can't be enforced that way is deleted.
The README now states this policy under "Configuration and modes".
pattern_ratchet.direct_tables, also advisory, stays because open pull
requests shrink it (perplexityai#1202).

The advisory file_size rule fails that test. Its baseline was seeded with
99 entries on 2026-10-13, reached 102, and lists 97 production files over
1,000 code lines today; perplexityai#1156 and perplexityai#1159 lowered caps. What can't be made
required is listed_no_growth: in the 14 days to 2026-10-31, 149 of main's
371 first-parent commits (40%) added more lines than they deleted to one
of the 97 listed files (git log --numstat, raw lines). Requiring it would
block about that share of pull requests.

Two sbx-mgr SQLite checks go too, since the required layers rule now
covers them: layers.storage_access lets only stores, storage helpers and
baselined edges depend on a database driver or a storage helper, and
layers.store_edges lets only a store's listed users depend on it.

- dependency_fence, the whole rule, with its sbxmgr_rusqlite and
  sbxmgr_sqlx instances. Every package they fenced (rusqlite,
  libsqlite3-sys, pplx-space-sqlite, sqlx, sqlx-core, sqlx-sqlite) is a
  [rules.layers.storage] driver or helper, which storage_access keeps out
  of the normal and build dependencies of every sbx-mgr crate but its two
  stores.
- pattern_ratchet.sbxmgr_sqlite: twelve regexes for SQLite calls and SQL
  literals in sbx-mgr's crates, which aliases escaped; storage_access
  confines SQLite to the stores at the crate level instead.

pattern_ratchet.sbxmgr_sqlite_schema stays, because layers does not see
what it catches: the storage checks read only dependency edges, skip
stores and helpers and tolerate baselined edges, and state.writes scans
only stores' and helpers' write statements, so db-migrate, a store with
sqlx and rusqlite, could name sbx-mgr's migrator again, and a node
service with a baselined driver edge could open SANDBOX_MANAGER_DB.
Its comment now says so instead of contrasting it with sbxmgr_sqlite.

What this narrows:

- pplx-space-sbxmgr-migrations, an l30_storage crate, may now take
  rusqlite, libsqlite3-sys and pplx-space-sqlite, which sbxmgr_rusqlite
  allowed only the store. CI's run_sbxmgr_production_checks, which
  asserts which crates bring rusqlite and pplx-space-sqlite into the
  server's normal graph, still rejects a normal dependency on either
  there, but not a build or dev dependency, nor libsqlite3-sys.
- The migrations crate's sources are no longer scanned for SQL text;
  state.writes still reports a write statement there that names another
  store's table.
- Dev-dependencies, which the fences covered and storage_access leaves
  to tests.
- pplx-space-sbx-mgr's baselined storage_access edge to the
  pplx-space-common-jobs helper, which it uses for job types today and
  which sbxmgr_sqlite watched for calls into common-jobs' SQL functions.

sbxmgr_sqlite's baseline table was empty. The rule's module,
configuration and context code, its cli test, sbxmgr_sqlite's repository
tests and their helper, and the rule's README section are deleted. The
fence test's dev-dependency case was the only test of a finding on a
dependency declared in a table of its own ([dependencies.sqlx]), which
places every layers finding on such a dependency; layers_storage_checks
gains that case. Comments in ARCHITECTURE.toml, run-rust-checks.sh, the
sbx-mgr store's AGENTS.md and pplx-space-sqlite's crate docs now name
storage_access where they named the fences. scripts/move_crates.py drops
its check of dependency_fence member globs, and that check's test.

The change removes the file_size module, its configuration code and its
[rules.file_size] tables in ARCHITECTURE.toml, its [file_size] baseline
table and base-copy read, its part of the generated header, and
--write-baseline=file_size. ProductionFile's code_lines field and
count_code_lines go too, since only file_size read them. The baseline was
edited by hand, so the other tables keep their comments, and its header
matches what --write-baseline now generates.

Kept: the shared count ratchet (rules/ratchet.rs), which pattern_ratchet
uses, and the code-line definition (context::code_lines). Two small
changes follow from [file_size] no longer always being present:

- Baseline::write puts one blank line between whichever parts it writes.
  Without this, a baseline with no table of caps gets a doubled blank line.
- When the base baseline is not TOML and no rule compares tables with it,
  skipped() falls back to each rule's own lines. Without this, the warning
  ends in ";  skipped". crate_names then reports the unreadable copy
  itself.

Tests that used file_size for behaviour the pattern ratchets share now
use a pattern_ratchet fixture. These cover renames (merged into
pattern_ratchet_renames), --renames from git, the human report, rdjson
(document, exact paths, stdout/stderr), base-baseline problems, the
skip-line and no-base checks, implicit members, scoped and unscoped
--write-baseline, and write errors. The rows of lenient_base_baseline and
behaviours not covered elsewhere moved into the pattern_ratchet tables.
Tests that only exercised file_size are deleted: its behaviours table,
its modes test, its limit digit grouping, write_baseline_output's limit
cases, and configuration rows for its keys. New assertions pin
Baseline::write's layout, with and without a table of caps, and the one
warning that names every pattern_ratchet instance's comparisons for a
base copy that is not TOML. Apart from the layers_storage_checks case
above, the layers tests change only their fixtures and the expectations
that follow from them: their [file_size] lines are gone from
configurations and baselines, so expected baseline line numbers move up
by one, the warning for a base copy that is not TOML no longer names
file_size's comparisons, and a scoped write keeps a stale [crate_names]
entry where it kept a stale [file_size] one.

The CI ordering script's comment no longer says the report lists
file_size findings first. Its test fixtures use a pattern_ratchet code
and table, with each case's meaning unchanged. Five api test modules'
doc comments no longer cite the file-size guidance.
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