Repository: mozilla/cargo-vet
Pull request #435 (2023-03-10 21:24:35)

Track the version of cargo-vet used to create the supply-chain store
This is important given changes like https://github.com/mozilla/cargo-vet/issues/431, which will change the meaning of imports.lock in such a way that earlier versions of cargo-vet may not understand parts of it correctly anymore. While we generally need to keep the audits.toml file somewhat forward-and-back compatible due to imports, this is
less of a concern for other files, so preventing downgrades could be valuable here.

This new approach allows implicit upgrades when running without --locked, but prevents downgrades or locked upgrades. cargo-vet versions with the same minor release are considered compatible, so only the major and minor components are considered.

Fixes https://github.com/mozilla/cargo-vet/issues/430

mystor: Builds on top of https://github.com/mozilla/cargo-vet/pull/434, so leaving as a draft until that is merged.
mystor: > How will we handle migrations in the future if there are incompatibilities? I was expecting to see a strategy (boilerplate) in the code, though it's fine to omit it until we need it.

I figured that pre-building a strategy for doing migrations before we know what we're going to need was a bit of pre-emptive optimization. So far we've never needed to e.g. break serialization and have managed to do migrations fairly painlessly, so I don't want to build an infrastructure for doing that before we need it. The main thing I was adding this for was for the interpretation of the machine-generated imports.lock file, which will be automatically updated the first time someone runs `cargo vet` with a new version because it'll be re-generated, so no explicit migration is necessary.

Patch:
diff --git a/src/errors.rs b/src/errors.rs
index 654e1db..632460a 100644
--- a/src/errors.rs
+++ b/src/errors.rs
@@ -1,6 +1,7 @@
 use std::{
     ffi::OsString,
     fmt::{Debug, Display},
+    num::ParseIntError,
     path::{PathBuf, StripPrefixError},
     string::FromUtf8Error,
     sync::Arc,
@@ -10,7 +11,9 @@ use cargo_metadata::semver;
 use miette::{Diagnostic, MietteSpanContents, SourceCode, SourceOffset, SourceSpan};
 use thiserror::Error;
 
-use crate::format::{CriteriaName, ForeignCriteriaName, ImportName, PackageName, VetVersion};
+use crate::format::{
+    CriteriaName, ForeignCriteriaName, ImportName, PackageName, StoreVersion, VetVersion,
+};
 use crate::network::PayloadEncoding;
 
 #[derive(Eq, PartialEq)]
@@ -89,34 +92,13 @@ pub enum VersionParseError {
     InvalidGitHash,
 }
 
-///////////////////////////////////////////////////////////
-// MetadataAcquireError
-///////////////////////////////////////////////////////////
-
-#[derive(Debug, Error, Diagnostic)]
-pub enum MetadataAcquireError {
-    #[error("`cargo metadata` exited with an error:\n{stderr}")]
-    #[diagnostic(help("You may need to run `cargo generate-lockfile` to create a Cargo.lock"))]
-    MetadataError {
-        /// Stderr returned by the `cargo metadata` command
-        stderr: String,
-    },
-    #[error(transparent)]
-    Other(cargo_metadata::Error),
-}
-
-impl From<cargo_metadata::Error> for MetadataAcquireError {
-    fn from(value: cargo_metadata::Error) -> Self {
-        match value {
-            // Wrap normal errors to provide extra help information.
-            cargo_metadata::Error::CargoMetadata { stderr } => {
-                MetadataAcquireError::MetadataError { stderr }
-            }
-            // Other errors couldn't be caused by that problem, so are
-            // unchanged.
-            other => MetadataAcquireError::Other(other),
-        }
-    }
+#[derive(Debug, Error)]
+#[non_exhaustive]
+pub enum StoreVersionParseError {
+    #[error("error parsing version component: {0}")]
+    ParseInt(#[from] ParseIntError),
+    #[error("missing '.' separator")]
+    MissingSeparator,
 }
 
 ///////////////////////////////////////////////////////////
@@ -345,6 +327,12 @@ pub enum StoreAcquireError {
         #[source]
         FlockError,
     ),
+    #[error("The supply-chain store was created with an incompatible version of cargo-vet ({0})")]
+    #[help("Run cargo vet without --locked to update the store to this version")]
+    OutdatedStore(StoreVersion),
+    #[error("The supply-chain store was created with a newer version of cargo-vet ({0})")]
+    #[help("Update to the latest version using `cargo install cargo-vet`")]
+    NewerStore(StoreVersion),
     #[error(transparent)]
     #[diagnostic(transparent)]
     LoadToml(#[from] LoadTomlError),
diff --git a/src/format.rs b/src/format.rs
index f9e8733..fae9bed 100644
--- a/src/format.rs
+++ b/src/format.rs
@@ -1,6 +1,6 @@
 //! Details of the file formats used by cargo vet
 
-use crate::errors::VersionParseError;
+use crate::errors::{StoreVersionParseError, VersionParseError};
 use crate::resolver::{DiffRecommendation, ViolationConflict};
 use crate::serialization::spanned::Spanned;
 use crate::{flock::Filesystem, serialization};
@@ -420,6 +420,10 @@ impl WildcardEntry {
 /// config.toml
 #[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
 pub struct ConfigFile {
+    #[serde(rename = "cargo-vet")]
+    #[serde(default = "CargoVetConfig::missing")]
+    pub cargo_vet: CargoVetConfig,
+
     /// This top-level key specifies the default criteria that cargo vet certify will use
     /// when recording audits. If unspecified, this defaults to "safe-to-deploy".
     #[serde(rename = "default-criteria")]
@@ -747,6 +751,109 @@ fn is_default_exemptions_suggest(val: &bool) -> bool {
     val == &DEFAULT_EXEMPTIONS_SUGGEST
 }
 
+/// Special version type used for store versions. Only contains two components
+/// (major/minor) to avoid patch version changes from causing changes to the
+/// store.
+#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
+pub struct StoreVersion {
+    pub major: u64,
+    pub minor: u64,
+}
+
+impl StoreVersion {
+    #[cfg(not(test))]
+    pub fn current() -> Self {
+        StoreVersion {
+            major: env!("CARGO_PKG_VERSION_MAJOR").parse().unwrap(),
+            minor: env!("CARGO_PKG_VERSION_MINOR").parse().unwrap(),
+        }
+    }
+
+    // To keep output from tests stable, when running unit tests we always
+    // pretend we're version 1.0
+    #[cfg(test)]
+    pub fn current() -> Self {
+        StoreVersion { major: 1, minor: 0 }
+    }
+}
+
+impl FromStr for StoreVersion {
+    type Err = StoreVersionParseError;
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        match s.split_once('.') {
+            Some((major, minor)) => Ok(StoreVersion {
+                major: major.parse()?,
+                minor: minor.parse()?,
+            }),
+            None => Err(StoreVersionParseError::MissingSeparator),
+        }
+    }
+}
+
+impl fmt::Display for StoreVersion {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "{}.{}", self.major, self.minor)
+    }
+}
+
+impl Serialize for StoreVersion {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        self.to_string().serialize(serializer)
+    }
+}
+
+impl<'de> Deserialize<'de> for StoreVersion {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        struct VersionVisitor;
+
+        impl<'de> Visitor<'de> for VersionVisitor {
+            type Value = StoreVersion;
+
+            fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
+                f.write_str("store version")
+            }
+            fn visit_str<E>(self, string: &str) -> Result<Self::Value, E>
+            where
+                E: de::Error,
+            {
+                StoreVersion::from_str(string).map_err(de::Error::custom)
+            }
+        }
+
+        deserializer.deserialize_str(VersionVisitor)
+    }
+}
+
+/// Cargo vet config metadata field for the store's config file.
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct CargoVetConfig {
+    pub version: StoreVersion,
+}
+
+impl CargoVetConfig {
+    /// Pretend that any store which was created without a version specified is
+    /// from version 0.4.
+    fn missing() -> Self {
+        Self {
+            version: StoreVersion { major: 0, minor: 4 },
+        }
+    }
+}
+
+impl Default for CargoVetConfig {
+    fn default() -> Self {
+        Self {
+            version: StoreVersion::current(),
+        }
+    }
+}
+
 ////////////////////////////////////////////////////////////////////////////////////
 //                                                                                //
 //                                                                                //
diff --git a/src/main.rs b/src/main.rs
index 565fc4d..8ac2303 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -34,9 +34,7 @@ use tracing::{error, info, trace, warn};
 
 use crate::cli::*;
 use crate::criteria::CriteriaMapper;
-use crate::errors::{
-    CommandError, DownloadError, FetchAndDiffError, FetchError, MetadataAcquireError, SourceFile,
-};
+use crate::errors::{CommandError, DownloadError, FetchAndDiffError, FetchError, SourceFile};
 use crate::format::{
     AuditEntry, AuditKind, AuditsFile, ConfigFile, CratesUserId, CriteriaEntry, ExemptedDependency,
     FetchCommand, ImportsFile, MetaConfig, MetaConfigInstance, PackageStr, RemoteImport, SortedMap,
@@ -361,12 +359,6 @@ fn real_main() -> Result<(), miette::Report> {
     } else {
         other_options.push("--locked".to_string());
     }
-    if !using_log_file
-        && cli.output_format == OutputFormat::Human
-        && console::colors_enabled_stderr()
-    {
-        other_options.push("--color=always".to_string());
-    }
     cmd.other_options(other_options);
 
     info!("Running: {:#?}", cmd.cargo_command());
@@ -374,7 +366,9 @@ fn real_main() -> Result<(), miette::Report> {
     // ERRORS: immediate fatal diagnostic
     let metadata = {
         let _spinner = indeterminate_spinner("Running", "`cargo metadata`");
-        cmd.exec().map_err(MetadataAcquireError::from)?
+        cmd.exec()
+            .into_diagnostic()
+            .wrap_err("'cargo metadata' exited unsuccessfully")?
     };
 
     // trace!("Got Metadata! {:#?}", metadata);
@@ -552,6 +546,7 @@ pub fn init_files(
                 .push(item);
         }
         ConfigFile {
+            cargo_vet: Default::default(),
             default_criteria: format::get_default_criteria(),
             imports: SortedMap::new(),
             exemptions: dependencies,
diff --git a/src/serialization.rs b/src/serialization.rs
index e0bb75a..e3d2fcb 100644
--- a/src/serialization.rs
+++ b/src/serialization.rs
@@ -756,6 +756,9 @@ mod test {
         );
 
         let formatted = super::to_formatted_toml(ConfigFile {
+            cargo_vet: CargoVetConfig {
+                version: StoreVersion { major: 1, minor: 0 },
+            },
             default_criteria: get_default_criteria(),
             imports: SortedMap::new(),
             policy,
diff --git a/src/storage.rs b/src/storage.rs
index ad2eb75..c2e68d2 100644
--- a/src/storage.rs
+++ b/src/storage.rs
@@ -35,7 +35,8 @@ use crate::{
         DiffCache, DiffStat, FastMap, FastSet, FetchCommand, ForeignAuditsFile, ImportName,
         ImportsFile, MetaConfig, PackageName, PackageStr, PublisherCache, PublisherCacheEntry,
         PublisherCacheUser, PublisherCacheVersion, RegistryEntry, RegistryFile, RemoteImport,
-        SortedMap, VetVersion, WildcardAudits, WildcardEntry, SAFE_TO_DEPLOY, SAFE_TO_RUN,
+        SortedMap, StoreVersion, VetVersion, WildcardAudits, WildcardEntry, SAFE_TO_DEPLOY,
+        SAFE_TO_RUN,
     },
     network::Network,
     out::{indeterminate_spinner, progress_bar, IncProgressOnDrop},
@@ -170,6 +171,7 @@ impl Store {
         Ok(Self {
             lock: Some(lock),
             config: ConfigFile {
+                cargo_vet: Default::default(),
                 default_criteria: String::new(),
                 imports: SortedMap::new(),
                 policy: Default::default(),
@@ -205,7 +207,20 @@ impl Store {
         // exclusive one isn't needed.
         let lock = StoreLock::new(&root)?;
 
-        let (config_src, config): (_, ConfigFile) = load_toml(CONFIG_TOML, lock.read_config()?)?;
+        let (config_src, mut config): (_, ConfigFile) =
+            load_toml(CONFIG_TOML, lock.read_config()?)?;
+
+        // Compare the version from the store with the current StoreVersion.
+        // It's always an error to downgrade cargo-vet versions, but only an
+        // error to upgrade versions when --locked.
+        let current_version = StoreVersion::current();
+        if config.cargo_vet.version < current_version && cfg.cli.locked {
+            return Err(StoreAcquireError::OutdatedStore(config.cargo_vet.version));
+        } else if config.cargo_vet.version > current_version {
+            return Err(StoreAcquireError::NewerStore(config.cargo_vet.version));
+        }
+        config.cargo_vet.version = current_version;
+
         let (audits_src, audits): (_, AuditsFile) = load_toml(AUDITS_TOML, lock.read_audits()?)?;
         let (imports_src, imports): (_, ImportsFile) =
             load_toml(IMPORTS_LOCK, lock.read_imports()?)?;
@@ -282,7 +297,6 @@ impl Store {
             network,
             cache,
             &wildcard_audits_packages(&self.audits, &live_imports),
-            false,
             &self.config,
             &self.audits,
             &self.imports,
@@ -339,7 +353,6 @@ impl Store {
                 network,
                 &cache,
                 &wildcard_audits_packages(&audits, &live_imports),
-                false,
                 &config,
                 &audits,
                 &imports,
@@ -707,7 +720,6 @@ impl Store {
                 network,
                 &cache,
                 &[package.to_owned()].into_iter().collect(),
-                true,
                 &self.config,
                 &self.audits,
                 &self.imports,
@@ -804,7 +816,6 @@ impl Store {
             network,
             cache,
             &wildcard_packages,
-            false,
             &self.config,
             &self.audits,
             &self.imports,
@@ -1288,7 +1299,6 @@ async fn import_publisher_versions(
     network: &Network,
     cache: &Cache,
     relevant_packages: &FastSet<PackageName>,
-    force: bool,
     config_file: &ConfigFile,
     audits_file: &AuditsFile,
     imports_lock: &ImportsFile,
@@ -1298,9 +1308,7 @@ async fn import_publisher_versions(
     // actually used in-tree.
     let mut relevant_versions: FastMap<PackageStr<'_>, FastSet<&semver::Version>> = FastMap::new();
     for pkg in &metadata.packages {
-        if relevant_packages.contains(&pkg.name)
-            && (force || pkg.is_third_party(&config_file.policy))
-        {
+        if relevant_packages.contains(&pkg.name) && pkg.is_third_party(&config_file.policy) {
             relevant_versions
                 .entry(&pkg.name)
                 .or_default()
@@ -1308,14 +1316,6 @@ async fn import_publisher_versions(
         }
     }
 
-    // If we're forcing fetching for this package, ensure an entry exists even
-    // if it isn't in the dependency graph.
-    if force {
-        for package in relevant_packages {
-            relevant_versions.entry(&package[..]).or_default();
-        }
-    }
-
     // We may care about other versions for these packages as well if they
     // appear on the `from` side of a delta audit, or if they are named by an
     // exemption.
diff --git a/supply-chain/config.toml b/supply-chain/config.toml
index 8eb005a..acd5d2c 100644
--- a/supply-chain/config.toml
+++ b/supply-chain/config.toml
@@ -1,6 +1,9 @@
 
 # cargo-vet config file
 
+[cargo-vet]
+version = "0.4"
+
 [imports.bytecodealliance]
 url = "https://raw.githubusercontent.com/bytecodealliance/wasmtime/main/supply-chain/audits.toml"
 


Test patch:
diff --git a/src/snapshots/cargo_vet__serialization__test__formatted_toml_long_inline.snap b/src/snapshots/cargo_vet__serialization__test__formatted_toml_long_inline.snap
index 02490a1..0310374 100644
--- a/src/snapshots/cargo_vet__serialization__test__formatted_toml_long_inline.snap
+++ b/src/snapshots/cargo_vet__serialization__test__formatted_toml_long_inline.snap
@@ -3,6 +3,9 @@ source: src/serialization.rs
 expression: formatted
 ---
 
+[cargo-vet]
+version = "1.0"
+
 [policy.long-criteria]
 criteria = "long-criteria"
 notes = "notes go here!"
diff --git a/src/tests/snapshots/cargo_vet__tests__import__peer_audits_exemption_minimize_certify.snap b/src/tests/snapshots/cargo_vet__tests__import__peer_audits_exemption_minimize_certify.snap
index a2dd988..b31363a 100644
--- a/src/tests/snapshots/cargo_vet__tests__import__peer_audits_exemption_minimize_certify.snap
+++ b/src/tests/snapshots/cargo_vet__tests__import__peer_audits_exemption_minimize_certify.snap
@@ -7,6 +7,9 @@ config.toml:
  
  # cargo-vet config file
  
+ [cargo-vet]
+ version = "1.0"
+ 
  [imports.peer-company]
  url = "https://peercompany.co.uk"
  
diff --git a/src/tests/snapshots/cargo_vet__tests__import__peer_audits_exemption_minimize_prune.snap b/src/tests/snapshots/cargo_vet__tests__import__peer_audits_exemption_minimize_prune.snap
index 14e722b..0f0c75c 100644
--- a/src/tests/snapshots/cargo_vet__tests__import__peer_audits_exemption_minimize_prune.snap
+++ b/src/tests/snapshots/cargo_vet__tests__import__peer_audits_exemption_minimize_prune.snap
@@ -7,6 +7,9 @@ config.toml:
  
  # cargo-vet config file
  
+ [cargo-vet]
+ version = "1.0"
+ 
  [imports.peer-company]
  url = "https://peercompany.co.uk"
 -
diff --git a/src/tests/snapshots/cargo_vet__tests__store_parsing__invalid_formatting.snap b/src/tests/snapshots/cargo_vet__tests__store_parsing__invalid_formatting.snap
index e462c53..4f0b0c6 100644
--- a/src/tests/snapshots/cargo_vet__tests__store_parsing__invalid_formatting.snap
+++ b/src/tests/snapshots/cargo_vet__tests__store_parsing__invalid_formatting.snap
@@ -10,7 +10,7 @@ Error:
   │ 
   │ --- old/config.toml
   │ +++ new/config.toml
-  │ @@ -8,14 +8,14 @@
+  │ @@ -11,14 +11,14 @@
   │  [imports.peer2]
   │  url = "https://peer1.com"
   │ 
diff --git a/src/tests/snapshots/cargo_vet__tests__store_parsing__many_bad_config.snap b/src/tests/snapshots/cargo_vet__tests__store_parsing__many_bad_config.snap
index 288d97a..e6e63e4 100644
--- a/src/tests/snapshots/cargo_vet__tests__store_parsing__many_bad_config.snap
+++ b/src/tests/snapshots/cargo_vet__tests__store_parsing__many_bad_config.snap
@@ -7,47 +7,47 @@ expression: acquire_errors
 
 Error: 
   × 'oops' is not a valid criteria name
-    ╭─[config.toml:18:1]
- 18 │ version = "1.0.0"
- 19 │ criteria = "oops"
+    ╭─[config.toml:21:1]
+ 21 │ version = "1.0.0"
+ 22 │ criteria = "oops"
     ·            ──────
- 20 │ 
+ 23 │ 
     ╰────
   help: the possible criteria are ["good", "safe-to-run", "safe-to-deploy"]
 Error: 
   × 'bad' is not a valid criteria name
-    ╭─[config.toml:12:1]
- 12 │ [policy.serde]
- 13 │ criteria = "bad"
+    ╭─[config.toml:15:1]
+ 15 │ [policy.serde]
+ 16 │ criteria = "bad"
     ·            ─────
- 14 │ dev-criteria = "nope"
+ 17 │ dev-criteria = "nope"
     ╰────
   help: the possible criteria are ["good", "safe-to-run", "safe-to-deploy"]
 Error: 
   × 'nope' is not a valid criteria name
-    ╭─[config.toml:13:1]
- 13 │ criteria = "bad"
- 14 │ dev-criteria = "nope"
+    ╭─[config.toml:16:1]
+ 16 │ criteria = "bad"
+ 17 │ dev-criteria = "nope"
     ·                ──────
- 15 │ dependency-criteria = { clap = ["safe-to-run", "unsafe-for-all", "good"], serde_derive = "nada" }
+ 18 │ dependency-criteria = { clap = ["safe-to-run", "unsafe-for-all", "good"], serde_derive = "nada" }
     ╰────
   help: the possible criteria are ["good", "safe-to-run", "safe-to-deploy"]
 Error: 
   × 'unsafe-for-all' is not a valid criteria name
-    ╭─[config.toml:14:1]
- 14 │ dev-criteria = "nope"
- 15 │ dependency-criteria = { clap = ["safe-to-run", "unsafe-for-all", "good"], serde_derive = "nada" }
+    ╭─[config.toml:17:1]
+ 17 │ dev-criteria = "nope"
+ 18 │ dependency-criteria = { clap = ["safe-to-run", "unsafe-for-all", "good"], serde_derive = "nada" }
     ·                                                ────────────────
- 16 │ 
+ 19 │ 
     ╰────
   help: the possible criteria are ["good", "safe-to-run", "safe-to-deploy"]
 Error: 
   × 'nada' is not a valid criteria name
-    ╭─[config.toml:14:1]
- 14 │ dev-criteria = "nope"
- 15 │ dependency-criteria = { clap = ["safe-to-run", "unsafe-for-all", "good"], serde_derive = "nada" }
+    ╭─[config.toml:17:1]
+ 17 │ dev-criteria = "nope"
+ 18 │ dependency-criteria = { clap = ["safe-to-run", "unsafe-for-all", "good"], serde_derive = "nada" }
     ·                                                                                          ──────
- 16 │ 
+ 19 │ 
     ╰────
   help: the possible criteria are ["good", "safe-to-run", "safe-to-deploy"]
 
diff --git a/src/tests/snapshots/cargo_vet__tests__store_parsing__unknown_field_config.snap b/src/tests/snapshots/cargo_vet__tests__store_parsing__unknown_field_config.snap
index b0bde1a..097eaa6 100644
--- a/src/tests/snapshots/cargo_vet__tests__store_parsing__unknown_field_config.snap
+++ b/src/tests/snapshots/cargo_vet__tests__store_parsing__unknown_field_config.snap
@@ -10,7 +10,7 @@ Error:
   │ 
   │ --- old/config.toml
   │ +++ new/config.toml
-  │ @@ -4,9 +4,7 @@
+  │ @@ -7,9 +7,7 @@
   │  [imports.peer1]
   │  url = "https://peer1.com"
   │  exclude = ["zzz", "aaa"]
diff --git a/src/tests/store_parsing.rs b/src/tests/store_parsing.rs
index 24e00d7..d2a2b28 100644
--- a/src/tests/store_parsing.rs
+++ b/src/tests/store_parsing.rs
@@ -1,5 +1,8 @@
 const EMPTY_CONFIG: &str = r##"
 # cargo-vet config file
+
+[cargo-vet]
+version = "1.0"
 "##;
 const EMPTY_AUDITS: &str = r##"
 # cargo-vet audits file
@@ -84,6 +87,9 @@ fn test_many_bad_config() {
     let config = r##"
 # cargo-vet config file
 
+[cargo-vet]
+version = "1.0"
+
 [policy.boring]
 audit-as-crates-io = true
 
@@ -125,6 +131,9 @@ fn test_outdated_imports_lock_extra_peer() {
     let config = r##"
 # cargo-vet config file
 
+[cargo-vet]
+version = "1.0"
+
 [imports.peer1]
 url = "https://peer1.com"
 "##;
@@ -150,6 +159,9 @@ fn test_outdated_imports_lock_missing_peer() {
     let config = r##"
 # cargo-vet config file
 
+[cargo-vet]
+version = "1.0"
+
 [imports.peer1]
 url = "https://peer1.com"
 
@@ -174,6 +186,9 @@ fn test_outdated_imports_lock_excluded_crate() {
     let config = r##"
 # cargo-vet config file
 
+[cargo-vet]
+version = "1.0"
+
 [imports.peer1]
 url = "https://peer1.com"
 exclude = ["third-party1"]
@@ -200,6 +215,9 @@ fn test_outdated_imports_lock_ok() {
     let config = r##"
 # cargo-vet config file
 
+[cargo-vet]
+version = "1.0"
+
 [imports.peer1]
 url = "https://peer1.com"
 exclude = ["third-party2"]
@@ -229,6 +247,9 @@ fn test_unknown_field_config() {
     let config = r##"
 # cargo-vet config file
 
+[cargo-vet]
+version = "1.0"
+
 [imports.peer1]
 url = "https://peer1.com"
 exclude = ["zzz", "aaa"]
@@ -334,6 +355,9 @@ fn test_invalid_formatting() {
     let config = r##"
 # cargo-vet config file
 
+[cargo-vet]
+version = "1.0"
+
 [imports.peer1]
 url = "https://peer1.com"
 exclude = ["zzz", "aaa"]
@@ -392,6 +416,9 @@ fn parse_criteria_map() {
     let config = r##"
 # cargo-vet config file
 
+[cargo-vet]
+version = "1.0"
+
 [imports.peer1]
 url = "https://peer1.com"
 
diff --git a/tests/test-project/supply-chain/config.toml b/tests/test-project/supply-chain/config.toml
index 2b2ccea..944ca16 100644
--- a/tests/test-project/supply-chain/config.toml
+++ b/tests/test-project/supply-chain/config.toml
@@ -1,6 +1,9 @@
 
 # cargo-vet config file
 
+[cargo-vet]
+version = "0.4"
+
 [policy."clap:3.1.8"]
 dependency-criteria = { atty = "safe-to-run", bitflags = ["audited", "fuzzed"] }