diff --git a/pacquet/crates/cli/tests/store.rs b/pacquet/crates/cli/tests/store.rs index 7356c458aa..ed6aa43eb7 100644 --- a/pacquet/crates/cli/tests/store.rs +++ b/pacquet/crates/cli/tests/store.rs @@ -1,4 +1,5 @@ use command_extra::CommandExtra; +use pacquet_store_dir::STORE_VERSION; use pacquet_testing_utils::bin::CommandTempCwd; use pipe_trait::Pipe; use pretty_assertions::assert_eq; @@ -39,7 +40,11 @@ fn store_path_should_return_store_dir_from_pnpm_workspace_yaml() { let normalize = |path: &str| path.replace('\\', "/"); assert_eq!( String::from_utf8_lossy(&output.stdout).trim_end().pipe(normalize), - canonicalize(&workspace).join("foo/bar").to_string_lossy().pipe_as_ref(normalize), + canonicalize(&workspace) + .join("foo/bar") + .join(STORE_VERSION) + .to_string_lossy() + .pipe_as_ref(normalize), ); drop(root); // cleanup diff --git a/pacquet/crates/config/src/defaults/tests.rs b/pacquet/crates/config/src/defaults/tests.rs index 284da88be9..6aca5e1c84 100644 --- a/pacquet/crates/config/src/defaults/tests.rs +++ b/pacquet/crates/config/src/defaults/tests.rs @@ -4,7 +4,7 @@ use super::{ resolve_child_concurrency_with_parallelism, }; use crate::api::{EnvVar, GetCurrentDir, GetHomeDir}; -use pacquet_store_dir::StoreDir; +use pacquet_store_dir::{STORE_VERSION, StoreDir}; use pretty_assertions::assert_eq; use std::{io, path::PathBuf}; @@ -48,7 +48,7 @@ fn test_default_store_dir_with_pnpm_home_env() { } } let store_dir = default_store_dir::(); - assert_eq!(display_store_dir(&store_dir), "/tmp/pnpm-home/store"); + assert_eq!(display_store_dir(&store_dir), format!("/tmp/pnpm-home/store/{STORE_VERSION}")); } /// `default_store_dir`'s `XDG_DATA_HOME` branch fires only when @@ -77,7 +77,10 @@ fn test_default_store_dir_with_xdg_env() { } } let store_dir = default_store_dir::(); - assert_eq!(display_store_dir(&store_dir), "/tmp/xdg_data_home/pnpm/store"); + assert_eq!( + display_store_dir(&store_dir), + format!("/tmp/xdg_data_home/pnpm/store/{STORE_VERSION}"), + ); } /// When neither `PNPM_HOME` nor `XDG_DATA_HOME` is set, the @@ -110,8 +113,8 @@ fn test_default_store_dir_falls_back_to_home_dir() { } let store_dir = default_store_dir::(); let expected = match std::env::consts::OS { - "linux" => "/home/test-user/.local/share/pnpm/store", - "macos" => "/home/test-user/Library/pnpm/store", + "linux" => format!("/home/test-user/.local/share/pnpm/store/{STORE_VERSION}"), + "macos" => format!("/home/test-user/Library/pnpm/store/{STORE_VERSION}"), other => panic!("unexpected target OS in test: {other}"), }; assert_eq!(display_store_dir(&store_dir), expected); diff --git a/pacquet/crates/config/src/lib.rs b/pacquet/crates/config/src/lib.rs index 6a29c0c68d..6eb98f5ce0 100644 --- a/pacquet/crates/config/src/lib.rs +++ b/pacquet/crates/config/src/lib.rs @@ -1222,13 +1222,17 @@ impl Config { // diverges from TypeScript's case-sensitive program when the // workspace is case-sensitive and the home is not. if !store_dir_explicit && let Some(home_dir) = Sys::home_dir() { - // The "pnpm home dir" pnpm probes against is the parent of - // the home store (`~/Library/pnpm` for `~/Library/pnpm/store`). - // Fall back to the user's actual home if no parent is - // available — same-volume linkability is what we're after, - // and the home dir is on the same volume as any of its - // children. - let store_root = self.store_dir.root().to_path_buf(); + // `store_dir.root()` already carries the [`STORE_VERSION`] + // suffix that [`StoreDir::from`] applied, so the + // un-suffixed home store sits one level above. The "pnpm + // home dir" pnpm probes against is the parent of that + // un-suffixed home store (`~/Library/pnpm` for + // `~/Library/pnpm/store`). Fall back to the user's actual + // home whenever a parent is unavailable — same-volume + // linkability is what we're after, and the home dir is on + // the same volume as any of its children. + let store_root_versioned = self.store_dir.root().to_path_buf(); + let store_root = store_root_versioned.parent().unwrap_or(&home_dir).to_path_buf(); let pnpm_home_dir = store_root.parent().unwrap_or(&home_dir).to_path_buf(); let resolved = store_path::resolve_store_dir::(store_root, &pnpm_home_dir, start_dir); @@ -1423,7 +1427,10 @@ mod tests { } } let store_dir = default_store_dir::(); - assert_eq!(display_store_dir(&store_dir), "/hello/store"); + assert_eq!( + display_store_dir(&store_dir), + format!("/hello/store/{}", pacquet_store_dir::STORE_VERSION), + ); } /// Companion to [`should_use_pnpm_home_env_var`]: when @@ -1451,7 +1458,10 @@ mod tests { } } let store_dir = default_store_dir::(); - assert_eq!(display_store_dir(&store_dir), "/hello/pnpm/store"); + assert_eq!( + display_store_dir(&store_dir), + format!("/hello/pnpm/store/{}", pacquet_store_dir::STORE_VERSION), + ); } #[test] diff --git a/pacquet/crates/config/src/store_path.rs b/pacquet/crates/config/src/store_path.rs index 9453865035..31a47326c6 100644 --- a/pacquet/crates/config/src/store_path.rs +++ b/pacquet/crates/config/src/store_path.rs @@ -32,12 +32,17 @@ //! question without touching disk. The production [`Host`] impl //! performs the real link attempts via [`host_can_link_between_dirs`]. //! -//! `STORE_VERSION` ("v11") is intentionally *not* appended here: -//! pacquet's [`pacquet_store_dir::StoreDir`] stores the root one -//! level above v11 and appends it at access time via -//! [`pacquet_store_dir::StoreDir::v11`], where pnpm's `getStorePath` -//! returns the v11-suffixed path directly. Both implementations land -//! on identical on-disk paths. +//! [`pacquet_store_dir::STORE_VERSION`] (`"v11"`) is *not* appended in +//! this module; the path returned here is the un-suffixed base. Every +//! caller wraps the result in [`pacquet_store_dir::StoreDir::from`], +//! which appends the suffix in one place — mirroring pnpm's +//! [`getStorePath`](https://github.com/pnpm/pnpm/blob/29a42efc3b/store/path/src/index.ts#L39-L42) +//! `if (!endsWith(v11)) append(v11)` branch. Doing the join at +//! construction guarantees that everything pacquet exposes externally +//! (the `storeDir` written to `.modules.yaml`, the path printed by +//! `pacquet store path`, the NDJSON `context` log event) matches the +//! value pnpm produces, so switching between the two tools no longer +//! trips `ERR_PNPM_UNEXPECTED_STORE`. //! //! [`Host`]: crate::api::Host diff --git a/pacquet/crates/package-manager/src/install/tests.rs b/pacquet/crates/package-manager/src/install/tests.rs index 550f106ac5..6b53cea5a9 100644 --- a/pacquet/crates/package-manager/src/install/tests.rs +++ b/pacquet/crates/package-manager/src/install/tests.rs @@ -12,6 +12,7 @@ use pacquet_reporter::{ PackageManifestMessage, ProgressLog, ProgressMessage, Reporter, SilentReporter, Stage, StageLog, StatsLog, StatsMessage, SummaryLog, }; +use pacquet_store_dir::STORE_VERSION; use pacquet_testing_utils::fs::{get_all_folders, is_symlink_or_junction}; use pacquet_workspace_state::{ self as workspace_state, NodeLinker as WorkspaceStateNodeLinker, load_workspace_state, @@ -578,7 +579,7 @@ async fn install_emits_pnpm_event_sequence() { unreachable!("second event is context, asserted above"); }; assert!(!current_lockfile_exists); - assert_eq!(emitted_store_dir, &store_dir.display().to_string()); + assert_eq!(emitted_store_dir, &store_dir.join(STORE_VERSION).display().to_string()); assert_eq!(emitted_virtual_store_dir, &virtual_store_dir.to_string_lossy().into_owned()); // Summary's `prefix` must equal the manifest-parent value @@ -675,7 +676,7 @@ async fn install_writes_modules_yaml() { assert!(included.dependencies); assert!(!included.dev_dependencies); assert!(included.optional_dependencies); - assert_eq!(emitted_store_dir, store_dir.display().to_string()); + assert_eq!(emitted_store_dir, store_dir.join(STORE_VERSION).display().to_string()); // `read_modules_manifest` resolves `virtualStoreDir` against // `modules_dir`, so a relative on-disk value round-trips back // to the absolute install-time path. diff --git a/pacquet/crates/store-dir/src/project_registry.rs b/pacquet/crates/store-dir/src/project_registry.rs index fc71d9a192..bd139fd005 100644 --- a/pacquet/crates/store-dir/src/project_registry.rs +++ b/pacquet/crates/store-dir/src/project_registry.rs @@ -454,13 +454,20 @@ mod tests { /// Subdir guard: when the store lives inside the project, the /// function is a silent no-op — registering would otherwise create - /// a self-referential symlink. + /// a self-referential symlink. The `STORE_VERSION` subdir + /// (`store_dir.root()` after [`StoreDir::new`] routes the path + /// through [`From`] and applies the suffix) is + /// materialised on disk so [`path_contains`]'s canonical-form + /// comparison sees both sides as canonical paths even on macOS, + /// where `/tmp` symlinks to `/private/tmp` and a missing target + /// would silently fall back to lexical comparison and miss the + /// containment. #[test] fn register_skips_when_store_is_inside_project() { let project = tempdir().unwrap(); let store_path = project.path().join("nested-store"); - fs::create_dir_all(&store_path).unwrap(); - let store_dir = StoreDir::new(store_path); + let store_dir = StoreDir::new(&store_path); + fs::create_dir_all(store_dir.root()).unwrap(); register_project(&store_dir, project.path()).expect("subdir case is a no-op"); // No projects/ dir should have been created. diff --git a/pacquet/crates/store-dir/src/store_dir.rs b/pacquet/crates/store-dir/src/store_dir.rs index a702b3a99f..106e35d64b 100644 --- a/pacquet/crates/store-dir/src/store_dir.rs +++ b/pacquet/crates/store-dir/src/store_dir.rs @@ -6,20 +6,41 @@ use std::path::{self, PathBuf}; /// Content hash of a file. pub type FileHash = digest::Output; +/// Major version of the pnpm store layout that pacquet writes to and reads +/// from. Mirrors pnpm's [`STORE_VERSION`](https://github.com/pnpm/pnpm/blob/29a42efc3b/core/constants/src/index.ts#L9). +/// +/// The constant is part of the public contract pnpm exposes to every +/// project's `.modules.yaml` (the recorded `storeDir` is the +/// `STORE_VERSION`-suffixed path), so changing it requires moving in +/// lockstep with pnpm — otherwise both tools start refusing each +/// other's stores with `ERR_PNPM_UNEXPECTED_STORE`. +pub const STORE_VERSION: &str = "v11"; + /// Represent a store directory. /// /// * The store directory stores all files that were acquired by installing packages with pacquet or pnpm. /// * The files in `node_modules` directories are hardlinks or reflinks to the files in the store directory. /// * The store directory can and often act as a global shared cache of all installation of different workspaces. /// * The location of the store directory can be customized by `store-dir` field. -/// * The on-disk layout matches pnpm v11 (`/v11/files/XX/…[-exec]` + `/v11/index.db`) -/// so the two tools can share a store. -#[derive(Debug, Serialize, Deserialize)] -#[serde(transparent)] +/// * The on-disk layout matches pnpm v11 (`/files/XX/…[-exec]` + `/index.db`) +/// where `` already includes the `v11` suffix, so the two tools share both the +/// physical layout *and* the user-visible `storeDir` string written to +/// `.modules.yaml`. +// +// `#[serde(from = "PathBuf", into = "PathBuf")]` routes both +// directions through the `PathBuf` boundary so deserialization goes +// back through [`From`] and the [`STORE_VERSION`] suffix +// invariant holds for persisted unsuffixed paths too — the previous +// `#[serde(transparent)]` derive deserialised straight into the +// `root` field and bypassed the auto-append. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(from = "PathBuf", into = "PathBuf")] pub struct StoreDir { - /// Path to the root of the store directory from which all sub-paths are derived. - /// - /// Consumer of this struct should interact with the sub-paths instead of this path. + /// The `STORE_VERSION`-suffixed store path, equivalent to pnpm's + /// [`storeDir`](https://github.com/pnpm/pnpm/blob/29a42efc3b/store/path/src/index.ts#L39-L42). + /// Consumers should reach for the purpose-built helpers + /// ([`Self::files`][], [`Self::tmp`], [`Self::links`], + /// [`Self::projects`]) rather than this raw path. root: PathBuf, /// Runtime cache of shard bytes (`files/XX/`) this process has already @@ -30,11 +51,19 @@ pub struct StoreDir { /// `stat` per file. After the first hit, the shard is cached and /// subsequent writes skip the syscall entirely. Populated lazily by /// [`StoreDir::write_cas_file`]; duplicate inserts across threads are - /// harmless since `create_dir_all` is idempotent. - #[serde(skip, default)] + /// harmless since `create_dir_all` is idempotent. Not part of the + /// serialised wire shape — `#[serde(from/into)]` round-trips only + /// through `PathBuf`, so the cache is regenerated empty on every + /// deserialise. ensured_shards: DashSet, } +impl From for PathBuf { + fn from(store_dir: StoreDir) -> Self { + store_dir.root + } +} + /// Manual `PartialEq` / `Eq`: the shard cache is runtime state, two stores /// are equal iff they point at the same path. impl PartialEq for StoreDir { @@ -46,7 +75,18 @@ impl PartialEq for StoreDir { impl Eq for StoreDir {} impl From for StoreDir { + /// Wrap a raw path into a [`StoreDir`], appending [`STORE_VERSION`] + /// when the path doesn't already end with that segment. Mirrors + /// pnpm's [`getStorePath`](https://github.com/pnpm/pnpm/blob/29a42efc3b/store/path/src/index.ts#L39-L42), + /// so both tools record the same `storeDir` string in + /// `.modules.yaml` and switching between them stops tripping + /// `ERR_PNPM_UNEXPECTED_STORE`. fn from(root: PathBuf) -> Self { + let root = if root.file_name().and_then(|name| name.to_str()) == Some(STORE_VERSION) { + root + } else { + root.join(STORE_VERSION) + }; StoreDir { root, ensured_shards: DashSet::new() } } } @@ -76,14 +116,9 @@ impl StoreDir { self.root.display() } - /// Get `{store}/v11` — the root of the pnpm v11 store layout. - pub fn v11(&self) -> PathBuf { - self.root.join("v11") - } - /// The directory that contains all content-addressed files. fn files(&self) -> PathBuf { - self.v11().join("files") + self.root.join("files") } /// Path to a file in the store directory. @@ -108,7 +143,7 @@ impl StoreDir { /// Path to the temporary directory inside the store. pub fn tmp(&self) -> PathBuf { - self.v11().join("tmp") + self.root.join("tmp") } /// Path to the shared global-virtual-store directory inside the @@ -117,27 +152,26 @@ impl StoreDir { /// `globalVirtualStoreDir = path.join(extendedOpts.storeDir, 'links')`. /// `extendedOpts.storeDir` has already been routed through /// [`getStorePath`](https://github.com/pnpm/pnpm/blob/29a42efc3b/store/path/src/index.ts#L39-L42) - /// by the time that join runs, and `getStorePath` appends - /// `STORE_VERSION` (`"v11"`) to whatever the user configured. So - /// the resulting on-disk location is `/v11/links`, not - /// `/links` — the latter would put pacquet one level - /// shallower than pnpm and split slot caches across the two - /// tools. Sharing the path across pnpm and pacquet is the whole - /// point, so anchor under [`Self::v11`]. + /// — which appends [`STORE_VERSION`] (`"v11"`) to whatever the + /// user configured — by the time that join runs. Pacquet's + /// [`StoreDir::from`] applies the same suffix, so `self.root` is + /// already the v11 path and the on-disk location is + /// `/links`, identical to pnpm's. Sharing this path across + /// pnpm and pacquet is the whole point. pub fn links(&self) -> PathBuf { - self.v11().join("links") + self.root.join("links") } /// Path to the per-store projects registry — a flat directory of - /// symlinks (`/v11/projects/` → project dir) - /// the global-virtual-store prune sweep walks when deciding which - /// `/v11/links/...` slots are still referenced. Mirrors - /// pnpm 11's - /// [`{storeDir}/v11/projects/` layout](https://github.com/pnpm/pnpm/blob/29a42efc3b/store/controller/CHANGELOG.md#L136) - /// — same `getStorePath`-driven `v11` reasoning as + /// symlinks (`/projects/` → project dir) the + /// global-virtual-store prune sweep walks when deciding which + /// `/links/...` slots are still referenced. Mirrors pnpm + /// 11's + /// [`{storeDir}/projects/` layout](https://github.com/pnpm/pnpm/blob/29a42efc3b/store/controller/CHANGELOG.md#L136) + /// — `` already carries the v11 suffix on both sides per /// [`Self::links`]. pub fn projects(&self) -> PathBuf { - self.v11().join("projects") + self.root.join("projects") } /// Borrow the raw store-root path. Most code should prefer the @@ -148,7 +182,7 @@ impl StoreDir { &self.root } - /// On a fresh store, eagerly create `/v11/files/` plus every + /// On a fresh store, eagerly create `/files/` plus every /// `files/XX/` shard (00..ff) and seed the shard cache with the /// bytes we just created, so CAFS writes never pay a /// `create_dir_all` syscall in the hot path. diff --git a/pacquet/crates/store-dir/src/store_dir/tests.rs b/pacquet/crates/store-dir/src/store_dir/tests.rs index ff1251cdc7..04f3a91ee0 100644 --- a/pacquet/crates/store-dir/src/store_dir/tests.rs +++ b/pacquet/crates/store-dir/src/store_dir/tests.rs @@ -1,7 +1,7 @@ -use super::StoreDir; +use super::{STORE_VERSION, StoreDir}; use pipe_trait::Pipe; use pretty_assertions::assert_eq; -use std::path::Path; +use std::path::{Path, PathBuf}; #[test] fn file_path_by_head_tail() { @@ -20,6 +20,59 @@ fn tmp() { assert_eq!(received, expected); } +/// `StoreDir::from(PathBuf)` appends [`STORE_VERSION`] to any path +/// that doesn't already end with it — matching pnpm's +/// [`getStorePath`](https://github.com/pnpm/pnpm/blob/29a42efc3b/store/path/src/index.ts#L39-L42) +/// branch. Both the auto-append happy path and the +/// already-suffixed idempotent path are pinned here so a regression +/// would surface as either a missing `v11` (the original bug — pnpm +/// rejects the resulting `.modules.yaml` with +/// `ERR_PNPM_UNEXPECTED_STORE`) or a duplicated `v11/v11` segment. +#[test] +fn from_pathbuf_auto_appends_store_version_when_missing() { + let store = StoreDir::from(PathBuf::from("/home/user/.local/share/pnpm/store")); + assert_eq!(store.root(), Path::new("/home/user/.local/share/pnpm/store/v11")); +} + +#[test] +fn from_pathbuf_does_not_double_append_when_already_suffixed() { + let store = StoreDir::from(PathBuf::from("/home/user/.local/share/pnpm/store/v11")); + assert_eq!(store.root(), Path::new("/home/user/.local/share/pnpm/store/v11")); +} + +/// Round-trip the `storeDir` string pacquet writes to `.modules.yaml` +/// against the pnpm comparison contract: pnpm rebuilds `/v11` +/// from the user's home and demands an exact match against the +/// recorded value. The constant test makes the bug +/// `ERR_PNPM_UNEXPECTED_STORE` triggered legible for future readers. +#[test] +fn modules_yaml_serialized_store_dir_carries_store_version() { + let store = StoreDir::new("/tmp/.pnpm-store"); + let recorded = store.display().to_string(); + let pnpm_would_emit = format!("/tmp/.pnpm-store/{STORE_VERSION}"); + assert_eq!(recorded, pnpm_would_emit); +} + +/// Deserialising an unsuffixed path (e.g. one persisted by an older +/// pacquet that hadn't normalised yet) must route through +/// [`From`] and gain the [`STORE_VERSION`] suffix. The +/// previous `#[serde(transparent)]` derive wrote straight into +/// `root` and bypassed the auto-append, leaving the suffix invariant +/// silently violated on the live `StoreDir`. +#[test] +fn deserialize_applies_store_version_to_unsuffixed_path() { + let json = r#""/home/user/.local/share/pnpm/store""#; + let store: StoreDir = serde_json::from_str(json).expect("deserialize StoreDir"); + assert_eq!(store.root(), Path::new("/home/user/.local/share/pnpm/store/v11")); +} + +#[test] +fn deserialize_preserves_already_suffixed_path() { + let json = r#""/home/user/.local/share/pnpm/store/v11""#; + let store: StoreDir = serde_json::from_str(json).expect("deserialize StoreDir"); + assert_eq!(store.root(), Path::new("/home/user/.local/share/pnpm/store/v11")); +} + /// `init` on a fresh store should materialize `v11/files/00..ff` /// and populate the shard cache so later `write_cas_file` calls /// can skip their lazy mkdir. diff --git a/pacquet/crates/store-dir/src/store_index.rs b/pacquet/crates/store-dir/src/store_index.rs index 7b385e5751..b489bd631d 100644 --- a/pacquet/crates/store-dir/src/store_index.rs +++ b/pacquet/crates/store-dir/src/store_index.rs @@ -120,10 +120,10 @@ impl StoreIndexWriter { pub fn spawn( store_dir: &StoreDir, ) -> (Arc, tokio::task::JoinHandle>) { - let v11_dir = store_dir.v11(); + let store_root = store_dir.root().to_path_buf(); let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::(); let handle = tokio::task::spawn_blocking(move || -> Result<(), StoreIndexError> { - let mut index = StoreIndex::open(&v11_dir)?; + let mut index = StoreIndex::open(&store_root)?; let mut batch: Vec = Vec::with_capacity(MAX_BATCH_SIZE); while let Some(first) = rx.blocking_recv() { batch.push(first); @@ -401,9 +401,9 @@ impl StoreIndex { Ok(StoreIndex { conn }) } - /// Open the `index.db` that lives under a [`StoreDir`]'s `v11` subdirectory. + /// Open the `index.db` that lives directly under a [`StoreDir`]'s root. pub fn open_in(store_dir: &StoreDir) -> Result { - StoreIndex::open(&store_dir.v11()) + StoreIndex::open(store_dir.root()) } /// Open an existing `index.db` read-only. Skips the schema-mutating @@ -427,7 +427,7 @@ impl StoreIndex { /// Read-only counterpart to [`StoreIndex::open_in`]. pub fn open_readonly_in(store_dir: &StoreDir) -> Result { - StoreIndex::open_readonly(&store_dir.v11()) + StoreIndex::open_readonly(store_dir.root()) } /// Open a read-only index wrapped in `Arc>` so it can be shared @@ -440,11 +440,11 @@ impl StoreIndex { /// redoing its PRAGMAs) on every package, which otherwise scales /// linearly with the snapshot count. pub fn shared_readonly_in(store_dir: &StoreDir) -> Option { - let v11_dir = store_dir.v11(); - if !v11_dir.join("index.db").exists() { + let store_root = store_dir.root(); + if !store_root.join("index.db").exists() { return None; } - StoreIndex::open_readonly(&v11_dir).ok().map(|index| Arc::new(Mutex::new(index))) + StoreIndex::open_readonly(store_root).ok().map(|index| Arc::new(Mutex::new(index))) } /// Look up a package-files index by key. Returns `Ok(None)` if no row exists. diff --git a/pacquet/crates/store-dir/src/store_index/tests.rs b/pacquet/crates/store-dir/src/store_index/tests.rs index ec535def1f..eb2cf626db 100644 --- a/pacquet/crates/store-dir/src/store_index/tests.rs +++ b/pacquet/crates/store-dir/src/store_index/tests.rs @@ -135,7 +135,7 @@ fn index_db_lives_at_store_dir_v11() { let store = StoreDir::new(root.path()); let idx = StoreIndex::open_in(&store).unwrap(); idx.set("k\tv", &sample_index()).unwrap(); - assert!(store.v11().join("index.db").exists()); + assert!(store.root().join("index.db").exists()); } /// A row whose bytes are msgpackr-records (as pnpm writes) must decode