Repository: mozilla/cargo-vet
Pull request #223 (2022-06-23 19:32:32)

Third policies


Patch:
diff --git a/src/resolver.rs b/src/resolver.rs
index 5f4953c..750afae 100644
--- a/src/resolver.rs
+++ b/src/resolver.rs
@@ -33,12 +33,15 @@
 //!         * as with third-parties, this is done per-criteria so we can granularly blame deps
 //!     * if there is a policy.dependency_criteria, then that dep isn't inherited normally
 //!       and is instead effectively no_criteria or all_criteria based on whether it passes or not
-//!     * if there is a policy.criteria (or it's a root), then we check our inherited criteria
-//!       against that policy and then set ourselves to all_criteria or no_criteria
-//!         * **This is the check that matters!** Anything that fails this check is registered
-//!           as a "root (policy) failure" and will be fed into the blame phase.
 //!
-//! * resolve_first_party_dev: same as above, but check dev-deps and dev-policies
+//! * resolve_self_policy: if there is a policy.criteria (or it's a root), then we check
+//!   the resolved criteria against that policy
+//!     * on success, we set ourselves to all_criteria
+//!     * on failure, we set ourselves to no_criteria
+//!     * **This is the check that matters!** Anything that fails this check is registered
+//!       as a "root (policy) failure" and will be fed into the blame phase.
+//!
+//! * resolve_dev: same as above, but check dev-deps and dev-policies
 //!     * this must be done as a second pass because dev-deps can introduce cycles. by doing
 //!       all other analysis first, we can guarantee all dev-deps are fully resolved, as you
 //!       cannot actually depend on the "dev" build of a package.
@@ -1165,9 +1168,10 @@ pub fn resolve<'a>(
     for &pkgidx in &graph.topo_index {
         let package = &graph.nodes[pkgidx];
 
+        trace!("resolving {}:{}", package.name, package.version,);
+
         if package.is_third_party {
             resolve_third_party(
-                metadata,
                 store,
                 &graph,
                 &criteria_mapper,
@@ -1178,7 +1182,6 @@ pub fn resolve<'a>(
             );
         } else {
             resolve_first_party(
-                metadata,
                 store,
                 &graph,
                 &criteria_mapper,
@@ -1188,6 +1191,17 @@ pub fn resolve<'a>(
                 pkgidx,
             );
         }
+
+        // Check that any policy on our resolved value is satisfied
+        resolve_self_policy(
+            store,
+            &graph,
+            &criteria_mapper,
+            &mut results,
+            &mut violations,
+            &mut root_failures,
+            pkgidx,
+        );
     }
 
     // Now that we've processed the "normal" graph we need to check the dev (test/bench) builds.
@@ -1217,9 +1231,9 @@ pub fn resolve<'a>(
     //   check against root policies.
     for &pkgidx in &graph.topo_index {
         let package = &graph.nodes[pkgidx];
+        trace!("resolving dev {}:{}", package.name, package.version,);
         if package.is_workspace_member {
             resolve_dev(
-                metadata,
                 store,
                 &graph,
                 &criteria_mapper,
@@ -1346,9 +1360,7 @@ pub fn resolve<'a>(
     }
 }
 
-#[allow(clippy::too_many_arguments, clippy::ptr_arg)]
 fn resolve_third_party<'a>(
-    _metadata: &'a Metadata,
     store: &'a Store,
     graph: &DepGraph<'a>,
     criteria_mapper: &CriteriaMapper,
@@ -1734,6 +1746,13 @@ fn resolve_third_party<'a>(
         }
     }
 
+    trace!(
+        "  third-party validation: {:?}",
+        criteria_mapper
+            .criteria_names(&validated_criteria)
+            .collect::<Vec<_>>()
+    );
+
     // We've completed our graph analysis for this package, now record the results
     results[pkgidx] = ResolveResult {
         validated_criteria,
@@ -1909,15 +1928,13 @@ fn search_for_path<'a>(
     }
 }
 
-#[allow(clippy::too_many_arguments, clippy::ptr_arg)]
 fn resolve_first_party<'a>(
-    _metadata: &'a Metadata,
     store: &'a Store,
     graph: &DepGraph<'a>,
     criteria_mapper: &CriteriaMapper,
     results: &mut [ResolveResult<'a>],
     _violations: &mut SortedMap<PackageIdx, Vec<ViolationConflict>>,
-    root_failures: &mut RootFailures,
+    _root_failures: &mut RootFailures,
     pkgidx: PackageIdx,
 ) {
     // Check the build-deps and normal-deps of this package. dev-deps are checking in `resolve_dep`
@@ -1971,39 +1988,63 @@ fn resolve_first_party<'a>(
         }
     }
     trace!(
-        " first-party {}:{} initial validation: {:?}",
-        package.name,
-        package.version,
+        "  first-party validation: {:?}",
         criteria_mapper
             .criteria_names(&validated_criteria)
             .collect::<Vec<_>>()
     );
+
     // Save the results
     results[pkgidx].search_results = search_results;
     results[pkgidx].validated_criteria = validated_criteria;
+}
+
+fn resolve_self_policy<'a>(
+    store: &'a Store,
+    graph: &DepGraph<'a>,
+    criteria_mapper: &CriteriaMapper,
+    results: &mut [ResolveResult<'a>],
+    _violations: &mut SortedMap<PackageIdx, Vec<ViolationConflict>>,
+    root_failures: &mut RootFailures,
+    pkgidx: PackageIdx,
+) {
+    let package = &graph.nodes[pkgidx];
 
     // Now check that we pass our own policy
     let entry = store.config.policy.get(package.name);
     let own_policy = if let Some(c) = entry.and_then(|p| p.criteria.as_ref()) {
+        trace!("  explicit policy: {:?}", c);
         criteria_mapper.criteria_from_list(c)
     } else if package.is_root {
+        trace!("  root policy: {:?}", [format::DEFAULT_POLICY_CRITERIA]);
         criteria_mapper.criteria_from_list([format::DEFAULT_POLICY_CRITERIA])
     } else {
-        trace!("   has no policy, done");
+        trace!("  has no policy, done");
         // We have no policy, we're done
         return;
     };
 
     let mut policy_failures = PolicyFailures::new();
     for criteria_idx in own_policy.indices() {
-        if let SearchResult::PossiblyConnected { failed_deps } =
-            &results[pkgidx].search_results[criteria_idx]
-        {
-            for (&dep, failed_criteria) in failed_deps {
+        match &results[pkgidx].search_results[criteria_idx] {
+            SearchResult::PossiblyConnected { failed_deps } => {
+                // Our children failed us
+                for (&dep, failed_criteria) in failed_deps {
+                    policy_failures
+                        .entry(dep)
+                        .or_insert_with(|| criteria_mapper.no_criteria())
+                        .unioned_with(failed_criteria);
+                }
+            }
+            SearchResult::Disconnected { .. } => {
+                // We failed ourselves
                 policy_failures
-                    .entry(dep)
+                    .entry(pkgidx)
                     .or_insert_with(|| criteria_mapper.no_criteria())
-                    .unioned_with(failed_criteria);
+                    .set_criteria(criteria_idx)
+            }
+            SearchResult::Connected { .. } => {
+                // A-OK
             }
         }
     }
@@ -2012,19 +2053,17 @@ fn resolve_first_party<'a>(
         // We had a policy and it passed, so now we're validated for all criteria
         // because our parents can never require anything else of us. No need
         // to update search_results, they'll be masked out by validated_criteria(?)
-        trace!("   passed policy");
+        trace!("  passed policy, all_criteria");
         results[pkgidx].validated_criteria = criteria_mapper.all_criteria();
     } else {
         // We had a policy and it failed, so now we're invalid for all criteria(?)
-        trace!("   failed policy");
+        trace!("  failed policy, no_criteria");
         results[pkgidx].validated_criteria = criteria_mapper.no_criteria();
         root_failures.push((pkgidx, policy_failures, false));
     }
 }
 
-#[allow(clippy::too_many_arguments, clippy::ptr_arg)]
 fn resolve_dev<'a>(
-    _metadata: &'a Metadata,
     store: &'a Store,
     graph: &DepGraph<'a>,
     criteria_mapper: &CriteriaMapper,
@@ -2084,9 +2123,7 @@ fn resolve_dev<'a>(
         }
     }
     trace!(
-        " first-party dev {}:{} initial validation: {:?}",
-        package.name,
-        package.version,
+        "  dev validation: {:?}",
         criteria_mapper
             .criteria_names(&validated_criteria)
             .collect::<Vec<_>>()
@@ -2099,27 +2136,42 @@ fn resolve_dev<'a>(
     // Now check that we pass our own policy
     let entry = store.config.policy.get(package.name);
     let own_policy = if let Some(c) = entry.and_then(|p| p.dev_criteria.as_ref()) {
+        trace!("  explicit policy: {:?}", c);
         criteria_mapper.criteria_from_list(c)
     } else {
+        trace!("  root policy: {:?}", [format::DEFAULT_POLICY_DEV_CRITERIA]);
         criteria_mapper.criteria_from_list([format::DEFAULT_POLICY_DEV_CRITERIA])
     };
 
     let mut policy_failures = PolicyFailures::new();
     for criteria_idx in own_policy.indices() {
-        if let SearchResult::PossiblyConnected { failed_deps } = &search_results[criteria_idx] {
-            for (&dep, failed_criteria) in failed_deps {
+        match &search_results[criteria_idx] {
+            SearchResult::PossiblyConnected { failed_deps } => {
+                // Our children failed us
+                for (&dep, failed_criteria) in failed_deps {
+                    policy_failures
+                        .entry(dep)
+                        .or_insert_with(|| criteria_mapper.no_criteria())
+                        .unioned_with(failed_criteria);
+                }
+            }
+            SearchResult::Disconnected { .. } => {
+                // We failed ourselves
                 policy_failures
-                    .entry(dep)
+                    .entry(pkgidx)
                     .or_insert_with(|| criteria_mapper.no_criteria())
-                    .unioned_with(failed_criteria);
+                    .set_criteria(criteria_idx)
+            }
+            SearchResult::Connected { .. } => {
+                // A-OK
             }
         }
     }
 
     if policy_failures.is_empty() {
-        trace!("   passed dev policy");
+        trace!("  passed dev policy");
     } else {
-        trace!("   failed dev policy");
+        trace!("  failed dev policy");
         root_failures.push((pkgidx, policy_failures, true));
     }
 }


Test patch:
diff --git a/src/tests/audit_as_crates_io.rs b/src/tests/audit_as_crates_io.rs
index a8382e4..f5b9cfd 100644
--- a/src/tests/audit_as_crates_io.rs
+++ b/src/tests/audit_as_crates_io.rs
@@ -21,3 +21,179 @@ fn simple_audit_as_crates_io() {
     let output = get_audit_as_crates_io(&cfg, &store);
     insta::assert_snapshot!("simple-audit-as-crates-io", output);
 }
+
+#[test]
+fn simple_audit_as_crates_io_all_true() {
+    let _enter = TEST_RUNTIME.enter();
+
+    let mock = MockMetadata::simple();
+    let metadata = mock.metadata();
+    let (mut config, audits, imports) = builtin_files_full_audited(&metadata);
+
+    for package in &mock.packages {
+        config
+            .policy
+            .insert(package.name.to_owned(), audit_as_policy(Some(true)));
+    }
+
+    let store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+
+    let output = get_audit_as_crates_io(&cfg, &store);
+    insta::assert_snapshot!("simple-audit-as-crates-io-all-true", output);
+}
+
+#[test]
+fn simple_audit_as_crates_io_all_false() {
+    let _enter = TEST_RUNTIME.enter();
+
+    let mock = MockMetadata::simple();
+    let metadata = mock.metadata();
+    let (mut config, audits, imports) = builtin_files_full_audited(&metadata);
+
+    for package in &mock.packages {
+        config
+            .policy
+            .insert(package.name.to_owned(), audit_as_policy(Some(false)));
+    }
+
+    let store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+
+    let output = get_audit_as_crates_io(&cfg, &store);
+    insta::assert_snapshot!("simple-audit-as-crates-io-all-false", output);
+}
+
+#[test]
+fn complex_audit_as_crates_io() {
+    let _enter = TEST_RUNTIME.enter();
+
+    let mock = MockMetadata::complex();
+    let metadata = mock.metadata();
+    let (config, audits, imports) = builtin_files_full_audited(&metadata);
+    let store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+
+    let output = get_audit_as_crates_io(&cfg, &store);
+    insta::assert_snapshot!("complex-audit-as-crates-io", output);
+}
+
+#[test]
+fn complex_audit_as_crates_io_all_true() {
+    let _enter = TEST_RUNTIME.enter();
+
+    let mock = MockMetadata::complex();
+    let metadata = mock.metadata();
+    let (mut config, audits, imports) = builtin_files_full_audited(&metadata);
+
+    for package in &mock.packages {
+        config
+            .policy
+            .insert(package.name.to_owned(), audit_as_policy(Some(true)));
+    }
+
+    let store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+
+    let output = get_audit_as_crates_io(&cfg, &store);
+    insta::assert_snapshot!("complex-audit-as-crates-io-all-true", output);
+}
+
+#[test]
+fn complex_audit_as_crates_io_all_false() {
+    let _enter = TEST_RUNTIME.enter();
+
+    let mock = MockMetadata::complex();
+    let metadata = mock.metadata();
+    let (mut config, audits, imports) = builtin_files_full_audited(&metadata);
+
+    for package in &mock.packages {
+        config
+            .policy
+            .insert(package.name.to_owned(), audit_as_policy(Some(false)));
+    }
+
+    let store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+
+    let output = get_audit_as_crates_io(&cfg, &store);
+    insta::assert_snapshot!("complex-audit-as-crates-io-all-false", output);
+}
+
+#[test]
+fn complex_audit_as_crates_io_max_wrong() {
+    let _enter = TEST_RUNTIME.enter();
+
+    let mock = MockMetadata::complex();
+    let metadata = mock.metadata();
+    let (mut config, audits, imports) = builtin_files_full_audited(&metadata);
+
+    config
+        .policy
+        .insert("rootA".to_owned(), audit_as_policy(Some(true)));
+    config
+        .policy
+        .insert("rootB".to_owned(), audit_as_policy(Some(true)));
+
+    let store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+
+    let output = get_audit_as_crates_io(&cfg, &store);
+    insta::assert_snapshot!("complex-audit-as-crates-io-max-wrong", output);
+}
+
+#[test]
+fn simple_deps_audit_as_crates_io() {
+    let _enter = TEST_RUNTIME.enter();
+
+    let mock = MockMetadata::simple_deps();
+    let metadata = mock.metadata();
+    let (config, audits, imports) = builtin_files_full_audited(&metadata);
+    let store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+
+    let output = get_audit_as_crates_io(&cfg, &store);
+    insta::assert_snapshot!("simple-deps-audit-as-crates-io", output);
+}
+
+#[test]
+fn dev_detection_audit_as_crates_io() {
+    let _enter = TEST_RUNTIME.enter();
+
+    let mock = MockMetadata::dev_detection();
+    let metadata = mock.metadata();
+    let (config, audits, imports) = builtin_files_full_audited(&metadata);
+    let store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+
+    let output = get_audit_as_crates_io(&cfg, &store);
+    insta::assert_snapshot!("dev-detection-audit-as-crates-io", output);
+}
+
+#[test]
+fn haunted_audit_as_crates_io() {
+    let _enter = TEST_RUNTIME.enter();
+
+    let mock = MockMetadata::haunted_tree();
+    let metadata = mock.metadata();
+    let (config, audits, imports) = builtin_files_full_audited(&metadata);
+    let store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+
+    let output = get_audit_as_crates_io(&cfg, &store);
+    insta::assert_snapshot!("haunted-audit-as-crates-io", output);
+}
+
+#[test]
+fn cycle_audit_as_crates_io() {
+    let _enter = TEST_RUNTIME.enter();
+
+    let mock = MockMetadata::cycle();
+    let metadata = mock.metadata();
+    let (config, audits, imports) = builtin_files_full_audited(&metadata);
+    let store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+
+    let output = get_audit_as_crates_io(&cfg, &store);
+    insta::assert_snapshot!("cycle-audit-as-crates-io", output);
+}
diff --git a/src/tests/mod.rs b/src/tests/mod.rs
index 2453e0e..a00c279 100644
--- a/src/tests/mod.rs
+++ b/src/tests/mod.rs
@@ -106,8 +106,32 @@ impl MockRegistry {
     pub fn testing_cinematic_universe() -> Self {
         Self {
             packages: [
+                ("root-package", vec![reg_ver(DEFAULT_VER)]),
                 ("third-party1", vec![reg_ver(DEFAULT_VER)]),
+                ("third-party2", vec![reg_ver(DEFAULT_VER)]),
+                ("transitive-third-party1", vec![reg_ver(DEFAULT_VER)]),
                 ("first-party", vec![reg_ver(DEFAULT_VER)]),
+                ("firstA", vec![reg_ver(DEFAULT_VER)]),
+                ("firstAB", vec![reg_ver(DEFAULT_VER)]),
+                ("firstB", vec![reg_ver(DEFAULT_VER)]),
+                ("firstB-nodeps", vec![reg_ver(DEFAULT_VER)]),
+                ("thirdA", vec![reg_ver(DEFAULT_VER)]),
+                ("thirdAB", vec![reg_ver(DEFAULT_VER)]),
+                ("third-core", vec![reg_ver(DEFAULT_VER), reg_ver(5)]),
+                ("normal", vec![reg_ver(DEFAULT_VER)]),
+                ("dev", vec![reg_ver(DEFAULT_VER)]),
+                ("build", vec![reg_ver(DEFAULT_VER)]),
+                ("proc-macro", vec![reg_ver(DEFAULT_VER)]),
+                ("dev-proc-macro", vec![reg_ver(DEFAULT_VER)]),
+                ("build-proc-macro", vec![reg_ver(DEFAULT_VER)]),
+                ("dev-cycle", vec![reg_ver(DEFAULT_VER)]),
+                ("both", vec![reg_ver(DEFAULT_VER)]),
+                ("simple-dev", vec![reg_ver(DEFAULT_VER)]),
+                ("simple-dev-indirect", vec![reg_ver(DEFAULT_VER)]),
+                ("dev-cycle-direct", vec![reg_ver(DEFAULT_VER)]),
+                ("dev-cycle-indirect", vec![reg_ver(DEFAULT_VER)]),
+                ("third-normal", vec![reg_ver(DEFAULT_VER)]),
+                ("third-dev", vec![reg_ver(DEFAULT_VER)]),
             ]
             .into_iter()
             .collect(),
@@ -401,6 +425,13 @@ fn default_policy() -> PolicyEntry {
     }
 }
 
+fn audit_as_policy(audit_as_crates_io: Option<bool>) -> PolicyEntry {
+    PolicyEntry {
+        audit_as_crates_io,
+        ..default_policy()
+    }
+}
+
 fn self_policy(criteria: impl IntoIterator<Item = impl Into<CriteriaName>>) -> PolicyEntry {
     PolicyEntry {
         criteria: Some(criteria.into_iter().map(|s| s.into()).collect()),
diff --git a/src/tests/regenerate_unaudited.rs b/src/tests/regenerate_unaudited.rs
index 37044ee..0fa1f5e 100644
--- a/src/tests/regenerate_unaudited.rs
+++ b/src/tests/regenerate_unaudited.rs
@@ -434,3 +434,59 @@ fn builtin_simple_unaudited_nested_stronger_req_regenerate() {
         unaudited
     );
 }
+
+#[test]
+fn builtin_simple_audit_as_default_root_regenerate() {
+    // (Pass) the root is audit-as-crates-io with a default root policy
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, audits, imports) = builtin_files_inited(&metadata);
+
+    config
+        .policy
+        .insert("root-package".to_string(), audit_as_policy(Some(true)));
+    config.unaudited.insert(
+        "root-package".to_string(),
+        vec![unaudited(ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+    );
+
+    let mut store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+    crate::minimize_unaudited(&cfg, &mut store, None).unwrap();
+
+    let unaudited = get_unaudited(&store);
+    insta::assert_snapshot!("builtin-simple-audit-as-default-root-regenerate", unaudited);
+}
+
+#[test]
+fn builtin_simple_audit_as_weaker_root_regenerate() {
+    // (Pass) the root is audit-as-crates-io with an explicit root policy
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, audits, imports) = builtin_files_inited(&metadata);
+
+    config.policy.insert(
+        "root-package".to_string(),
+        PolicyEntry {
+            criteria: Some(vec![SAFE_TO_RUN.to_string()]),
+            ..audit_as_policy(Some(true))
+        },
+    );
+    config.unaudited.insert(
+        "root-package".to_string(),
+        vec![unaudited(ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+    );
+
+    let mut store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+    crate::minimize_unaudited(&cfg, &mut store, None).unwrap();
+
+    let unaudited = get_unaudited(&store);
+    insta::assert_snapshot!("builtin-simple-audit-as-weaker-root-regenerate", unaudited);
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__complex-audit-as-crates-io-all-false.snap b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__complex-audit-as-crates-io-all-false.snap
new file mode 100644
index 0000000..14b0b25
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__complex-audit-as-crates-io-all-false.snap
@@ -0,0 +1,5 @@
+---
+source: src/tests/audit_as_crates_io.rs
+expression: output
+---
+
diff --git a/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__complex-audit-as-crates-io-all-true.snap b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__complex-audit-as-crates-io-all-true.snap
new file mode 100644
index 0000000..c0d10f7
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__complex-audit-as-crates-io-all-true.snap
@@ -0,0 +1,13 @@
+---
+source: src/tests/audit_as_crates_io.rs
+expression: output
+---
+
+  × There are some issues with your policy.audit-as-crates-io entries
+
+Error: 
+  × some audit-as-crates-io packages don't match published crates.io versions
+  │   rootA:10.0.0
+  │   rootB:10.0.0
+  help: Remove the audit-as-crates-io entries or make them `false`
+
diff --git a/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__complex-audit-as-crates-io-max-wrong.snap b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__complex-audit-as-crates-io-max-wrong.snap
new file mode 100644
index 0000000..7453e3d
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__complex-audit-as-crates-io-max-wrong.snap
@@ -0,0 +1,20 @@
+---
+source: src/tests/audit_as_crates_io.rs
+expression: output
+---
+
+  × There are some issues with your policy.audit-as-crates-io entries
+
+Error: 
+  × Some first-party packages match published crates.io versions
+  │   firstA:10.0.0
+  │   firstAB:10.0.0
+  │   firstB:10.0.0
+  │   firstB-nodeps:10.0.0
+  help: Add a `policy.*.audit-as-crates-io` entry for them
+Error: 
+  × some audit-as-crates-io packages don't match published crates.io versions
+  │   rootA:10.0.0
+  │   rootB:10.0.0
+  help: Remove the audit-as-crates-io entries or make them `false`
+
diff --git a/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__complex-audit-as-crates-io.snap b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__complex-audit-as-crates-io.snap
new file mode 100644
index 0000000..1dfad47
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__complex-audit-as-crates-io.snap
@@ -0,0 +1,15 @@
+---
+source: src/tests/audit_as_crates_io.rs
+expression: output
+---
+
+  × There are some issues with your policy.audit-as-crates-io entries
+
+Error: 
+  × Some first-party packages match published crates.io versions
+  │   firstA:10.0.0
+  │   firstAB:10.0.0
+  │   firstB:10.0.0
+  │   firstB-nodeps:10.0.0
+  help: Add a `policy.*.audit-as-crates-io` entry for them
+
diff --git a/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__cycle-audit-as-crates-io.snap b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__cycle-audit-as-crates-io.snap
new file mode 100644
index 0000000..14b0b25
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__cycle-audit-as-crates-io.snap
@@ -0,0 +1,5 @@
+---
+source: src/tests/audit_as_crates_io.rs
+expression: output
+---
+
diff --git a/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__dev-detection-audit-as-crates-io.snap b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__dev-detection-audit-as-crates-io.snap
new file mode 100644
index 0000000..14b0b25
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__dev-detection-audit-as-crates-io.snap
@@ -0,0 +1,5 @@
+---
+source: src/tests/audit_as_crates_io.rs
+expression: output
+---
+
diff --git a/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__haunted-audit-as-crates-io.snap b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__haunted-audit-as-crates-io.snap
new file mode 100644
index 0000000..14b0b25
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__haunted-audit-as-crates-io.snap
@@ -0,0 +1,5 @@
+---
+source: src/tests/audit_as_crates_io.rs
+expression: output
+---
+
diff --git a/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__simple-audit-as-crates-io-all-false.snap b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__simple-audit-as-crates-io-all-false.snap
new file mode 100644
index 0000000..14b0b25
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__simple-audit-as-crates-io-all-false.snap
@@ -0,0 +1,5 @@
+---
+source: src/tests/audit_as_crates_io.rs
+expression: output
+---
+
diff --git a/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__simple-audit-as-crates-io-all-true.snap b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__simple-audit-as-crates-io-all-true.snap
new file mode 100644
index 0000000..14b0b25
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__simple-audit-as-crates-io-all-true.snap
@@ -0,0 +1,5 @@
+---
+source: src/tests/audit_as_crates_io.rs
+expression: output
+---
+
diff --git a/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__simple-audit-as-crates-io.snap b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__simple-audit-as-crates-io.snap
index 0662b49..90263bd 100644
--- a/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__simple-audit-as-crates-io.snap
+++ b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__simple-audit-as-crates-io.snap
@@ -7,6 +7,7 @@ expression: output
 
 Error: 
   × Some first-party packages match published crates.io versions
+  │   root-package:10.0.0
   │   first-party:10.0.0
   help: Add a `policy.*.audit-as-crates-io` entry for them
 
diff --git a/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__simple-deps-audit-as-crates-io.snap b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__simple-deps-audit-as-crates-io.snap
new file mode 100644
index 0000000..14b0b25
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__simple-deps-audit-as-crates-io.snap
@@ -0,0 +1,5 @@
+---
+source: src/tests/audit_as_crates_io.rs
+expression: output
+---
+
diff --git a/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-audit-as-default-root-regenerate.snap b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-audit-as-default-root-regenerate.snap
new file mode 100644
index 0000000..ffac36b
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-audit-as-default-root-regenerate.snap
@@ -0,0 +1,20 @@
+---
+source: src/tests/regenerate_unaudited.rs
+expression: unaudited
+---
+[[root-package]]
+version = "10.0.0"
+criteria = "safe-to-deploy"
+
+[[third-party1]]
+version = "10.0.0"
+criteria = "safe-to-deploy"
+
+[[third-party2]]
+version = "10.0.0"
+criteria = "safe-to-deploy"
+
+[[transitive-third-party1]]
+version = "10.0.0"
+criteria = "safe-to-deploy"
+
diff --git a/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-audit-as-weaker-root-regenerate.snap b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-audit-as-weaker-root-regenerate.snap
new file mode 100644
index 0000000..f2e8775
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-audit-as-weaker-root-regenerate.snap
@@ -0,0 +1,20 @@
+---
+source: src/tests/regenerate_unaudited.rs
+expression: unaudited
+---
+[[root-package]]
+version = "10.0.0"
+criteria = "safe-to-run"
+
+[[third-party1]]
+version = "10.0.0"
+criteria = "safe-to-run"
+
+[[third-party2]]
+version = "10.0.0"
+criteria = "safe-to-run"
+
+[[transitive-third-party1]]
+version = "10.0.0"
+criteria = "safe-to-run"
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-audit-as-default-root-no-audit.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-audit-as-default-root-no-audit.snap
new file mode 100644
index 0000000..9f89c55
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-audit-as-default-root-no-audit.snap
@@ -0,0 +1,16 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Failed!
+
+1 unvetted dependencies:
+  root-package:10.0.0 missing ["safe-to-deploy"]
+
+recommended audits for safe-to-deploy:
+    cargo vet inspect root-package 10.0.0  (used by )  (100 lines)
+
+estimated audit backlog: 100 lines
+
+Use |cargo vet certify| to record the audits.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-audit-as-default-root-too-weak.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-audit-as-default-root-too-weak.snap
new file mode 100644
index 0000000..9f89c55
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-audit-as-default-root-too-weak.snap
@@ -0,0 +1,16 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Failed!
+
+1 unvetted dependencies:
+  root-package:10.0.0 missing ["safe-to-deploy"]
+
+recommended audits for safe-to-deploy:
+    cargo vet inspect root-package 10.0.0  (used by )  (100 lines)
+
+estimated audit backlog: 100 lines
+
+Use |cargo vet certify| to record the audits.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-audit-as-default-root.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-audit-as-default-root.snap
new file mode 100644
index 0000000..778ec53
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-audit-as-default-root.snap
@@ -0,0 +1,8 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (1 fully audited, 3 unaudited)
+  warning: some dependencies are listed in unaudited, but didn't need it:
+    root-package:10.0.0
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-audit-as-weaker-root.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-audit-as-weaker-root.snap
new file mode 100644
index 0000000..778ec53
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-audit-as-weaker-root.snap
@@ -0,0 +1,8 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (1 fully audited, 3 unaudited)
+  warning: some dependencies are listed in unaudited, but didn't need it:
+    root-package:10.0.0
+
diff --git a/src/tests/vet.rs b/src/tests/vet.rs
index e9f1b09..d776514 100644
--- a/src/tests/vet.rs
+++ b/src/tests/vet.rs
@@ -2389,3 +2389,103 @@ fn builtin_haunted_minimal_audited() {
     let output = get_report(&metadata, report);
     insta::assert_snapshot!("builtin-haunted-minimal-audited", output);
 }
+
+#[test]
+fn builtin_simple_audit_as_default_root_no_audit() {
+    // (Fail) the root is audit-as-crates-io but has no audits
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, audits, imports) = builtin_files_inited(&metadata);
+
+    config
+        .policy
+        .insert("root-package".to_string(), audit_as_policy(Some(true)));
+
+    let store = Store::mock(config, audits, imports);
+    let report = crate::resolver::resolve(&metadata, None, &store, ResolveDepth::Shallow);
+
+    let output = get_report(&metadata, report);
+    insta::assert_snapshot!("builtin-simple-audit-as-default-root-no-audit", output);
+}
+
+#[test]
+fn builtin_simple_audit_as_default_root() {
+    // (Pass) the root is audit-as-crates-io and only needs to be safe-to-run
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, audits, imports) = builtin_files_inited(&metadata);
+
+    config
+        .policy
+        .insert("root-package".to_string(), audit_as_policy(Some(true)));
+    config.unaudited.insert(
+        "root-package".to_string(),
+        vec![unaudited(ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+    );
+
+    let store = Store::mock(config, audits, imports);
+    let report = crate::resolver::resolve(&metadata, None, &store, ResolveDepth::Shallow);
+
+    let output = get_report(&metadata, report);
+    insta::assert_snapshot!("builtin-simple-audit-as-default-root", output);
+}
+
+#[test]
+fn builtin_simple_audit_as_default_root_too_weak() {
+    // (Fail) the root is audit-as-crates-io but is only safe-to-run
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, audits, imports) = builtin_files_inited(&metadata);
+
+    config
+        .policy
+        .insert("root-package".to_string(), audit_as_policy(Some(true)));
+    config.unaudited.insert(
+        "root-package".to_string(),
+        vec![unaudited(ver(DEFAULT_VER), SAFE_TO_RUN)],
+    );
+
+    let store = Store::mock(config, audits, imports);
+    let report = crate::resolver::resolve(&metadata, None, &store, ResolveDepth::Shallow);
+
+    let output = get_report(&metadata, report);
+    insta::assert_snapshot!("builtin-simple-audit-as-default-root-too-weak", output);
+}
+
+#[test]
+fn builtin_simple_audit_as_weaker_root() {
+    // (Pass) the root is audit-as-crates-io and only needs to be safe-to-run
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, audits, imports) = builtin_files_inited(&metadata);
+
+    config.policy.insert(
+        "root-package".to_string(),
+        PolicyEntry {
+            criteria: Some(vec![SAFE_TO_RUN.to_string()]),
+            ..audit_as_policy(Some(true))
+        },
+    );
+    config.unaudited.insert(
+        "root-package".to_string(),
+        vec![unaudited(ver(DEFAULT_VER), SAFE_TO_RUN)],
+    );
+
+    let store = Store::mock(config, audits, imports);
+    let report = crate::resolver::resolve(&metadata, None, &store, ResolveDepth::Shallow);
+
+    let output = get_report(&metadata, report);
+    insta::assert_snapshot!("builtin-simple-audit-as-weaker-root", output);
+}