Skip to content

sandbox-store: move the orphan, tombstone and remaining sweeper row writes into the store - #1335

Merged
ac-pplx merged 1 commit into
ac/lifecycle-move-internal-apifrom
ac/lifecycle-move-cc-rest
Nov 3, 2026
Merged

ac-pplx merged 1 commit into
ac/lifecycle-move-internal-apifrom
ac/lifecycle-move-cc-rest

Conversation

@ac-pplx

@ac-pplx ac-pplx commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Mechanical move, no functional change. The sandboxes row writes listed below move into pplx-space-sandbox-store with their SQL byte-for-byte; callers keep their locks, transactions and error handling. Everything else is doc comments. Part of the wave-2 stack (8 of 10, on #1333); it lands with the stack.

Summary

Three cluster-controller loops still wrote the sandboxes row with inline SQL: the orphaned-sandbox reconciler, tombstone retention and the transient-lifecycle sweeper. Their last five statements move into pplx-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 to suspended and 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 an OrphanGuard with today's primitive binds; the reconciler converts its ClassifyGuard at 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 rows deleted past retention. It is not delete_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 with FailGuard, is_activation_owned and the shared evacuation_guards, which the candidate select still uses. The controller re-exports FailGuard and is_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 crate pplx_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::OrphanResolution
  • the DELETE FROM sandboxes in TombstoneRetentionStore::prune_tombstones → tombstone::prune_deleted_sandboxes
  • transient_lifecycle_sweeper::store::{fail_candidate_query, evacuation_guards, REPAIR_FAILED_ACTIVE_EXECUTION_SQL} → sweeper:: (same names), bound by the new sweeper::fail_aged_candidate and sweeper::repair_failed_active_execution
  • transient_lifecycle_sweeper::{FailGuard, is_activation_owned} → sweeper::

sql! locators change: the moved statements now report pplx_space_sandbox_store::{orphan,tombstone,sweeper}, visible in pg_stat_activity and query monitoring.

Baseline: the sandbox_writes entries for the three controller files are removed; the file_size cap for orphaned_sandboxes.rs drops 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.
  • Test files change only use paths: tests.rs 6 +-, tests/activation_owner.rs 3 +-, tests/crashed_create.rs 5 +-.
  • Mutations (not committed), each caught by a named test:
    • prune also takes started rows → tombstone_retention::tests::store_prunes_only_expired_deleted_sandboxes_of_every_stored_lifecycle
    • fail CAS without the pending-receipt guard → crashed_create::postgres_backed::only_a_create_no_receipt_can_finish_is_failed_alive
    • repair without placement = 'on_node' → postgres_backed::r16_failed_active_repair_cas_requires_k8s_failed_active_execution
    • recover keeps target_node_id → orphaned_sandboxes::lifecycle_writer_tests::classification_moves_only_the_lifecycles_its_guard_owns
    • Forgotten guard without state = 'forgotten' → …::classification_acts_only_on_the_execution_its_guard_names
  • Kind: fresh cluster (up.sh, 12m08s), then scripts/tests/smoke-kind-typed.sh all (52m33s). These ran on a tree that differs from this head only in doc comments.
    • ECW: 85 passed, 0 failed, 3 inconclusive. Native: 35 passed.
    • ecw.cold_create_environment and ecw.warm_pool_environment rerun with SPACE_SMOKE_KIND_COLD_RECEIPTS: both passed.
    • Not run (need otel telemetry; bench ran datadog): local.restarted_btrfs_send_is_rejected_and_retried (inconclusive); skipped local.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

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 onto 42a99002, 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 is 8892c3ae.

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

github-actions Bot commented Nov 2, 2026

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/lifecycle-move-cc-rest branch from a93b9c6 to a749e8b Compare November 2, 2026 21:09
@ac-pplx
ac-pplx force-pushed the ac/lifecycle-move-cc-rest branch from a749e8b to 23ba147 Compare November 2, 2026 23:25
@ac-pplx
ac-pplx changed the base branch from main to ac/lifecycle-move-internal-api November 3, 2026 03:50
@ac-pplx
ac-pplx added this pull request to stack #1382 November 3, 2026 03:50
@ac-pplx
ac-pplx marked this pull request as ready for review November 3, 2026 03:51
@ac-pplx
ac-pplx marked this pull request as draft November 3, 2026 03:52
@ac-pplx
ac-pplx force-pushed the ac/lifecycle-move-cc-rest branch from 23ba147 to 44fb77b Compare November 3, 2026 06:00
@ac-pplx
ac-pplx marked this pull request as ready for review November 3, 2026 06:29
…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
ac-pplx force-pushed the ac/lifecycle-move-cc-rest branch from 44fb77b to 8892c3a Compare November 3, 2026 21:24
@ac-pplx
ac-pplx added this pull request to the merge queue Nov 3, 2026
Merged via the queue into main with commit 27d1388 Nov 3, 2026
41 checks passed
@ac-pplx
ac-pplx deleted the ac/lifecycle-move-cc-rest branch November 3, 2026 22:03
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