Repository navigation
sandbox-store: move the orphan, tombstone and remaining sweeper row writes into the store - #1335
Merged
Conversation
|
Thanks for opening this pull request! A SPACE maintainer will take a look. CONTRIBUTING.md explains how we review outside contributions. |
ac-pplx
force-pushed
the
ac/lifecycle-move-cc-rest
branch
from
November 2, 2026 21:09
a93b9c6 to
a749e8b
Compare
ac-pplx
force-pushed
the
ac/lifecycle-move-cc-rest
branch
from
November 2, 2026 23:25
a749e8b to
23ba147
Compare
This was referenced Nov 3, 2026
Merged
ac-pplx
marked this pull request as ready for review
November 3, 2026 03:51
ac-pplx
marked this pull request as draft
November 3, 2026 03:52
ac-pplx
force-pushed
the
ac/lifecycle-move-cc-rest
branch
from
November 3, 2026 06:00
23ba147 to
44fb77b
Compare
ac-pplx
marked this pull request as ready for review
November 3, 2026 06:29
kumarpallav-pplx
requested review from
kumarpallav-pplx and
nikhilbirmiwal-pplx
November 3, 2026 06:30
nikhilbirmiwal-pplx
approved these changes
Nov 3, 2026
…rites into the store
The cluster controller's orphaned-sandbox reconciler, tombstone-retention
loop and transient-lifecycle sweeper still wrote the control-plane
`sandboxes` row from inline SQL. Their last five statements move into
`pplx-space-sandbox-store`, one function per transition, each running on
the transaction or executor its caller passes:
- `orphan` (new): the orphan recovery to `suspended` and the orphan
quarantine, each one statement whose CTEs also retire the active
execution and reset open port exposures. The classification guard
fragments move with them and are public, because the reconciler's plan
read (`SELECT ... FOR UPDATE`) interpolates the same guards. The store
takes an `OrphanGuard` holding the binds the reconciler passes today,
including its lifecycle sets and create age. The reconciler converts its
own guard into one at the call site.
- `tombstone` (new): the retention prune of `deleted` rows.
- `sweeper` (extended): the per-lifecycle fail CAS (with `FailGuard`,
`is_activation_owned` and the shared `evacuation_guards`, which the
sweeper's candidate select still interpolates) and the failed-active-
execution repair. The controller re-exports `FailGuard` and
`is_activation_owned` from their old paths.
Statement text is unchanged byte for byte, and each statement is still built
the way it was. Locking reads, transactions, the quarantine state and history
inserts, and the warm-pool slot delete stay at the call sites. Test files
change only their `use` paths.
The moved statements' `sql!` locators now name
`pplx_space_sandbox_store::{orphan,tombstone,sweeper}`. The `sandbox_writes`
entries for the three controller files are removed, and the `file_size` cap
of `orphaned_sandboxes.rs` drops from 1581 to 1418.
ac-pplx
force-pushed
the
ac/lifecycle-move-cc-rest
branch
from
November 3, 2026 21:24
44fb77b to
8892c3a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three cluster-controller loops still wrote the
sandboxesrow with inline SQL: the orphaned-sandbox reconciler, tombstone retention and the transient-lifecycle sweeper. Their last five statements move intopplx-space-sandbox-store, one function per transition, each running on the caller's transaction or executor. Each doc names the fact it writes; all five are inference.orphan(new): recovery tosuspendedand quarantine. Each is one statement whose CTEs also retire the active execution and reset open port exposures. The classify-guard fragments move too and are public, because the reconciler's plan read (SELECT … FOR UPDATE) uses the same guards. The store takes anOrphanGuardwith today's primitive binds; the reconciler converts itsClassifyGuardat the call site. Note: the CTEs re-check the plan read's guard and eligibility. The plan read locks the sandbox row (and, for a pod guard, the execution) but not the snapshot rows the eligibility reads. No test reaches these re-checks apart from the plan read.tombstone(new): the prune of rowsdeletedpast retention. It is notdelete_with_dependents, so the caller still deletes the claimed warm-pool slots.sweeper(extended): the per-lifecycle fail CAS and the failed-active-execution repair. The CAS moves withFailGuard,is_activation_ownedand the sharedevacuation_guards, which the candidate select still uses. The controller re-exportsFailGuardandis_activation_owned.SQL text is byte-identical and each statement is built as before. Locking reads, transactions, the quarantine inserts and the slot delete stay at the call sites.
Moved (
old → new; store cratepplx_space_sandbox_store):orphaned_sandboxes::{recover_orphan_to_suspended, quarantine_orphan, classify_guard_sql, classify_guard_from_sql, classify_guard_lock_sql, fetch_guarded_resolution}→orphan::(same names);OrphanResolutionRow→orphan::OrphanResolutionDELETE FROM sandboxesinTombstoneRetentionStore::prune_tombstones→tombstone::prune_deleted_sandboxestransient_lifecycle_sweeper::store::{fail_candidate_query, evacuation_guards, REPAIR_FAILED_ACTIVE_EXECUTION_SQL}→sweeper::(same names), bound by the newsweeper::fail_aged_candidateandsweeper::repair_failed_active_executiontransient_lifecycle_sweeper::{FailGuard, is_activation_owned}→sweeper::sql!locators change: the moved statements now reportpplx_space_sandbox_store::{orphan,tombstone,sweeper}, visible inpg_stat_activityand query monitoring.Baseline: the
sandbox_writesentries for the three controller files are removed; thefile_sizecap fororphaned_sandboxes.rsdrops from 1581 to 1418.Validation
On this head:
cargo nextest run -p pplx-space-sandbox-store -p pplx-space-cluster-controller-server --no-fail-fast(Postgres setup binary built first): 1211 passed, 0 skipped.cargo clippy -p pplx-space-sandbox-store -p pplx-space-cluster-controller-server --all-targets -- -D warnings,RUSTDOCFLAGS="-D warnings" cargo doc --no-deps(same packages),cargo fmt --check: clean.cargo arch --base-baseline … --base-architecture … --renames …: 0 required findings, none from these files.usepaths:tests.rs6 +-,tests/activation_owner.rs3 +-,tests/crashed_create.rs5 +-.startedrows →tombstone_retention::tests::store_prunes_only_expired_deleted_sandboxes_of_every_stored_lifecyclecrashed_create::postgres_backed::only_a_create_no_receipt_can_finish_is_failed_aliveplacement = 'on_node'→postgres_backed::r16_failed_active_repair_cas_requires_k8s_failed_active_executiontarget_node_id→orphaned_sandboxes::lifecycle_writer_tests::classification_moves_only_the_lifecycles_its_guard_ownsstate = 'forgotten'→…::classification_acts_only_on_the_execution_its_guard_namesup.sh, 12m08s), thenscripts/tests/smoke-kind-typed.sh all(52m33s). These ran on a tree that differs from this head only in doc comments.ecw.cold_create_environmentandecw.warm_pool_environmentrerun withSPACE_SMOKE_KIND_COLD_RECEIPTS: both passed.local.restarted_btrfs_send_is_rejected_and_retried(inconclusive); skippedlocal.btrfs_send_restore_pulls_parent_template,local.leader_election_brownout_keeps_one_leader,local.telemetry_reaches_local_backends.Severity and risk
Severity: does not apply (refactor). Risk: a bind mistake could change a guarded write; the SQL is byte-identical, and the tests and mutations above exercise each statement. Roll back by reverting; no schema or data change.
Rollout
Same statements, so old and new pods can run together; only the
sql!locators differ.Open PRs that will need to rebase
classify_guard_sql→orphan::classify_guard_sql;fail_candidate_query→sweeper::fail_candidate_query. Its newactivation_receipt_pendinghelper, whichfail_candidate_queryand its new abandoned-create claim both call, has to move into the store too, because the store cannot call the controller. Its newUPDATE sandboxesstatements inorphaned_sandboxes.rsno longer have a ratchet entry, so they belong inorphan.REPAIR_FAILED_ACTIVE_EXECUTION_SQL→sweeper::REPAIR_FAILED_ACTIVE_EXECUTION_SQL.lib.rs: api: close six admin API gaps the admin console works around #1303, Add idempotent ECW network policy replacement and live status #1297, Add trusted lifecycle registration and advisory candidate inventory #579.architecture-baseline.toml: [cluster-controller] record pod-create intent so the pod-less drain give-up can release a stalled recovery #1323, api: page and filter the admin sandbox list, count spaced versions, and share one sandbox ID parser #1321, Add idempotent ECW network policy replacement and live status #1297, archcheck: delete file_size and the checks the layers rule covers #1222, archcheck: give every database table one declared owning store #1164, archcheck: keep the domain layer free of I/O and its models off protocols #1162, archcheck: keep database and object-storage access in the storage layer #1161, archcheck: add a layers rule for crate placement and dependency direction #1152, api: refuse a cold create with a 503 when every eligible node's network slots are held #1061, api: expose process recovery sessions #976, spaced: gate routed process recovery on VM activation #969, spaced: advertise process output gap recovery #889, Enforce template lookup scope after compatible rollout #806, [space] fence every placement writer against the provider-terminate path #559.sandboxeswrites into the store (sandbox-store: move the node drain's sandbox row writes into the store #1328, api: move the create path's sandboxes writes into sandbox-store #1329, cluster-controller: gauge sandboxes wedged in a transient lifecycle #1332, sandbox-store: move the publish path's stopped, activity, absent-delete and rollback writes #1333) each add a store module line or edit their own baseline entries; those conflicts are textual.architecture-baseline.tomlalso conflicts textually with [cluster-controller] give up on pod-less drain residents only when the node vouches #705 (merged), which added asandbox_writesentry fornode_drain/store/pod_less_giveup.rs; a rebase keeps that entry.AI assistance: Claude Code (an AI agent) wrote the code changes, ran the validation and drafted this description.
After the wave-2 restack
This PR is one layer of a 10-PR stack, restacked onto main
c5fdf0c7(which includes #826) and then rebased mechanically onto42a99002, which changed only baseline context lines, in order #1329 → #1336 → #1338 → #1339 → #1342 → #1347 → #1333 → #1335 → #1328 → #1332. The validation earlier in this description ran at pre-restack heads; this layer's head is8892c3ae.REPAIR_FAILED_ACTIVE_EXECUTION_SQLalso drop adiskartifact pin. The movedsweeper::REPAIR_FAILED_ACTIVE_EXECUTION_SQLis byte-identical to main's, doc paragraph included. For every file [cluster-controller] re-place an evacuated stopped sandbox as stopped on another node, materialized by the shim without a VM #826 and this stack share, each SQL line [cluster-controller] re-place an evacuated stopped sandbox as stopped on another node, materialized by the shim without a VM #826 added was checked to be present verbatim at the top head, in place or in the store module it moved to.cargo fmt --all -- --check,cargo check --workspace --all-targets --all-featuresandcargo arch(0 required findings, with main's layers and table-owner rules), and the ratchet and caps this layer changes are recounted exactly.e90aa284before that rebase; nextest passed 4370 tests again at77fbae1e):cargo clippy --workspace --locked --all-targets --all-features -- -D warningsis clean, andSPACE_CI_REQUIRE_TEST_DEPS=1 cargo nextest run --all-featurespassed 4355 tests across the store, api, internal-api server, cluster-controller server, metrics, db-test-fixtures, common-core, common-svc and portability crates, [cluster-controller] re-place an evacuated stopped sandbox as stopped on another node, materialized by the shim without a VM #826's suites included.scripts/tests/smoke-kind-typed.sh allgave ECW 91/91 and native 42/42, including [cluster-controller] re-place an evacuated stopped sandbox as stopped on another node, materialized by the shim without a VM #826'snative.forced_drain_re_places_stopped_sandboxand its re-drain and restarted variants. The Direct substrate lane'sallgave ECW 51/51 and native 27/27. No flakes and no reruns.+/-lines and its SQL string literals match its reviewed head (range-diff plus a literal multiset compare).