Skip to content

sbx-mgr: extract the SQLite store into pplx-space-sbx-mgr-store - #578

Merged
ac-pplx merged 1 commit into
ac/sbxmgr-store-2-handlesfrom
ac/sbxmgr-store-3-crate
Oct 19, 2026
Merged

ac-pplx merged 1 commit into
ac/sbxmgr-store-2-handlesfrom
ac/sbxmgr-store-3-crate

Conversation

@ac-pplx

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

Copy link
Copy Markdown
Contributor

Summary

Milestone 3 of 3, stacked on #570. This concerns sbx-mgr's node-local SQLite database (sandboxes and every other sbx-mgr table), not the control-plane Postgres. The store moves out of the server crate into its own crate, crates/libs/sbx/mgr/store (package pplx-space-sbx-mgr-store), as a pure move. The guarantee that "only that crate touches the database" is now a compile-time boundary. The cargo arch line ratchets remain as tripwires.

The crate

  • What moved: src/db.rs, src/db/** and the leaf type modules db/ decodes into: state, auto_lifecycle, auto_lifecycle_delete_receipts, endpoint_proof and restore_operation, with their tests. That is 72 files, plus a new lib.rs and db/migration.rs.
  • Same module paths: each module keeps its path inside the store (pplx_space_sbx_mgr_store::db, …::state, …), so the moved files' own crate::db::… and crate::state::… paths are unchanged. The server re-exports the six modules from lib.rs, so none of its crate::db::… callers changed.
  • Dependencies: the store depends on pplx-space-sbxmgr-migrations and owns rusqlite. It also depends on pplx-space-observability, with default-features = false as the server does, because db::cleanup_jobs::persist records the persisting caller's traceparent since main's Observability wave 7: warm-pool pass roots, JSON runtime-supervisor logs, sandbox events with their request trace #573. Feature unification gives it the server's otel/datadog configuration in every image build, so the recorded value is unchanged. crate_names passes: store is not a banned token.
  • Layering: ARCHITECTURE.toml has no crate-layering table, so none applies.
  • The server's manifest: it drops rusqlite, sqlx, ref-cast and pplx-space-sbxmgr-migrations, normal and dev, and its features = ["sqlite"] on pplx-space-common-svc. The server used none of them directly once the store left; sqlx and the sqlite feature were already unused. The dev-dependency is pplx-space-sbx-mgr-store with test-support. No rusqlite dev-dependency is needed.
  • Test fixtures: tests outside the store reach raw SQL through the test-support feature:
    • Connection::raw();
    • the statement methods and test-only constructors on db::Connection;
    • db::fixture (the params! re-export and the insert_test_sandbox{,_with_policy} seeds, moved from service/tests.rs);
    • db::test_fixtures (state_write_fixtures, database_rows and the quarantine-recovery seeds, moved out of db/tests.rs).
  • Workspace wiring:
    • Cargo.toml registers the member and workspace dependency.
    • ci/rust-artifact-inputs.json is regenerated: the store joins space-db-migrate's and the sbx-mgr images' inputs, and so do its dependencies common/jobs and events, which the migrator now builds through the store.
    • The server's AGENTS.md store rules move to store/AGENTS.md, which has a CLAUDE.md symlink.

The compile-time boundary (owner decisions)

Decision How it holds
1. No rusqlite-typed value reachable from server production code The server has no rusqlite dependency, so it cannot name rusqlite. Outside test-support, the store's public API returns, exposes and re-exports no rusqlite value. Connection::raw(), Error::raw() and the statement methods (execute, prepare, prepare_cached, query_row) are pub(in crate::db). db_private_outside_tests! makes them pub only with test-support.
1. SharedConnection::background_worker() Already builds common-jobs' BackgroundWorker inside the store, from the private Arc<Mutex<rusqlite::Connection>>, and returns only the worker; the Arc never leaves db/handle.rs. Kept as is.
1. Every pplx_space_common_jobs::db call goes through the store Server production code makes none; they are all in db::cleanup_jobs, db::teardown_* and db::auto_lifecycle_delete_receipts. Server tests that inspect the jobs table pass conn.raw() under test-support.
1. Connection::raw() behind test-support only Yes, as above. --all-features builds enable test-support (workspace clippy and nextest), so a new CI step, run_sbxmgr_production_checks (called from run_clippy_checks), lints the production graph without it. For each feature configuration sbx-mgr's images are built with, it runs cargo clippy --locked -p pplx-space-sbx-mgr-store -p pplx-space-sbx-mgr -p pplx-space-sbxmgr-svc -p pplx-space-db-migrate -p pplx-space-db-migrator [--features …] --lib --bins -- -D warnings. The configurations are: none; the release images' pplx-space-sbxmgr-svc/otel,datadog (docker/Dockerfile.rust-build-common); and the Kind images' set, which adds test-hooks (docker/Dockerfile.rust-build-kind). The migrator has no features. For each configuration it then asserts with cargo tree -e normal,features that pplx-space-sbx-mgr-store feature "test-support" is not in the graph. Adding conn.execute("SELECT 1", []) to server production code fails that step with E0624 (probe removed).
1. API-surface check See "API-surface audit" below. A committed check would need a nightly-only cargo-features manifest key, so it ran once, and the exact command is given for reviewers.
2. Manifest rule A new archcheck rule, dependency_fence, not a regex. It reads each member's dependency records from cargo metadata, which names the real package even under package = "…", and includes kind and target. Two required instances: sbxmgr_rusqlite (rusqlite, libsqlite3-sys; only the store may) and sbxmgr_sqlx (sqlx, sqlx-core, sqlx-sqlite; only the store and the migrations crate may). Every members and allow glob must match a workspace member, or archcheck exits 2. Both check crates/libs/sbx/mgr/** and crates/apps/sbx/**, and normal, dev, build and target-specific dependencies are all rejected. On this tree, adding [target.'cfg(unix)'.dev-dependencies] sqlite = { package = "rusqlite", … } to the server fails cargo arch with exit 1, reporting pplx-space-sbx-mgr depends on rusqlite (renamed sqlite, a dev-dependency, for target cfg(unix)).
3. Migrator crates/libs/db/migrator takes sbx-mgr's database name, default path and service migrator from pplx_space_sbx_mgr_store::db::migration::target(), through From<MigrationTarget> for SqliteDatabase. db::migrate_at uses the same target, so there is one definition. The shared db/common schema is not in the target: each runner applies it from its own embedded copy, as it does for every database with that schema. The migrator no longer names the migrations crate, SANDBOX_MANAGER_DB or a sqlite_sandbox_manager helper, and the sbxmgr_sqlite_schema baseline is empty. The regression case for those patterns landed in #561 (sbxmgr_sqlite_schema_matches_the_migrators_sandbox_manager_database).
4. PR 1 and PR 2 bodies Rewritten in this round; see the report.

Line ratchets:

  • sbxmgr_sqlite and sbxmgr_sqlite_schema now allow only crates/libs/sbx/mgr/store/src/**. The schema rule still also allows the migrations crate and the path's definition in common-core.
  • Every mode of both is required, and both baselines are empty.
  • Their ARCHITECTURE.toml comments call them tripwires on top of the crate boundary.

API-surface audit

Run against the store's production API (no test-support), with rusqlite as a private dependency:

# temporary, not committed: prepend `cargo-features = ["public-dependency"]` to
# crates/libs/sbx/mgr/store/Cargo.toml
RUSTC_BOOTSTRAP=1 CARGO_TARGET_DIR=/tmp/pubdep cargo check -Zpublic-dependency \
  -p pplx-space-sbx-mgr-store --lib --message-format json

rustc's exported_private_dependencies reports every rusqlite type in the store's public interface. There are exactly three sites, all trait impls:

  • db::Error's #[from] rusqlite::Error (the ? conversion everywhere in the store): inbound only.
  • From<rusqlite::Error> for db::inventory_read::InventoryReadError: inbound only.
  • impl FromSql for db::timestamps::EpochSeconds, so rows decode into it. Its column_result takes a rusqlite ValueRef and returns rusqlite's FromSqlResult. So a caller holding a ValueRef would get a rusqlite FromSqlError back: an outbound rusqlite error type, but no database capability. The server cannot obtain a ValueRef without a rusqlite dependency.

No public function, field, associated type or re-export returns a rusqlite connection, transaction, statement or row. Removing the three impls would mean replacing hundreds of ? conversions inside the store, which is not a move.

What the boundary does not cover (milestone 4 closes the first item):

  • common-svc's whole sql::sqlite module stays reachable from the server. The store enables common-svc's sqlite feature, and Cargo feature unification turns it on for every crate in the build that depends on common-svc, the server included, whether or not the server asks for it (it no longer does). That leaves these reachable:
    • open_rusqlite, which returns a raw rusqlite::Connection;
    • migrate_embedded and migrate;
    • remove_database_files and restrict_database_permissions.
      The path-taking functions can act on sbx-mgr's database without the server naming any rusqlite type. The server calls none of them today, and their qualified path is an sbxmgr_sqlite tripwire (sql::sqlite) that aliases escape.
  • Non-Rust operator scripts still name sbx-manager.db, as sbx-mgr: route every SQLite statement through db/ and fence the rest with cargo arch #561 lists. archcheck does not read them.

Pure-move proof

The proof compares each moved file at #570's head (bc51f2f, rebased onto main 825b786) with its new path under the store. After the rebase it has one more file, main's new db/tests/stopped_artifact_pin.rs (#551), which moves with the store unchanged. The union of differences is exactly the one the original base gave. main's other changes under db/ moved with the store and contain no normalized difference: the retired-teardown claim, the job traceparent, row_to_local_artifact's visibility, the stopped-pin writers and fixtures, the eviction-claim doc and the quarantine-recovery backoff test. #551's new fixture begin_cold_start is pub, like the other moved fixtures. For every file it takes the multiset of non-blank code lines (archcheck's code lines: not starting with //), after these transformations on both sides:

  1. Visibility keywords removed: pub, pub(crate), pub(super) and pub(in crate::db). Items the server uses became pub; fixture items became pub under test-support.
  2. cfg widening: #[cfg(any(test, feature = "test-support"))] reads as #[cfg(test)], and not(any(…)) as not(test). This covers the test fixtures, test-only constructors and db_private_outside_tests!.
  3. Path rewrites (the first one lengthens a line in state/tests.rs, which rustfmt splits in two):
    • crate::proto → pplx_space_sbx_mgr_client::proto (in state.rs, auto_lifecycle.rs and their tests);
    • crate::service::tests::service_tests::insert_test_sandbox → crate::db::fixture::insert_test_sandbox (seven db test files);
    • crate::db::tests:: → crate::db::test_fixtures:: in server tests.
  4. File renames: db/tests/state_write_fixtures.rs → db/test_fixtures/state_write_fixtures.rs.

Result:

  • Line multisets: 60 of 72 files are identical. The other 12 differ only by rustfmt reflow, where a signature collapses onto one line once pub(crate) shortens to pub, plus the listed additions below.
  • Statement fragments: a second, reflow-insensitive comparison joins each file's normalized code lines without whitespace and drops a trailing comma before a closer, then splits at ;/{/}. On those fragments 68 of 72 files are identical.
  • Union: across all moved files, the new files and the fixtures moved from service/tests.rs, the remaining differences are exactly:
    • Added: 9 #[allow(clippy::unwrap_used | expect_used | indexing_slicing | panic)] attributes on fixtures, which now also compile outside cfg(test) under test-support.
    • Added: lib.rs (crate docs, forbid(unsafe_code), six pub mods) and db/migration.rs (MigrationTarget, target()), plus pub mod migration; and pub mod test_fixtures; in db.rs.
    • Changed: db::migrate_at's body takes its name and service migrator from migration::target() instead of naming them. It still names COMMON_SCHEMA_MIGRATOR, and calls migrate_embedded with the same arguments.
    • Moved: insert_test_sandbox{,_with_policy} from service/tests.rs into db::fixture, with the four use lines they need. service/tests.rs re-exports them, so the server's call sites are unchanged.
    • Changed (review round 1): db_module_files() in db/tests.rs also skips test_* directories, so the source-shape guards don't scan the test-support fixtures as production source.
    • Moved: TestSandboxInsert, database_rows and the four quarantine-recovery seeds from db/tests.rs into db/test_fixtures.rs. db/tests.rs re-imports them (one use line), and test_fixtures.rs has use super::*;.
    • Removed: one test assertion in db/tests.rs (mark_start_succeeded_requires_starting_state), assert!(crate::snapshot_encryption::capture_volume_is_fresh(&started), …). It called server code. It was the only test of that predicate's true result, so the server gains service_tests::a_successful_cold_start_captures_a_fresh_memory_volume, beside the existing false-case assertion. That test seeds a Starting row with a full/suspend artifact through the store's test-support fixtures, checks the predicate is false before, runs mark_start_succeeded and get_sandbox, and asserts it is true. An always-false mutant of capture_volume_is_fresh fails it, while the existing test still passes.
    • Comments: new doc comments on the added items, and the reworded doc of db_private_outside_tests!.
  • Server code changes: apart from the moved fixtures and test path rewrites, the only change to server code is service/cleanup_executor.rs. Its inherent impl CleanupOutcome { fn into_caller_result } cannot cross the crate boundary, so it becomes a local fn caller_result(outcome) with the same match.

Hidden observable changes

  • Log targets (default tracing targets follow module_path!()): 26 events in 12 modules move from pplx_space_sbx_mgr::db::<m> to pplx_space_sbx_mgr_store::db::<m>, for <m> in auto_lifecycle (2), cleanup_episodes (4), inventory (3), inventory_read (1), quarantine (1), quarantine_recovery (2), restore_transfer_scopes (1), sandbox_row (3), sandboxes (3), snapshot_gc (2), startup_recovery (2) and state_write (2).
    • No event sets an explicit target, and the leaf modules emit none.
    • An EnvFilter directive of pplx_space_sbx_mgr still matches, because targets match by prefix; one of pplx_space_sbx_mgr::db no longer does.
    • No deployed configuration in this repository names either.
  • sql! locators (/*module_path!():line!()*/ in the SQL text, visible in SQLite traces): all 24 change prefix from pplx_space_sbx_mgr::db:: to pplx_space_sbx_mgr_store::db::.
    • 11 also shift line, because rustfmt collapsed signatures above them:
      • auto_lifecycle 164→160, 184→180;
      • cleanup_episodes 1794→1791, 1812→1809, 1913→1910, 1926→1923;
      • cleanup_jobs 366→353;
      • local_artifact_leases 326→317;
      • sandboxes 1167→1161, 1265→1259, 1272→1266.
    • The other 13 keep their lines. No SQL text changed otherwise.
  • file!()-derived values: panic locations of expect/unwrap in moved code now print crates/libs/sbx/mgr/store/src/… instead of crates/libs/sbx/mgr/server/src/…. Nothing else in the moved code uses file!(), line!() (beyond sql!) or #[track_caller].
  • Error texts: unchanged. db::Error/db::OpenError and every store error enum keep their Display.
  • Migrator build graph: space-db-migrate now builds the store crate and its dependencies. Its build.rs no longer watches db/sandbox-manager, since the migrations crate's build.rs does and the migrator no longer embeds them itself.

Counts

#570 head this PR
SQL statement lines outside the store (sbx-mgr production) 0 (outside db/) 0 (outside the store crate)
rusqlite mentions outside the store, sbx-mgr crates (production / tests) 0 / 0 outside db/ 0 / 0 outside the store crate
rusqlite in the server's manifest [dependencies] + [dev-dependencies] none
[pattern_ratchet.sbxmgr_sqlite] baseline 0 entries 0 entries
[pattern_ratchet.sbxmgr_sqlite_schema] baseline 1 entry, 5 lines (migrator) 0 entries
dependency_fence findings — 0 (two required instances)
Tests (#[test]/#[tokio::test] attributes) server 1729 server 1363 + store 366 = 1729 moved, plus 1 new server test (1364); migrator 53. nextest lists 1732 → 1367 + 366 (+1, the new test); nextest lists 3 more server tests than the attribute count, before and after.

file_size:

  • Moved files carry their caps under the new paths: db/auto_lifecycle_delete_receipts.rs, db/cleanup_episodes.rs, db/quarantine_recovery.rs, db/sandboxes.rs and db/state_transitions.rs. no_new_entries (advisory) reports the five renames for review.
  • Lowered where rustfmt shrank a file: db/auto_lifecycle_delete_receipts.rs 1149→1147, db/cleanup_episodes.rs 1833→1830, db/sandboxes.rs 1541→1535.
  • No cap raised.

Decisions

  • Module paths kept inside the store, and re-exported by the server. This keeps the move pure: no crate:: rewrites inside moved files. It also leaves the server's ~730 crate::db::… call sites alone.
  • auto_lifecycle moved whole. db/ calls its runtime-state functions. Splitting off its coordinator (which db/ does not use) would be a non-move refactor. It brings tonic (for Status in extend_access_deadline) and pplx-space-sbx-mgr-client (for proto conversions) into the store.
  • One cfg-visibility macro instead of a raw-only test API. db_private_outside_tests! declares each method twice, once per cfg, so the server's tests keep calling conn.execute(..). Under test-support the statement methods expose nothing beyond raw(), which is public there anyway.
  • A cargo-metadata rule for the manifest, not a regex. The owner ruled out more regex hardening. Cargo's own dependency records make renames, [target.*] and every dependency kind exact, so there is nothing to harden.
  • sqlx is fenced too. sqlx's SQLite driver can open the database as well, and the server's sqlx dependency was unused, so it is removed and fenced.
  • The migrator depends on the store, not a smaller crate. The owner asked for the store's entry point. migration::target() is a const fn over the migrations crate's statics, so the migrator's ALL_MIGRATORS test table can use it.
  • No new behavior tests. This is a move; existing coverage runs in its new location. The new tests are archcheck's dependency_fence rule cases, which the task allows.
  • Rebased with the stack onto main 825b786. Conflicts: Cargo.lock (regenerated); db/tests.rs, where main added quarantine_recovery_no_progress_schedules_the_capped_doubling_backoff beside the fixtures this PR moves to db::test_fixtures (the test stays in the store's db/tests.rs, the fixtures stay moved); db/tests/stopped_artifact_pin.rs, added by main in the directory this PR moves, placed under the store; the file_size baseline; and the archcheck README's exit-2 paragraph (archcheck: accept a renamed baseline entry instead of reporting it as new #596's --renames errors kept). ci/rust-artifact-inputs.json regenerates to the same content.
  • Rebased with the stack onto main c4be884. The only conflict was in architecture-baseline.toml. main's Extract guest execution service and guest protocol crates #16 deleted the spaced/guest/server filesystem/router.rs and process.rs caps right next to where this PR adds the five store caps. Both sides are kept: the store caps are added, and the two spaced caps stay deleted. No cap changes, because main changed no file under sbx-mgr. This PR's additions to ci/rust-artifact-inputs.json are the same ones regeneration adds on top of main.

Review round 1

Finding Action
4 [MAJOR] no pre-merge CI job compiles the server without test-support New step in run_clippy_checks: no-feature --lib --bins clippy over the store, pplx-space-sbx-mgr, pplx-space-sbxmgr-svc, pplx-space-db-migrate and pplx-space-db-migrator, plus a cargo tree -e normal,features assertion that the store's test-support is not in that production graph. With dev edges included, the same cargo tree does show the feature, so the assertion can fire. A probe calling Connection::execute from server production code fails the step (E0624); the probe is removed.
5 [MAJOR/MINOR] server enables common-svc's sqlite but doesn't use it Dropped. The residual paragraph now states the real cause, feature unification through the store, and lists the whole reachable sql::sqlite surface; milestone 4 closes it.
6 [MINOR] the dropped assertion was the only true-result test of capture_volume_is_fresh New server test a_successful_cold_start_captures_a_fresh_memory_volume, beside the existing false-case assertion. An always-false mutant fails it.
7 db_module_files() Also skips test_* directories.
7 dependency_fence Added the combined renamed + [target.'cfg(unix)'.dev-dependencies] case. Every members/allow glob must now match a workspace member (exit 2 otherwise), tested for both keys; the repository's real globs all match.
7 sbxmgr_sqlx Now also lists sqlx-core and sqlx-sqlite.
7 migrator target.common Dropped the field: the migrator never used it, and each runner applies db/common from its own copy, as documented on MigrationTarget. migrate_at names COMMON_SCHEMA_MIGRATOR as before.
7 body sql! locators corrected (11 shifted, 13 unchanged). Also: the state/tests.rs line split, the common-svc cause, the FromSql::column_result outbound FromSqlError (an error type, no database capability), test counts by attribute (1664 = 1310 + 354), and --all-features vs release/Kind builds.

Rebased onto #570's round-2 head 20e4085, without conflicts.

Rebased with the stack onto main 825b786; see Decisions.

Review round 2 (final)

Finding Action
1 [MAJOR] release feature sets weren't compiled without test-support The production step loops over three configurations: none, the release images' pplx-space-sbxmgr-svc/otel,pplx-space-sbxmgr-svc/datadog, and the Kind images' set with test-hooks. Each gets clippy (--lib --bins, no dev targets) and the cargo tree test-support assertion. Probe: a #[cfg(feature = "otel")] function in server production code calling conn.raw(). The no-feature configuration passes, because the probe is compiled out. The release configuration fails with error[E0624]: method \raw` is privateatwatchdog.rs:3125`. Probe removed.
2 [NIT] "inbound only" wording The store's crate docs, ARCHITECTURE.toml, the store's AGENTS.md and the commit message now say it exactly. Outside test-support, no public item returns a rusqlite connection, transaction, statement or row. The remaining rusqlite types are trait impls: From<rusqlite::Error> (inbound), and FromSql, whose column_result returns rusqlite's FromSqlError outbound, an error type, not a database capability.

Validation

Check Result
bash ci/scripts/run-rust-checks.sh fmt (includes the Rust error policy) pass
bash ci/scripts/run-rust-checks.sh clippy (CI's clippy mode: the workspace --all-features pass, the existing feature-isolation passes, and the new sbx-mgr production step with its cargo tree assertion) pass
Probes through the production step: conn.execute("SELECT 1", []) in server production code (round 1), and #[cfg(feature = "otel")] + conn.raw() (round 2) both fail with E0624, the second only in the release configuration (probes removed)
cargo arch --base-baseline <main> --base-architecture <main> 0 required findings (95 advisory on 0cd506b). The advisory ones are pre-existing file_size drift, plus no_new_entries for the five moved files' caps under their new paths.
cargo test --locked -p pplx-space-archcheck pass: 48 CLI tests, including dependency_fence with the combined renamed and target-specific dev-dependency case and the unmatched-glob errors, and 5 repository tests
SPACE_CI_REQUIRE_TEST_DEPS=1 cargo nextest run --locked -p pplx-space-sbx-mgr -p pplx-space-sbx-mgr-store -p pplx-space-db-migrate --retries 0 On the rebased head 92f0dc5: 1785 passed (server 1366, store 366, migrator 53), 0 failed. Before the rebase, rounds 1 and 2: 1716 passed, 0 failed, each time (1715 plus the new server test); the original head ran it three times: 1715 passed, 0 failed, each run. The 1 skip is the root-only #[ignore] transfer_directory::tests::distinct_service_uids_can_exchange_downloads, not a dependency skip.
API-surface audit (exported_private_dependencies, above), re-run on the rebased head 3 inbound trait impls; no outbound rusqlite value
Negative check: a renamed, target-specific rusqlite dev-dependency on the server cargo arch exit 1 with dependency_fence.sbxmgr_rusqlite.no_crossing (change reverted)

After the c4be884 rebase, fmt, clippy, cargo arch against main and the archcheck tests re-ran on every head of the stack and passed. Nextest ran once, on #586's head d9f6b99: SPACE_CI_REQUIRE_TEST_DEPS=1 cargo nextest run --locked --all-features -p pplx-space-sbx-mgr -p pplx-space-sbx-mgr-store -p pplx-space-db-migrate -p pplx-space-sqlite -p pplx-space-db-test-fixtures --retries 0 gave 1808 passed, 0 failed (server 1367, store 366, migrator 53, sqlite 12, test fixtures 10). The 1 skip is the root-only #[ignore] transfer_directory UID test. Kind was not re-run, because the rebase changed no code.

Kind ran on #586's head, which contains this commit: on the final rebase (1fbf246) and on the previous one (6ceee0c), each on a fresh cluster. Both runs passed smoke-kind-e2e.sh and smoke-kind-typed.sh all (ECW 80 passed, native 25 passed, 0 failed, 0 inconclusive), and the deployed sbx-mgr binary was proven to be from that head. See #586 for the full table.

🤖 Generated with Claude Code

@ac-pplx
ac-pplx force-pushed the ac/sbxmgr-store-3-crate branch from 70444b9 to 6984b02 Compare October 16, 2026 17:08
@ac-pplx
ac-pplx force-pushed the ac/sbxmgr-store-3-crate branch 2 times, most recently from 3e795d5 to 51364ca Compare October 16, 2026 18:59
@ac-pplx
ac-pplx marked this pull request as ready for review October 16, 2026 19:18
@ac-pplx
ac-pplx added this pull request to stack #576 October 16, 2026 20:58
@the-gigi-pplx

Copy link
Copy Markdown
Collaborator

Review — 3 findings

No correctness findings in the moved store code. I checked that no #[cfg(test)] behavior hook the server's tests relied on was left behind in the store, that no stale server/src/db paths remain, and that the migrations crate's build.rs still watches db/sandbox-manager, so dropping that watch from the migrator loses nothing. I also compared the new production step's Kind feature set with docker/Dockerfile.rust-build-kind. The Dockerfile enables pplx-space-sbx-mgr/test-hooks directly, and the CI copy reaches it through pplx-space-sbxmgr-svc/test-hooks = ["pplx-space-sbx-mgr/test-hooks"], so the two are equivalent today.

Low

  1. ci/scripts/run-rust-checks.sh:254: the production step's feature sets and package list are hand-copied.
    The three configurations are copies of the --features strings in docker/Dockerfile.rust-build-common and docker/Dockerfile.rust-build-kind, and the -p list names four consumers of the store. Nothing ties either to its source. If a Dockerfile gains a new server feature, or a new crate takes a normal dependency on the store, the step keeps linting the old shape. Meanwhile the --all-features workspace pass turns test-support on and hides the gated methods. The round-2 otel probe showed this class of miss is real. Derive the consumers from cargo tree --workspace -e normal -i pplx-space-sbx-mgr-store, or at least add a comment at each Dockerfile --features line pointing back here.
  2. crates/tools/archcheck/src/context.rs:469: dependency_line cites the first manifest line naming the key, whatever its section.
    If a manifest declares rusqlite under both [dependencies] and [target.'cfg(unix)'.dev-dependencies], both findings point at the [dependencies] line. The message for the second one says "a dev-dependency, for target cfg(unix)", and removing the cited line leaves that crossing in place at the same reported location. Matching the section header against the record's kind and target would fix the location.
  3. crates/tools/archcheck/src/context.rs:211: allow globs are checked against every workspace member, not against the instance's members.
    Suppose the store moves and allow is updated to a path outside members. The glob still matches some workspace member, so loading succeeds, the moved crate isn't fenced, and the exit-2 typo check this round added never fires. Requiring each allow glob to match a member that members also matches closes it.

Verified against 51364ca5c7. Reproduced locally: I read the fence loader and dependency_line at head, compared the CI feature sets with both Dockerfiles and the svc crate's test-hooks definition, and compared the rust_static / Rust clippy durations on this PR and on PR #561 (about 3.5 minutes each), so the three extra production builds don't measurably lengthen the required check. I didn't rerun the test suite; CI is green on this head.

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

@ac-pplx
ac-pplx force-pushed the ac/sbxmgr-store-3-crate branch from 51364ca to 92f0dc5 Compare October 19, 2026 21:28
sbx-mgr's node-local SQLite store (`sandboxes` and every other sbx-mgr
table) moves out of the server crate into `crates/libs/sbx/mgr/store`,
package `pplx-space-sbx-mgr-store`, as a pure move: `src/db` and the leaf
type modules it decodes into (`state`, `auto_lifecycle`,
`auto_lifecycle_delete_receipts`, `endpoint_proof`, `restore_operation`)
keep their module paths inside the new crate, and the server re-exports
them, so `crate::db::...` callers are unchanged. Only visibility changes,
`cfg(test)` widening to the store's `test-support` feature, and path
rewrites (`crate::proto` to the client crate's) touch the moved lines;
the fixtures the server's tests share move into `db::fixture` and
`db::test_fixtures`, and one store test assertion that called server code
is replaced by a server test of that predicate.

The boundary is now the compiler's:

- The server has no rusqlite or sqlx dependency, normal or dev, and no
  longer asks for common-svc's `sqlite` feature.
- Outside `test-support`, no public store item returns, exposes or
  re-exports a rusqlite connection, transaction, statement or row. The
  raw connection, error and statement methods are private to `db` unless
  the feature is on. What remains are trait impls: `From<rusqlite::Error>`
  into the store's errors, and `FromSql` for a timestamp column, whose
  `column_result` returns rusqlite's `FromSqlError` outbound, an error
  type rather than a database capability.
- `--all-features` builds turn `test-support` on, so CI's clippy step now
  also lints sbx-mgr's production libraries and binaries in each feature
  configuration their images are built with (none, release, Kind), without
  dev targets, and asserts that `test-support` is not in those dependency
  graphs.
- A new archcheck rule, `dependency_fence`, reads dependency records from
  `cargo metadata`, so a renamed, target-specific or dev dependency on
  rusqlite or sqlx (or sqlx's core and SQLite crates) in any sbx-mgr
  crate other than the store (and, for sqlx, the migrations crate) fails
  `cargo arch`. Every fence glob must match a workspace member.

The database migrator takes sbx-mgr's database name, path and migrator from
the store's `db::migration::target()`, which `db::migrate_at` uses too,
so the `sbxmgr_sqlite_schema` baseline is empty. Both line ratchets now
allow only the store crate, with every mode required.
@ac-pplx
ac-pplx force-pushed the ac/sbxmgr-store-3-crate branch from 92f0dc5 to 0cd506b Compare October 19, 2026 22:14
@ac-pplx
ac-pplx added this pull request to the merge queue Oct 19, 2026
Merged via the queue into main with commit d462d29 Oct 19, 2026
25 of 38 checks passed
@ac-pplx
ac-pplx deleted the ac/sbxmgr-store-3-crate branch October 19, 2026 23:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants