diff --git a/README.md b/README.md index f38c045..dad0758 100644 --- a/README.md +++ b/README.md @@ -111,11 +111,12 @@ Generate the complete production manifest set: just catalog-generate-production /srv/lanspread/packages ``` -The publisher independently reads and hashes each selected package twice before -publication. Under a durable publication marker, it atomically replaces each -manifest and writes the complete content index last. Interruption leaves the -marker in place so later checks and builds fail closed instead of accepting a -mixed generation. +The publisher first checks every selected package's `version.ini` against +`game.db`, then independently reads and hashes each selected package twice +before publication. Under a durable publication marker, it atomically replaces +each manifest and writes the complete content index last. Interruption leaves +the marker in place so later checks and builds fail closed instead of accepting +a mixed generation. Verify the published database/index/manifest set without rereading the package corpus: diff --git a/crates/lanspread-compat/src/catalog_publisher/mod.rs b/crates/lanspread-compat/src/catalog_publisher/mod.rs index e106f4d..9592d45 100644 --- a/crates/lanspread-compat/src/catalog_publisher/mod.rs +++ b/crates/lanspread-compat/src/catalog_publisher/mod.rs @@ -120,6 +120,13 @@ pub async fn generate_catalog_manifests( let versions = catalog_versions(&catalog); reject_incomplete_publication(&options.manifests_dir)?; let selected = options.selection.select(&catalog)?; + for game in &selected { + package::validate_package_version( + &options.packages_dir.join(&game.game_id), + &game.game_version, + ) + .wrap_err_with(|| format!("failed to preflight package version for {}", game.game_id))?; + } let prepared = selected .into_iter() .map(|game| prepare_manifest(game, options)) @@ -766,11 +773,13 @@ mod tests { } #[tokio::test] - async fn entire_selection_is_prepared_before_any_manifest_is_published() { + async fn entire_selection_versions_are_preflighted_before_archive_work() { let temp = TempDir::new(); let catalog_db = temp.0.join("game.db"); create_catalog(&catalog_db, &[("a", "20240101"), ("b", "20240102")]).await; - simple_package(&temp, "a", "20240101"); + let a_package = simple_package(&temp, "a", "20240101"); + fs::write(a_package.join("a.eti"), b"not an archive") + .expect("archive-shaped input should be written"); simple_package(&temp, "b", "wrong-version"); let manifests_dir = temp.0.join("manifests"); fs::create_dir(&manifests_dir).expect("manifest directory should be created"); @@ -785,12 +794,17 @@ mod tests { selection: CatalogSelection::All, }) .await - .expect_err("late package failure should reject the whole selection"); + .expect_err("all versions should be checked before archive processing"); assert!( error .to_string() - .contains("failed to generate manifest for b") + .contains("failed to preflight package version for b"), + "unexpected error: {error:#}" + ); + assert!( + format!("{error:#}").contains("package version mismatch"), + "unexpected error: {error:#}" ); assert_eq!( fs::read(existing).expect("existing artifact should remain readable"), diff --git a/crates/lanspread-compat/src/catalog_publisher/package.rs b/crates/lanspread-compat/src/catalog_publisher/package.rs index 43c1535..1ea4608 100644 --- a/crates/lanspread-compat/src/catalog_publisher/package.rs +++ b/crates/lanspread-compat/src/catalog_publisher/package.rs @@ -121,7 +121,7 @@ pub(super) fn validate_manifest_destination(path: &Path) -> eyre::Result<()> { Ok(()) } -fn validate_package_version(package_root: &Path, expected: &str) -> eyre::Result<()> { +pub(super) fn validate_package_version(package_root: &Path, expected: &str) -> eyre::Result<()> { let path = package_root.join("version.ini"); let bytes = read_bounded_regular_file(&path, MAX_VERSION_INI_BYTES)?; let version = std::str::from_utf8(&bytes)