Repository navigation
sbx-mgr: one refusal-to-status mapping for update_state_from - #438
Conversation
Each update_state_from conversion wrote its own StateWriteOutcome-to-Status
helper, with different names, message text and policies. Add one shared
mapping and best-effort writer in service/cleanup_support.rs, with no
production callers; the conversions adopt them and delete their copies.
- require_state_write_from(result, context) -> Result<(), Status>. A
from-state refusal is Aborted "{context}: sandbox state write was
superseded: sandbox is {found}". A quarantine or cleanup-ownership refusal
and a missing row keep the exact Aborted status require_state_write
returns for update_state (blocked_by_quarantine,
superseded_by_cleanup_episode, missing). A database error is Internal
"{context}: {error}", as before.
- state_write_result(outcome) -> Result<SandboxState, StateWriteRejected> is
the typed form. It returns the overwritten state, or one of FromState
{ found }, Guard { refusal, found } and Missing, with into_status(context).
A caller that answers one case differently, such as warm start with
FailedPrecondition, matches it and passes the rest on. Guard carries
found for callers that decide by edge.from().contains(found).
- update_state_from_best_effort(db, id, edge) -> Option<StateWriteOutcome>
is update_state_best_effort for update_state_from, with a wildcard-free
match. It logs refusals and errors with that function's messages and
field names, but logs outcome with Debug where the legacy used Display.
Only an applied write marks the sandbox dirty. None means a poisoned
connection or a database error.
The legacy require_state_write is unchanged. Outside tests the new items
expect dead_code. Adopting require_state_write_from leaves several of these
expectations unfulfilled at once: the function, StateWriteRejected,
StateWriteGuard, state_write_result and the impl. unfulfilled_lint_expectations
is warn-by-default and an error under CI's clippy -D warnings, so the
adopting conversion must drop those attributes.
Tests:
- Wildcard-free matches cover every StateWriteOutcome and StateWriteRefusal
and assert each one's typed rejection and exact code and message. The
refusal list is built from such a match.
- The same row gets the same status from the new mapping and from the
update_state caller path, and every row stays unchanged, for: a
quarantined row, a stopping row revived to started, a cleanup row with a
delete_row intent, and a missing row. The row's own state is expected, and
the messages match literals copied from 8f12bd6.
- The best-effort writer applies a write and marks only that sandbox dirty.
A refusal and a database error return their result and change nothing. A
poisoned connection is None, changes nothing, and wakes the dirty-outbox
worker, which exits on the poisoned connection and fails its critical-task
guard. The test pauses the clock so the worker's poll timer cannot fire.
The sbx-mgr AGENTS.md writer rule names both helpers and the override.
nikhilgupta-pplx
left a comment
There was a problem hiding this comment.
Approve. Mapping, typed rejection and best-effort writer match the legacy update_state paths (guard/missing/DB-error text is byte-identical; dirty only on Applied; poison still signals). No production callers, and the dead-code handoff to #418/#421 is consistent.
Non-blocking nits:
update_state_from_best_effortlogsMissingas "superseded" (inherited from the legacy writer); worth a distinct message while this is new.require_state_write_fromtakes&'static strwhileinto_statustakes&str; minor inconsistency.
Every write of sbx-mgr's node-local SQLite `sandboxes.state` now goes
through `update_state_from` with an explicit edge, or through a named
specialized writer. Nothing here touches the control-plane Postgres
`public.sandboxes.lifecycle`.
The three remaining db-internal `update_state` callers:
- The `deleting` re-entry inside `update_state` had no production caller:
Stop and Delete admission always write inside their own transaction.
`update_state_from` already opens an `IMMEDIATE` transaction in
autocommit for every target, so the deletion and the teardown jobs it
fails still commit together.
- `begin_resident_resume_with_policy` writes `edges::RESUME_BEGIN`
(`paused` -> `resuming`) inside its policy transaction. The edge now
refuses a non-paused row, so the separate state pre-check is gone; the
generation fence, the lock and the transaction are unchanged.
- Stop and Delete admission
(`update_state_superseding_activation_in_transaction`) cannot use an
exact edge without changing behavior. Admission re-admits a row that is
already in its target: a `stopping` row for a repeated Stop of a live
runtime, and a `deleting` row for a repeated Delete. No edge lists its
own target. The write therefore moves to `write_cleanup_admission_state`,
a private writer for only those two targets. It writes over every state
the generic guards admit, using `update_state_from`'s statement, guards
and warnings, as admission always has. `STOP_ADMISSION` and
`DELETE_ADMISSION` keep listing the transitions it makes.
`update_state`, `SandboxStateWriteOutcome`, the legacy
`require_state_write` and `CleanupCompletionError::StateWriteNotApplied`
are deleted. The guard types collapse: `db::StateWriteGuard { Quarantined,
CleanupOwned }`, `generic_state_write_refusal` returns
`Option<StateWriteGuard>`, and `StateWriteRefusal` is
`FromState | Guard(StateWriteGuard)`. That removes the unreachable
"guards never answer FromState" arm. `SandboxStateWriteNotApplied` becomes
`Guard(StateWriteGuard) | Missing` and renders the same client-visible text
byte for byte. `into_status`, checkpoint admission and cleanup admission
all render through it.
Tests:
- The `update_state` outcome matrix already ran through `update_state_from`
with every state expected, standalone and in a transaction (#388). Only
its `update_state` arm is dropped, so every guard cell stays pinned. The
guard tests in `db/tests.rs` now target production edges, and the #438
status tests keep their literal texts without the legacy comparison.
- New: Delete admission from every source state. It admits every plain row,
a `deleting` row included, except a `suspending` row whose suspend has
not reached the cluster. Nothing pinned Delete re-admission before.
- Test-only callers use an edge where production has one, `force_state`
where only the state matters, and an explicit-source
`update_state_from_states` for the two `started` -> `restoring` seeds.
- The five copies of the Stop fixture (superseding write, `mark_stopped`
intent, row-intent completion) are now `admit_stop`, `complete_stop` and
`stop` in `db/tests/state_write_fixtures.rs`.
Review comments from the stack's approval, folded in:
- `update_state_from_best_effort` logs a missing row with its own message,
instead of "superseded".
- `require_state_write_from` takes `context: &str`, as `into_status` does.
- Pause's comment on `mark_quarantined_best_effort` now says it also
answers false on a poisoned connection or a database error.
- A refused startup recovery write names the edge, the found state and the
refusal, instead of printing the outcome's `Debug` form. A missing row
has its own error.
- Auto-lifecycle Stop gains a case for a Delete admitted mid-write: a
`deleting` row with a current `delete_row` intent, which the
cleanup-ownership guard refuses and the Stop skips.
- Suspend's guest-prep rollback refusal and auto-pause refusal still answer
`Internal`. Both are unreachable under suspend's fence and locks, and a
comment now says so.
- Warm start's doc says why a guarded `suspended` row stays `Aborted`, and
that answering a transient row with `FailedPrecondition` is deliberate.
- Checkpoint admission renders through `SandboxStateWriteNotApplied`, not
the legacy outcome. A missing-row case pins its text alongside the
existing guard literals.
The sbx-mgr AGENTS.md writer rule is updated, and the archcheck cap for
`db/state_transitions.rs` is lowered to its new size.
Review — 2 findingsLow
What I checked and found sound:
Verified against |
the-gigi-pplx
left a comment
There was a problem hiding this comment.
Approved. 2 non-blocking findings in the review comment above.
Every write of sbx-mgr's node-local SQLite `sandboxes.state` now goes
through `update_state_from` with an explicit edge, or through a named
specialized writer. Nothing here touches the control-plane Postgres
`public.sandboxes.lifecycle`.
The three remaining db-internal `update_state` callers:
- The `deleting` re-entry inside `update_state` had no production caller:
Stop and Delete admission always write inside their own transaction.
`update_state_from` already opens an `IMMEDIATE` transaction in
autocommit for every target, so the deletion and the teardown jobs it
fails still commit together.
- `begin_resident_resume_with_policy` writes `edges::RESUME_BEGIN`
(`paused` -> `resuming`) inside its policy transaction. The edge now
refuses a non-paused row, so the separate state pre-check is gone; the
generation fence, the lock and the transaction are unchanged.
- Stop and Delete admission
(`update_state_superseding_activation_in_transaction`) cannot use an
exact edge without changing behavior. Admission re-admits a row that is
already in its target: a `stopping` row for a repeated Stop of a live
runtime, and a `deleting` row for a repeated Delete. No edge lists its
own target. The write therefore moves to `write_cleanup_admission_state`,
a private writer for only those two targets. It writes over every state
the generic guards admit, using `update_state_from`'s statement, guards
and warnings, as admission always has. `STOP_ADMISSION` and
`DELETE_ADMISSION` keep listing the transitions it makes.
`update_state`, `SandboxStateWriteOutcome`, the legacy
`require_state_write` and `CleanupCompletionError::StateWriteNotApplied`
are deleted. The guard types collapse: `db::StateWriteGuard { Quarantined,
CleanupOwned }`, `generic_state_write_refusal` returns
`Option<StateWriteGuard>`, and `StateWriteRefusal` is
`FromState | Guard(StateWriteGuard)`. That removes the unreachable
"guards never answer FromState" arm. `SandboxStateWriteNotApplied` becomes
`Guard(StateWriteGuard) | Missing` and renders the same client-visible text
byte for byte. `into_status`, checkpoint admission and cleanup admission
all render through it.
Tests:
- The `update_state` outcome matrix already ran through `update_state_from`
with every state expected, standalone and in a transaction (#388). Only
its `update_state` arm is dropped, so every guard cell stays pinned. The
guard tests in `db/tests.rs` now target production edges, and the #438
status tests keep their literal texts without the legacy comparison.
- New: Delete admission from every source state. It admits every plain row,
a `deleting` row included, except a `suspending` row whose suspend has
not reached the cluster. Nothing pinned Delete re-admission before.
- Test-only callers use an edge where production has one, `force_state`
where only the state matters, and an explicit-source
`update_state_from_states` for the two `started` -> `restoring` seeds.
- The five copies of the Stop fixture (superseding write, `mark_stopped`
intent, row-intent completion) are now `admit_stop`, `complete_stop` and
`stop` in `db/tests/state_write_fixtures.rs`.
Review comments from the stack's approval, folded in:
- `update_state_from_best_effort` logs a missing row with its own message,
instead of "superseded".
- `require_state_write_from` takes `context: &str`, as `into_status` does.
- Pause's comment on `mark_quarantined_best_effort` now says it also
answers false on a poisoned connection or a database error.
- A refused startup recovery write names the edge, the found state and the
refusal, instead of printing the outcome's `Debug` form. A missing row
has its own error.
- Auto-lifecycle Stop gains a case for a Delete admitted mid-write: a
`deleting` row with a current `delete_row` intent, which the
cleanup-ownership guard refuses and the Stop skips.
- Suspend's guest-prep rollback refusal and auto-pause refusal still answer
`Internal`. Both are unreachable under suspend's fence and locks, and a
comment now says so.
- Warm start's doc says why a guarded `suspended` row stays `Aborted`, and
that answering a transient row with `FailedPrecondition` is deliberate.
- Checkpoint admission renders through `SandboxStateWriteNotApplied`, not
the legacy outcome. A missing-row case pins its text alongside the
existing guard literals.
The sbx-mgr AGENTS.md writer rule is updated, and the archcheck cap for
`db/state_transitions.rs` is lowered to its new size.
Every write of sbx-mgr's node-local SQLite `sandboxes.state` now goes
through `update_state_from` with an explicit edge, or through a named
specialized writer. Nothing here touches the control-plane Postgres
`public.sandboxes.lifecycle`.
The three remaining db-internal `update_state` callers:
- The `deleting` re-entry inside `update_state` had no production caller:
Stop and Delete admission always write inside their own transaction.
`update_state_from` already opens an `IMMEDIATE` transaction in
autocommit for every target, so the deletion and the teardown jobs it
fails still commit together.
- `begin_resident_resume_with_policy` writes `edges::RESUME_BEGIN`
(`paused` -> `resuming`) inside its policy transaction. The edge now
refuses a non-paused row, so the separate state pre-check is gone; the
generation fence, the lock and the transaction are unchanged.
- Stop and Delete admission
(`update_state_superseding_activation_in_transaction`) cannot use an
exact edge without changing behavior. Admission re-admits a row that is
already in its target: a `stopping` row for a repeated Stop of a live
runtime, and a `deleting` row for a repeated Delete. No edge lists its
own target. The write therefore moves to `write_cleanup_admission_state`,
a private writer for only those two targets. It writes over every state
the generic guards admit, as admission always has, using
`update_state_from`'s statement and guards. `update_state`'s guard
warnings move with it; their text and fields are unchanged, but the log
target is now `db::state_write`. `STOP_ADMISSION` and `DELETE_ADMISSION`
keep listing the transitions it makes. One behavior change: a row whose
state label this binary cannot parse, reachable only after a downgrade or
with a corrupt row, now fails Stop admission instead of being overwritten
with `stopping`. Delete already failed earlier, in `get_sandbox`.
`update_state`, `SandboxStateWriteOutcome`, the legacy
`require_state_write` and `CleanupCompletionError::StateWriteNotApplied`
are deleted. The guard types collapse: `db::StateWriteGuard { Quarantined,
CleanupOwned }`, `generic_state_write_refusal` returns
`Option<StateWriteGuard>`, and `StateWriteRefusal` is
`FromState | Guard(StateWriteGuard)`. That removes the unreachable
"guards never answer FromState" arm. `SandboxStateWriteNotApplied` becomes
`Guard(StateWriteGuard) | Missing` and renders the same client-visible text
byte for byte. `into_status`, checkpoint admission and cleanup admission
all render through it.
Tests:
- The `update_state` outcome matrix already ran through `update_state_from`
with every state expected, standalone and in a transaction (#388). Only
its `update_state` arm is dropped, so every guard cell stays pinned. The
guard tests in `db/tests.rs` now target production edges, and the #438
status tests keep their literal texts without the legacy comparison.
- New: admission's same-state write. Re-admitting a `stopping` or
`deleting` row clears the failure reason and another target's receipt and
advances `updated_at`. For `deleting`, it also fails the active teardown
jobs. The Stop sweep now pins the full client text for `deleting` and
`quarantined` rows.
- New: Delete admission from every source state. It admits every plain row,
a `deleting` row included, except a `suspending` row whose suspend has
not reached the cluster. Nothing pinned Delete re-admission before.
- Test-only callers use an edge where production has one, `force_state`
where only the state matters, and an explicit-source
`update_state_from_states` for the two `started` -> `restoring` seeds.
- The five copies of the Stop fixture (superseding write, `mark_stopped`
intent, row-intent completion) are now `admit_stop`, `complete_stop` and
`stop` in `db/tests/state_write_fixtures.rs`.
Review comments from the stack's approval, folded in:
- `update_state_from_best_effort` logs a missing row with its own message,
instead of "superseded".
- `require_state_write_from` takes `context: &str`, as `into_status` does.
- Pause's comment on `mark_quarantined_best_effort` now says it also
answers false on a poisoned connection or a database error.
- A refused startup recovery write names the edge, the found state and the
refusal, instead of printing the outcome's `Debug` form. A missing row
has its own error.
- Auto-lifecycle Stop gains a case for a Delete admitted mid-write: a
`deleting` row with a current `delete_row` intent, which the
cleanup-ownership guard refuses and the Stop skips.
- Suspend's guest-prep rollback refusal and auto-pause refusal still answer
`Internal`. Both are unreachable under suspend's fence and locks, and a
comment now says so.
- Warm start's doc says why a guarded `suspended` row stays `Aborted`, and
that answering a transient row with `FailedPrecondition` is deliberate.
- Checkpoint admission renders through `SandboxStateWriteNotApplied`, not
the legacy outcome. A missing-row case pins its text alongside the
existing guard literals.
The sbx-mgr AGENTS.md writer rule is updated, and the archcheck cap for
`db/state_transitions.rs` is lowered to its new size.
Every write of sbx-mgr's node-local SQLite `sandboxes.state` now goes
through `update_state_from` with an explicit edge, or through a named
specialized writer. Nothing here touches the control-plane Postgres
`public.sandboxes.lifecycle`.
The three remaining db-internal `update_state` callers:
- The `deleting` re-entry inside `update_state` had no production caller:
Stop and Delete admission always write inside their own transaction.
`update_state_from` already opens an `IMMEDIATE` transaction in
autocommit for every target, so the deletion and the teardown jobs it
fails still commit together.
- `begin_resident_resume_with_policy` writes `edges::RESUME_BEGIN`
(`paused` -> `resuming`) inside its policy transaction. The edge now
refuses a non-paused row, so the separate state pre-check is gone; the
generation fence, the lock and the transaction are unchanged.
- Stop and Delete admission
(`update_state_superseding_activation_in_transaction`) cannot use an
exact edge without changing behavior. Admission re-admits a row that is
already in its target: a `stopping` row for a repeated Stop of a live
runtime, and a `deleting` row for a repeated Delete. No edge lists its
own target. The write therefore moves to `write_cleanup_admission_state`,
a private writer for only those two targets. It writes over every state
the generic guards admit, as admission always has, using
`update_state_from`'s statement and guards. `update_state`'s guard
warnings move with it; their text and fields are unchanged, but the log
target is now `db::state_write`. `STOP_ADMISSION` and `DELETE_ADMISSION`
keep listing the transitions it makes. One behavior change: a row whose
state label this binary cannot parse, reachable only after a downgrade or
with a corrupt row, now fails Stop admission instead of being overwritten
with `stopping`. Delete already failed earlier, in `get_sandbox`.
`update_state`, `SandboxStateWriteOutcome`, the legacy
`require_state_write` and `CleanupCompletionError::StateWriteNotApplied`
are deleted. The guard types collapse: `db::StateWriteGuard { Quarantined,
CleanupOwned }`, `generic_state_write_refusal` returns
`Option<StateWriteGuard>`, and `StateWriteRefusal` is
`FromState | Guard(StateWriteGuard)`. That removes the unreachable
"guards never answer FromState" arm. `SandboxStateWriteNotApplied` becomes
`Guard(StateWriteGuard) | Missing` and renders the same client-visible text
byte for byte. `into_status`, checkpoint admission and cleanup admission
all render through it.
Tests:
- The `update_state` outcome matrix already ran through `update_state_from`
with every state expected, standalone and in a transaction (#388). Only
its `update_state` arm is dropped, so every guard cell stays pinned. The
guard tests in `db/tests.rs` now target production edges, and the #438
status tests keep their literal texts without the legacy comparison.
- New: admission's same-state write. Re-admitting a `stopping` or
`deleting` row clears the failure reason and another target's receipt and
advances `updated_at`. For `deleting`, it also fails the active teardown
jobs. The Stop sweep now pins the full client text for `deleting` and
`quarantined` rows.
- New: Delete admission from every source state. It admits every plain row,
a `deleting` row included, except a `suspending` row whose suspend has
not reached the cluster. Nothing pinned Delete re-admission before.
- Test-only callers use an edge where production has one, `force_state`
where only the state matters, and an explicit-source
`update_state_from_states` for the two `started` -> `restoring` seeds.
- The five copies of the Stop fixture (superseding write, `mark_stopped`
intent, row-intent completion) are now `admit_stop`, `complete_stop` and
`stop` in `db/tests/state_write_fixtures.rs`.
Review comments from the stack's approval, folded in:
- `update_state_from_best_effort` logs a missing row with its own message,
instead of "superseded".
- `require_state_write_from` takes `context: &str`, as `into_status` does.
- Pause's comment on `mark_quarantined_best_effort` now says it also
answers false on a poisoned connection or a database error.
- A refused startup recovery write names the edge, the found state and the
refusal, instead of printing the outcome's `Debug` form. A missing row
has its own error.
- Auto-lifecycle Stop gains a case for a Delete admitted mid-write: a
`deleting` row with a current `delete_row` intent, which the
cleanup-ownership guard refuses and the Stop skips.
- Suspend's guest-prep rollback refusal and auto-pause refusal still answer
`Internal`. Both are unreachable under suspend's fence and locks, and a
comment now says so.
- Warm start's doc says why a guarded `suspended` row stays `Aborted`, and
that answering a transient row with `FailedPrecondition` is deliberate.
- Checkpoint admission renders through `SandboxStateWriteNotApplied`, not
the legacy outcome. A missing-row case pins its text alongside the
existing guard literals.
The sbx-mgr AGENTS.md writer rule is updated, and the archcheck cap for
`db/state_transitions.rs` is lowered to its new size.
Stacked on main. Stack, bottom to top: #438 → #418 → #413 → #416 → #421 → #417 → #414. #372 and #388 have merged to main.
Replaces #424, which closed automatically when its base branch merged.
Each
update_state_fromconversion first written on #388 wrote its ownStateWriteOutcome→Statushelper, with different names, message text and policies. This adds one shared mapping and one best-effort writer inservice/cleanup_support.rs, with no production callers. The conversions, now stacked on this PR, delete their copies. The legacyrequire_state_writeforupdate_statestays unchanged.API
require_state_write_from(result, context) -> Result<(), Status>is the primary mapping.state_write_result(outcome) -> Result<SandboxState, StateWriteRejected>is the typed form. It returns the overwritten state, or one ofFromState { found },Guard { refusal: StateWriteGuard, found }andMissing.into_status(context)turns a rejection into a status.FailedPrecondition) matches that case and passes the rest tointo_status.Guardcarriesfoundfor callers that decide byedge.from().contains(found).update_state_from_best_effort(db, sandbox_id, edge) -> Option<StateWriteOutcome>isupdate_state_best_effortforupdate_state_from, with a wildcard-free match.outcomeis logged withDebug(?outcome), where the legacy function usesDisplay.Appliedmarks the sandbox dirty.Nonemeans a poisoned connection (failure signalled) or a database error.Messages
Applied { from }Ok(())(state_write_result:Ok(from))Refused { refusal: FromState, found }Aborted{context}: sandbox state write was superseded: sandbox is {found}Refused { refusal: Quarantined, .. }Aborted{context}: sandbox state write was not applied: blocked_by_quarantineRefused { refusal: CleanupOwned, .. }Aborted{context}: sandbox state write was not applied: superseded_by_cleanup_episodeMissingAborted{context}: sandbox state write was not applied: missingInternal{context}: {error}The guard, missing and database-error rows are byte-for-byte what
require_state_writereturns forupdate_state, at 8f12bd6 and now.Dead-code expectations
Outside tests the new items are
#[cfg_attr(not(test), expect(dead_code, ...))]. Adoptingrequire_state_write_fromleaves several expectations unfulfilled at once: the function,StateWriteRejected,StateWriteGuard,state_write_resultand theimpl.unfulfilled_lint_expectationsis warn-by-default and an error only under CI's clippy-D warnings, so the adopting conversion must drop those attributes: #418, the first conversion above this PR, drops them.update_state_from_best_effortkeeps its own expectation until a caller uses it: #421 drops it.Deferred:
StateWriteGuardduplicatesStateWriteRefusal::{Quarantined, CleanupOwned}, and the legacySandboxStateWriteOutcomeis a third copy. The fix nests them:generic_state_write_refusal -> Option<StateWriteGuard>andStateWriteRefusal::Guard(StateWriteGuard). PR 6, which retires the legacy enum, does that, so this PR doesn't churn #388's types under the conversions stacked above it.Tests (
service/cleanup_support/tests.rs)StateWriteOutcome×StateWriteRefusal× state assert the typed rejection and the exact code and message. The refusal list itself comes from a wildcard-free successor match, so a new variant does not compile until it is swept.update_statecaller path return the same status, and every row stays unchanged. The rows are a quarantined row, astoppingrow revived tostarted, acleanup_requiredrow with adelete_rowintent, and a missing row. The row's own state is in the from-set, so the guard is what refuses. The messages are also checked against literals copied from 8f12bd6.stopped), a cleanup-owned row and a missing row;Noneand changes nothing on a database error.Noneand changes nothing. It also wakes the dirty-outbox worker, which exits on the poisoned connection and fails its critical-task guard. The test pauses the clock and only yields, so the worker's poll timer cannot fire and only the signal can wake it.Validation of the restacked stack
The stack was restacked linearly on main (
b18051f) after #372 and #388 merged: #438 → #418 → #413 → #416 → #421 → #417 → #414. The per-PR validation further down ran on this PR's pre-restack head.This PR's head (
bf43fd5): pull CI is green. fmt, workspace clippy--all-targets --all-features -D warningsandcargo arch(0 required findings) all pass.SPACE_CI_REQUIRE_TEST_DEPS=1 cargo nextest run --locked -p pplx-space-sbx-mgr --retries 0passed 1634/1634, with 0 dependency skips. Kind Pull CI ran on #417, which contains this PR: all 4 Firecracker smoke shards passed.The top of the stack (#414 @
9a0a251, which contains this PR):cargo test -p pplx-space-sbx-mgr(shared process)smoke-kind-e2e.shpassed.smoke-kind-typed.sh all: native 23/23; ECW 76 passed, 2 infra errors.local.ecw_template_build_from_oci_bootspassed, and so did the leader-election brownout.node_group_boundary_schedulesandnode_selector_extras_render_and_enforce. Both scenarios restart the API Deployment, and the same failure family appears on main in an A/B on the same cluster. In the failed runs the kill never reached sbx-mgr.sandboxes.state, control-plane Postgreslifecycleand the VM agree. No change-induced violation: across 880 sandboxes, no quarantine, panic, restart or stuck state. Every violation reproduces on main with the same signature: an ECW pause that loses to Stop answers 503 instead of 409; after Suspend vs Delete, the node row can outlive the control-plane row until the next eviction pass (pass timing, not the build); and a refused quarantine write after Pause vs Delete is still logged. The soak's 0–200 ms API-level offsets did not reproduce the interleavings this stack closes, so the unit-level race tests remain the direct evidence for those.Validation on the pre-restack head
cargo fmt --all -- --checkcargo clippy --workspace --locked --all-targets -- -D warningscargo archSPACE_CI_REQUIRE_TEST_DEPS=1 cargo nextest run -p pplx-space-sbx-mgr×3#[ignore]root-onlydistinct_service_uids_can_exchange_downloads; 0 dependency skipsfounddropped fromGuard, and each best-effort dirty, return and poison-signal arm)🤖 Generated with Claude Code