Skip to content

Move the shared SQLite helpers out of common-svc so only sbx-mgr's store can reach them - #586

Merged
ac-pplx merged 1 commit into
ac/sbxmgr-store-3-cratefrom
ac/sbxmgr-store-4-sqlite-crate
Oct 19, 2026
Merged

ac-pplx merged 1 commit into
ac/sbxmgr-store-3-cratefrom
ac/sbxmgr-store-4-sqlite-crate

Conversation

@ac-pplx

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

Copy link
Copy Markdown
Contributor

Summary

Milestone 4, stacked on #578. This concerns sbx-mgr's node-local SQLite database, not the control-plane Postgres.

After #578, the one remaining way for sbx-mgr's server to reach its database outside the store was common-svc's sql::sqlite module. Cargo feature unification exposes it to every crate that depends on common-svc as soon as any crate in the build enables common-svc's sqlite feature, and the store did. Its path-taking functions (open_rusqlite, migrate_embedded, migrate, remove_database_files, restrict_database_permissions) act on any SQLite database by path without naming a rusqlite type.

This PR moves those helpers into their own crate, crates/libs/sqlite (package pplx-space-sqlite). It removes the sqlite feature from common-svc and fences the new crate with dependency_fence. Only sbx-mgr's store, among sbx-mgr's crates, can depend on it, and a crate can only name what it depends on.

What moved (pure move)

From (common-svc) To (pplx-space-sqlite)
src/sql/sqlite.rs src/lib.rs
src/sql/sqlite/runtime_manager.rs src/runtime_manager.rs
src/sql/sqlite/tests.rs src/tests.rs
src/sql/baselines.rs (only PUBLIC_SQLITE_BASELINE, already gated on sqlite) src/baselines.rs
  • sql::migration_checksums stays in common-svc. It opens nothing: it validates sqlx migrators against an existing Migrate connection, and the migrator's Postgres retry uses it too. It moves from the sqlite feature to a narrower migration-checksums feature (dep:toml, sqlx/migrate), which the new crate and the migrator enable.
  • The sqlite feature is gone from common-svc, and so is its rusqlite dependency. Its accepted-checksums tests migrate in-memory SQLite, so they get sqlx/sqlite-bundled as a dev-dependency.
  • crate_names passes: pplx, space and sqlite are not banned tokens.

Callers:

  • Switched to the new crate: the store, node-ctl, node-mgr, runtime-manager, template-mgr, vol-mgr and the database migrator. Each drops common-svc's sqlite feature and depends on pplx-space-sqlite; the migrator also enables migration-checksums.
  • build-mgr: it only asked for the feature on a dev-dependency and never used the module, so it just drops the feature.
  • The rewrites:
    • pplx_space_common_svc::sql::sqlite → pplx_space_sqlite;
    • pplx_space_common_svc::sql::baselines → pplx_space_sqlite::baselines;
    • the migrator's use …::sql::sqlite::{self, SqliteError} → use pplx_space_sqlite::{self as sqlite, SqliteError}, so its sqlite::… call sites are unchanged;
    • one grouped import in the store is split in two.
      rustfmt rejoined a few calls onto one line where the shorter path now fits, dropping their trailing commas. It also reordered use lines in three files: the new crate's lib.rs, the store's db.rs and vol-mgr's run.rs. No other code changed.

Fence: dependency_fence.sbxmgr_rusqlite now lists pplx-space-sqlite beside rusqlite and libsqlite3-sys; only crates/libs/sbx/mgr/store may depend on it. The server already had no direct dependency on it, so it cannot name it.

Transitive reach: dependency_fence reads only sbx-mgr's own manifests, so CI's run_sbxmgr_production_checks adds sbxmgr_assert_direct_dependents. For each feature configuration sbx-mgr's images are built with (none, the release set and the Kind set), it runs cargo tree --locked -p pplx-space-sbx-mgr -p pplx-space-sbxmgr-svc -e normal -i <package> --depth 1 and fails on any direct dependent outside an explicit list:

  • pplx-space-sqlite: only pplx-space-sbx-mgr-store;
  • rusqlite: only pplx-space-sbx-mgr-store, pplx-space-sqlite, pplx-space-common-jobs and pplx-space-common-core.

common-jobs' functions take a connection in; common-core only implements rusqlite's value-conversion traits.

Isolated builds: run_clippy_checks also lints pplx-space-sqlite alone (--lib) and common-svc with only migration-checksums. Built alone like that, CI catches a feature that only unification supplied, as sqlx's macros was here.

Pure-move proof

Per moved file, the multiset of non-blank code lines at #578's head (92f0dc5, rebased onto main 825b786), compared with the new file, after these path rewrites:

  • super::migration_checksums::AcceptedChecksums → pplx_space_common_svc::sql::migration_checksums::AcceptedChecksums;
  • super::super::baselines:: → crate::baselines:: (once in runtime_manager.rs, three times in tests.rs);
  • relative paths one or more directories shorter:
    • sqlx::migrate!("../../../../db/runtime-manager") → ("../../../db/runtime-manager");
    • include_str!("../../../../../../../db/runtime-manager/history/prototype-baseline.sql") → ("../../../../db/…");
    • CARGO_MANIFEST_DIR + "../../../../db/node-manager" → "../../../db/node-manager" (test).
File Result
runtime_manager.rs identical
tests.rs identical
lib.rs identical except one added line, pub mod baselines; (plus the new crate doc comment)
baselines.rs identical except the removed #[cfg(feature = "sqlite")], which no longer applies

Hidden observable changes

  • Tracing and OTel: the sqlite.migrate span's metadata changes. OTel exports these fields as code.namespace, code.filepath and code.lineno:

    • module path: pplx_space_common_svc::sql::sqlite → pplx_space_sqlite;
    • file: crates/libs/common/svc/src/sql/sqlite.rs → crates/libs/sqlite/src/lib.rs;
    • line: 350 → 360.

    The query-trace event keeps its explicit rusqlite::query target, and no other event is in the moved code. No configuration in this repository filters on these targets.

  • sql! locators in the moved code:

    • production: lib.rs:252, /*pplx_space_sqlite:252*/ (was /*pplx_space_common_svc::sql::sqlite:242*/);
    • test-only: lib.rs:469 (under #[cfg(test)], was 459), and the four in tests.rs (lines 172, 277, 284, 355, unchanged), whose module path becomes pplx_space_sqlite::tests.

    Their SQL text is unchanged.

  • sql! line shifts in callers (module paths unchanged), caused by removed lines above them:

    • common-svc sql/mod.rs: 188→185 and 264→261;
    • runtime-manager installs/store.rs: 175→172, 225→222 and 248→245.
  • file!() panic locations in the moved code now print crates/libs/sqlite/src/….

  • Messages: every SqliteError message and every RunSbxMgrError/RunDbMigrateError text is unchanged. No other behavior changed in any service.

Audit of the server's dependency graph

The question for each crate: does any public API in pplx-space-sbx-mgr's normal dependency graph return a rusqlite connection, transaction or statement, or open, migrate or delete a database by path?

Method:

  • cargo tree -p pplx-space-sbx-mgr -e normal -i <crate> --depth 1 gives the direct dependents of rusqlite, libsqlite3-sys, sqlx-sqlite, sqlx and pplx-space-sqlite in that graph.
  • For the ones the server can name, I grepped the public API for connection-yielding functions (Connection::open*, open_in_memory, Sqlite{Pool,Connection,ConnectOptions}) and for path-taking or deleting operations.
Crate in the server's graph Server can name it? SQLite surface Disposition
pplx-space-sqlite (new) no (only via the store) open_rusqlite, migrate_embedded, migrate, remove_database_files, restrict_database_permissions, SqliteU64, runtime_manager::catalog Closed: own crate, dependency_fence admits only the store.
common-svc yes No SQLite surface after this PR. sql::migration_checksums (on through unification) has from_toml, embedded, validate, accepts and run<C: Migrate>(…, conn: &mut C). Leave: nothing opens or takes a path. run needs an existing sqlx Migrate connection. The server has no sqlx dependency, and common-svc hands out only Postgres pools (another database).
common-jobs yes db::* functions and BackgroundWorker::new take a &rusqlite::Connection / Arc<Mutex<rusqlite::Connection>>. Leave: inbound only; the server cannot obtain a connection. try_prune_terminal_jobs_once's rusqlite error was closed in #570. No function opens by path.
common-core yes types::sqlite ToSql/FromSql impls. host::linux::paths::SANDBOX_MANAGER_DB, the database path as a &str. Leave, justified: the trait impls need rusqlite to use. The path constant is data without a SQLite library to act on it. Naming it, or sbx-manager.db, outside the store is an sbxmgr_sqlite_schema tripwire.
the store yes db::open, db::migrate, db::migrate_at(path), db::migration::target() By design: the store's own entry points. run.rs opens and migrates through them, and the migrator takes its target.
sbxmgr-migrations, sqlx, rusqlite, libsqlite3-sys no — Transitive only; the server has no dependency to name them. dependency_fence keeps it that way.
generic filesystem crates (std::fs, tokio::fs, rustix, nix, tempfile) yes can remove or overwrite any file, the database file included Leave, justified: not database APIs; sbx-mgr needs them for its own files. A compile-time fence cannot single out one path. Deleting the file is a filesystem operation the type system does not model; the schema tripwire covers the path's name.

Result: after this PR no crate the server can name returns a rusqlite connection, transaction or statement, or opens or migrates a SQLite database by path, other than the store.

Review round 1 (final)

Finding Action
3 [MINOR] no check guards transitive reach to pplx-space-sqlite sbxmgr_assert_direct_dependents asserts the direct dependents of pplx-space-sqlite (store only) and of rusqlite (store, pplx-space-sqlite, common-jobs, common-core). It runs over the server's and service's normal graph in each of the three feature configurations. Probes: giving pplx-space-events, a non-sbx-mgr crate in that graph, a dependency on pplx-space-sqlite fails with "pplx-space-sqlite enters sbx-mgr's production graph (no features) through pplx-space-events; only pplx-space-sbx-mgr-store may depend on it". Giving it rusqlite fails with "rusqlite enters … through pplx-space-events; only pplx-space-sbx-mgr-store pplx-space-sqlite pplx-space-common-jobs pplx-space-common-core may depend on it". Probes removed, lockfile restored.
4 [NIT] observable changes incomplete Listed above and in the commit body: the span's module path, file and line (OTel code.*); production vs test-only locators; the five caller line shifts.
5 [NIT] use reorders Added: lib.rs, the store's db.rs and vol-mgr's run.rs.
6 [NIT] stale prose ARCHITECTURE.toml's sbxmgr_sqlite comment now names the fence and the graph assertion, and says the sql::sqlite pattern matches nothing since the move. The store's AGENTS.md and the archcheck README (its example now lists pplx-space-sqlite, plus a note that fences are direct-only and what covers the transitive side) are updated too.
7 isolated builds Two new clippy commands in run_clippy_checks: -p pplx-space-sqlite --lib, and -p pplx-space-common-svc --no-default-features --features migration-checksums.

Rebased onto #578's final head 51364ca.

Rebased with the stack onto main 825b786, without conflicts. main's common-svc changes since the original base (#567's bootstrap.rs split, the routability and residency SQL, template_lookup) touch none of the moved files or the sqlite feature, and no new caller names sql::sqlite. The pure-move result above and every locator in "Hidden observable changes" are re-checked on the rebased heads and unchanged.

Rebased with the stack onto main c4be884, without conflicts. The caps are unchanged. One advisory file_size finding comes from this PR itself and predates the rebase: the import split grows the store's db/auto_lifecycle_delete_receipts.rs from 1147 to 1148 code lines, and its cap is not raised.

Decisions

  • migration_checksums stays in common-svc. It opens no database and takes no path. The migrator's Postgres retry needs it as much as the SQLite helpers do. A narrower migration-checksums feature keeps its toml/sqlx/migrate dependencies opt-in, as sqlite did.
  • PUBLIC_SQLITE_BASELINE moves with the helpers. It was already sqlite-gated and names the first supported SQLite migration epoch.
  • The sbxmgr_sqlite sql::sqlite pattern is left in place. It no longer matches any helper, but per the owner decision the regexes are not touched. The fence on pplx-space-sqlite replaces it.
  • The new crate's sqlx features are explicit (derive, macros, migrate, runtime-tokio, sqlite-bundled, tls-rustls-aws-lc-rs). In common-svc, migrate! compiled only because other crates' features were unified in. A lib-only cargo check -p pplx-space-sqlite showed macros was missing.
  • No new tests. This is a move: the moved tests run in the new crate, and every caller's tests run unchanged.
  • No behavior change in any service, so this did not hit the stop condition. The changes visible from outside are the tracing target and sql! locator renames listed above.

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: the workspace --all-features pass, the feature-isolation passes, the new isolated pplx-space-sqlite and common-svc --features migration-checksums builds, and the sbx-mgr production step. That step runs clippy plus the test-support, pplx-space-sqlite and rusqlite graph assertions, once per feature configuration (none, release, Kind). pass (initial run and the final round)
Lib-only cargo check -p <crate> --lib without features, each separately: common-svc, pplx-space-sqlite, store, server, migrator, node-ctl, node-mgr, runtime-manager, template-mgr, vol-mgr, build-mgr, common-jobs pass
cargo arch --base-baseline <main> --base-architecture <main> 0 required findings (96 advisory on d9f6b99: pre-existing file_size drift and #578's moved-file entries)
cargo test --locked -p pplx-space-archcheck pass (48 CLI, 5 repository)
SPACE_CI_REQUIRE_TEST_DEPS=1 cargo nextest run --locked over all 12 affected packages, as CI splits it: -E 'not group(privileged)' as the user On the final head 1fbf246: 3251 passed, 0 failed. On the previous rebase (6ceee0c, patch-identical, one main commit set earlier), three runs from one build: 3250 passed, 0 failed, each run. Before the rebase: 3101 passed, 0 failed (initial run and the final round)
… the privileged group as root, on CI's own ci/scripts/with-nocow-test-filesystem.sh loop-backed btrfs fixture, from the same build's archive (--run-ignored all) On the final head: 35 passed, 0 failed; on 6ceee0c, three runs: 35 passed, 0 failed, each run. Before the rebase: 33 passed, 0 failed (initial run and the final round)
Environment notes The first privileged attempt on the rebased stack stopped in nextest's Postgres setup script before any test ran: sudo had dropped DOCKER_HOST, so root could not reach this host's Docker daemon. ENVIRONMENTAL; with DOCKER_HOST passed through, the same archive passed (row above). Before the rebase, the first full-inventory run as a non-root user without the btrfs fixture failed 5 NoCoW tests closed ("CI must provide the disposable NoCoW btrfs fixture"), also ENVIRONMENTAL: those tests are in the privileged group.

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 the final head 1fbf246, on a fresh cluster on Linux/KVM; no cluster existed on the host beforehand. The run on the previous rebase (6ceee0c, patch-identical) also passed both smokes; the final rebase brought in main's common-svc changes, so Kind ran again on the final head.

Step Result
Disk gate 40,275,537,920 bytes free in Docker before the bring-up. Before the typed run, docker builder prune -af, docker image prune -f and crictl rmi --prune (unused images only, 37 images) on this run's own fresh nodes took it from 20,720,427,008 to 31,543,037,952 bytes. No volume was pruned; tilt trigger was never used.
Bring-up scripts/tests/smoke-kind-e2e.sh (bootstrap, Tilt, Rust build from this worktree), with DATA_PLANE_URL and BOOTSTRAP_TIMEOUT=1800 as CI sets them.
Deployed sbx-mgr binary is from this head Both space-sandbox-manager pods run /opt/space/bin/space-sbxmgr-svc, /proc/<pid>/exe sha256 413d40bf…1b070a8e, equal to the binary in the locally built space-rust-build-kind:local. The worktree was clean at 1fbf246 before the build. The binary contains pplx_space_sbx_mgr_store::db::teardown_jobs:57, pplx_space_sqlite:252 and main's #551 message drop stopped artifact pin, and no pplx_space_sbx_mgr::db:: or pplx_space_common_svc::sql::sqlite: locator.
Tilt quiet 46 resources ok, 2 not_applicable, none pending or in progress, after the e2e and again after the prune.
smoke-kind-e2e.sh PASS: create, mkdir, write and read back, delete.
smoke-kind-typed.sh all, set up as CI's step does but unsharded PASS. SKIP_BOOTSTRAP=1, an admin token file, SPACE_SMOKE_KIND_COLD_RECEIPTS in a 0700 directory, and the data-plane health wait. ECW surface: 80 passed, 0 failed, 0 inconclusive. Native surface: 25 passed, 0 failed, 0 inconclusive. The suite skipped two scenarios as environmental: local.telemetry_reaches_local_backends and local.btrfs_send_restore_pulls_parent_template need telemetry mode otel, and this bench runs datadog.
Run on 6ceee0c e2e PASS; typed PASS (ECW 80, native 25, 0 failed, 0 inconclusive, same two skips); binary sha256 5c66eb14…05b33e3 matched its artifact image.
Reruns None needed.
Teardown infra/space/kind/scripts/teardown.sh after each run: Tilt stopped, cluster deleted, no Kind containers left.

🤖 Generated with Claude Code

@ac-pplx
ac-pplx force-pushed the ac/sbxmgr-store-4-sqlite-crate branch from 6cf2675 to ef22a34 Compare October 16, 2026 19:05
@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. The four moved files match their originals after the listed path rewrites. CI's new sbxmgr_assert_direct_dependents is wired into run_sbxmgr_production_checks. The dependency_fence.sbxmgr_sqlx instance from the previous PR keeps a direct sqlx, sqlx-core or sqlx-sqlite dependency out of every sbx-mgr crate except the store and the migrations crate, so pplx-space-sqlite compiling sqlx/sqlite-bundled into the graph doesn't give the server a nameable SQLite driver.

Low

  1. ARCHITECTURE.toml:87 and crates/libs/sbx/mgr/store/AGENTS.md:7: the prose overstates the graph assertion.
    Both say run_sbxmgr_production_checks "asserts that nothing else in the server's production graph brings either in". For rusqlite, run-rust-checks.sh explicitly allows pplx-space-common-jobs and pplx-space-common-core as direct dependents, so rusqlite does enter the server's production graph through crates other than the store. What the assertion enforces is that only a named set of crates may depend on it. Say that, and name the set, so nobody reasons from "only the store links rusqlite".
  2. ci/scripts/run-rust-checks.sh:260: sbxmgr_assert_direct_dependents assumes cargo tree -i <package> resolves to exactly one package.
    Cargo.lock has one rusqlite today. If a lockfile bump pulls in a second major version, cargo tree -i rusqlite stops with cargo's "multiple packages, specification is ambiguous" error and the clippy step goes red with a message that doesn't point at the fence. It fails closed, so this is about diagnosability. Pinning the spec (rusqlite@<version> from cargo metadata), or a comment saying why the check fails, would cover it.
  3. crates/libs/sqlite/Cargo.toml:21: the new helper crate depends on all of pplx-space-common-svc to import sql::migration_checksums::AcceptedChecksums.
    Every crate that only needs to open its SQLite database now builds common-svc through this crate, and common-svc can never depend on pplx-space-sqlite without a cycle. The description's decision to keep migration_checksums in common-svc is reasonable for this move. A small crate for it, used by common-svc, the helpers and the migrator, would make pplx-space-sqlite a real leaf. That's a follow-up, not a blocker.

Verified against ef22a347dd. Reproduced locally: I read the assertion script and the fence instances at head and counted rusqlite entries in Cargo.lock. The new cargo tree -i checks were also run against this head with no features and with the Kind feature set, and their output parses the way the script's awk expects. I didn't rerun the test suite; CI is green on this head. I didn't re-litigate the decision to keep the dead sql::sqlite pattern.

@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.

…ore can reach them

common-svc's `sql::sqlite` module opens, migrates and deletes a service's
SQLite database by path (`open_rusqlite`, `migrate_embedded`, `migrate`,
`remove_database_files`, `restrict_database_permissions`). It sat behind
common-svc's `sqlite` feature, and Cargo feature unification turns that
on for every crate that depends on common-svc once any crate in the build
asks for it. So sbx-mgr's server could reach those helpers, and through
them sbx-mgr's database, although only its store enables them.

The module moves, as a pure move, into its own crate, `crates/libs/sqlite`
(`pplx-space-sqlite`), with its runtime-manager catalog, its tests and the
SQLite migration baseline constant. Only path rewrites change in the
moved lines: `super::` paths into common-svc, `crate::baselines`, and
the relative `migrate!`/`include_str!` paths, which are shorter now.
common-svc loses the `sqlite` feature and its rusqlite dependency. Its
`migration_checksums` module, which opens nothing and which the migrator's
Postgres retry also uses, moves to a narrower `migration-checksums`
feature.

The store, node-ctl, node-mgr, runtime-manager, template-mgr, vol-mgr and
the database migrator depend on the new crate instead; build-mgr never
used the module and drops the feature. `dependency_fence.sbxmgr_rusqlite`
now lists `pplx-space-sqlite`, so among sbx-mgr's crates only the store
may depend on it, and a crate can only name what it depends on.

`dependency_fence` reads only sbx-mgr's own manifests, so CI's
`run_sbxmgr_production_checks` also asserts, for each feature
configuration sbx-mgr's images are built with, that `pplx-space-sqlite`
enters the server's and service's production graph only through the store,
and rusqlite only through the store, the helpers, common-jobs and
common-core. The new crate and common-svc's `migration-checksums` feature
are also linted alone, so a feature that only unification supplies (as
sqlx's `macros` was) fails CI.

No behavior or message changes. Observable differences:
- the `sqlite.migrate` span's module path, file and line, which OTel
  exports as `code.namespace`, `code.filepath` and `code.lineno`
  (`pplx_space_common_svc::sql::sqlite`, `sql/sqlite.rs:350` becomes
  `pplx_space_sqlite`, `crates/libs/sqlite/src/lib.rs:360`);
- `sql!` locators in the moved code: the production one becomes
  `pplx_space_sqlite:252` (was `…::sql::sqlite:242`); the test-only
  `lib.rs:469` (was 459) and the four in `tests.rs` (same lines) change
  module path;
- locator line shifts in callers: common-svc `sql/mod.rs` 188->185 and
  264->261, runtime-manager `installs/store.rs` 175->172, 225->222 and
  248->245.
@ac-pplx
ac-pplx force-pushed the ac/sbxmgr-store-4-sqlite-crate branch from 1fbf246 to d9f6b99 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 e653d26 Oct 19, 2026
25 of 43 checks passed
@ac-pplx
ac-pplx deleted the ac/sbxmgr-store-4-sqlite-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.

2 participants