Repository: mozilla/cargo-vet
Pull request #384 (2023-01-20 22:57:28)

Remove uncertainty and the blaming step from the resolver
This patch does some fairly-major changes to the underlying resolver algorithm. Thanks to the removal of dependency-criteria from audits and exemptions, it's now possible to pre-compute the required criteria for every crate in the dependency graph ahead-of-time, and then validate each crate's criteria separately.

This removes the need for the blame phase, as we already know the responsible packages ahead of time, allowing these phases to be disentangled from one-another and simplified.

As there is no more uncertainty, the CriteriaFailureSet type is no longer necessary, and the output of a significant number of commands has been re-ordered to reflect that.

In addition, the algorithm page in the book has been updated to reflect the new implementation.

mystor: Yeah, the diff is certainly not the most readable, especially with the scale of the changes in parts.

Patch:
diff --git a/book/src/algorithm.md b/book/src/algorithm.md
index 069edc6..06d2b3b 100644
--- a/book/src/algorithm.md
+++ b/book/src/algorithm.md
@@ -33,14 +33,14 @@ initial summary implies:
 1. Build data structures
     1. Construct the `DepGraph`
     2. Construct the `CriteriaMapper`
-    3. Construct the `AuditGraphs` for each package (and check violations)
-2. Resolve the validated criteria for each package
-    1. Resolve third parties (crates.io)
-    2. Resolve first parties (non-crates.io)
-3. Check that policies are satisfied (find "root failures")
-    1. Check explicit self-policies and root packages
-    2. Check tests (dev-policies)
-4. Blame packages for policy failures (find "leaf failures")
+2. Determine the required criteria for each package
+    1. Apply requirements for dev-dependencies
+    2. Propagate policy requirements from roots out to leaves
+3. Resolve the validated criteria for each third party (crates.io) package
+    1. Construct the `AuditGraphs` for each package (and check violations)
+    2. Search for paths in the audit graph validating each requirement
+4. Check if each crate validates for the required criteria
+    1. Record caveats which were required in order to satisfy these criteria
 5. Suggest audits to fix leaf failures (the dance of a thousand diffs)
 
 Here in all of its glory is the entirety of the resolver algorithm today in
@@ -48,35 +48,30 @@ abbreviated pseudo-rust. Each of these steps will of course be elaborated on
 in the previous sections or subsequent sections.
 
 ```rust ,ignore
-// Step 1: Build Datastructures
-let violations = vec![];
-let root_failures = vec![];
-
 // Step 1a: Build the DepGraph
 let graph = DepGraph::new(..);
 // Step 1b: Build the CriteriaMapper
 let mapper = CriteriaMapper::new(..);
 
+// Step 2: Determine the required criteria for each package
+let requirements = resolve_requirements(..);
 
-// Analyze all the packages, ignoring dev-dependencies
-for package in &graph.topo_index {
-    // Step 2: Resolve Validated Criteria
-    if package.is_third_party {
-        // Step 2a: Compute validated criteria (also Step 1c: Build AuditGraph)
-        resolve_third_party(package, ..);
-    } else {
-        // Step 2b: Inherit validated criteria from dependencies
-        resolve_first_party(package, ..);
+// Step 3: Resolve the validated criteria for each third-party package
+for package in &graph.nodes {
+    if !package.is_third_party {
+        continue;
     }
 
-    // Step 3a: Check any policy on self, or default root policies
-    resolve_self_policy(package, ..);
-}
+    // Step 3a: Construct the AuditGraph for each package
+    let audit_graph = AuditGraph::build(..);
+    // Step 3b: Search for paths in the audit graph validating each requirement
+    let search_results = all_criteria.map(|criteria| audit_graph.search(criteria, ..));
 
-// Step 3b: Check dev-dependencies (dev-policy)
-for package in &graph.topo_index {
-    if package.is_workspace_member {
-        resolve_dev_policy(package, ..);
+    // Step 4: Check if the crate validates for the required criteria
+    for criteria in requirements[package] {
+        match &search_results[criteria] {
+            ..
+        }
     }
 }
 
@@ -86,29 +81,19 @@ if !violations.is_empty() {
 }
 
 // If there were no failures, we're done!
-if root_failures.is_empty() {
+if failures.is_empty() {
     return ResolveReport { conclusion: Conclusion::Success(..), .. };
 }
 
-// Step 4: Blame time! If there were root failures, find the leaf failures that caused them!
-let leaf_failures = visit_failures(..);
-
-// Step 5: Suggest time! Compute the simplest audits to fix the leaf failures!
+// Step 5: Suggest time! Compute the simplest audits to fix the failures!
 let suggest = compute_suggest(..);
 
 return ResolveReport { conclusion: Conclusion::FailForVet(..), .. };
 ```
 
-One perhaps surprising detail of all of this is that **analysis is inherently bottom-up**.
-We start at the leaves of your dependency tree and work our way up to the roots. As a
-result of this, we don't know any of the policies that are our actual *goals* until
-we work our way up to a node with a policy (usually a root).
-
-Only if we find root failures do we then descend back down to compute the leaves which
-are the origin of these failures, because only then do we actually know that they
-weren't good enough, and why not. However the "blame edges" that we descend are all
-precomputed during the bottom-up analysis, we're just choosing which ones to follow
-based on the required criteria.
+As we determine the required criteria in an separate pass, all analysis after
+that point can be performed in any order. Requirements analysis starts on root
+nodes and is propagated downwards to leaf nodes.
 
 
 
@@ -214,10 +199,17 @@ when we get to our desugaring. (You may have workspace members which in fact are
 but as far as `vet` is concerned those are proper packages in their own right -- those packages are
 however good candidates for a `safe-to-run` policy override.)
 
-The key property of a DAG is that if you visit every node in a topological order, then all the transitive dependencies of a node will be visited before it. You can use this fact to compute any
-property of a node which recursively depends on the properties of its dependencies. More plainly,
-you can just have a for-loop that computes the properties of each node, and blindly assume that
-any query about your dependencies will have its results already computed. Nice!
+The key property of a DAG is that if you visit every node in a topological
+order, then all the transitive dependencies of a node will be visited before it.
+You can use this fact to compute any property of a node which recursively
+depends on the properties of its dependencies. More plainly, you can just have a
+for-loop that computes the properties of each node, and blindly assume that any
+query about your dependencies will have its results already computed. Nice!
+
+In our algorithm, however, we actually visit in reverse-topological order, so
+that we know all reverse-dependencies of a node will be visited before it. This
+is because criteria requirements are inherited by reverse-dependency, (or pushed
+out from a crate to its dependencies).
 
 With that established, here is the *actual* approach we use to emulate the "fake" node desugaring:
 
@@ -244,6 +236,11 @@ for node in topological_sort:
 
 The second loop is essentially handling all the "fake" dev nodes.
 
+Note that when we run this in a reversed manner to ensure that
+reverse-dependencies have been checked before a crate is visited, we need to do
+the dev-dependency analysis first, as the dev-dependency "fake" nodes are
+effectively appended to the topological sort.
+
 
 
 ## The DepGraph's Contents
@@ -293,11 +290,11 @@ The resolver primarily operates on CriteriaSets, which are sets of CriteriaIndic
 The purpose of this is to try to handle all the subtleties of criteria in one place
 to avoid bugs, and to make everything more efficient.
 
-Most of the resolver's operations are things like "union these criteria sets" or 
+Most of the resolver's operations are things like "union these criteria sets" or
 "check if this criteria set is a superset of the required one".
 
 There is currently an artificial maximum limit of 64 criteria for you and all your
-imports to make CriteriaSets effecient (they're just a u64 internally). 
+imports to make CriteriaSets effecient (they're just a u64 internally).
 The code is designed to allow this limit to be easily raised if anyone ever hits it
 (either with a u128 or a proper BitSet).
 
@@ -308,7 +305,7 @@ where both criteria imply eachother).
 This makes a criteria like safe-to-deploy *actually* safe-to-deploy AND safe-to-run
 in most situations. The CriteriaMapper will precompute the [transitive closure][] of
 implies relationships for each criteria as a CriteriaSet. When mapping the name of
-a criteria to CriteriaIndices, this CriteriaSet is the thing returned. 
+a criteria to CriteriaIndices, this CriteriaSet is the thing returned.
 
 When mapping a criteria set to a list of criteria names, we will add `import_name::`
 in front of any imported criteria. So if you import a "fuzzed" criteria from "google",
@@ -352,7 +349,33 @@ a criteria got added to a criteria set (which is happening constantly in the res
 
 
 
-# Step 1c: The AuditGraph
+# Step 2: Determine the required criteria for each package
+
+In general, every package requires that all dependencies satisfy the same
+criteria which were required for the original package. This is handled by
+starting at the root crates, and propagating the required `CriteriaSet` outwards
+towards the leaves. In some cases, the `policy` table will specify alternative
+criteria to place as a requirement on dependencies, which will be used instead
+of normal propagation.
+
+In order to avoid the cyclic nature of dev-deps, these targets are handled
+first. As all dependencies of dev-dependencies are normal dependencies, we can
+rely on the normal non-cyclic requirement propagation after the first edge, so
+we only need to apply the requirements one-level deep in this first phase. By
+default, this requirement is `safe-to-run`, though it cna be customized through
+the `policy`.
+
+Afterwards, we start at the root crate in the graph and work outwards, checking
+if we need to apply policy requirements, and then propagating requirements to
+dependencies. This results in every crate having a corresponding `CritseriaSet`
+of the criteria required for the audit.
+
+
+
+
+
+
+# Step 3a: The AuditGraph
 
 The AuditGraph is the graph of all audits for a particular package *name*.
 The nodes of the graph are [Version][]s and the edges are delta audits (e.g. `0.1.0 -> 0.2.0`).
@@ -384,54 +407,6 @@ Target Version along edges that certify that criteria. Suggesting an audit simil
 finding the "best" edge to add to make the Root and Target connected for the desired criteria.
 
 
-## Dependency Criteria
-
-dependency_criteria are the source of basically all complexity in cargo-vet, and why
-the resolver isn't completely precise when blaming packages for errors, and therefore
-suggesting fixes for errors.
-
-When an edge (audit/exemption) has explicit dependency_criteria, the edge is only
-valid (traversable when searching for a path) if the dependency satisfies that criteria.
-
-The absence of a dependency_criteria for a dependency is *almost* equivalent to
-the certified criteria, but is more powerful than that. This is because audits are
-considered "decomposable" into audits for each of their individual criteria, including
-inherited criteria.
-
-So for instance, if an audit claimed `["safe-to-deploy", "fuzzed"]`
-then this is equivalent to three separate audits for "safe-to-deploy", "safe-to-run",
-and "fuzzed". This distinction doesn't matter with explicit dependency criteria,
-but with implicit dependency criteria this means that if some of your dependencies
-are only "safe-to-run", the edge will still be valid for certifying "safe-to-run".
-
-We originally considered requiring you to be explicit about this and manually
-make 3 different audits, but we couldn't think of any particular realistic situations
-where this wasn't desirable (and you can use explicit dependency criteria if you
-don't want this behaviour).
-
-## The Fundamental Imprecision Of The Resolver
-
-If the search for a path ever reaches an edge that has the desired criteria but isn't valid,
-because of dependency criteria, this is noted for the purposes of the blaming step.
-
-This is the fundamental imprecision of resolving: at best it's difficult to say why
-a path doesn't exist, and at worse it's genuinely ambiguous. You could have two
-possible paths with different edges failing for different dependencies. Fixing either
-one would work, so which one do we recommend? This is only made more complicated by
-the possibility of a path that requires multiple edges to be fixed with
-various different dependencies and criteria.
-
-To be completely conservative, the resolver generally just takes the union of
-every problem it finds and recommends you fix them all. In the vast majority of
-cases this will be perfectly precise, (in particular, I believe this will always
-be precise if you only use implicit dependency_criteria). Only in situations
-where there are multiple possible paths and explicit dependency_criteria
-will we start conservatively recommending potentially excessive things.
-
-Also if there's no possible path regardless of dependency_criteria, any
-audits we recommend for dependencies have to in some sense be a guess, because
-the way you resolve this package can change the requirements for your dependencies.
-
 ## Checking Violations
 
 During AuditGraph construction violations are also checked. Violations have a [VersionReq][] and
@@ -479,9 +454,9 @@ enough of a problem we may eventually add such a feature.
 
 
 
-# Step 2a: Resolving Third Parties (Analyzing Audits)
+# Step 3b: Searching for paths in the `AuditGraph`
 
-A lot of the heavy lifting for this task is in Step 1c (AuditGraph).
+A lot of the heavy lifting for this task is in Step 3a (AuditGraph).
 
 Trying to validate all criteria at once is slightly brain-melty (because
 different criteria may be validated by different paths), so as a simplifying
@@ -491,7 +466,7 @@ describe happens in a for loop).
 If all we care about is finding out if a package has some criteria, then all
 we need to do is run depth-first-search ([DFS][]) from the Root Node and see if it reaches
 the Target Node, with the constraint that we'll only follow edges that are
-valid (based on the already validated criteria of our dependencies). 
+valid (based on the already validated criteria of our dependencies).
 
 If it does, we've validated the criteria for the Target Version. If it doesn't,
 then we haven't.
@@ -502,7 +477,6 @@ about the state of the audits:
 * Did this validation require an exemption? (Is it fully audited?)
 * Did this validation even use any audits? (Is it at least partially audited?)
 * Did this validation need any new imports? (Should we update imports.lock?)
-* If we failed, was there a possible path? (Should we blame our deps for our failure?)
 * What nodes were reachable from the Root and reverse-reachable from the Target? (candidates for suggest)
 
 This is accomplished by running the search off of a priority queue, rather than
@@ -525,32 +499,24 @@ whether something is "fully audited" when updating imports, as subsequent
 runs won't get this far. We think this is worth the upside of minimizing
 imports.lock updates.)
 
-If any of those succeed, then we return SearchResult::Connected and the
-criteria is unioned into the this package's validated_criteria.
-
-If none of that worked, then we start allowing ourselves to follow *invalid*
-edges (edges which exist but have unsatisfied dependency_criteria). If we
-manage to find a path with those edges, then in some sense we are "blameless"
-for our failure, and we return SearchResult::PossiblyConnected with a list
-of the failed edges. During blaming (Step 4) we will use these results
-to compute leaf failures.
+If any of those succeed, then we return Ok(..), communicating both that the
+package validates this criteria, plus any caveats back to the caller.
 
-If even invalid edges are insufficient, then we will return
-SearchResult::Disconnected and consider ourselves fundamentally to blame
-for our failures, and this node will be a leaf failure if blaming reaches
-it with this required criteria.
+Otherwise, we'll return Err(..), and consider the current node to blame. If this
+criteria is required, this package will require additional audits or exemptions
+to successfully vet.
 
 In doing this, we also compute the nodes that are reachable from the Root
 Version and the nodes that are reverse-reachable from the Target Version.
 The latter is computed by following all edges backwards, which is to say
-in Step 1c we actually build another copy of the AuditGraph, with the edges
+in Step 3a the AuditGraph also contains another directed graph with the edges
 all reversed, and rerun the algorithm with Root and Target reversed.
 
-This information is useful because in the Disconnected case we want
-to suggest a diff to audit, and any diff from the Root Reachable nodes
-to the Target Reachable nodes is sufficient.
+This information is useful because in the Err case we want to suggest a diff to
+audit, and any diff from the Root Reachable nodes to the Target Reachable nodes
+is sufficient.
 
-All SearchResults are stored in the ResolveResult for a node along with
+All search results are stored in the ResolveResult for a node along with
 validated criteria and other fun facts we found along the way. The
 contents of the ResolveResult will be used by our reverse-dependencies
 in steps 2 and 3.
@@ -561,181 +527,31 @@ It *does* make it possible for there to be cycles in the AuditGraph, but
 [DFS][] doesn't care about cycles at all since you keep track of nodes you've
 visited to avoid revisits (slightly complicated by us iteratively introducing edges).
 
-Note that when checking dependencies, dependencies that are dev-only
-are ignored (this only matters for workspace members).
-
-
-# Step 2b: Resolving First Parties (Inheriting Audits)
-
-First parties (non-crates.io packages) simply "inherit" the intersection of the
-validated criteria of all their dependencies. If they have no dependencies then
-the become validated for *all* criteria by default. 
-
-If they have a dependency_criteria in their policy then that dependency is
-treated as either having all_criteria or no_criteria based on whether it passed
-the dependency_criteria. This is equivalent to how explicit dependency_criteria
-are handled in step 2a, and self-policies are handled in step 3a.
-
-If a criteria isn't in the intersection, then we record the dependencies that
-failed to satisfy this condition as a SearchResult::PossiblyConnected, as
-first parties are *never* to blame. It's those dang peasant third-parties! 
-
-Note that when checking dependencies, dependencies that are dev-only
-are ignored (this only matters for workspace members).
-
-...That's it! Everything's a lot easier when audits aren't involved!
-
-
-
-
-# Step 3a: Checking Self-Policies
-
-Any package which is a root of the DepGraph or has a `policy.criteria` needs to
-check their validated criteria against that self_policy (if it's only a root,
-then the default root policy, safe-to-deploy, is used). If the policy is satisfied
-(`validated_criteria.contains(self_policy)`), then everything's fine.
-
-If the policy fails, then this node becomes a "root failure" (as in, it's a root
-of the "failure/blame graph", it doesn't have to be a root in the DepGraph,
-although it's usually also that). When registering the root failure, we record
-which criteria were missing, which will be used in blaming (Step 4).
-
-It's worth noting that if a package has an explicit `policy.criteria`, then
-its reverse-dependencies (parents) can never make any demands of it. This is necessary
-to allow a self_policy to be either *weaker* or *stronger* than the requirements
-of the reverse-dependency.
-
-To further indicate this, when checking a self_policy we either set the node's
-validated criteria to all_criteria or no_criteria.
-
-It's also worth noting that, due to audit-as-crates-io and `[patch]`
-declarations, you can end up in a situation where a third-party depends on a
-first-party, or third-parties have `policy.criteria` entries. This is why
-Step 3a is interleaved with Step 2.
-
-
-
-
-# Step 3b: Checking Dev-Policies (Tests)
-
-This is the one and only place where we consider dev-dependencies, and happens
-strictly after the primary loop that processes Step 2, and 3a. In this step
-we're validating the "fake" test node that's required to break cycles, as 
-discussed in Step 1a.
-
-We essentially repeat the steps of 2b here, but include *all* dependencies,
-where in 2b we ignored dev-dependencies. The resulting dev_validated_criteria
-is then checked against the dev_policy for this node.
-
-If the node has a policy.dev-criteria then that's the dev_policy. Otherwise
-it gets the default dev_policy, safe-to-run. If the dev_policy is satisfied
-(`dev_validated_criteria.contains(dev_policy)`), then we silently continue
-on. Unlike in Step 3a we don't update the validated_criteria.
-
-If the node fails the dev_policy, then we register the "root failure" as in
-Step 3a.
-
-(It's perhaps notable that we recheck the normal dependencies of the package,
-and don't use the validated_criteria of the package itself. I don't *think*
-this really matters but there's an argument that this is semantically wrong,
-as the "fake" node depends on the "real" node. But the "fake" node also
-contains all the same code as the "real" node and the only source of
-divergence is a self_policy, which will handle its own root failure reporting.
-I think the only situation where this could matter is if the dev_policy is *stronger*
-than the self_policy, which feels like... An Incorrect Decision.)
-
-
-
-# Step 4: Blaming Children For Our Problems
-
-If there are any "root failures" recorded by 3a or 3b, then we need to
-descend down the "blame graph" to find the dependencies that *caused*
-this failure (the "leaf failures"). Blame The Children!
-
-The blame graph is something we've already implicitly constructed.
-The nodes of the blame graph are the ResolveResults for each package
-(populated in Step 2), and the edges of the blame graph are SearchResults
-inside those ResolveResults which have SearchResult::PossiblyConnected
-for the criteria that we are trying to blame. Any node which has
-SearchResult::Disconnected for the criteria we're interested in is a leaf.
-
-At a high level, the idea is to run depth-first-search ([DFS][]) from the
-root failures and report any leaves we reach.
-
-However this is complicated by two factors:
-
-* Our traversals have criteria associated with them, so the notion of
-"visited" must keep which blame-criteria a node has been visited with
-
-* To get as much information as possible at once, we want to speculatively
-blame "deeper" than a leaf. This may be useful if e.g. you update both of
-serde and serde_derive at once, and therefore need audits for both, even
-if the latter only appears as a dependency of the former.
-
-To handle these problems, we use augmented CriteriaSets -- CriteriaFailureSets.
-These contain *two* criteria sets, "confident" and "all". Each search path
-originates from a "root failure" and has a CriteriaFailureSet for the missing
-criteria that caused that failure.
-
-Initially, all search paths have the same values for "confident" and "all".
-However, whenever a search path speculatively pushes "deeper" than a leaf,
-that part of the path is marked as a "guess" and any node visited from there
-will only mark "all".
-
-When a search path reaches a node, we union the current CriteriaFailureSet
-into the visited entry for that node. If this doesn't change the value
-of the CriteriaFailureSet then visiting it won't change anything and
-we don't need to perform the visit. We also refuse to visit any node
-which was itself a root failure, as this indicates they had a self-policy
-and are therefore immune to parent demands.
-
-The edges that a search path will try to follow are the SearchResult::PossiblyConnected
-entries for the criteria this search path is currently trying to blame for
-(it's CriteriaFailureSet). Explicit dependency_criteria may modify the blame
-criteria, as for instance if we're blaming for "safe-to-deploy" but a dependency
-explicitly only needed to be "safe-to-run" we don't want to claim that it should
-have been "safe-to-deploy".
 
-If a search path reaches a node that has some SearchResult::Disconnected entries
-then we record that overlap as a leaf failure (unioning into a CriteriaFailureSet
-for that node's leaf failures).
 
-This is where the guessing is performed. We assume any audit you add to fix
-this package will only have default dependency_criteria, and therefore any
-dependency that *also* doesn't have any of the leaf's blamed criteria will
-cause a cascading failure. We push the search path onto those nodes as if
-there was a PartiallyConnected entry for them, and then mark those search
-heads as "guesses" as disucssed above.
+# Step 4: Checking if each crate validates for the required criteria
 
-This is *probably* correct, and is perfectly precise in the happy path where
-no one ever uses custom dependency_criteria. But the auditor may add any
-explicit dependency_criteria they please, invalidating our guess.
+This step is a fairly trivial combination of the results from Step 2 (computing
+requirements) and Step 3 (resolving validated criteria) - for each package, we
+check if the validated criteria is a superset of the requirements, and if it is
+then we're successful, otherwise we're not.
 
-Now the *reason* we do all this careful work to track whether things are
-guesses or not is so that, when we're done all our searching, we can determine
-if all the blames for a leaf failure are "confident" (whether "confident" == "all").
-We always report "all" to the end-user, but we de-emphasize any result
-that isn't completely confident, indicating that they should prefer resolving
-the fully confident (parent) failures first, because it might change the suggestion
-(or completely eliminate the failure!).
-
-The guesses are useful for helping the user gauge how much work they have
-ahead of them, and let us have *something* to use if they disregard our
-recommendation and decide they want to work bottom up and start `certify`ing
-those packages.
+We'll record which criteria failed so we can suggest better audits in the
+errored case, and combine the caveats from successful runs in the success case
+to get a combined result for each crate, rather than for each individual
+criteria.
 
 
 
 # Step 5: Suggesting Audits (Death By A Thousand Diffs)
 
-This step takes the "blamed" "leaf failures" from Step 4 and recommends
-audits that will fix them. In Step 2a we compute the Root Reachable Nodes
-and the Target Reachable Nodes for a SearchResult::Disconnected package.
-In this phase we use those as candidates and try to find the best possible
-diff audit.
+This step takes the failed packages from Step 4 and recommends audits that will
+fix them. In Step 3b we compute the Root Reachable Nodes and the Target
+Reachable Nodes for a disconnected package.  In this phase we use those as
+candidates and try to find the best possible diff audit.
 
 More specifically, we use the intersection of all the Root Reachable Nodes
-for every criteria this package was blamed for (ditto for Target Reachable).
+for every criteria this package failed (ditto for Target Reachable).
 By using the intersection, any diff we recommend from one set to the other
 is guaranteed to cover all required criteria, allowing us to suggest a single
 diff to fix everything. Since the Root and Target are always in their respective
@@ -786,4 +602,4 @@ Root Version.
 [guppy]: https://docs.rs/guppy/latest/guppy/
 [topological sort]: https://en.wikipedia.org/wiki/Topological_sorting
 [transitive closure]: https://en.wikipedia.org/wiki/Transitive_closure
-[DFS]: https://en.wikipedia.org/wiki/Depth-first_search
\ No newline at end of file
+[DFS]: https://en.wikipedia.org/wiki/Depth-first_search
diff --git a/src/errors.rs b/src/errors.rs
index 92853bf..311dca6 100644
--- a/src/errors.rs
+++ b/src/errors.rs
@@ -84,6 +84,8 @@ pub enum VersionParseError {
     Semver(#[from] semver::Error),
     #[error("unrecognized revision type, expected 'git:' prefix")]
     UnknownRevision,
+    #[error("unrecognized git hash, expected 40 hex digits")]
+    InvalidGitHash,
 }
 
 ///////////////////////////////////////////////////////////
diff --git a/src/format.rs b/src/format.rs
index 354e326..aef46ab 100644
--- a/src/format.rs
+++ b/src/format.rs
@@ -68,10 +68,14 @@ impl VetVersion {
     pub fn parse(s: &str) -> Result<Self, VersionParseError> {
         if let Some((ver, rev)) = s.split_once('@') {
             if let Some(hash) = rev.trim_start().strip_prefix("git:") {
-                Ok(VetVersion {
-                    semver: ver.trim_end().parse()?,
-                    git_rev: Some(hash.to_owned()),
-                })
+                if hash.len() != 40 || !hash.bytes().all(|b| b.is_ascii_hexdigit()) {
+                    Err(VersionParseError::InvalidGitHash)
+                } else {
+                    Ok(VetVersion {
+                        semver: ver.trim_end().parse()?,
+                        git_rev: Some(hash.to_owned()),
+                    })
+                }
             } else {
                 Err(VersionParseError::UnknownRevision)
             }
@@ -753,3 +757,42 @@ pub struct JsonPackage {
     /// Version of the package
     pub version: VetVersion,
 }
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn vet_version_parsing() {
+        assert_eq!(
+            VetVersion::parse("1.0.0").unwrap(),
+            VetVersion {
+                semver: "1.0.0".parse().unwrap(),
+                git_rev: None
+            }
+        );
+
+        assert_eq!(
+            VetVersion::parse("1.0.1@git:00112233445566778899aabbccddeeff00112233").unwrap(),
+            VetVersion {
+                semver: "1.0.1".parse().unwrap(),
+                git_rev: Some("00112233445566778899aabbccddeeff00112233".into())
+            }
+        );
+
+        match VetVersion::parse("1.0.1@git:00112233445566778899aabbccddeeff0011223g") {
+            Err(VersionParseError::InvalidGitHash) => (),
+            _ => panic!("expected invalid git hash"),
+        }
+
+        match VetVersion::parse("1.0.1@git:00112233") {
+            Err(VersionParseError::InvalidGitHash) => (),
+            _ => panic!("expected invalid git hash"),
+        }
+
+        match VetVersion::parse("1.0.1@pijul:00112233") {
+            Err(VersionParseError::UnknownRevision) => (),
+            _ => panic!("expected unknown revision"),
+        }
+    }
+}
diff --git a/src/resolver.rs b/src/resolver.rs
index 104bc36..91134be 100644
--- a/src/resolver.rs
+++ b/src/resolver.rs
@@ -1,15 +1,16 @@
 //! The Resolver is the heart of cargo-vet, and does all the work to validate the audits
-//! for your current packages and to suggest fixes. This is done in 3 phases:
+//! for your current packages and to suggest fixes. This is done in 4 phases:
 //!
-//! 1. Validating the audits against your policies
-//! 2. Blaming packages for failed policies
+//! 1. Resolving required criteria based on your policies
+//! 2. Searching for audits which satisfy the required criteria
 //! 3. Suggesting audits that would make your project pass validation
 //!
 //! # High-level Usage
 //!
-//! * [`resolve`] is the main entry point, Validating and Blaming and producing a [`ResolveReport`]
+//! * [`resolve`] is the main entry point, Validating and Searching and producing a [`ResolveReport`]
 //! * [`ResolveReport::compute_suggest`] does Suggesting and produces a [`Suggest`]
 //! * various methods on [`ResolveReport`] and [`Suggest`] handle printing
+//! * [`regenerate_exemptions`] handles automatically minimizing and generating exemptions
 //!
 //! # Low-level Design
 //!
@@ -21,86 +22,62 @@
 //!       and includes a special topological sorting of the packages that prioritizes the normal
 //!       build over the dev build (it's complicated...)
 //!
-//! * resolve_third_party: for each third-party package, resolve what criteria it's audited for
-//!     * compute the [`AuditGraph`] and check for violations
-//!     * for each criteria, search_for_path (check if it has a connected path in the audit graph)
-//!         * if it does, then we have validated that it has that criteria
-//!         * if it doesn't, but only because dependency_criteria, blame those dependencies
-//!         * otherwise, blame ourselves and note the reachable nodes from root and target
-//!
-//! * resolve_first_party: inherit third-party criteria from normal/build deps, check policies
-//!     * first-parties "inherit" the intersection of all their dependencies' validated criteria
-//!         * 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
-//!
-//! * 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.
-//!
-//!
-//!
-//! ## Blame
-//!
-//! * take the "root failures" and descend back down the DepGraph as a tree, following
-//!   every package's "blames" until we hit packages that blame themselves. Packages that
-//!   blame themselves are "leaf failures" and are will be the basis for `suggest`
+//! * resolve_requirements: for each package, resolve what criteria it needs to be audited for
+//!     * start with root targets and propagate requirements out towards leaf crates
+//!     * policies can override requirements on the target crate and its dependencies
 //!
+//! * resolve_audits: for each package, resolve what criteria it's audited for
+//!     * compute the [`AuditGraph`] and check for violations
+//!     * for each criteria, search the for a connected path in the audit graph
+//!     * check if the criteria are satisfied
+//!         * if they are, record caveats which were required for the criteria
+//!         * if they aren't record the criteria which failed, and how to fix them
 //!
 //!
 //! ## Suggest
 //!
-//! * take the blame and do a huge pile of diffstats on the reachable versions
-//!   (from search_for_path) to figure out which audits to recommend for which criteria
+//! * enumerate the failures and perform a number of diffstats based on the
+//!   existing set of criteria, to suggest the best audit and criteria which could
+//!   be used to allow the crate to vet successfully.
 
 use cargo_metadata::{DependencyKind, Metadata, Node, PackageId};
-use core::fmt;
 use futures_util::future::join_all;
 use miette::IntoDiagnostic;
 use serde::{Deserialize, Serialize};
 use std::cmp::Reverse;
 use std::collections::BinaryHeap;
-use std::mem;
 use std::sync::Arc;
+use std::{fmt, mem};
 use tracing::{error, trace, trace_span};
 
+use crate::cli::{DumpGraphArgs, GraphFilter, GraphFilterProperty, GraphFilterQuery};
 use crate::errors::{RegenerateExemptionsError, SuggestError};
 use crate::format::{
-    self, AuditKind, AuditsFile, CriteriaName, CriteriaStr, Delta, DiffStat, ExemptedDependency,
-    ImportName, JsonPackage, JsonReport, JsonReportConclusion, JsonReportFailForVet,
-    JsonReportFailForViolationConflict, JsonReportSuccess, JsonSuggest, JsonSuggestItem,
-    JsonVetFailure, PackageName, PackageStr, PolicyEntry, RemoteImport, VetVersion, SAFE_TO_DEPLOY,
-    SAFE_TO_RUN,
+    self, AuditEntry, AuditKind, AuditsFile, CriteriaEntry, CriteriaName, CriteriaStr, Delta,
+    DiffStat, ExemptedDependency, FastMap, FastSet, ImportName, JsonPackage, JsonReport,
+    JsonReportConclusion, JsonReportFailForVet, JsonReportFailForViolationConflict,
+    JsonReportSuccess, JsonSuggest, JsonSuggestItem, JsonVetFailure, PackageName, PackageStr,
+    PolicyEntry, RemoteImport, VetVersion, SAFE_TO_DEPLOY, SAFE_TO_RUN,
 };
-use crate::format::{FastMap, FastSet, SortedMap, SortedSet};
+use crate::format::{SortedMap, SortedSet};
 use crate::network::Network;
 use crate::out::{progress_bar, IncProgressOnDrop, Out};
-use crate::{
-    AuditEntry, Cache, Config, CriteriaEntry, DumpGraphArgs, GraphFilter, GraphFilterProperty,
-    GraphFilterQuery, PackageExt, Store,
-};
+use crate::storage::Cache;
+use crate::{Config, PackageExt, Store};
 
-/// A report of the results of running `resolve`.
-#[derive(Debug, Clone)]
 pub struct ResolveReport<'a> {
     /// The Cargo dependency graph as parsed and understood by cargo-vet.
     ///
     /// All [`PackageIdx`][] values are indices into this graph's nodes.
     pub graph: DepGraph<'a>,
+
     /// Mappings between criteria names and CriteriaSets/Indices.
     pub criteria_mapper: CriteriaMapper,
 
-    /// Low-level results for each package's individual criteria resolving analysis,
-    /// indexed by [`PackageIdx`][].
-    pub results: Vec<ResolveResult>,
+    /// Low-level results for each package's individual criteria resolving
+    /// analysis, indexed by [`PackageIdx`][]. Will be `None` for first-party
+    /// crates or crates with violation conflicts.
+    pub results: Vec<Option<ResolveResult<'a>>>,
 
     /// The final conclusion of our analysis.
     pub conclusion: Conclusion,
@@ -127,13 +104,13 @@ pub struct Success {
 
 #[derive(Debug, Clone)]
 pub struct FailForViolationConflict {
-    pub violations: SortedMap<PackageIdx, Vec<ViolationConflict>>,
+    pub violations: Vec<(PackageIdx, Vec<ViolationConflict>)>,
 }
 
 #[derive(Debug, Clone)]
 pub struct FailForVet {
     /// These packages are to blame and need to be fixed
-    pub failures: SortedMap<PackageIdx, AuditFailure>,
+    pub failures: Vec<(PackageIdx, AuditFailure)>,
     pub suggest: Option<Suggest>,
 }
 
@@ -163,7 +140,7 @@ pub struct Suggest {
 #[derive(Debug, Clone)]
 pub struct SuggestItem {
     pub package: PackageIdx,
-    pub suggested_criteria: CriteriaFailureSet,
+    pub suggested_criteria: CriteriaSet,
     pub suggested_diff: DiffRecommendation,
     pub notable_parents: String,
 }
@@ -295,77 +272,39 @@ pub struct DepGraph<'a> {
 
 /// Results and notes from running vet on a particular package.
 #[derive(Debug, Clone)]
-pub struct ResolveResult {
-    /// The set of criteria we validated for this package.
-    pub validated_criteria: CriteriaSet,
-    /// Individual search results for each criteria.
-    pub search_results: Vec<SearchResult>,
-    /// Whether there was an exemption for this exact version.
-    pub directly_exempted: bool,
+pub struct ResolveResult<'a> {
+    /// Graph used to compute audits for this package.
+    pub audit_graph: AuditGraph<'a>,
+    /// Cache of search results for each criteria.
+    pub search_results: Vec<Result<Caveats, SearchFailure>>,
 }
 
-pub type PolicyFailures = SortedMap<PackageIdx, CriteriaSet>;
-/// (FailedPackage, Failures, is_dev)
-pub type RootFailures = Vec<(PackageIdx, PolicyFailures, bool)>;
-
 #[derive(Debug, Clone)]
 pub struct AuditFailure {
-    pub criteria_failures: CriteriaFailureSet,
+    pub criteria_failures: CriteriaSet,
 }
 
-/// The possible results of search for an audit chain for a Criteria
+/// Value indicating a failure to find a path in the audit graph between two nodes.
 #[derive(Debug, Clone)]
-pub enum SearchResult {
-    /// We found a path, criteria validated.
-    Connected {
-        /// Caveats which were required to build the audit chain for this
-        /// Criteria.
-        caveats: Caveats,
-    },
-    /// We failed to find a *proper* path, criteria not valid, but adding in failing
-    /// edges caused by our dependencies not meeting criteria created a connection!
-    /// If you fix these dependencies then we should validate this criteria!
-    PossiblyConnected {
-        /// The dependencies that failed on some edges (blame them).
-        /// This is currently overbroad in corner cases where there are two possible
-        /// paths blocked by two different dependencies and so only fixing one would
-        /// actually be sufficient, but, whatever.
-        failed_deps: SortedMap<PackageIdx, CriteriaSet>,
-    },
-    /// We failed to find any path, criteria not valid.
-    Disconnected {
-        /// Nodes we could reach from "root"
-        reachable_from_root: SortedSet<Option<VetVersion>>,
-        /// Nodes we could reach from the "target"
-        ///
-        /// We will only ever fill in the other one, but on failure we run the algorithm
-        /// in reverse and will merge that result into this value.
-        reachable_from_target: SortedSet<Option<VetVersion>>,
-    },
+pub struct SearchFailure {
+    /// Nodes we could reach from "root"
+    pub reachable_from_root: SortedSet<Option<VetVersion>>,
+    /// Nodes we could reach from the "target"
+    pub reachable_from_target: SortedSet<Option<VetVersion>>,
 }
 
-// NOTE: There's probably a more efficient representation for these in the
-// general case.
 /// Caveats which apply to the results of an audit.
 #[derive(Debug, Clone, Default)]
 pub struct Caveats {
-    /// The set of packages which required exemptions in order to successfully
-    /// audit.
-    pub needed_exemptions: SortedSet<PackageIdx>,
-
-    /// The set of packages which required fresh imports in order to
-    /// successfully audit.
-    pub needed_fresh_imports: SortedSet<PackageIdx>,
+    /// The audit was certified directly because of an exemption.
+    pub directly_exempted: bool,
+    /// The audit required at least one exemption.
+    pub needed_exemptions: bool,
+    /// The audit required at least one fresh import.
+    pub needed_fresh_imports: bool,
 }
 
-impl Caveats {
-    /// Union the given caveat set with this set of caveats, mutating `self`.
-    fn add(&mut self, other: &Caveats) {
-        self.needed_exemptions.extend(&other.needed_exemptions);
-        self.needed_fresh_imports
-            .extend(&other.needed_fresh_imports);
-    }
-}
+type DirectedAuditGraph<'a> = SortedMap<Option<&'a VetVersion>, Vec<DeltaEdge<'a>>>;
 
 /// A graph of the audits for a package.
 ///
@@ -392,7 +331,11 @@ impl Caveats {
 /// we can't find the desired path in the forward graph, and is used to compute the
 /// reachability set of the target version for that criteria. That reachability is
 /// used for `suggest`.
-pub type AuditGraph<'a> = SortedMap<Option<&'a VetVersion>, Vec<DeltaEdge<'a>>>;
+#[derive(Debug, Clone)]
+pub struct AuditGraph<'a> {
+    forward_audits: DirectedAuditGraph<'a>,
+    backward_audits: DirectedAuditGraph<'a>,
+}
 
 #[derive(Debug, Copy, Clone, Eq, PartialEq)]
 enum DeltaEdgeOrigin {
@@ -413,7 +356,7 @@ enum DeltaEdgeOrigin {
 /// depending on if we're searching from "roots" (forward) or the target (backward).
 /// The source isn't included because that's implicit in the Node.
 #[derive(Debug, Clone)]
-pub struct DeltaEdge<'a> {
+struct DeltaEdge<'a> {
     /// The version this edge goes to.
     version: Option<&'a VetVersion>,
     /// The criteria that this edge is valid for.
@@ -618,17 +561,6 @@ impl CriteriaMapper {
     ) {
         set.unioned_with(&self.implied_criteria[self.index[namespace][criteria]])
     }
-    pub fn clear_criteria(&self, set: &mut CriteriaFailureSet, criteria: CriteriaStr) {
-        self.clear_namespaced_criteria(set, &CriteriaNamespace::Local, criteria)
-    }
-    pub fn clear_namespaced_criteria(
-        &self,
-        set: &mut CriteriaFailureSet,
-        namespace: &CriteriaNamespace,
-        criteria: CriteriaStr,
-    ) {
-        set.clear_criteria(&self.implied_criteria[self.index[namespace][criteria]])
-    }
     /// An iterator over every criteria in order, with 'implies' fully applied.
     ///
     /// This includes any foreign criteria that has been eliminated as redundant.
@@ -656,9 +588,6 @@ impl CriteriaMapper {
     pub fn all_criteria(&self) -> CriteriaSet {
         CriteriaSet::_all(self.len())
     }
-    pub fn no_criteria_failures(&self) -> CriteriaFailureSet {
-        CriteriaFailureSet::none(self.len())
-    }
 
     /// Like [`CriteriaSet::indices`] but uses knowledge of things like
     /// `implies` relationships to remove redundant information. For
@@ -690,30 +619,6 @@ impl CriteriaMapper {
         self.minimal_indices(criteria)
             .map(|idx| &*self.list[idx].namespaced_name)
     }
-
-    pub fn all_criteria_names<'a>(
-        &'a self,
-        criteria: &'a CriteriaFailureSet,
-    ) -> impl Iterator<Item = CriteriaStr<'a>> + 'a {
-        self.criteria_names(criteria.all())
-    }
-
-    pub fn confident_criteria_names<'a>(
-        &'a self,
-        criteria: &'a CriteriaFailureSet,
-    ) -> impl Iterator<Item = CriteriaStr<'a>> + 'a {
-        self.criteria_names(criteria.confident())
-    }
-
-    pub fn unconfident_criteria_names<'a>(
-        &'a self,
-        criteria: &'a CriteriaFailureSet,
-    ) -> impl Iterator<Item = CriteriaStr<'a>> + 'a {
-        // Filter criteria present in `confident()`
-        self.minimal_indices(criteria.all())
-            .filter(|idx| !criteria.confident().has_criteria(*idx))
-            .map(|idx| &*self.list[idx].namespaced_name)
-    }
 }
 
 impl CriteriaSet {
@@ -770,10 +675,6 @@ impl CriteriaSet {
             }
         })
     }
-    /// Clear all the bits in the set
-    fn clear(&mut self) {
-        self.0 = 0;
-    }
 }
 
 impl fmt::Debug for CriteriaSet {
@@ -782,70 +683,6 @@ impl fmt::Debug for CriteriaSet {
     }
 }
 
-impl CriteriaFailureSet {
-    pub fn none(count: usize) -> Self {
-        CriteriaFailureSet {
-            confident: CriteriaSet::none(count),
-            all: CriteriaSet::none(count),
-        }
-    }
-    pub fn from(criteria: &CriteriaSet, confident: bool) -> Self {
-        CriteriaFailureSet {
-            confident: if confident {
-                criteria.clone()
-            } else {
-                // Kinda jank but lets us copy the capacity without knowing it
-                let mut set = criteria.clone();
-                set.clear();
-                set
-            },
-            all: criteria.clone(),
-        }
-    }
-    pub fn set_criteria(&mut self, idx: usize, confident: bool) {
-        self.all.set_criteria(idx);
-        if confident {
-            self.confident.set_criteria(idx);
-        }
-    }
-    pub fn clear_criteria(&mut self, other: &CriteriaSet) {
-        self.confident.clear_criteria(other);
-        self.all.clear_criteria(other);
-    }
-    pub fn unioned_with(&mut self, other: &CriteriaFailureSet) {
-        self.all.unioned_with(&other.all);
-        self.confident.unioned_with(&other.confident);
-    }
-    pub fn contains(&self, other: &CriteriaFailureSet) -> bool {
-        self.all.contains(&other.all) && self.confident.contains(&other.confident)
-    }
-    pub fn is_empty(&self) -> bool {
-        self.all.is_empty()
-    }
-    pub fn is_fully_confident(&self) -> bool {
-        self.confident.contains(&self.all)
-    }
-    pub fn is_fully_unconfident(&self) -> bool {
-        self.confident.is_empty()
-    }
-    pub fn all(&self) -> &CriteriaSet {
-        &self.all
-    }
-    pub fn confident(&self) -> &CriteriaSet {
-        &self.confident
-    }
-}
-
-impl ResolveResult {
-    fn with_no_criteria(empty: CriteriaSet) -> Self {
-        Self {
-            validated_criteria: empty,
-            search_results: vec![],
-            directly_exempted: false,
-        }
-    }
-}
-
 impl<'a> DepGraph<'a> {
     pub fn new(
         metadata: &'a Metadata,
@@ -1325,7 +1162,9 @@ pub fn resolve<'a>(
     );
     trace!("built CriteriaMapper!");
 
-    let (results, conclusion) = resolve_core(&graph, store, &criteria_mapper);
+    let requirements = resolve_requirements(&graph, &store.config.policy, &criteria_mapper);
+
+    let (results, conclusion) = resolve_audits(&graph, store, &criteria_mapper, &requirements);
 
     ResolveReport {
         graph,
@@ -1335,602 +1174,410 @@ pub fn resolve<'a>(
     }
 }
 
-fn resolve_core(
+fn resolve_requirements(
     graph: &DepGraph<'_>,
-    store: &Store,
+    policy: &SortedMap<PackageName, PolicyEntry>,
     criteria_mapper: &CriteriaMapper,
-) -> (Vec<ResolveResult>, Conclusion) {
-    let _resolve_span = trace_span!("validate").entered();
-
-    // This uses the same indexing pattern as graph.resolve_index_by_pkgid
-    let mut results =
-        vec![ResolveResult::with_no_criteria(criteria_mapper.no_criteria()); graph.nodes.len()];
+) -> Vec<CriteriaSet> {
+    let _resolve_requirements = trace_span!("resolve_requirements").entered();
 
-    let mut root_failures = RootFailures::new();
-    let mut violations = SortedMap::new();
-    let mut root_caveats = Caveats::default();
+    let mut requirements = vec![criteria_mapper.no_criteria(); graph.nodes.len()];
 
-    // Actually vet the build graph
-    for &pkgidx in &graph.topo_index {
-        let package = &graph.nodes[pkgidx];
+    // For any packages which have dev-dependencies, apply policy-specified
+    // dependency-criteria or dev-criteria to those dependencies.
+    for package in &graph.nodes {
+        if package.dev_deps.is_empty() {
+            continue;
+        }
 
-        trace!("resolving {}:{}", package.name, package.version,);
-
-        if package.is_third_party {
-            resolve_third_party(
-                store,
-                graph,
-                criteria_mapper,
-                &mut results,
-                &mut violations,
-                &mut root_failures,
-                pkgidx,
-            );
+        let policy = policy.get(package.name);
+        let dev_criteria = if let Some(c) = policy.and_then(|p| p.dev_criteria.as_ref()) {
+            criteria_mapper.criteria_from_list(c)
         } else {
-            resolve_first_party(
-                store,
-                graph,
-                criteria_mapper,
-                &mut results,
-                &mut violations,
-                &mut root_failures,
-                pkgidx,
-            );
-        }
+            criteria_mapper.criteria_from_list([format::DEFAULT_POLICY_DEV_CRITERIA])
+        };
 
-        // Check that any policy on our resolved value is satisfied
-        resolve_self_policy(
-            store,
-            graph,
-            criteria_mapper,
-            &mut results,
-            &mut violations,
-            &mut root_failures,
-            &mut root_caveats,
-            pkgidx,
-        );
+        for &depidx in &package.dev_deps {
+            let dep_package = &graph.nodes[depidx];
+            let dependency_criteria = policy
+                .and_then(|policy| policy.dependency_criteria.get(dep_package.name))
+                .map(|criteria| criteria_mapper.criteria_from_list(criteria));
+            requirements[depidx]
+                .unioned_with(dependency_criteria.as_ref().unwrap_or(&dev_criteria));
+        }
     }
 
-    // Now that we've processed the "normal" graph we need to check the dev (test/bench) builds.
-    // This needs to be done as a separate pass because tests can introduce apparent cycles in
-    // the Cargo graph, because our X's tests can depend on Y which in turn depend on X again.
-    //
-    // This is fine for Cargo because the tests are a completely different build from X itself
-    // so the cycle completely disappears once the graph is "desugarred" into actual build units,
-    // but it's an annoying problem for us because it essentially means we cannot fully analyze
-    // X in one shot. However we have a few useful insights we can leverage:
-    //
-    // * Nothing can "depend" on X's tests, and therefore ignoring the tests on our first pass
-    //   shouldn't logically affect any other node's results. i.e. UsesX being safe-to-run
-    //   does not depend on X's tests being safe-to-run. If we were to desugar X into two nodes
-    //   as Cargo does, the "dev" node would always be a root, so we should treat it as such
-    //   during this second pass.
-    //
-    // * We don't actually *care* if a node *has* tests/benches. All we care about is if
-    //   the node has dev-dependencies, because that's the only thing that could change the
-    //   results of the previous pass. (TODO: is this actually true? Root nodes get special
-    //   default policies, so maybe all nodes that are testable should get rescanned? But
-    //   also tests are expected to have weaker requirements... hmm...)
-    //
-    // * We will only ever test/bench a workspace member, and workspace members are always
-    //   first-party packages. This means we can avoid thinking about all the complicated
-    //   graph search stuff and just need to do simple one-shot analysis of our deps and
-    //   check against root policies.
-    for &pkgidx in &graph.topo_index {
+    // Walk the topo graph in reverse, so that we visit each package before any
+    // dependencies.
+    for &pkgidx in graph.topo_index.iter().rev() {
         let package = &graph.nodes[pkgidx];
-        trace!("resolving dev {}:{}", package.name, package.version,);
-        if package.is_workspace_member {
-            resolve_dev(
-                store,
-                graph,
-                criteria_mapper,
-                &mut results,
-                &mut violations,
-                &mut root_failures,
-                &mut root_caveats,
-                pkgidx,
-            );
-        } else {
-            assert!(
-                package.dev_deps.is_empty(),
-                "{}:{} isn't a workspace member but has dev-deps!",
-                package.name,
-                package.version
+        let policy = policy.get(package.name);
+
+        if let Some(c) = policy.and_then(|p| p.criteria.as_ref()) {
+            // If we specify a policy on ourselves, override any requirements we've
+            // had placed on us by reverse-dependencies.
+            requirements[pkgidx] = criteria_mapper.criteria_from_list(c);
+        } else if package.is_root {
+            // If this is a root crate, it will require at least
+            // `DEFAULT_POLICY_CRITERIA` by default, unless overridden.
+            requirements[pkgidx].unioned_with(
+                &criteria_mapper.criteria_from_list([format::DEFAULT_POLICY_CRITERIA]),
             );
         }
-    }
+        let normal_criteria = requirements[pkgidx].clone();
 
-    // If there were violations, report that
-    if !violations.is_empty() {
-        return (
-            results,
-            Conclusion::FailForViolationConflict(FailForViolationConflict { violations }),
-        );
+        // For each dependency, elaborate the dependency criteria from the configured policy and add it to the dependency requirements.
+        for &depidx in &package.normal_and_build_deps {
+            let dep_package = &graph.nodes[depidx];
+            let dependency_criteria = policy
+                .and_then(|policy| policy.dependency_criteria.get(dep_package.name))
+                .map(|criteria| criteria_mapper.criteria_from_list(criteria));
+            requirements[depidx]
+                .unioned_with(dependency_criteria.as_ref().unwrap_or(&normal_criteria));
+        }
     }
-    _resolve_span.exit();
-    let _blame_span = trace_span!("blame").entered();
 
-    // There weren't any violations, so now compute the final failures by pushing blame
-    // down from the roots to the leaves that caused those failures.
-    let mut failures = SortedMap::<PackageIdx, AuditFailure>::new();
-    visit_failures(
-        graph,
-        criteria_mapper,
-        &results,
-        &root_failures,
-        |failure, depth, own_failure| {
-            if let Some(criteria_failures) = own_failure {
-                trace!(
-                    " {:width$}blaming: {}:{} for {:?} + {:?}",
-                    "",
-                    graph.nodes[failure].name,
-                    graph.nodes[failure].version,
-                    criteria_mapper
-                        .confident_criteria_names(criteria_failures)
-                        .collect::<Vec<_>>(),
-                    criteria_mapper
-                        .unconfident_criteria_names(criteria_failures)
-                        .collect::<Vec<_>>(),
-                    width = depth
-                );
-                failures
-                    .entry(failure)
-                    .or_insert_with(|| AuditFailure {
-                        criteria_failures: criteria_mapper.no_criteria_failures(),
-                    })
-                    .criteria_failures
-                    .unioned_with(criteria_failures);
+    requirements
+}
+
+fn resolve_audits<'a>(
+    graph: &DepGraph<'_>,
+    store: &'a Store,
+    criteria_mapper: &CriteriaMapper,
+    requirements: &[CriteriaSet],
+) -> (Vec<Option<ResolveResult<'a>>>, Conclusion) {
+    let _resolve_audits = trace_span!("resolve_audits").entered();
+    let mut violations = Vec::new();
+    let mut failures = Vec::new();
+    let mut vetted_with_exemptions = Vec::new();
+    let mut vetted_partially = Vec::new();
+    let mut vetted_fully = Vec::new();
+    let mut needed_fresh_imports = SortedSet::new();
+    let results: Vec<_> = requirements
+        .iter()
+        .enumerate()
+        .map(|(pkgidx, required_criteria)| {
+            let package = &graph.nodes[pkgidx];
+            if !package.is_third_party {
+                return None; // first-party crates don't need audits
+            }
+
+            let audit_graph = AuditGraph::build(store, criteria_mapper, package.name)
+                .map_err(|v| violations.push((pkgidx, v)))
+                .ok()?;
+
+            // NOTE: We currently always compute all search results even if we
+            // only need those in `req_criteria` because some later passes using
+            // the resolver results might need that information. We might want
+            // to look into simplifying this in the future.
+            let search_results: Vec<_> = (0..criteria_mapper.len())
+                .map(|criteria_idx| audit_graph.search(criteria_idx, &package.version))
+                .collect();
+
+            let mut caveats = Caveats::default();
+            let mut criteria_failures = criteria_mapper.no_criteria();
+            for criteria_idx in required_criteria.indices() {
+                match &search_results[criteria_idx] {
+                    Ok(criteria_caveats) => {
+                        caveats.directly_exempted |= criteria_caveats.directly_exempted;
+                        caveats.needed_exemptions |= criteria_caveats.needed_exemptions;
+                        caveats.needed_fresh_imports |= criteria_caveats.needed_fresh_imports;
+                    }
+                    Err(_) => criteria_failures.set_criteria(criteria_idx),
+                }
             }
-            Ok::<(), ()>(())
-        },
-    )
-    .unwrap();
-
-    // There should always be leaf failures if there were root failures!
-    assert_eq!(
-        root_failures.is_empty(),
-        failures.is_empty(),
-        "failure blaming system bugged out"
-    );
 
-    // If there are any failures, report that
-    if !failures.is_empty() {
-        return (
-            results,
-            Conclusion::FailForVet(FailForVet {
-                failures,
-                suggest: None,
-            }),
-        );
-    }
+            if !criteria_failures.is_empty() {
+                failures.push((pkgidx, AuditFailure { criteria_failures }));
+            }
 
-    // Ok, we've actually completely succeeded! Gather up stats on that success.
-    let mut vetted_with_exemptions = vec![];
-    let mut vetted_partially = vec![];
-    let mut vetted_fully = vec![];
-    for &pkgidx in &graph.topo_index {
-        let package = &graph.nodes[pkgidx];
-        if !package.is_third_party {
-            // We only want to report on third-parties.
-            continue;
-        }
-        let result = &results[pkgidx];
+            // XXX: Callers using these fields in success should perhaps be
+            // changed to instead walk the results?
+            if !caveats.needed_exemptions {
+                vetted_fully.push(pkgidx);
+            } else if caveats.directly_exempted {
+                vetted_with_exemptions.push(pkgidx);
+            } else {
+                vetted_partially.push(pkgidx);
+            }
+            if caveats.needed_fresh_imports {
+                needed_fresh_imports.insert(pkgidx);
+            }
 
-        if !root_caveats.needed_exemptions.contains(&pkgidx) {
-            vetted_fully.push(pkgidx);
-        } else if result.directly_exempted {
-            vetted_with_exemptions.push(pkgidx);
-        } else {
-            vetted_partially.push(pkgidx);
-        }
-    }
+            Some(ResolveResult {
+                audit_graph,
+                search_results,
+            })
+        })
+        .collect();
 
-    (
-        results,
+    let conclusion = if !violations.is_empty() {
+        Conclusion::FailForViolationConflict(FailForViolationConflict { violations })
+    } else if !failures.is_empty() {
+        Conclusion::FailForVet(FailForVet {
+            failures,
+            suggest: None,
+        })
+    } else {
         Conclusion::Success(Success {
             vetted_with_exemptions,
             vetted_partially,
             vetted_fully,
-            needed_fresh_imports: root_caveats.needed_fresh_imports,
-        }),
-    )
+            needed_fresh_imports,
+        })
+    };
+
+    (results, conclusion)
 }
 
-fn resolve_third_party(
-    store: &Store,
-    graph: &DepGraph<'_>,
-    criteria_mapper: &CriteriaMapper,
-    results: &mut [ResolveResult],
-    violations: &mut SortedMap<PackageIdx, Vec<ViolationConflict>>,
-    _root_failures: &mut RootFailures,
-    pkgidx: PackageIdx,
-) {
-    let package = &graph.nodes[pkgidx];
-    let exemptions = store.config.exemptions.get(package.name);
-
-    // Get custom policies for our dependencies
-    let dep_criteria = store
-        .config
-        .policy
-        .get(package.name)
-        .map(|policy| {
-            policy
-                .dependency_criteria
-                .iter()
-                .map(|(dep_name, criteria)| {
-                    (&**dep_name, criteria_mapper.criteria_from_list(criteria))
-                })
-                .collect::<FastMap<_, _>>()
-        })
-        .unwrap_or_default();
-
-    // Pre-build the namespaces for each audit so that we can take a reference
-    // to each one as-needed rather than cloning the name each time.
-    let foreign_namespaces: Vec<CriteriaNamespace> = store
-        .imported_audits()
-        .keys()
-        .map(|import_name| CriteriaNamespace::Foreign(import_name.clone()))
-        .collect();
+impl<'a> AuditGraph<'a> {
+    /// Given the store, and a package name, builds up an audit graph. This can
+    /// then be searched in order to find a specific path which satisfies a
+    /// given criteria.
+    pub fn build(
+        store: &'a Store,
+        criteria_mapper: &CriteriaMapper,
+        package: PackageStr<'_>,
+    ) -> Result<Self, Vec<ViolationConflict>> {
+        // Pre-build the namespaces for each audit so that we can take a reference
+        // to each one as-needed rather than cloning the name each time.
+        let foreign_namespaces: Vec<CriteriaNamespace> = store
+            .imported_audits()
+            .keys()
+            .map(|import_name| CriteriaNamespace::Foreign(import_name.clone()))
+            .collect();
 
-    // Each of our own audits should be put into the "local" criteria namespace.
-    let own_audits = store
-        .audits
-        .audits
-        .get(package.name)
-        .map(|v| &v[..])
-        .unwrap_or(&[])
-        .iter()
-        .map(|audit| (&CriteriaNamespace::Local, audit));
+        // Each of our own audits should be put into the "local" criteria namespace.
+        let own_audits = store
+            .audits
+            .audits
+            .get(package)
+            .map(|v| &v[..])
+            .unwrap_or(&[])
+            .iter()
+            .map(|audit| (&CriteriaNamespace::Local, audit));
+
+        // Each foreign audit should be put into a "foreign" criteria namespace.
+        let foreign_audits =
+            store
+                .imported_audits()
+                .values()
+                .enumerate()
+                .flat_map(|(idx, audits)| {
+                    let namespace = &foreign_namespaces[idx];
+                    audits
+                        .audits
+                        .get(package)
+                        .map(|v| &v[..])
+                        .unwrap_or(&[])
+                        .iter()
+                        .map(move |audit| (namespace, audit))
+                });
 
-    // Each foreign audit should be put into a "foreign" criteria namespace.
-    let foreign_audits = store
-        .imported_audits()
-        .values()
-        .enumerate()
-        .flat_map(|(idx, audits)| {
-            let namespace = &foreign_namespaces[idx];
-            audits
-                .audits
-                .get(package.name)
-                .map(|v| &v[..])
-                .unwrap_or(&[])
-                .iter()
-                .map(move |audit| (namespace, audit))
-        });
+        let all_audits = own_audits.chain(foreign_audits);
+        let exemptions = store.config.exemptions.get(package);
 
-    let all_audits = own_audits.chain(foreign_audits);
-
-    // See AuditGraph's docs for details on the lowering we do here
-    let mut forward_audits = AuditGraph::new();
-    let mut backward_audits = AuditGraph::new();
-    let mut violation_nodes = Vec::new();
-
-    // Collect up all the deltas, and their criteria
-    for (namespace, entry) in all_audits.clone() {
-        // For uniformity, model a Full Audit as `None -> x.y.z`
-        let (from_ver, to_ver) = match &entry.kind {
-            AuditKind::Full { version } => (None, version),
-            AuditKind::Delta { from, to } => (Some(from), to),
-            AuditKind::Violation { .. } => {
-                violation_nodes.push((namespace.clone(), entry));
-                continue;
-            }
-        };
+        let mut forward_audits = DirectedAuditGraph::new();
+        let mut backward_audits = DirectedAuditGraph::new();
+        let mut violation_nodes = Vec::new();
 
-        let criteria = criteria_mapper.criteria_from_namespaced_entry(namespace, entry);
+        // Collect up all the deltas, and their criteria
+        for (namespace, entry) in all_audits.clone() {
+            // For uniformity, model a Full Audit as `None -> x.y.z`
+            let (from_ver, to_ver) = match &entry.kind {
+                AuditKind::Full { version } => (None, version),
+                AuditKind::Delta { from, to } => (Some(from), to),
+                AuditKind::Violation { .. } => {
+                    violation_nodes.push((namespace.clone(), entry));
+                    continue;
+                }
+            };
 
-        let origin = if entry.is_fresh_import {
-            DeltaEdgeOrigin::FreshImportedAudit
-        } else {
-            DeltaEdgeOrigin::StoredAudit
-        };
+            let criteria = criteria_mapper.criteria_from_namespaced_entry(namespace, entry);
 
-        forward_audits.entry(from_ver).or_default().push(DeltaEdge {
-            version: Some(to_ver),
-            criteria: criteria.clone(),
-            origin,
-        });
-        backward_audits
-            .entry(Some(to_ver))
-            .or_default()
-            .push(DeltaEdge {
-                version: from_ver,
-                criteria,
+            let origin = if entry.is_fresh_import {
+                DeltaEdgeOrigin::FreshImportedAudit
+            } else {
+                DeltaEdgeOrigin::StoredAudit
+            };
+
+            forward_audits.entry(from_ver).or_default().push(DeltaEdge {
+                version: Some(to_ver),
+                criteria: criteria.clone(),
                 origin,
             });
-    }
-
-    // Reject forbidden packages (violations)
-    for (violation_source, violation_entry) in &violation_nodes {
-        // Ok this is kind of weird. We want to reject any audits which contain any of these criteria.
-        // Normally we would slap all the criteria in this entry into a set and do some kind of set
-        // comparison, but that's not quite right. Here are the cases we want to work:
-        //
-        // * violation: safe-to-deploy, audit: safe-to-deploy -- ERROR!
-        // * violation: safe-to-deploy, audit: safe-to-run    -- OK!
-        // * violation: safe-to-run,    audit: safe-to-deploy -- ERROR!
-        // * violation: [a, b],         audit: [a, c]         -- ERROR!
-        //
-        // The first 3 cases are correctly handled by audit.contains(violation)
-        // but the last one isn't. I think the correct solution to this is to
-        // *for each individual entry in the violation* do audit.contains(violation).
-        // If any of those queries trips, then it's an ERROR.
-        //
-        // Note that this would also more correctly handle [safe-to-deploy, safe-to-run]
-        // as a violation entry because it would effectively become safe-to-run instead
-        // of safe-to-deploy, which is the correct and desirable behaviour!
-        //
-        // So here we make a criteria set for each entry in the violation.
-        let violation_criterias = violation_entry
-            .criteria
-            .iter()
-            .map(|c| criteria_mapper.criteria_from_namespaced_list(violation_source, [&c]))
-            .collect::<Vec<_>>();
-        let violation_range = if let AuditKind::Violation { violation } = &violation_entry.kind {
-            violation
-        } else {
-            unreachable!("violation_entry wasn't a Violation?");
-        };
+            backward_audits
+                .entry(Some(to_ver))
+                .or_default()
+                .push(DeltaEdge {
+                    version: from_ver,
+                    criteria,
+                    origin,
+                });
+        }
 
-        // Note if this entry conflicts with any exemptions
+        // Exempted entries are equivalent to full-audits
         if let Some(alloweds) = exemptions {
             for allowed in alloweds {
-                let audit_criteria = criteria_mapper.criteria_from_list(&allowed.criteria);
-                let has_violation = violation_criterias
-                    .iter()
-                    .any(|v| audit_criteria.contains(v));
-                if !has_violation {
-                    continue;
-                }
-                if violation_range.matches(&allowed.version) {
-                    violations.entry(pkgidx).or_default().push(
-                        ViolationConflict::UnauditedConflict {
+                let from_ver = None;
+                let to_ver = Some(&allowed.version);
+                let criteria = criteria_mapper.criteria_from_list(&allowed.criteria);
+
+                // For simplicity, turn 'exemptions' entries into deltas from None.
+                forward_audits.entry(from_ver).or_default().push(DeltaEdge {
+                    version: to_ver,
+                    criteria: criteria.clone(),
+                    origin: DeltaEdgeOrigin::Exemption,
+                });
+                backward_audits.entry(to_ver).or_default().push(DeltaEdge {
+                    version: from_ver,
+                    criteria,
+                    origin: DeltaEdgeOrigin::Exemption,
+                });
+            }
+        }
+
+        // Reject forbidden packages (violations)
+        let mut violations = Vec::new();
+        for (violation_source, violation_entry) in &violation_nodes {
+            // Ok this is kind of weird. We want to reject any audits which contain any of these criteria.
+            // Normally we would slap all the criteria in this entry into a set and do some kind of set
+            // comparison, but that's not quite right. Here are the cases we want to work:
+            //
+            // * violation: safe-to-deploy, audit: safe-to-deploy -- ERROR!
+            // * violation: safe-to-deploy, audit: safe-to-run    -- OK!
+            // * violation: safe-to-run,    audit: safe-to-deploy -- ERROR!
+            // * violation: [a, b],         audit: [a, c]         -- ERROR!
+            //
+            // The first 3 cases are correctly handled by audit.contains(violation)
+            // but the last one isn't. I think the correct solution to this is to
+            // *for each individual entry in the violation* do audit.contains(violation).
+            // If any of those queries trips, then it's an ERROR.
+            //
+            // Note that this would also more correctly handle [safe-to-deploy, safe-to-run]
+            // as a violation entry because it would effectively become safe-to-run instead
+            // of safe-to-deploy, which is the correct and desirable behaviour!
+            //
+            // So here we make a criteria set for each entry in the violation.
+            let violation_criterias = violation_entry
+                .criteria
+                .iter()
+                .map(|c| criteria_mapper.criteria_from_namespaced_list(violation_source, [&c]))
+                .collect::<Vec<_>>();
+            let violation_range = if let AuditKind::Violation { violation } = &violation_entry.kind
+            {
+                violation
+            } else {
+                unreachable!("violation_entry wasn't a Violation?");
+            };
+
+            // Note if this entry conflicts with any exemptions
+            if let Some(alloweds) = exemptions {
+                for allowed in alloweds {
+                    let audit_criteria = criteria_mapper.criteria_from_list(&allowed.criteria);
+                    let has_violation = violation_criterias
+                        .iter()
+                        .any(|v| audit_criteria.contains(v));
+                    if !has_violation {
+                        continue;
+                    }
+                    if violation_range.matches(&allowed.version) {
+                        violations.push(ViolationConflict::UnauditedConflict {
                             violation_source: violation_source.clone(),
                             violation: (*violation_entry).clone(),
                             exemptions: allowed.clone(),
-                        },
-                    );
+                        });
+                    }
                 }
             }
-        }
 
-        // Note if this entry conflicts with any audits
-        for (namespace, audit) in all_audits.clone() {
-            let audit_criteria = criteria_mapper.criteria_from_namespaced_entry(namespace, audit);
-            let has_violation = violation_criterias
-                .iter()
-                .any(|v| audit_criteria.contains(v));
-            if !has_violation {
-                continue;
-            }
-            match &audit.kind {
-                AuditKind::Full { version, .. } => {
-                    if violation_range.matches(version) {
-                        violations.entry(pkgidx).or_default().push(
-                            ViolationConflict::AuditConflict {
+            // Note if this entry conflicts with any audits
+            for (namespace, audit) in all_audits.clone() {
+                let audit_criteria =
+                    criteria_mapper.criteria_from_namespaced_entry(namespace, audit);
+                let has_violation = violation_criterias
+                    .iter()
+                    .any(|v| audit_criteria.contains(v));
+                if !has_violation {
+                    continue;
+                }
+                match &audit.kind {
+                    AuditKind::Full { version, .. } => {
+                        if violation_range.matches(version) {
+                            violations.push(ViolationConflict::AuditConflict {
                                 violation_source: violation_source.clone(),
                                 violation: (*violation_entry).clone(),
                                 audit_source: namespace.clone(),
                                 audit: audit.clone(),
-                            },
-                        );
+                            });
+                        }
                     }
-                }
-                AuditKind::Delta { from, to, .. } => {
-                    if violation_range.matches(from) || violation_range.matches(to) {
-                        violations.entry(pkgidx).or_default().push(
-                            ViolationConflict::AuditConflict {
+                    AuditKind::Delta { from, to, .. } => {
+                        if violation_range.matches(from) || violation_range.matches(to) {
+                            violations.push(ViolationConflict::AuditConflict {
                                 violation_source: violation_source.clone(),
                                 violation: (*violation_entry).clone(),
                                 audit_source: namespace.clone(),
                                 audit: audit.clone(),
-                            },
-                        );
+                            });
+                        }
+                    }
+                    AuditKind::Violation { .. } => {
+                        // don't care
                     }
-                }
-                AuditKind::Violation { .. } => {
-                    // don't care
                 }
             }
         }
 
-        // FIXME: this kind of violation is annoying to catch, but you kind of don't have to.
-        //
-        // It's impossible to validate a package with some criteria without an audit/exemptions
-        // entry with that criteria (or a criteria that implies it) touching that version.
-        // Therefore we can catch any "true" violations by just looking at the AuditGraph's
-        // edges as we do above. However if you current version is a violation and but doesn't
-        // have an audit, we may suggest an audit that *is* a violation. This is bad.
-        //
-        // This commented out code stands as a memorial to that problem for later.
-
-        /*
-        // Note if this entry conflicts with the current package's version
-        if violation_range.matches(package.version) {
-            violations
-                .entry(pkgidx)
-                .or_default()
-                .push(ViolationConflict::CurVersionConflict {
-                    source: violation_source.clone(),
-                    violation: (*violation_entry).clone(),
-                });
+        // If we enountered any violations, report them.
+        if !violations.is_empty() {
+            return Err(violations);
         }
-        */
-    }
-
-    let mut directly_exempted = false;
-    // Unaudited entries are equivalent to full-audits
-    if let Some(alloweds) = exemptions {
-        for allowed in alloweds {
-            if allowed.version == package.version {
-                directly_exempted = true;
-            }
-            let from_ver = None;
-            let to_ver = Some(&allowed.version);
-            let criteria = criteria_mapper.criteria_from_list(&allowed.criteria);
 
-            // For simplicity, turn 'exemptions' entries into deltas from None.
-            forward_audits.entry(from_ver).or_default().push(DeltaEdge {
-                version: to_ver,
-                criteria: criteria.clone(),
-                origin: DeltaEdgeOrigin::Exemption,
-            });
-            backward_audits.entry(to_ver).or_default().push(DeltaEdge {
-                version: from_ver,
-                criteria,
-                origin: DeltaEdgeOrigin::Exemption,
-            });
-        }
+        Ok(AuditGraph {
+            forward_audits,
+            backward_audits,
+        })
     }
 
-    let mut validated_criteria = criteria_mapper.no_criteria();
-    let mut search_results = vec![];
-    for criteria in criteria_mapper.all_criteria_iter() {
-        let result = search_for_path(
-            criteria,
-            None,
-            Some(&package.version),
-            &forward_audits,
-            graph,
-            criteria_mapper,
-            results,
-            pkgidx,
-            &package.normal_and_build_deps,
-            &dep_criteria,
-        );
-        match result {
-            SearchResult::Connected { caveats } => {
-                // We found a patch to satisfy this criteria.
-                validated_criteria.unioned_with(criteria);
-                search_results.push(SearchResult::Connected { caveats });
-            }
-            SearchResult::PossiblyConnected { failed_deps } => {
-                // We failed but found a possible solution if our dependencies were better.
-                // Just forward this along so that we can blame them if it comes up!
-                search_results.push(SearchResult::PossiblyConnected { failed_deps });
-            }
-            SearchResult::Disconnected {
-                reachable_from_root,
-                ..
-            } => {
-                // We failed to find a path, boo! Run the algorithm backwards to see what we
-                // can reach from the other side, so we have our candidates for suggestions.
-                let rev_result = search_for_path(
-                    criteria,
-                    Some(&package.version),
-                    None,
-                    &backward_audits,
-                    graph,
-                    criteria_mapper,
-                    results,
-                    pkgidx,
-                    &package.normal_and_build_deps,
-                    &dep_criteria,
-                );
-                if let SearchResult::Disconnected {
-                    reachable_from_root: reachable_from_target,
-                    ..
-                } = rev_result
-                {
-                    search_results.push(SearchResult::Disconnected {
-                        reachable_from_root,
-                        reachable_from_target,
-                    })
-                } else {
-                    unreachable!("We managed to find a path but only from one direction?!");
+    /// Search for a path in this AuditGraph which indicates that the given
+    /// version of the crate satisfies the given criteria. Returns the `Caveats`
+    /// which were required for that proof if successful, and information about
+    /// the versions which could be reached from both the target and root if
+    /// unsuccessful.
+    pub fn search(
+        &self,
+        criteria_idx: usize,
+        version: &VetVersion,
+    ) -> Result<Caveats, SearchFailure> {
+        search_for_path(&self.forward_audits, criteria_idx, None, Some(version)).map_err(
+            |reachable_from_root| {
+                // The search failed, perform the search backwards in order to
+                // also get the set of nodes reachable from the target. We can
+                // `unwrap_err()` here, as we'll definitely fail.
+                let reachable_from_target =
+                    search_for_path(&self.backward_audits, criteria_idx, Some(version), None)
+                        .unwrap_err();
+                SearchFailure {
+                    reachable_from_root,
+                    reachable_from_target,
                 }
-            }
-        }
-    }
-
-    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,
-        search_results,
-        directly_exempted,
-    };
-}
-
-/// Updates `caveats`, and `failed_deps` to include any caveats or failed
-/// dependencies involved in requiring `dependencies` to satisfy the given
-/// criteria.
-#[allow(clippy::too_many_arguments)]
-fn get_dependency_criteria_caveats(
-    dep_graph: &DepGraph,
-    criteria_mapper: &CriteriaMapper,
-    results: &[ResolveResult],
-    dependencies: &[PackageIdx],
-    base_criteria: &CriteriaSet,
-    dependency_criteria: &FastMap<PackageStr<'_>, CriteriaSet>,
-    caveats: &mut Caveats,
-    failed_deps: &mut SortedMap<PackageIdx, CriteriaSet>,
-) {
-    for &depidx in dependencies {
-        let dep_package = &dep_graph.nodes[depidx];
-        let dep_results = &results[depidx];
-
-        // If no custom criteria is specified, then require our dependency to match
-        // the base criteria that we're trying to validate. This makes audits effectively
-        // break down their criteria into individually verifiable components instead of
-        // purely "all or nothing".
-        //
-        // e.g. a safe-to-deploy audit with some deps that are only safe-to-run
-        // still audits for safe-to-run, but not safe-to-deploy. Similarly so for
-        // `[safe-to-run, some-other-criteria]` validating each criteria individually.
-        let dep_req = dependency_criteria
-            .get(dep_package.name)
-            .unwrap_or(base_criteria);
-
-        if !dep_results.validated_criteria.contains(dep_req) {
-            // This dependency's criteria is not satisfied, so add it to the
-            // failed deps map.
-            failed_deps
-                .entry(depidx)
-                .or_insert_with(|| criteria_mapper.no_criteria())
-                .unioned_with(dep_req);
-            continue;
-        }
-
-        // Only iterate the minimal set of indices to reduce the number of
-        // search results we check for caveats, as implied results will have a
-        // subset of the caveats of the stronger criteria.
-        for required_criteria_idx in criteria_mapper.minimal_indices(dep_req) {
-            // Check if the original search results succeeded here, and if it
-            // did record the relevant caveats. It's OK if we don't see
-            // `Connected` here, as that just means a policy overwrote our
-            // failure.
-            if let SearchResult::Connected {
-                caveats: dep_caveats,
-            } = &dep_results.search_results[required_criteria_idx]
-            {
-                caveats.add(dep_caveats);
-            }
-        }
+            },
+        )
     }
 }
 
-#[allow(clippy::too_many_arguments)]
+/// Core algorithm used to search for a path between two versions within a
+/// DirectedAuditGraph. A path with the fewest "caveats" will be used in order
+/// to minimize dependence on exemptions and freshly imported audits.
 fn search_for_path(
-    cur_criteria: &CriteriaSet,
+    audit_graph: &DirectedAuditGraph<'_>,
+    criteria_idx: usize,
     from_version: Option<&VetVersion>,
     to_version: Option<&VetVersion>,
-    audit_graph: &AuditGraph<'_>,
-    dep_graph: &DepGraph<'_>,
-    criteria_mapper: &CriteriaMapper,
-    results: &[ResolveResult],
-    pkgidx: PackageIdx,
-    dependencies: &[PackageIdx],
-    dependency_criteria: &FastMap<PackageStr<'_>, CriteriaSet>,
-) -> SearchResult {
-    // Search for any path through the graph with edges that satisfy
-    // cur_criteria.  Finding any path validates that we satisfy that criteria.
+) -> Result<Caveats, SortedSet<Option<VetVersion>>> {
+    // Search for any path through the graph with edges that satisfy criteria.
+    // Finding any path validates that we satisfy that criteria.
     //
     // All full-audits and exemptions have been "desugarred" to a delta from
     // None, meaning our graph now has exactly one source and one sink,
@@ -1943,24 +1590,22 @@ fn search_for_path(
     // find a patch without using a node with caveats, it's unambiguous proof we
     // don't need edges with caveats.
     //
-    // These caveats extend all the way from exemptions (the least-important
-    // caveat) to fresh imports and even failed edges (the most-important
-    // caveat).
+    // These caveats extend from exemptions (the least-important caveat) to
+    // fresh imports (the most-important caveat).
     struct Node<'a> {
         version: Option<&'a VetVersion>,
         caveats: Caveats,
-        failed_deps: SortedMap<PackageIdx, CriteriaSet>,
     }
 
     impl<'a> Node<'a> {
-        fn key(&self) -> Reverse<(usize, usize, usize)> {
-            // Nodes are compared by the number of failed dependencies, fresh
-            // imports, and exemptions, in that order. Fewer caveats makes the
-            // node sort higher, as it will be stored in a max heap.
+        fn key(&self) -> Reverse<(bool, bool, bool)> {
+            // Nodes are compared whether they require fresh imports or
+            // exemptions, in that order. Fewer caveats makes the node sort
+            // higher, as it will be stored in a max heap.
             Reverse((
-                self.failed_deps.len(),
-                self.caveats.needed_fresh_imports.len(),
-                self.caveats.needed_exemptions.len(),
+                self.caveats.needed_fresh_imports,
+                self.caveats.needed_exemptions,
+                self.caveats.directly_exempted,
             ))
         }
     }
@@ -1985,16 +1630,10 @@ fn search_for_path(
     queue.push(Node {
         version: from_version,
         caveats: Caveats::default(),
-        failed_deps: SortedMap::new(),
     });
 
     let mut visited = SortedSet::new();
-    while let Some(Node {
-        version,
-        caveats,
-        failed_deps,
-    }) = queue.pop()
-    {
+    while let Some(Node { version, caveats }) = queue.pop() {
         // If We've been to a version before, We're not going to get a better
         // result revisiting it, as we visit the "best" edges first.
         if !visited.insert(version) {
@@ -2004,18 +1643,14 @@ fn search_for_path(
         // We found a path! Return a search result reflecting what we
         // discovered.
         if version == to_version {
-            return if failed_deps.is_empty() {
-                SearchResult::Connected { caveats }
-            } else {
-                SearchResult::PossiblyConnected { failed_deps }
-            };
+            return Ok(caveats);
         }
 
         // Apply deltas to move along to the next layer of the search, adding it
         // to our queue.
         let edges = audit_graph.get(&version).map(|v| &v[..]).unwrap_or(&[]);
         for edge in edges {
-            if !edge.criteria.contains(cur_criteria) {
+            if !edge.criteria.has_criteria(criteria_idx) {
                 // This edge never would have been useful to us.
                 continue;
             }
@@ -2025,478 +1660,29 @@ fn search_for_path(
             }
 
             let mut edge_caveats = caveats.clone();
-            let mut edge_failed_deps = failed_deps.clone();
+            edge_caveats.directly_exempted = false;
 
             match edge.origin {
                 DeltaEdgeOrigin::Exemption => {
-                    edge_caveats.needed_exemptions.insert(pkgidx);
+                    edge_caveats.needed_exemptions = true;
+                    edge_caveats.directly_exempted = true;
                 }
                 DeltaEdgeOrigin::FreshImportedAudit => {
-                    edge_caveats.needed_fresh_imports.insert(pkgidx);
+                    edge_caveats.needed_fresh_imports = true;
                 }
                 DeltaEdgeOrigin::StoredAudit => {}
             }
 
-            get_dependency_criteria_caveats(
-                dep_graph,
-                criteria_mapper,
-                results,
-                dependencies,
-                cur_criteria,
-                dependency_criteria,
-                &mut edge_caveats,
-                &mut edge_failed_deps,
-            );
-
             queue.push(Node {
                 version: edge.version,
                 caveats: edge_caveats,
-                failed_deps: edge_failed_deps,
             });
         }
     }
 
     // Complete failure, we need more audits for this package, so all that
     // matters is what nodes were reachable.
-    SearchResult::Disconnected {
-        reachable_from_root: visited.into_iter().map(|v| v.cloned()).collect(),
-        // This will get filled in by a second pass.
-        reachable_from_target: Default::default(),
-    }
-}
-
-fn resolve_first_party(
-    store: &Store,
-    graph: &DepGraph<'_>,
-    criteria_mapper: &CriteriaMapper,
-    results: &mut [ResolveResult],
-    _violations: &mut SortedMap<PackageIdx, Vec<ViolationConflict>>,
-    _root_failures: &mut RootFailures,
-    pkgidx: PackageIdx,
-) {
-    // Check the build-deps and normal-deps of this package. dev-deps are checking in `resolve_dep`
-    // In this pass we properly use package.is_root, but in the next pass all nodes are "roots"
-    let package = &graph.nodes[pkgidx];
-
-    // Get custom policies for our dependencies
-    let dep_criteria = store
-        .config
-        .policy
-        .get(package.name)
-        .map(|policy| {
-            policy
-                .dependency_criteria
-                .iter()
-                .map(|(dep_name, criteria)| {
-                    (&**dep_name, criteria_mapper.criteria_from_list(criteria))
-                })
-                .collect::<FastMap<_, _>>()
-        })
-        .unwrap_or_default();
-
-    // Compute whether we have each criteria based on our dependencies
-    let mut validated_criteria = criteria_mapper.no_criteria();
-    let mut search_results = Vec::with_capacity(criteria_mapper.len());
-    for criteria in criteria_mapper.all_criteria_iter() {
-        // Find any build/normal dependencies that don't satisfy this criteria
-        let mut caveats = Caveats::default();
-        let mut failed_deps = SortedMap::new();
-
-        get_dependency_criteria_caveats(
-            graph,
-            criteria_mapper,
-            results,
-            &package.normal_and_build_deps,
-            criteria,
-            &dep_criteria,
-            &mut caveats,
-            &mut failed_deps,
-        );
-
-        if failed_deps.is_empty() {
-            // All our deps passed the test, so we have this criteria
-            search_results.push(SearchResult::Connected { caveats });
-            validated_criteria.unioned_with(criteria);
-        } else {
-            // Some of our deps failed to satisfy this criteria, record this
-            search_results.push(SearchResult::PossiblyConnected { failed_deps })
-        }
-    }
-    trace!(
-        "  first-party validation: {:?}",
-        criteria_mapper
-            .criteria_names(&validated_criteria)
-            .collect::<Vec<_>>()
-    );
-
-    // Save the results
-    results[pkgidx] = ResolveResult {
-        validated_criteria,
-        search_results,
-        directly_exempted: false,
-    };
-}
-
-fn get_policy_caveats(
-    criteria_mapper: &CriteriaMapper,
-    search_results: &[SearchResult],
-    own_policy: &CriteriaSet,
-    pkgidx: PackageIdx,
-    root_caveats: &mut Caveats,
-    policy_failures: &mut PolicyFailures,
-) {
-    for criteria_idx in own_policy.indices() {
-        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(pkgidx)
-                    .or_insert_with(|| criteria_mapper.no_criteria())
-                    .set_criteria(criteria_idx)
-            }
-            SearchResult::Connected { caveats } => {
-                // A-OK
-                root_caveats.add(caveats);
-            }
-        }
-    }
-}
-
-#[allow(clippy::too_many_arguments)]
-fn resolve_self_policy(
-    store: &Store,
-    graph: &DepGraph<'_>,
-    criteria_mapper: &CriteriaMapper,
-    results: &mut [ResolveResult],
-    _violations: &mut SortedMap<PackageIdx, Vec<ViolationConflict>>,
-    root_failures: &mut RootFailures,
-    root_caveats: &mut Caveats,
-    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");
-        // We have no policy, we're done
-        return;
-    };
-
-    let mut policy_failures = PolicyFailures::new();
-
-    get_policy_caveats(
-        criteria_mapper,
-        &results[pkgidx].search_results,
-        &own_policy,
-        pkgidx,
-        root_caveats,
-        &mut policy_failures,
-    );
-
-    // NOTE: We don't update search results here, just `validated_criteria`.
-    // This is the only way that these two should get out of sync, but we want
-    // to keep around the old search results as it will be useful for blame.
-    if policy_failures.is_empty() {
-        // 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, 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, no_criteria");
-        results[pkgidx].validated_criteria = criteria_mapper.no_criteria();
-        root_failures.push((pkgidx, policy_failures, false));
-    }
-}
-
-#[allow(clippy::too_many_arguments)]
-fn resolve_dev(
-    store: &Store,
-    graph: &DepGraph<'_>,
-    criteria_mapper: &CriteriaMapper,
-    results: &mut [ResolveResult],
-    _violations: &mut SortedMap<PackageIdx, Vec<ViolationConflict>>,
-    root_failures: &mut RootFailures,
-    root_caveats: &mut Caveats,
-    pkgidx: PackageIdx,
-) {
-    // Check the dev-deps of this package. It is assumed to be a root in this context,
-    // so the default root dev policy will always be applicable.
-    let package = &graph.nodes[pkgidx];
-
-    // Get custom policies for our dependencies
-    let dep_criteria = store
-        .config
-        .policy
-        .get(package.name)
-        .map(|policy| {
-            policy
-                .dependency_criteria
-                .iter()
-                .map(|(dep_name, criteria)| {
-                    (&**dep_name, criteria_mapper.criteria_from_list(criteria))
-                })
-                .collect::<FastMap<_, _>>()
-        })
-        .unwrap_or_default();
-
-    // Compute whether we have each criteria based on our dependencies
-    let mut validated_criteria = criteria_mapper.no_criteria();
-    let mut search_results = vec![];
-    for criteria in criteria_mapper.all_criteria_iter() {
-        // Find any build/normal dependencies that don't satisfy this criteria
-        let mut caveats = Caveats::default();
-        let mut failed_deps = SortedMap::new();
-
-        get_dependency_criteria_caveats(
-            graph,
-            criteria_mapper,
-            results,
-            &package.dev_deps,
-            criteria,
-            &dep_criteria,
-            &mut caveats,
-            &mut failed_deps,
-        );
-
-        if failed_deps.is_empty() {
-            // All our deps passed the test, so we have this criteria
-            search_results.push(SearchResult::Connected { caveats });
-            validated_criteria.unioned_with(criteria);
-        } else {
-            // Some of our deps failed to satisfy this criteria, record this
-            search_results.push(SearchResult::PossiblyConnected { failed_deps })
-        }
-    }
-    trace!(
-        "  dev validation: {:?}",
-        criteria_mapper
-            .criteria_names(&validated_criteria)
-            .collect::<Vec<_>>()
-    );
-    // NOTE: DON'T save the results, because we're analyzing this as a dev-node
-    // and anything that depends on us only cares about the "normal" results.
-
-    // 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();
-
-    get_policy_caveats(
-        criteria_mapper,
-        &search_results,
-        &own_policy,
-        pkgidx,
-        root_caveats,
-        &mut policy_failures,
-    );
-
-    if policy_failures.is_empty() {
-        trace!("  passed dev policy");
-    } else {
-        trace!("  failed dev policy");
-        root_failures.push((pkgidx, policy_failures, true));
-    }
-}
-
-/// Traverse the build graph from the root failures to the leaf failures.
-fn visit_failures<T>(
-    graph: &DepGraph<'_>,
-    criteria_mapper: &CriteriaMapper,
-    results: &[ResolveResult],
-    root_failures: &RootFailures,
-    mut callback: impl FnMut(PackageIdx, usize, Option<&CriteriaFailureSet>) -> Result<(), T>,
-) -> Result<(), T> {
-    trace!(" traversing blame tree");
-
-    // The crate graph can be messy in several ways:
-    //
-    // 1. The depth of the graph can be very large, so recursion could blow the stack.
-    //
-    // 2. The "tree" view of the graph can have an exponential number of nodes because
-    //    of duplicated subgraphs from shared dependencies. Unfortunately, that is
-    //    the representation we "have" to traverse to properly blame packages for
-    //    specific policty failures.
-    //
-    // 3. Different "targets" for one package are actually logically separate crates,
-    //    and naively unifying them can result in cycles. In particular, the "dev"
-    //    version of a crate (tests and benches) can have dev-dependencies which depend
-    //    on the "real" version of that crate, so if you combine "dev" and "normal"
-    //    then you can have cycles. Horrifyingly, people do rely on this.
-    //
-    // Problem 1 is easy to fix: just use an explicit stack instead of recursion.
-    //
-    // Problem 2 can be solved by noting that, as long as we don't actually care about
-    // *printing* the entire blame-tree and just want to figure out what the leaves
-    // are and what criteria they need, we can use the usual "keep a set of visited
-    // nodes and don't revisit nodes in that set" pattern *except* modified to include
-    // the set of criteria it has been visited with. We then only revisit a node if
-    // our current path has criteria that aren't yet in the visitor set.
-    //
-    // Problem 3 is trickier. Really we should "desugar" a node into its different
-    // targets, but there are effectively infinite targets due to all the possible
-    // cfg combinations and feature flags, so we'd *really* like to be able to abstractly
-    // handle those. I *think* we only *really* need to split up "dev" from "everything else"
-    // but I want to think about this more. For now we just rely on the solution to problem 2
-    // to avoid infinite loops and just don't worry about the semantics.
-    let mut search_stack = Vec::new();
-
-    let mut immune_to_parent_demands = FastSet::new();
-    for (failed_idx, policy_failures, is_dev) in root_failures {
-        let failed_package = &graph.nodes[*failed_idx];
-        trace!(
-            " policy failure for {}:{}",
-            failed_package.name,
-            failed_package.version
-        );
-        for (&failed_dep_idx, failed_criteria) in policy_failures {
-            let failed_dep = &graph.nodes[failed_dep_idx];
-            trace!(
-                "   {}:{} needed {:?}",
-                failed_dep.name,
-                failed_dep.version,
-                criteria_mapper
-                    .criteria_names(failed_criteria)
-                    .collect::<Vec<_>>()
-            );
-            search_stack.push((
-                failed_dep_idx,
-                0,
-                Some(CriteriaFailureSet::from(failed_criteria, true)),
-            ));
-        }
-        if !is_dev {
-            // If we have a root failure and it's not from the virtual dev-node, then
-            // we have a self-policy that "shadows" anything a parent could require.
-            // We will properly blame our children based on the existence of this entry,
-            // so we should refuse any attempts from a parent to blame us for anything.
-            immune_to_parent_demands.insert(failed_idx);
-        }
-    }
-    let mut visited = FastMap::<PackageIdx, CriteriaFailureSet>::new();
-    let no_criteria = criteria_mapper.no_criteria_failures();
-
-    while let Some((failure_idx, depth, cur_criteria)) = search_stack.pop() {
-        match visited.entry(failure_idx) {
-            std::collections::hash_map::Entry::Vacant(entry) => {
-                entry.insert(cur_criteria.clone().unwrap_or_else(|| no_criteria.clone()));
-            }
-            std::collections::hash_map::Entry::Occupied(mut entry) => {
-                let cur = cur_criteria.as_ref().unwrap_or(&no_criteria);
-                if entry.get().contains(cur) {
-                    continue;
-                } else {
-                    entry.get_mut().unioned_with(cur);
-                }
-            }
-        }
-
-        let result = &results[failure_idx];
-        let package = &graph.nodes[failure_idx];
-        trace!(
-            " {:width$}visiting {}:{} for {:?} + {:?}",
-            "",
-            package.name,
-            package.version,
-            cur_criteria.as_ref().map(|c| criteria_mapper
-                .confident_criteria_names(c)
-                .collect::<Vec<_>>()),
-            cur_criteria.as_ref().map(|c| criteria_mapper
-                .unconfident_criteria_names(c)
-                .collect::<Vec<_>>()),
-            width = depth
-        );
-
-        if let Some(failed_criteria) = cur_criteria {
-            let mut own_fault = no_criteria.clone();
-            let mut dep_faults = FastMap::<PackageIdx, CriteriaFailureSet>::new();
-
-            // Collect up details of how we failed the criteria
-            for criteria_idx in failed_criteria.all().indices() {
-                let confident = failed_criteria.confident().has_criteria(criteria_idx);
-                match &result.search_results[criteria_idx] {
-                    SearchResult::Connected { .. } => {
-                        // Do nothing, this package is good
-                    }
-                    SearchResult::PossiblyConnected { failed_deps } => {
-                        // We're not to blame, it's our children who failed!
-                        for (&failed_dep, failed_criteria) in failed_deps {
-                            dep_faults
-                                .entry(failed_dep)
-                                .or_insert_with(|| no_criteria.clone())
-                                .unioned_with(&CriteriaFailureSet::from(
-                                    failed_criteria,
-                                    confident,
-                                ));
-                        }
-                    }
-                    SearchResult::Disconnected { .. } => {
-                        // Oh dang ok we *are* to blame, our bad
-                        own_fault.set_criteria(criteria_idx, confident);
-
-                        // Try to Guess Deeper by blaming our children for all |self| failures
-                        // by assuming we would need them to conform to our own criteria too.
-                        //
-                        // Dev-deps should never be chased here because any issues with those show
-                        // up as root_failures and have already been pushed into the search-stack.
-                        // All recursive blaming is about deps for a "normal" build, which requires
-                        // only these two kinds of deps.
-                        for &dep_idx in &package.normal_and_build_deps {
-                            let dep_result = &results[dep_idx];
-                            if !dep_result.validated_criteria.has_criteria(criteria_idx) {
-                                dep_faults
-                                    .entry(dep_idx)
-                                    .or_insert_with(|| no_criteria.clone())
-                                    .set_criteria(criteria_idx, false);
-                            }
-                        }
-                    }
-                }
-            }
-
-            // Visit ourselves based on whether we're to blame at all
-            if own_fault.is_empty() {
-                callback(failure_idx, depth, None)?;
-            } else {
-                callback(failure_idx, depth, Some(&own_fault))?;
-            }
-
-            // Now visit our children
-            for (failed_dep, failed_criteria) in dep_faults {
-                if immune_to_parent_demands.contains(&failed_dep) {
-                    continue;
-                }
-                search_stack.push((failed_dep, depth + 1, Some(failed_criteria.clone())));
-            }
-        } else {
-            unreachable!("I don't think this should happen..?");
-        }
-    }
-    Ok(())
+    Err(visited.into_iter().map(|v| v.cloned()).collect())
 }
 
 impl<'a> ResolveReport<'a> {
@@ -2535,7 +1721,9 @@ impl<'a> ResolveReport<'a> {
 
                     let failure_idx = *failure_idx;
                     let package = &self.graph.nodes[failure_idx];
-                    let result = &self.results[failure_idx];
+                    let result = self.results[failure_idx]
+                        .as_ref()
+                        .expect("failed package without ResolveResults?");
 
                     // Precompute some "notable" parents
                     let notable_parents = {
@@ -2568,12 +1756,12 @@ impl<'a> ResolveReport<'a> {
                     // Collect up the details of how we failed
                     let mut from_root = None::<SortedSet<&Option<VetVersion>>>;
                     let mut from_target = None::<SortedSet<&Option<VetVersion>>>;
-                    for criteria_idx in audit_failure.criteria_failures.all().indices() {
+                    for criteria_idx in audit_failure.criteria_failures.indices() {
                         let search_result = &result.search_results[criteria_idx];
-                        if let SearchResult::Disconnected {
+                        if let Err(SearchFailure {
                             reachable_from_root,
                             reachable_from_target,
-                        } = search_result
+                        }) = search_result
                         {
                             if let (Some(from_root), Some(from_target)) =
                                 (from_root.as_mut(), from_target.as_mut())
@@ -2704,13 +1892,12 @@ impl<'a> ResolveReport<'a> {
         suggestions.sort_by_key(|item| &self.graph.nodes[item.package].version);
         suggestions.sort_by_key(|item| self.graph.nodes[item.package].name);
         suggestions.sort_by_key(|item| item.suggested_diff.diffstat.count());
-        suggestions.sort_by_key(|item| item.suggested_criteria.is_fully_unconfident());
 
         let mut suggestions_by_criteria = SortedMap::<CriteriaName, Vec<SuggestItem>>::new();
         for s in suggestions.clone().into_iter() {
             let criteria_names = self
                 .criteria_mapper
-                .all_criteria_names(&s.suggested_criteria)
+                .criteria_names(&s.suggested_criteria)
                 .collect::<Vec<_>>()
                 .join(", ");
 
@@ -2754,19 +1941,21 @@ impl<'a> ResolveReport<'a> {
 
         // Enumerate over the recorded failures, adding any criteria for this
         // delta which would connect that package version into the audit graph.
-        for (&failure_idx, audit_failure) in &fail.failures {
-            let package = &self.graph.nodes[failure_idx];
+        for (failure_idx, audit_failure) in &fail.failures {
+            let package = &self.graph.nodes[*failure_idx];
             if package.name != package_name {
                 continue;
             }
 
-            let result = &self.results[failure_idx];
-            for criteria_idx in audit_failure.criteria_failures.all().indices() {
+            let result = &self.results[*failure_idx]
+                .as_ref()
+                .expect("failure without ResolveResults?");
+            for criteria_idx in audit_failure.criteria_failures.indices() {
                 let search_result = &result.search_results[criteria_idx];
-                if let SearchResult::Disconnected {
+                if let Err(SearchFailure {
                     reachable_from_root,
                     reachable_from_target,
-                } = search_result
+                }) = search_result
                 {
                     if reachable_from_target.contains(&to) && reachable_from_root.contains(&from) {
                         criteria.set_criteria(criteria_idx);
@@ -2865,7 +2054,7 @@ impl<'a> ResolveReport<'a> {
                             notable_parents: item.notable_parents.to_owned(),
                             suggested_criteria: self
                                 .criteria_mapper
-                                .all_criteria_names(&item.suggested_criteria)
+                                .criteria_names(&item.suggested_criteria)
                                 .map(|s| s.to_owned())
                                 .collect(),
                             suggested_diff: item.suggested_diff.clone(),
@@ -2875,14 +2064,14 @@ impl<'a> ResolveReport<'a> {
                         failures: fail
                             .failures
                             .iter()
-                            .map(|(&pkgidx, audit_fail)| {
-                                let package = &self.graph.nodes[pkgidx];
+                            .map(|(pkgidx, audit_fail)| {
+                                let package = &self.graph.nodes[*pkgidx];
                                 JsonVetFailure {
                                     name: package.name.to_owned(),
                                     version: package.version.clone(),
                                     missing_criteria: self
                                         .criteria_mapper
-                                        .all_criteria_names(&audit_fail.criteria_failures)
+                                        .criteria_names(&audit_fail.criteria_failures)
                                         .map(|s| s.to_owned())
                                         .collect(),
                                 }
@@ -2998,12 +2187,7 @@ impl Suggest {
                         Some(_) => format!("({})", item.suggested_diff.diffstat),
                         None => format!("({} lines)", item.suggested_diff.diffstat.count()),
                     };
-                    let style = if item.suggested_criteria.is_fully_unconfident() {
-                        out.style().dim()
-                    } else {
-                        out.style()
-                    };
-                    (cmd, parents, diffstat, style)
+                    (cmd, parents, diffstat)
                 })
                 .collect::<Vec<_>>();
 
@@ -3014,21 +2198,16 @@ impl Suggest {
                 max1 = max1.max(console::measure_text_width(s1));
             }
 
-            for (s0, s1, s2, style) in strings {
+            for (s0, s1, s2) in strings {
                 write!(
                     out,
                     "{}",
-                    style
-                        .clone()
+                    out.style()
                         .cyan()
                         .bold()
                         .apply_to(format_args!("    {s0:width$}", width = max0))
                 );
-                writeln!(
-                    out,
-                    "{}",
-                    style.apply_to(format_args!("  {s1:width$}  {s2}", width = max1))
-                );
+                writeln!(out, "  {s1:width$}  {s2}", width = max1);
             }
 
             writeln!(out);
@@ -3056,40 +2235,18 @@ impl FailForVet {
         let mut failures = self
             .failures
             .iter()
-            .map(|(&failed_idx, failure)| (&report.graph.nodes[failed_idx], failure))
+            .map(|(failed_idx, failure)| (&report.graph.nodes[*failed_idx], failure))
             .collect::<Vec<_>>();
         failures.sort_by_key(|(failed, _)| &failed.version);
         failures.sort_by_key(|(failed, _)| failed.name);
-        failures.sort_by_key(|(_, failure)| failure.criteria_failures.is_fully_unconfident());
         for (failed_package, failed_audit) in failures {
-            let confident_criteria = report
-                .criteria_mapper
-                .confident_criteria_names(&failed_audit.criteria_failures)
-                .collect::<Vec<_>>();
-            let unconfident_criteria = report
+            let criteria = report
                 .criteria_mapper
-                .unconfident_criteria_names(&failed_audit.criteria_failures)
+                .criteria_names(&failed_audit.criteria_failures)
                 .collect::<Vec<_>>();
 
             let label = format!("  {}:{}", failed_package.name, failed_package.version);
-            if !confident_criteria.is_empty() {
-                writeln!(out, "{} missing {:?}", label, confident_criteria);
-            }
-            if !unconfident_criteria.is_empty() {
-                writeln!(
-                    out,
-                    "{}",
-                    out.style().dim().apply_to(format_args!(
-                        "{} likely missing {:?}",
-                        if confident_criteria.is_empty() {
-                            label
-                        } else {
-                            format!("{:width$}", "", width = console::measure_text_width(&label))
-                        },
-                        unconfident_criteria
-                    ))
-                );
-            }
+            writeln!(out, "{} missing {:?}", label, criteria);
         }
 
         // Suggest output generally requires hitting the network.
@@ -3111,8 +2268,8 @@ impl FailForViolationConflict {
     ) -> Result<(), std::io::Error> {
         writeln!(out, "Violations Found!");
 
-        for (&pkgidx, violations) in &self.violations {
-            let package = &report.graph.nodes[pkgidx];
+        for (pkgidx, violations) in &self.violations {
+            let package = &report.graph.nodes[*pkgidx];
             writeln!(out, "  {}:{}", package.name, package.version);
             for violation in violations {
                 match violation {
@@ -3206,8 +2363,8 @@ pub fn regenerate_exemptions(
     force_update_imports: bool,
 ) -> Result<(), RegenerateExemptionsError> {
     // While minimizing exemptions, a number of different calls to the resolver
-    // will be made using the same DepGraph and CriteriaMapper, which will be
-    // generated up-front.
+    // will be made using the same DepGraph, CriteriaMapper and requirements,
+    // which will be generated up-front.
     let graph = DepGraph::new(
         &cfg.metadata,
         cfg.cli.filter_graph.as_ref(),
@@ -3218,13 +2375,14 @@ pub fn regenerate_exemptions(
         store.imported_audits(),
         &store.config.imports,
     );
+    let requirements = resolve_requirements(&graph, &store.config.policy, &criteria_mapper);
 
     // If we're not allowing new exemptions, we must already be passing `vet`.
     // If we aren't, we can't do anything, so just update imports and return
     // immediately. We'll also do this if automatic exemption minimization is
     // disabled through the CLI.
     if !allow_new_exemptions {
-        let (_, conclusion) = resolve_core(&graph, store, &criteria_mapper);
+        let (_, conclusion) = resolve_audits(&graph, store, &criteria_mapper, &requirements);
         if !matches!(conclusion, Conclusion::Success(_)) || cfg.cli.no_minimize_exemptions {
             store.imports =
                 store.get_updated_imports_file(&graph, &conclusion, force_update_imports);
@@ -3275,7 +2433,7 @@ pub fn regenerate_exemptions(
 
     loop {
         // Try to vet.
-        let (results, conclusion) = resolve_core(&graph, store, &criteria_mapper);
+        let (results, conclusion) = resolve_audits(&graph, store, &criteria_mapper, &requirements);
 
         // We only need to do more work here if we have any failures which we
         // can work with.
@@ -3309,9 +2467,11 @@ pub fn regenerate_exemptions(
                 potential.useful_criteria.unioned_with(new_criteria);
             };
 
-        for (&failure_idx, failure) in &fail.failures {
-            let package = &graph.nodes[failure_idx];
-            let result = &results[failure_idx];
+        for (failure_idx, failure) in &fail.failures {
+            let package = &graph.nodes[*failure_idx];
+            let result = results[*failure_idx]
+                .as_ref()
+                .expect("failed package without ResolveResult?");
 
             trace!("minimizing exemptions for {}", package.name);
 
@@ -3390,7 +2550,7 @@ pub fn regenerate_exemptions(
             // requirements for. We'll loop around again to add more audits
             // until we successfully vet.
             'min_criteria: for criteria_idx in
-                criteria_mapper.minimal_indices(failure.criteria_failures.confident())
+                criteria_mapper.minimal_indices(&failure.criteria_failures)
             {
                 let implied = &criteria_mapper.implied_criteria[criteria_idx];
 
@@ -3407,10 +2567,10 @@ pub fn regenerate_exemptions(
                     // Check if this implied criteria actually failed, if it
                     // didn't, we don't need to add any new exemptions for it.
                     let reachable_from_target = match &result.search_results[implied_idx] {
-                        SearchResult::Disconnected {
+                        Err(SearchFailure {
                             reachable_from_target,
                             ..
-                        } => reachable_from_target,
+                        }) => reachable_from_target,
                         _ => continue,
                     };
                     let mut found = false;
@@ -3441,10 +2601,10 @@ pub fn regenerate_exemptions(
                 }
 
                 let reachable_from_target = match &result.search_results[criteria_idx] {
-                    SearchResult::Disconnected {
+                    Err(SearchFailure {
                         reachable_from_target,
                         ..
-                    } => reachable_from_target,
+                    }) => reachable_from_target,
                     _ => unreachable!("minimal criteria didn't actually fail?"),
                 };
 


Test patch:
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-complex-full-audited.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-complex-full-audited.json.snap
index 4c167e2..4502744 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin-complex-full-audited.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-complex-full-audited.json.snap
@@ -7,11 +7,11 @@ expression: json
   "vetted_fully": [
     {
       "name": "third-core",
-      "version": "5.0.0"
+      "version": "10.0.0"
     },
     {
       "name": "third-core",
-      "version": "10.0.0"
+      "version": "5.0.0"
     },
     {
       "name": "thirdA",
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-complex-inited.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-complex-inited.json.snap
index c01059d..8a3c620 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin-complex-inited.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-complex-inited.json.snap
@@ -9,11 +9,11 @@ expression: json
   "vetted_with_exemptions": [
     {
       "name": "third-core",
-      "version": "5.0.0"
+      "version": "10.0.0"
     },
     {
       "name": "third-core",
-      "version": "10.0.0"
+      "version": "5.0.0"
     },
     {
       "name": "thirdA",
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-complex-minimal-audited.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-complex-minimal-audited.json.snap
index 4c167e2..4502744 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin-complex-minimal-audited.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-complex-minimal-audited.json.snap
@@ -7,11 +7,11 @@ expression: json
   "vetted_fully": [
     {
       "name": "third-core",
-      "version": "5.0.0"
+      "version": "10.0.0"
     },
     {
       "name": "third-core",
-      "version": "10.0.0"
+      "version": "5.0.0"
     },
     {
       "name": "thirdA",
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-cycle-full-audited.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-cycle-full-audited.json.snap
index 8cce198..7135feb 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin-cycle-full-audited.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-cycle-full-audited.json.snap
@@ -6,11 +6,11 @@ expression: json
   "conclusion": "success",
   "vetted_fully": [
     {
-      "name": "normal",
+      "name": "dev-cycle",
       "version": "10.0.0"
     },
     {
-      "name": "dev-cycle",
+      "name": "normal",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-cycle-inited.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-cycle-inited.json.snap
index d7db86f..65f97b3 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin-cycle-inited.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-cycle-inited.json.snap
@@ -8,11 +8,11 @@ expression: json
   "vetted_partially": [],
   "vetted_with_exemptions": [
     {
-      "name": "normal",
+      "name": "dev-cycle",
       "version": "10.0.0"
     },
     {
-      "name": "dev-cycle",
+      "name": "normal",
       "version": "10.0.0"
     }
   ]
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-cycle-minimal-audited.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-cycle-minimal-audited.json.snap
index 8cce198..7135feb 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin-cycle-minimal-audited.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-cycle-minimal-audited.json.snap
@@ -6,11 +6,11 @@ expression: json
   "conclusion": "success",
   "vetted_fully": [
     {
-      "name": "normal",
+      "name": "dev-cycle",
       "version": "10.0.0"
     },
     {
-      "name": "dev-cycle",
+      "name": "normal",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-cursed-full.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-cursed-full.json.snap
index a7b896a..b1be8f4 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-cursed-full.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-cursed-full.json.snap
@@ -10,7 +10,7 @@ expression: json
       "version": "10.0.0"
     },
     {
-      "name": "normal",
+      "name": "dev-cycle-direct",
       "version": "10.0.0"
     },
     {
@@ -18,15 +18,15 @@ expression: json
       "version": "10.0.0"
     },
     {
-      "name": "dev-cycle-direct",
+      "name": "normal",
       "version": "10.0.0"
     },
     {
-      "name": "simple-dev-indirect",
+      "name": "simple-dev",
       "version": "10.0.0"
     },
     {
-      "name": "simple-dev",
+      "name": "simple-dev-indirect",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-cursed-minimal.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-cursed-minimal.json.snap
index a7b896a..b1be8f4 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-cursed-minimal.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-cursed-minimal.json.snap
@@ -10,7 +10,7 @@ expression: json
       "version": "10.0.0"
     },
     {
-      "name": "normal",
+      "name": "dev-cycle-direct",
       "version": "10.0.0"
     },
     {
@@ -18,15 +18,15 @@ expression: json
       "version": "10.0.0"
     },
     {
-      "name": "dev-cycle-direct",
+      "name": "normal",
       "version": "10.0.0"
     },
     {
-      "name": "simple-dev-indirect",
+      "name": "simple-dev",
       "version": "10.0.0"
     },
     {
-      "name": "simple-dev",
+      "name": "simple-dev-indirect",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-empty-deeper.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-empty-deeper.json.snap
index 298c760..645292c 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-empty-deeper.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-empty-deeper.json.snap
@@ -83,10 +83,10 @@ expression: json
         }
       },
       {
-        "name": "normal",
-        "notable_parents": "root",
+        "name": "dev-cycle-indirect",
+        "notable_parents": "dev-cycle-direct",
         "suggested_criteria": [
-          "safe-to-deploy"
+          "safe-to-run"
         ],
         "suggested_diff": {
           "from": null,
@@ -99,10 +99,10 @@ expression: json
         }
       },
       {
-        "name": "simple-dev",
+        "name": "normal",
         "notable_parents": "root",
         "suggested_criteria": [
-          "safe-to-run"
+          "safe-to-deploy"
         ],
         "suggested_diff": {
           "from": null,
@@ -115,8 +115,8 @@ expression: json
         }
       },
       {
-        "name": "dev-cycle-indirect",
-        "notable_parents": "dev-cycle-direct",
+        "name": "simple-dev",
+        "notable_parents": "root",
         "suggested_criteria": [
           "safe-to-run"
         ],
@@ -200,8 +200,8 @@ expression: json
           }
         },
         {
-          "name": "simple-dev",
-          "notable_parents": "root",
+          "name": "dev-cycle-indirect",
+          "notable_parents": "dev-cycle-direct",
           "suggested_criteria": [
             "safe-to-run"
           ],
@@ -216,8 +216,8 @@ expression: json
           }
         },
         {
-          "name": "dev-cycle-indirect",
-          "notable_parents": "dev-cycle-direct",
+          "name": "simple-dev",
+          "notable_parents": "root",
           "suggested_criteria": [
             "safe-to-run"
           ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-empty-deeper.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-empty-deeper.snap
index 06fe65d..a3ba552 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-empty-deeper.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-empty-deeper.snap
@@ -1,16 +1,16 @@
 ---
 source: src/tests/vet.rs
-expression: output
+expression: human
 ---
 Vetting Failed!
 
 6 unvetted dependencies:
   both:10.0.0 missing ["safe-to-deploy"]
   dev-cycle-direct:10.0.0 missing ["safe-to-run"]
+  dev-cycle-indirect:10.0.0 missing ["safe-to-run"]
   normal:10.0.0 missing ["safe-to-deploy"]
   simple-dev:10.0.0 missing ["safe-to-run"]
-  dev-cycle-indirect:10.0.0 likely missing ["safe-to-run"]
-  simple-dev-indirect:10.0.0 likely missing ["safe-to-run"]
+  simple-dev-indirect:10.0.0 missing ["safe-to-run"]
 
 recommended audits for safe-to-deploy:
     cargo vet inspect both 10.0.0    (used by root)  (100 lines)
@@ -18,8 +18,8 @@ recommended audits for safe-to-deploy:
 
 recommended audits for safe-to-run:
     cargo vet inspect dev-cycle-direct 10.0.0     (used by root)              (100 lines)
-    cargo vet inspect simple-dev 10.0.0           (used by root)              (100 lines)
     cargo vet inspect dev-cycle-indirect 10.0.0   (used by dev-cycle-direct)  (100 lines)
+    cargo vet inspect simple-dev 10.0.0           (used by root)              (100 lines)
     cargo vet inspect simple-dev-indirect 10.0.0  (used by simple-dev)        (100 lines)
 
 estimated audit backlog: 600 lines
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-empty.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-empty.json.snap
index 298c760..645292c 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-empty.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-empty.json.snap
@@ -83,10 +83,10 @@ expression: json
         }
       },
       {
-        "name": "normal",
-        "notable_parents": "root",
+        "name": "dev-cycle-indirect",
+        "notable_parents": "dev-cycle-direct",
         "suggested_criteria": [
-          "safe-to-deploy"
+          "safe-to-run"
         ],
         "suggested_diff": {
           "from": null,
@@ -99,10 +99,10 @@ expression: json
         }
       },
       {
-        "name": "simple-dev",
+        "name": "normal",
         "notable_parents": "root",
         "suggested_criteria": [
-          "safe-to-run"
+          "safe-to-deploy"
         ],
         "suggested_diff": {
           "from": null,
@@ -115,8 +115,8 @@ expression: json
         }
       },
       {
-        "name": "dev-cycle-indirect",
-        "notable_parents": "dev-cycle-direct",
+        "name": "simple-dev",
+        "notable_parents": "root",
         "suggested_criteria": [
           "safe-to-run"
         ],
@@ -200,8 +200,8 @@ expression: json
           }
         },
         {
-          "name": "simple-dev",
-          "notable_parents": "root",
+          "name": "dev-cycle-indirect",
+          "notable_parents": "dev-cycle-direct",
           "suggested_criteria": [
             "safe-to-run"
           ],
@@ -216,8 +216,8 @@ expression: json
           }
         },
         {
-          "name": "dev-cycle-indirect",
-          "notable_parents": "dev-cycle-direct",
+          "name": "simple-dev",
+          "notable_parents": "root",
           "suggested_criteria": [
             "safe-to-run"
           ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-empty.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-empty.snap
index 9134e35..a3ba552 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-empty.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-empty.snap
@@ -7,10 +7,10 @@ Vetting Failed!
 6 unvetted dependencies:
   both:10.0.0 missing ["safe-to-deploy"]
   dev-cycle-direct:10.0.0 missing ["safe-to-run"]
+  dev-cycle-indirect:10.0.0 missing ["safe-to-run"]
   normal:10.0.0 missing ["safe-to-deploy"]
   simple-dev:10.0.0 missing ["safe-to-run"]
-  dev-cycle-indirect:10.0.0 likely missing ["safe-to-run"]
-  simple-dev-indirect:10.0.0 likely missing ["safe-to-run"]
+  simple-dev-indirect:10.0.0 missing ["safe-to-run"]
 
 recommended audits for safe-to-deploy:
     cargo vet inspect both 10.0.0    (used by root)  (100 lines)
@@ -18,8 +18,8 @@ recommended audits for safe-to-deploy:
 
 recommended audits for safe-to-run:
     cargo vet inspect dev-cycle-direct 10.0.0     (used by root)              (100 lines)
-    cargo vet inspect simple-dev 10.0.0           (used by root)              (100 lines)
     cargo vet inspect dev-cycle-indirect 10.0.0   (used by dev-cycle-direct)  (100 lines)
+    cargo vet inspect simple-dev 10.0.0           (used by root)              (100 lines)
     cargo vet inspect simple-dev-indirect 10.0.0  (used by simple-dev)        (100 lines)
 
 estimated audit backlog: 600 lines
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-unaudited-adds-uneeded-criteria-indirect.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-unaudited-adds-uneeded-criteria-indirect.json.snap
index a7b896a..b1be8f4 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-unaudited-adds-uneeded-criteria-indirect.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-unaudited-adds-uneeded-criteria-indirect.json.snap
@@ -10,7 +10,7 @@ expression: json
       "version": "10.0.0"
     },
     {
-      "name": "normal",
+      "name": "dev-cycle-direct",
       "version": "10.0.0"
     },
     {
@@ -18,15 +18,15 @@ expression: json
       "version": "10.0.0"
     },
     {
-      "name": "dev-cycle-direct",
+      "name": "normal",
       "version": "10.0.0"
     },
     {
-      "name": "simple-dev-indirect",
+      "name": "simple-dev",
       "version": "10.0.0"
     },
     {
-      "name": "simple-dev",
+      "name": "simple-dev-indirect",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection.json.snap
index a7b896a..b1be8f4 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection.json.snap
@@ -10,7 +10,7 @@ expression: json
       "version": "10.0.0"
     },
     {
-      "name": "normal",
+      "name": "dev-cycle-direct",
       "version": "10.0.0"
     },
     {
@@ -18,15 +18,15 @@ expression: json
       "version": "10.0.0"
     },
     {
-      "name": "dev-cycle-direct",
+      "name": "normal",
       "version": "10.0.0"
     },
     {
-      "name": "simple-dev-indirect",
+      "name": "simple-dev",
       "version": "10.0.0"
     },
     {
-      "name": "simple-dev",
+      "name": "simple-dev-indirect",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-haunted-full-audited.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-haunted-full-audited.json.snap
index b4dad66..41edbc2 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin-haunted-full-audited.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-haunted-full-audited.json.snap
@@ -6,11 +6,11 @@ expression: json
   "conclusion": "success",
   "vetted_fully": [
     {
-      "name": "third-normal",
+      "name": "third-dev",
       "version": "10.0.0"
     },
     {
-      "name": "third-dev",
+      "name": "third-normal",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-haunted-init.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-haunted-init.json.snap
index 1ae4879..77c62a4 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin-haunted-init.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-haunted-init.json.snap
@@ -8,11 +8,11 @@ expression: json
   "vetted_partially": [],
   "vetted_with_exemptions": [
     {
-      "name": "third-normal",
+      "name": "third-dev",
       "version": "10.0.0"
     },
     {
-      "name": "third-dev",
+      "name": "third-normal",
       "version": "10.0.0"
     }
   ]
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-haunted-minimal-audited.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-haunted-minimal-audited.json.snap
index b4dad66..41edbc2 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin-haunted-minimal-audited.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-haunted-minimal-audited.json.snap
@@ -6,11 +6,11 @@ expression: json
   "conclusion": "success",
   "vetted_fully": [
     {
-      "name": "third-normal",
+      "name": "third-dev",
       "version": "10.0.0"
     },
     {
-      "name": "third-dev",
+      "name": "third-normal",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-audit-as-default-root.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-audit-as-default-root.json.snap
index 7e64e0b..33c8b49 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-audit-as-default-root.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-audit-as-default-root.json.snap
@@ -8,7 +8,7 @@ expression: json
   "vetted_partially": [],
   "vetted_with_exemptions": [
     {
-      "name": "transitive-third-party1",
+      "name": "root-package",
       "version": "10.0.0"
     },
     {
@@ -20,7 +20,7 @@ expression: json
       "version": "10.0.0"
     },
     {
-      "name": "root-package",
+      "name": "transitive-third-party1",
       "version": "10.0.0"
     }
   ]
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-audit-as-weaker-root.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-audit-as-weaker-root.json.snap
index 7e64e0b..33c8b49 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-audit-as-weaker-root.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-audit-as-weaker-root.json.snap
@@ -8,7 +8,7 @@ expression: json
   "vetted_partially": [],
   "vetted_with_exemptions": [
     {
-      "name": "transitive-third-party1",
+      "name": "root-package",
       "version": "10.0.0"
     },
     {
@@ -20,7 +20,7 @@ expression: json
       "version": "10.0.0"
     },
     {
-      "name": "root-package",
+      "name": "transitive-third-party1",
       "version": "10.0.0"
     }
   ]
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-delta-cycle.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-delta-cycle.json.snap
index 327e9fb..4818073 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-delta-cycle.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-delta-cycle.json.snap
@@ -6,15 +6,15 @@ expression: json
   "conclusion": "success",
   "vetted_fully": [
     {
-      "name": "transitive-third-party1",
+      "name": "third-party1",
       "version": "10.0.0"
     },
     {
-      "name": "third-party1",
+      "name": "third-party2",
       "version": "10.0.0"
     },
     {
-      "name": "third-party2",
+      "name": "transitive-third-party1",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-delta-double-cycle.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-delta-double-cycle.json.snap
index 327e9fb..4818073 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-delta-double-cycle.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-delta-double-cycle.json.snap
@@ -6,15 +6,15 @@ expression: json
   "conclusion": "success",
   "vetted_fully": [
     {
-      "name": "transitive-third-party1",
+      "name": "third-party1",
       "version": "10.0.0"
     },
     {
-      "name": "third-party1",
+      "name": "third-party2",
       "version": "10.0.0"
     },
     {
-      "name": "third-party2",
+      "name": "transitive-third-party1",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-deps-full-audited.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-deps-full-audited.json.snap
index 3b6faa5..3e31252 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-deps-full-audited.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-deps-full-audited.json.snap
@@ -14,19 +14,19 @@ expression: json
       "version": "10.0.0"
     },
     {
-      "name": "normal",
+      "name": "dev",
       "version": "10.0.0"
     },
     {
-      "name": "proc-macro",
+      "name": "dev-proc-macro",
       "version": "10.0.0"
     },
     {
-      "name": "dev",
+      "name": "normal",
       "version": "10.0.0"
     },
     {
-      "name": "dev-proc-macro",
+      "name": "proc-macro",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-deps-init.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-deps-init.json.snap
index 0ab4b75..96f48da 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-deps-init.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-deps-init.json.snap
@@ -16,19 +16,19 @@ expression: json
       "version": "10.0.0"
     },
     {
-      "name": "normal",
+      "name": "dev",
       "version": "10.0.0"
     },
     {
-      "name": "proc-macro",
+      "name": "dev-proc-macro",
       "version": "10.0.0"
     },
     {
-      "name": "dev",
+      "name": "normal",
       "version": "10.0.0"
     },
     {
-      "name": "dev-proc-macro",
+      "name": "proc-macro",
       "version": "10.0.0"
     }
   ]
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-deps-minimal-audited.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-deps-minimal-audited.json.snap
index 3b6faa5..3e31252 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-deps-minimal-audited.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-deps-minimal-audited.json.snap
@@ -14,19 +14,19 @@ expression: json
       "version": "10.0.0"
     },
     {
-      "name": "normal",
+      "name": "dev",
       "version": "10.0.0"
     },
     {
-      "name": "proc-macro",
+      "name": "dev-proc-macro",
       "version": "10.0.0"
     },
     {
-      "name": "dev",
+      "name": "normal",
       "version": "10.0.0"
     },
     {
-      "name": "dev-proc-macro",
+      "name": "proc-macro",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-deps-unaudited-adds-uneeded-criteria.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-deps-unaudited-adds-uneeded-criteria.json.snap
index 3b6faa5..3e31252 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-deps-unaudited-adds-uneeded-criteria.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-deps-unaudited-adds-uneeded-criteria.json.snap
@@ -14,19 +14,19 @@ expression: json
       "version": "10.0.0"
     },
     {
-      "name": "normal",
+      "name": "dev",
       "version": "10.0.0"
     },
     {
-      "name": "proc-macro",
+      "name": "dev-proc-macro",
       "version": "10.0.0"
     },
     {
-      "name": "dev",
+      "name": "normal",
       "version": "10.0.0"
     },
     {
-      "name": "dev-proc-macro",
+      "name": "proc-macro",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-full-audited.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-full-audited.json.snap
index 327e9fb..4818073 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-full-audited.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-full-audited.json.snap
@@ -6,15 +6,15 @@ expression: json
   "conclusion": "success",
   "vetted_fully": [
     {
-      "name": "transitive-third-party1",
+      "name": "third-party1",
       "version": "10.0.0"
     },
     {
-      "name": "third-party1",
+      "name": "third-party2",
       "version": "10.0.0"
     },
     {
-      "name": "third-party2",
+      "name": "transitive-third-party1",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-init.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-init.json.snap
index 72c07ad..956caa3 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-init.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-init.json.snap
@@ -8,15 +8,15 @@ expression: json
   "vetted_partially": [],
   "vetted_with_exemptions": [
     {
-      "name": "transitive-third-party1",
+      "name": "third-party1",
       "version": "10.0.0"
     },
     {
-      "name": "third-party1",
+      "name": "third-party2",
       "version": "10.0.0"
     },
     {
-      "name": "third-party2",
+      "name": "transitive-third-party1",
       "version": "10.0.0"
     }
   ]
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-long-cycle.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-long-cycle.json.snap
index 327e9fb..4818073 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-long-cycle.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-long-cycle.json.snap
@@ -6,15 +6,15 @@ expression: json
   "conclusion": "success",
   "vetted_fully": [
     {
-      "name": "transitive-third-party1",
+      "name": "third-party1",
       "version": "10.0.0"
     },
     {
-      "name": "third-party1",
+      "name": "third-party2",
       "version": "10.0.0"
     },
     {
-      "name": "third-party2",
+      "name": "transitive-third-party1",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-no-unaudited.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-no-unaudited.snap
index 12cde7a..b2df947 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-no-unaudited.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-no-unaudited.snap
@@ -7,7 +7,7 @@ Vetting Failed!
 3 unvetted dependencies:
   third-party1:10.0.0 missing ["safe-to-deploy"]
   third-party2:10.0.0 missing ["safe-to-deploy"]
-  transitive-third-party1:10.0.0 likely missing ["safe-to-deploy"]
+  transitive-third-party1:10.0.0 missing ["safe-to-deploy"]
 
 recommended audits for safe-to-deploy:
     cargo vet inspect third-party1 10.0.0             (used by first-party)   (100 lines)
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-noop-delta.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-noop-delta.json.snap
index 327e9fb..4818073 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-noop-delta.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-noop-delta.json.snap
@@ -6,15 +6,15 @@ expression: json
   "conclusion": "success",
   "vetted_fully": [
     {
-      "name": "transitive-third-party1",
+      "name": "third-party1",
       "version": "10.0.0"
     },
     {
-      "name": "third-party1",
+      "name": "third-party2",
       "version": "10.0.0"
     },
     {
-      "name": "third-party2",
+      "name": "transitive-third-party1",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-not-a-real-dep.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-not-a-real-dep.json.snap
index 327e9fb..4818073 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-not-a-real-dep.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-not-a-real-dep.json.snap
@@ -6,15 +6,15 @@ expression: json
   "conclusion": "success",
   "vetted_fully": [
     {
-      "name": "transitive-third-party1",
+      "name": "third-party1",
       "version": "10.0.0"
     },
     {
-      "name": "third-party1",
+      "name": "third-party2",
       "version": "10.0.0"
     },
     {
-      "name": "third-party2",
+      "name": "transitive-third-party1",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-extra.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-extra.json.snap
index afca1ca..9a6450b 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-extra.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-extra.json.snap
@@ -6,11 +6,11 @@ expression: json
   "conclusion": "success",
   "vetted_fully": [
     {
-      "name": "transitive-third-party1",
+      "name": "third-party2",
       "version": "10.0.0"
     },
     {
-      "name": "third-party2",
+      "name": "transitive-third-party1",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-in-delta.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-in-delta.json.snap
index 327e9fb..4818073 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-in-delta.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-in-delta.json.snap
@@ -6,15 +6,15 @@ expression: json
   "conclusion": "success",
   "vetted_fully": [
     {
-      "name": "transitive-third-party1",
+      "name": "third-party1",
       "version": "10.0.0"
     },
     {
-      "name": "third-party1",
+      "name": "third-party2",
       "version": "10.0.0"
     },
     {
-      "name": "third-party2",
+      "name": "transitive-third-party1",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-in-direct-full.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-in-direct-full.json.snap
index 327e9fb..4818073 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-in-direct-full.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-in-direct-full.json.snap
@@ -6,15 +6,15 @@ expression: json
   "conclusion": "success",
   "vetted_fully": [
     {
-      "name": "transitive-third-party1",
+      "name": "third-party1",
       "version": "10.0.0"
     },
     {
-      "name": "third-party1",
+      "name": "third-party2",
       "version": "10.0.0"
     },
     {
-      "name": "third-party2",
+      "name": "transitive-third-party1",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-in-full.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-in-full.json.snap
index 327e9fb..4818073 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-in-full.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-in-full.json.snap
@@ -6,15 +6,15 @@ expression: json
   "conclusion": "success",
   "vetted_fully": [
     {
-      "name": "transitive-third-party1",
+      "name": "third-party1",
       "version": "10.0.0"
     },
     {
-      "name": "third-party1",
+      "name": "third-party2",
       "version": "10.0.0"
     },
     {
-      "name": "third-party2",
+      "name": "transitive-third-party1",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-nested-stronger-req.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-nested-stronger-req.json.snap
index 1a9db90..6d8b8ee 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-nested-stronger-req.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-nested-stronger-req.json.snap
@@ -12,11 +12,11 @@ expression: json
   ],
   "vetted_partially": [
     {
-      "name": "transitive-third-party1",
+      "name": "third-party1",
       "version": "10.0.0"
     },
     {
-      "name": "third-party1",
+      "name": "transitive-third-party1",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-nested-weaker-req.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-nested-weaker-req.json.snap
index 1a9db90..6d8b8ee 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-nested-weaker-req.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-nested-weaker-req.json.snap
@@ -12,11 +12,11 @@ expression: json
   ],
   "vetted_partially": [
     {
-      "name": "transitive-third-party1",
+      "name": "third-party1",
       "version": "10.0.0"
     },
     {
-      "name": "third-party1",
+      "name": "transitive-third-party1",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-overbroad.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-overbroad.json.snap
index 4680c77..faa9aa6 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-overbroad.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-overbroad.json.snap
@@ -14,15 +14,15 @@ expression: json
       "version": "10.0.0"
     },
     {
-      "name": "normal",
+      "name": "dev-proc-macro",
       "version": "10.0.0"
     },
     {
-      "name": "proc-macro",
+      "name": "normal",
       "version": "10.0.0"
     },
     {
-      "name": "dev-proc-macro",
+      "name": "proc-macro",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-twins.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-twins.json.snap
index a4cb2f3..3b47325 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-twins.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-twins.json.snap
@@ -18,11 +18,11 @@ expression: json
   "vetted_with_exemptions": [
     {
       "name": "third-core",
-      "version": "5.0.0"
+      "version": "10.0.0"
     },
     {
       "name": "third-core",
-      "version": "10.0.0"
+      "version": "5.0.0"
     }
   ]
 }
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-useless-long-cycle.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-useless-long-cycle.json.snap
index 327e9fb..4818073 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-useless-long-cycle.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-useless-long-cycle.json.snap
@@ -6,15 +6,15 @@ expression: json
   "conclusion": "success",
   "vetted_fully": [
     {
-      "name": "transitive-third-party1",
+      "name": "third-party1",
       "version": "10.0.0"
     },
     {
-      "name": "third-party1",
+      "name": "third-party2",
       "version": "10.0.0"
     },
     {
-      "name": "third-party2",
+      "name": "transitive-third-party1",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin_simple_foreign_audited.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin_simple_foreign_audited.json.snap
index 327e9fb..4818073 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin_simple_foreign_audited.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin_simple_foreign_audited.json.snap
@@ -6,15 +6,15 @@ expression: json
   "conclusion": "success",
   "vetted_fully": [
     {
-      "name": "transitive-third-party1",
+      "name": "third-party1",
       "version": "10.0.0"
     },
     {
-      "name": "third-party1",
+      "name": "third-party2",
       "version": "10.0.0"
     },
     {
-      "name": "third-party2",
+      "name": "transitive-third-party1",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin_simple_foreign_tag_team.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin_simple_foreign_tag_team.json.snap
index 327e9fb..4818073 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin_simple_foreign_tag_team.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin_simple_foreign_tag_team.json.snap
@@ -6,15 +6,15 @@ expression: json
   "conclusion": "success",
   "vetted_fully": [
     {
-      "name": "transitive-third-party1",
+      "name": "third-party1",
       "version": "10.0.0"
     },
     {
-      "name": "third-party1",
+      "name": "third-party2",
       "version": "10.0.0"
     },
     {
-      "name": "third-party2",
+      "name": "transitive-third-party1",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin_simple_mega_foreign_tag_team.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin_simple_mega_foreign_tag_team.json.snap
index 327e9fb..4818073 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__builtin_simple_mega_foreign_tag_team.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin_simple_mega_foreign_tag_team.json.snap
@@ -6,15 +6,15 @@ expression: json
   "conclusion": "success",
   "vetted_fully": [
     {
-      "name": "transitive-third-party1",
+      "name": "third-party1",
       "version": "10.0.0"
     },
     {
-      "name": "third-party1",
+      "name": "third-party2",
       "version": "10.0.0"
     },
     {
-      "name": "third-party2",
+      "name": "transitive-third-party1",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-full-audited.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-full-audited.json.snap
index 4c167e2..4502744 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-full-audited.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-full-audited.json.snap
@@ -7,11 +7,11 @@ expression: json
   "vetted_fully": [
     {
       "name": "third-core",
-      "version": "5.0.0"
+      "version": "10.0.0"
     },
     {
       "name": "third-core",
-      "version": "10.0.0"
+      "version": "5.0.0"
     },
     {
       "name": "thirdA",
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-inited.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-inited.json.snap
index c01059d..8a3c620 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-inited.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-inited.json.snap
@@ -9,11 +9,11 @@ expression: json
   "vetted_with_exemptions": [
     {
       "name": "third-core",
-      "version": "5.0.0"
+      "version": "10.0.0"
     },
     {
       "name": "third-core",
-      "version": "10.0.0"
+      "version": "5.0.0"
     },
     {
       "name": "thirdA",
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-full-audit.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-full-audit.json.snap
index 327e9fb..4818073 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-full-audit.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-full-audit.json.snap
@@ -6,15 +6,15 @@ expression: json
   "conclusion": "success",
   "vetted_fully": [
     {
-      "name": "transitive-third-party1",
+      "name": "third-party1",
       "version": "10.0.0"
     },
     {
-      "name": "third-party1",
+      "name": "third-party2",
       "version": "10.0.0"
     },
     {
-      "name": "third-party2",
+      "name": "transitive-third-party1",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-unaudited.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-unaudited.json.snap
index b87a0f0..81ce05a 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-unaudited.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-unaudited.json.snap
@@ -6,11 +6,11 @@ expression: json
   "conclusion": "success",
   "vetted_fully": [
     {
-      "name": "transitive-third-party1",
+      "name": "third-party2",
       "version": "10.0.0"
     },
     {
-      "name": "third-party2",
+      "name": "transitive-third-party1",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-full-audited.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-full-audited.json.snap
index 327e9fb..4818073 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-full-audited.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-full-audited.json.snap
@@ -6,15 +6,15 @@ expression: json
   "conclusion": "success",
   "vetted_fully": [
     {
-      "name": "transitive-third-party1",
+      "name": "third-party1",
       "version": "10.0.0"
     },
     {
-      "name": "third-party1",
+      "name": "third-party2",
       "version": "10.0.0"
     },
     {
-      "name": "third-party2",
+      "name": "transitive-third-party1",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-init.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-init.json.snap
index 72c07ad..956caa3 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-init.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-init.json.snap
@@ -8,15 +8,15 @@ expression: json
   "vetted_partially": [],
   "vetted_with_exemptions": [
     {
-      "name": "transitive-third-party1",
+      "name": "third-party1",
       "version": "10.0.0"
     },
     {
-      "name": "third-party1",
+      "name": "third-party2",
       "version": "10.0.0"
     },
     {
-      "name": "third-party2",
+      "name": "transitive-third-party1",
       "version": "10.0.0"
     }
   ]
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-no-unaudited.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-no-unaudited.snap
index 471ff55..3735798 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-no-unaudited.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-no-unaudited.snap
@@ -7,7 +7,7 @@ Vetting Failed!
 3 unvetted dependencies:
   third-party1:10.0.0 missing ["reviewed"]
   third-party2:10.0.0 missing ["reviewed"]
-  transitive-third-party1:10.0.0 likely missing ["reviewed"]
+  transitive-third-party1:10.0.0 missing ["reviewed"]
 
 recommended audits for reviewed:
     cargo vet inspect third-party1 10.0.0             (used by first-party)   (100 lines)
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-reverse-delta-to-full-audit.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-reverse-delta-to-full-audit.json.snap
index 327e9fb..4818073 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-reverse-delta-to-full-audit.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-reverse-delta-to-full-audit.json.snap
@@ -6,15 +6,15 @@ expression: json
   "conclusion": "success",
   "vetted_fully": [
     {
-      "name": "transitive-third-party1",
+      "name": "third-party1",
       "version": "10.0.0"
     },
     {
-      "name": "third-party1",
+      "name": "third-party2",
       "version": "10.0.0"
     },
     {
-      "name": "third-party2",
+      "name": "transitive-third-party1",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-reverse-delta-to-unaudited.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-reverse-delta-to-unaudited.json.snap
index b87a0f0..81ce05a 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-reverse-delta-to-unaudited.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-reverse-delta-to-unaudited.json.snap
@@ -6,11 +6,11 @@ expression: json
   "conclusion": "success",
   "vetted_fully": [
     {
-      "name": "transitive-third-party1",
+      "name": "third-party2",
       "version": "10.0.0"
     },
     {
-      "name": "third-party2",
+      "name": "transitive-third-party1",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-weaker-transitive-req-using-implies.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-weaker-transitive-req-using-implies.json.snap
index 327e9fb..4818073 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-weaker-transitive-req-using-implies.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-weaker-transitive-req-using-implies.json.snap
@@ -6,15 +6,15 @@ expression: json
   "conclusion": "success",
   "vetted_fully": [
     {
-      "name": "transitive-third-party1",
+      "name": "third-party1",
       "version": "10.0.0"
     },
     {
-      "name": "third-party1",
+      "name": "third-party2",
       "version": "10.0.0"
     },
     {
-      "name": "third-party2",
+      "name": "transitive-third-party1",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-weaker-transitive-req.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-weaker-transitive-req.json.snap
index 327e9fb..4818073 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-weaker-transitive-req.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-weaker-transitive-req.json.snap
@@ -6,15 +6,15 @@ expression: json
   "conclusion": "success",
   "vetted_fully": [
     {
-      "name": "transitive-third-party1",
+      "name": "third-party1",
       "version": "10.0.0"
     },
     {
-      "name": "third-party1",
+      "name": "third-party2",
       "version": "10.0.0"
     },
     {
-      "name": "third-party2",
+      "name": "transitive-third-party1",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock_simple_foreign_audited_pun_mapped.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock_simple_foreign_audited_pun_mapped.json.snap
index 327e9fb..4818073 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__mock_simple_foreign_audited_pun_mapped.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock_simple_foreign_audited_pun_mapped.json.snap
@@ -6,15 +6,15 @@ expression: json
   "conclusion": "success",
   "vetted_fully": [
     {
-      "name": "transitive-third-party1",
+      "name": "third-party1",
       "version": "10.0.0"
     },
     {
-      "name": "third-party1",
+      "name": "third-party2",
       "version": "10.0.0"
     },
     {
-      "name": "third-party2",
+      "name": "transitive-third-party1",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock_simple_foreign_audited_pun_no_mapping.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock_simple_foreign_audited_pun_no_mapping.snap
index 471ff55..3735798 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__mock_simple_foreign_audited_pun_no_mapping.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock_simple_foreign_audited_pun_no_mapping.snap
@@ -7,7 +7,7 @@ Vetting Failed!
 3 unvetted dependencies:
   third-party1:10.0.0 missing ["reviewed"]
   third-party2:10.0.0 missing ["reviewed"]
-  transitive-third-party1:10.0.0 likely missing ["reviewed"]
+  transitive-third-party1:10.0.0 missing ["reviewed"]
 
 recommended audits for reviewed:
     cargo vet inspect third-party1 10.0.0             (used by first-party)   (100 lines)
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock_simple_foreign_audited_pun_wrong_mapped.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock_simple_foreign_audited_pun_wrong_mapped.snap
index 471ff55..3735798 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__mock_simple_foreign_audited_pun_wrong_mapped.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock_simple_foreign_audited_pun_wrong_mapped.snap
@@ -7,7 +7,7 @@ Vetting Failed!
 3 unvetted dependencies:
   third-party1:10.0.0 missing ["reviewed"]
   third-party2:10.0.0 missing ["reviewed"]
-  transitive-third-party1:10.0.0 likely missing ["reviewed"]
+  transitive-third-party1:10.0.0 missing ["reviewed"]
 
 recommended audits for reviewed:
     cargo vet inspect third-party1 10.0.0             (used by first-party)   (100 lines)
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-extra.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-extra.json.snap
index 327e9fb..4818073 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-extra.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-extra.json.snap
@@ -6,15 +6,15 @@ expression: json
   "conclusion": "success",
   "vetted_fully": [
     {
-      "name": "transitive-third-party1",
+      "name": "third-party1",
       "version": "10.0.0"
     },
     {
-      "name": "third-party1",
+      "name": "third-party2",
       "version": "10.0.0"
     },
     {
-      "name": "third-party2",
+      "name": "transitive-third-party1",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-stronger.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-stronger.json.snap
index 327e9fb..4818073 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-stronger.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-stronger.json.snap
@@ -6,15 +6,15 @@ expression: json
   "conclusion": "success",
   "vetted_fully": [
     {
-      "name": "transitive-third-party1",
+      "name": "third-party1",
       "version": "10.0.0"
     },
     {
-      "name": "third-party1",
+      "name": "third-party2",
       "version": "10.0.0"
     },
     {
-      "name": "third-party2",
+      "name": "transitive-third-party1",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-too-strong.snap b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-too-strong.snap
index 0a3f4bd..17a4847 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-too-strong.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-too-strong.snap
@@ -6,7 +6,7 @@ Vetting Failed!
 
 2 unvetted dependencies:
   third-party1:10.0.0 missing ["strong-reviewed"]
-  transitive-third-party1:10.0.0 likely missing ["strong-reviewed"]
+  transitive-third-party1:10.0.0 missing ["strong-reviewed"]
 
 recommended audits for strong-reviewed:
     cargo vet inspect third-party1 10.0.0             (used by first-party)   (100 lines)
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-weaker-needed.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-weaker-needed.json.snap
index 327e9fb..4818073 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-weaker-needed.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-weaker-needed.json.snap
@@ -6,15 +6,15 @@ expression: json
   "conclusion": "success",
   "vetted_fully": [
     {
-      "name": "transitive-third-party1",
+      "name": "third-party1",
       "version": "10.0.0"
     },
     {
-      "name": "third-party1",
+      "name": "third-party2",
       "version": "10.0.0"
     },
     {
-      "name": "third-party2",
+      "name": "transitive-third-party1",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-weaker.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-weaker.json.snap
index 327e9fb..4818073 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-weaker.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-weaker.json.snap
@@ -6,15 +6,15 @@ expression: json
   "conclusion": "success",
   "vetted_fully": [
     {
-      "name": "transitive-third-party1",
+      "name": "third-party1",
       "version": "10.0.0"
     },
     {
-      "name": "third-party1",
+      "name": "third-party2",
       "version": "10.0.0"
     },
     {
-      "name": "third-party2",
+      "name": "transitive-third-party1",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-extra-partially-missing.snap b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-extra-partially-missing.snap
index a5bd182..eda793e 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-extra-partially-missing.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-extra-partially-missing.snap
@@ -6,7 +6,7 @@ Vetting Failed!
 
 2 unvetted dependencies:
   third-party1:10.0.0 missing ["fuzzed"]
-  transitive-third-party1:10.0.0 likely missing ["fuzzed"]
+  transitive-third-party1:10.0.0 missing ["fuzzed"]
 
 recommended audits for fuzzed:
     cargo vet inspect third-party1 10.0.0             (used by first-party)   (100 lines)
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-policy-redundant.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-policy-redundant.json.snap
index 327e9fb..4818073 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-policy-redundant.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-policy-redundant.json.snap
@@ -6,15 +6,15 @@ expression: json
   "conclusion": "success",
   "vetted_fully": [
     {
-      "name": "transitive-third-party1",
+      "name": "third-party1",
       "version": "10.0.0"
     },
     {
-      "name": "third-party1",
+      "name": "third-party2",
       "version": "10.0.0"
     },
     {
-      "name": "third-party2",
+      "name": "transitive-third-party1",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-too-strong.snap b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-too-strong.snap
index c3df2e9..fa0644e 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-too-strong.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-too-strong.snap
@@ -7,7 +7,7 @@ Vetting Failed!
 3 unvetted dependencies:
   third-party1:10.0.0 missing ["strong-reviewed"]
   third-party2:10.0.0 missing ["strong-reviewed"]
-  transitive-third-party1:10.0.0 likely missing ["strong-reviewed"]
+  transitive-third-party1:10.0.0 missing ["strong-reviewed"]
 
 recommended audits for strong-reviewed:
     cargo vet inspect third-party1 10.0.0             (used by first-party)   (100 lines)
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-weaker.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-weaker.json.snap
index 327e9fb..4818073 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-weaker.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-weaker.json.snap
@@ -6,15 +6,15 @@ expression: json
   "conclusion": "success",
   "vetted_fully": [
     {
-      "name": "transitive-third-party1",
+      "name": "third-party1",
       "version": "10.0.0"
     },
     {
-      "name": "third-party1",
+      "name": "third-party2",
       "version": "10.0.0"
     },
     {
-      "name": "third-party2",
+      "name": "transitive-third-party1",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-root-dep-too-strong.snap b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-root-dep-too-strong.snap
index c3df2e9..fa0644e 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-root-dep-too-strong.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-root-dep-too-strong.snap
@@ -7,7 +7,7 @@ Vetting Failed!
 3 unvetted dependencies:
   third-party1:10.0.0 missing ["strong-reviewed"]
   third-party2:10.0.0 missing ["strong-reviewed"]
-  transitive-third-party1:10.0.0 likely missing ["strong-reviewed"]
+  transitive-third-party1:10.0.0 missing ["strong-reviewed"]
 
 recommended audits for strong-reviewed:
     cargo vet inspect third-party1 10.0.0             (used by first-party)   (100 lines)
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-root-dep-weaker.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-root-dep-weaker.json.snap
index 327e9fb..4818073 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-root-dep-weaker.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-root-dep-weaker.json.snap
@@ -6,15 +6,15 @@ expression: json
   "conclusion": "success",
   "vetted_fully": [
     {
-      "name": "transitive-third-party1",
+      "name": "third-party1",
       "version": "10.0.0"
     },
     {
-      "name": "third-party1",
+      "name": "third-party2",
       "version": "10.0.0"
     },
     {
-      "name": "third-party2",
+      "name": "transitive-third-party1",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-root-too-strong.snap b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-root-too-strong.snap
index c3df2e9..fa0644e 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-root-too-strong.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-root-too-strong.snap
@@ -7,7 +7,7 @@ Vetting Failed!
 3 unvetted dependencies:
   third-party1:10.0.0 missing ["strong-reviewed"]
   third-party2:10.0.0 missing ["strong-reviewed"]
-  transitive-third-party1:10.0.0 likely missing ["strong-reviewed"]
+  transitive-third-party1:10.0.0 missing ["strong-reviewed"]
 
 recommended audits for strong-reviewed:
     cargo vet inspect third-party1 10.0.0             (used by first-party)   (100 lines)
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-root-weaker.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-root-weaker.json.snap
index 327e9fb..4818073 100644
--- a/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-root-weaker.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-root-weaker.json.snap
@@ -6,15 +6,15 @@ expression: json
   "conclusion": "success",
   "vetted_fully": [
     {
-      "name": "transitive-third-party1",
+      "name": "third-party1",
       "version": "10.0.0"
     },
     {
-      "name": "third-party1",
+      "name": "third-party2",
       "version": "10.0.0"
     },
     {
-      "name": "third-party2",
+      "name": "transitive-third-party1",
       "version": "10.0.0"
     }
   ],
diff --git a/src/tests/snapshots/cargo_vet__tests__violations__builtin-simple-deps-violation-dodged.json.snap b/src/tests/snapshots/cargo_vet__tests__violations__builtin-simple-deps-violation-dodged.json.snap
index be4d288..0729c08 100644
--- a/src/tests/snapshots/cargo_vet__tests__violations__builtin-simple-deps-violation-dodged.json.snap
+++ b/src/tests/snapshots/cargo_vet__tests__violations__builtin-simple-deps-violation-dodged.json.snap
@@ -14,19 +14,19 @@ expression: json
       "version": "10.0.0"
     },
     {
-      "name": "normal",
+      "name": "dev",
       "version": "10.0.0"
     },
     {
-      "name": "proc-macro",
+      "name": "dev-proc-macro",
       "version": "10.0.0"
     },
     {
-      "name": "dev",
+      "name": "normal",
       "version": "10.0.0"
     },
     {
-      "name": "dev-proc-macro",
+      "name": "proc-macro",
       "version": "10.0.0"
     }
   ],
diff --git a/tests/snapshots/test_cli__test-project-json.snap b/tests/snapshots/test_cli__test-project-json.snap
index 736cb4f..9b66621 100644
--- a/tests/snapshots/test_cli__test-project-json.snap
+++ b/tests/snapshots/test_cli__test-project-json.snap
@@ -10,21 +10,21 @@ stdout:
       "name": "atty",
       "version": "0.2.14"
     },
-    {
-      "name": "bitflags",
-      "version": "1.3.2"
-    },
     {
       "name": "autocfg",
       "version": "1.1.0"
     },
-    {
-      "name": "clap",
-      "version": "3.1.8"
-    },
     {
       "name": "base64",
       "version": "0.13.0"
+    },
+    {
+      "name": "bitflags",
+      "version": "1.3.2"
+    },
+    {
+      "name": "clap",
+      "version": "3.1.8"
     }
   ],
   "vetted_partially": [
@@ -35,400 +35,400 @@ stdout:
   ],
   "vetted_with_exemptions": [
     {
-      "name": "libc",
-      "version": "0.2.123"
-    },
-    {
-      "name": "hermit-abi",
-      "version": "0.1.19"
-    },
-    {
-      "name": "winapi-i686-pc-windows-gnu",
-      "version": "0.4.0"
-    },
-    {
-      "name": "winapi-x86_64-pc-windows-gnu",
-      "version": "0.4.0"
-    },
-    {
-      "name": "winapi",
-      "version": "0.3.9"
-    },
-    {
-      "name": "hashbrown",
-      "version": "0.11.2"
+      "name": "bumpalo",
+      "version": "3.9.1"
     },
     {
-      "name": "indexmap",
-      "version": "1.8.1"
+      "name": "bytes",
+      "version": "1.1.0"
     },
     {
-      "name": "memchr",
-      "version": "2.4.1"
+      "name": "cc",
+      "version": "1.0.73"
     },
     {
-      "name": "os_str_bytes",
-      "version": "6.0.0"
+      "name": "cfg-if",
+      "version": "1.0.0"
     },
     {
-      "name": "strsim",
-      "version": "0.10.0"
+      "name": "core-foundation",
+      "version": "0.9.3"
     },
     {
-      "name": "winapi-util",
-      "version": "0.1.5"
+      "name": "core-foundation-sys",
+      "version": "0.8.3"
     },
     {
-      "name": "termcolor",
-      "version": "1.1.3"
+      "name": "encoding_rs",
+      "version": "0.8.31"
     },
     {
-      "name": "textwrap",
-      "version": "0.15.0"
+      "name": "fastrand",
+      "version": "1.7.0"
     },
     {
-      "name": "unicode-xid",
-      "version": "0.2.2"
+      "name": "fnv",
+      "version": "1.0.7"
     },
     {
-      "name": "proc-macro2",
-      "version": "1.0.37@git:4445659b0f753a928059244c875a58bb12f791e9"
+      "name": "foreign-types",
+      "version": "0.3.2"
     },
     {
-      "name": "bytes",
-      "version": "1.1.0"
+      "name": "foreign-types-shared",
+      "version": "0.1.1"
     },
     {
-      "name": "cfg-if",
-      "version": "1.0.0"
+      "name": "form_urlencoded",
+      "version": "1.0.1"
     },
     {
-      "name": "encoding_rs",
-      "version": "0.8.31"
+      "name": "futures-channel",
+      "version": "0.3.21"
     },
     {
       "name": "futures-core",
       "version": "0.3.21"
     },
     {
-      "name": "futures-task",
+      "name": "futures-sink",
       "version": "0.3.21"
     },
     {
-      "name": "pin-project-lite",
-      "version": "0.2.8"
-    },
-    {
-      "name": "pin-utils",
-      "version": "0.1.0"
+      "name": "futures-task",
+      "version": "0.3.21"
     },
     {
       "name": "futures-util",
       "version": "0.3.21"
     },
     {
-      "name": "fnv",
-      "version": "1.0.7"
+      "name": "h2",
+      "version": "0.3.13"
     },
     {
-      "name": "futures-sink",
-      "version": "0.3.21"
+      "name": "hashbrown",
+      "version": "0.11.2"
     },
     {
-      "name": "itoa",
-      "version": "1.0.1"
+      "name": "hermit-abi",
+      "version": "0.1.19"
     },
     {
       "name": "http",
       "version": "0.2.6"
     },
     {
-      "name": "slab",
-      "version": "0.4.6"
-    },
-    {
-      "name": "log",
-      "version": "0.4.16"
+      "name": "http-body",
+      "version": "0.4.4"
     },
     {
-      "name": "miow",
-      "version": "0.3.7"
+      "name": "httparse",
+      "version": "1.7.0"
     },
     {
-      "name": "ntapi",
-      "version": "0.3.7"
+      "name": "httpdate",
+      "version": "1.0.2"
     },
     {
-      "name": "wasi",
-      "version": "0.11.0+wasi-snapshot-preview1"
+      "name": "hyper",
+      "version": "0.14.18"
     },
     {
-      "name": "mio",
-      "version": "0.8.2"
+      "name": "hyper-tls",
+      "version": "0.5.0"
     },
     {
-      "name": "socket2",
-      "version": "0.4.4"
+      "name": "idna",
+      "version": "0.2.3"
     },
     {
-      "name": "tokio",
-      "version": "1.17.0"
+      "name": "indexmap",
+      "version": "1.8.1"
     },
     {
-      "name": "proc-macro2",
-      "version": "1.0.37"
+      "name": "instant",
+      "version": "0.1.12"
     },
     {
-      "name": "quote",
-      "version": "1.0.18"
+      "name": "ipnet",
+      "version": "2.4.0"
     },
     {
-      "name": "syn",
-      "version": "1.0.91"
+      "name": "itoa",
+      "version": "1.0.1"
     },
     {
-      "name": "tracing-attributes",
-      "version": "0.1.20"
+      "name": "js-sys",
+      "version": "0.3.57"
     },
     {
       "name": "lazy_static",
       "version": "1.4.0"
     },
     {
-      "name": "tracing-core",
-      "version": "0.1.25"
+      "name": "libc",
+      "version": "0.2.123"
     },
     {
-      "name": "tracing",
-      "version": "0.1.33"
+      "name": "log",
+      "version": "0.4.16"
     },
     {
-      "name": "tokio-util",
-      "version": "0.7.1"
+      "name": "matches",
+      "version": "0.1.9"
     },
     {
-      "name": "h2",
-      "version": "0.3.13"
+      "name": "memchr",
+      "version": "2.4.1"
     },
     {
-      "name": "http-body",
-      "version": "0.4.4"
+      "name": "mime",
+      "version": "0.3.16"
     },
     {
-      "name": "futures-channel",
-      "version": "0.3.21"
+      "name": "mio",
+      "version": "0.8.2"
     },
     {
-      "name": "httparse",
-      "version": "1.7.0"
+      "name": "miow",
+      "version": "0.3.7"
     },
     {
-      "name": "httpdate",
-      "version": "1.0.2"
+      "name": "native-tls",
+      "version": "0.2.10"
     },
     {
-      "name": "tower-service",
-      "version": "0.3.1"
+      "name": "ntapi",
+      "version": "0.3.7"
     },
     {
-      "name": "try-lock",
-      "version": "0.2.3"
+      "name": "once_cell",
+      "version": "1.10.0"
     },
     {
-      "name": "want",
-      "version": "0.3.0"
+      "name": "openssl",
+      "version": "0.10.38"
     },
     {
-      "name": "hyper",
-      "version": "0.14.18"
+      "name": "openssl-probe",
+      "version": "0.1.5"
     },
     {
-      "name": "foreign-types-shared",
-      "version": "0.1.1"
+      "name": "openssl-sys",
+      "version": "0.9.72"
     },
     {
-      "name": "foreign-types",
-      "version": "0.3.2"
+      "name": "os_str_bytes",
+      "version": "6.0.0"
     },
     {
-      "name": "once_cell",
-      "version": "1.10.0"
+      "name": "percent-encoding",
+      "version": "2.1.0"
     },
     {
-      "name": "cc",
-      "version": "1.0.73"
+      "name": "pin-project-lite",
+      "version": "0.2.8"
+    },
+    {
+      "name": "pin-utils",
+      "version": "0.1.0"
     },
     {
       "name": "pkg-config",
       "version": "0.3.25"
     },
     {
-      "name": "vcpkg",
-      "version": "0.2.15"
+      "name": "proc-macro2",
+      "version": "1.0.37@git:4445659b0f753a928059244c875a58bb12f791e9"
     },
     {
-      "name": "openssl-sys",
-      "version": "0.9.72"
+      "name": "proc-macro2",
+      "version": "1.0.37"
     },
     {
-      "name": "openssl",
-      "version": "0.10.38"
+      "name": "quote",
+      "version": "1.0.18"
     },
     {
-      "name": "openssl-probe",
-      "version": "0.1.5"
+      "name": "redox_syscall",
+      "version": "0.2.13"
     },
     {
-      "name": "schannel",
-      "version": "0.1.19"
+      "name": "remove_dir_all",
+      "version": "0.5.3"
     },
     {
-      "name": "core-foundation-sys",
-      "version": "0.8.3"
+      "name": "reqwest",
+      "version": "0.11.10"
     },
     {
-      "name": "core-foundation",
-      "version": "0.9.3"
+      "name": "ryu",
+      "version": "1.0.9"
     },
     {
-      "name": "security-framework-sys",
-      "version": "2.6.1"
+      "name": "schannel",
+      "version": "0.1.19"
     },
     {
       "name": "security-framework",
       "version": "2.6.1"
     },
     {
-      "name": "instant",
-      "version": "0.1.12"
+      "name": "security-framework-sys",
+      "version": "2.6.1"
     },
     {
-      "name": "fastrand",
-      "version": "1.7.0"
+      "name": "serde",
+      "version": "1.0.136"
     },
     {
-      "name": "redox_syscall",
-      "version": "0.2.13"
+      "name": "serde_json",
+      "version": "1.0.79"
     },
     {
-      "name": "remove_dir_all",
-      "version": "0.5.3"
+      "name": "serde_urlencoded",
+      "version": "0.7.1"
+    },
+    {
+      "name": "slab",
+      "version": "0.4.6"
+    },
+    {
+      "name": "socket2",
+      "version": "0.4.4"
+    },
+    {
+      "name": "strsim",
+      "version": "0.10.0"
+    },
+    {
+      "name": "syn",
+      "version": "1.0.91"
     },
     {
       "name": "tempfile",
       "version": "3.3.0"
     },
     {
-      "name": "native-tls",
-      "version": "0.2.10"
+      "name": "termcolor",
+      "version": "1.1.3"
     },
     {
-      "name": "tokio-native-tls",
-      "version": "0.3.0"
+      "name": "textwrap",
+      "version": "0.15.0"
     },
     {
-      "name": "hyper-tls",
-      "version": "0.5.0"
+      "name": "tinyvec",
+      "version": "1.5.1"
     },
     {
-      "name": "ipnet",
-      "version": "2.4.0"
+      "name": "tinyvec_macros",
+      "version": "0.1.0"
     },
     {
-      "name": "bumpalo",
-      "version": "3.9.1"
+      "name": "tokio",
+      "version": "1.17.0"
     },
     {
-      "name": "wasm-bindgen-shared",
-      "version": "0.2.80"
+      "name": "tokio-native-tls",
+      "version": "0.3.0"
     },
     {
-      "name": "wasm-bindgen-backend",
-      "version": "0.2.80"
+      "name": "tokio-util",
+      "version": "0.7.1"
     },
     {
-      "name": "wasm-bindgen-macro-support",
-      "version": "0.2.80"
+      "name": "tower-service",
+      "version": "0.3.1"
     },
     {
-      "name": "wasm-bindgen-macro",
-      "version": "0.2.80"
+      "name": "tracing",
+      "version": "0.1.33"
     },
     {
-      "name": "wasm-bindgen",
-      "version": "0.2.80"
+      "name": "tracing-attributes",
+      "version": "0.1.20"
     },
     {
-      "name": "js-sys",
-      "version": "0.3.57"
+      "name": "tracing-core",
+      "version": "0.1.25"
     },
     {
-      "name": "mime",
-      "version": "0.3.16"
+      "name": "try-lock",
+      "version": "0.2.3"
     },
     {
-      "name": "percent-encoding",
-      "version": "2.1.0"
+      "name": "unicode-normalization",
+      "version": "0.1.19"
     },
     {
-      "name": "serde",
-      "version": "1.0.136"
+      "name": "unicode-xid",
+      "version": "0.2.2"
     },
     {
-      "name": "ryu",
-      "version": "1.0.9"
+      "name": "url",
+      "version": "2.2.2"
     },
     {
-      "name": "serde_json",
-      "version": "1.0.79"
+      "name": "vcpkg",
+      "version": "0.2.15"
     },
     {
-      "name": "matches",
-      "version": "0.1.9"
+      "name": "want",
+      "version": "0.3.0"
     },
     {
-      "name": "form_urlencoded",
-      "version": "1.0.1"
+      "name": "wasi",
+      "version": "0.11.0+wasi-snapshot-preview1"
     },
     {
-      "name": "serde_urlencoded",
-      "version": "0.7.1"
+      "name": "wasm-bindgen",
+      "version": "0.2.80"
     },
     {
-      "name": "tinyvec_macros",
-      "version": "0.1.0"
+      "name": "wasm-bindgen-backend",
+      "version": "0.2.80"
     },
     {
-      "name": "tinyvec",
-      "version": "1.5.1"
+      "name": "wasm-bindgen-futures",
+      "version": "0.4.30"
     },
     {
-      "name": "unicode-normalization",
-      "version": "0.1.19"
+      "name": "wasm-bindgen-macro",
+      "version": "0.2.80"
     },
     {
-      "name": "idna",
-      "version": "0.2.3"
+      "name": "wasm-bindgen-macro-support",
+      "version": "0.2.80"
     },
     {
-      "name": "url",
-      "version": "2.2.2"
+      "name": "wasm-bindgen-shared",
+      "version": "0.2.80"
     },
     {
       "name": "web-sys",
       "version": "0.3.57"
     },
     {
-      "name": "wasm-bindgen-futures",
-      "version": "0.4.30"
+      "name": "winapi",
+      "version": "0.3.9"
     },
     {
-      "name": "winreg",
-      "version": "0.10.1"
+      "name": "winapi-i686-pc-windows-gnu",
+      "version": "0.4.0"
     },
     {
-      "name": "reqwest",
-      "version": "0.11.10"
+      "name": "winapi-util",
+      "version": "0.1.5"
+    },
+    {
+      "name": "winapi-x86_64-pc-windows-gnu",
+      "version": "0.4.0"
+    },
+    {
+      "name": "winreg",
+      "version": "0.10.1"
     }
   ]
 }
diff --git a/tests/snapshots/test_cli__test-project-suggest-json.snap b/tests/snapshots/test_cli__test-project-suggest-json.snap
index fff5e09..595a185 100644
--- a/tests/snapshots/test_cli__test-project-suggest-json.snap
+++ b/tests/snapshots/test_cli__test-project-suggest-json.snap
@@ -702,182 +702,6 @@ stdout:
   ],
   "suggest": {
     "suggestions": [
-      {
-        "name": "hermit-abi",
-        "notable_parents": "atty",
-        "suggested_criteria": [
-          "safe-to-run"
-        ],
-        "suggested_diff": {
-          "from": null,
-          "to": "0.1.19",
-          "diffstat": {
-            "insertions": 932,
-            "deletions": 0,
-            "files_changed": 9
-          }
-        }
-      },
-      {
-        "name": "termcolor",
-        "notable_parents": "clap",
-        "suggested_criteria": [
-          "safe-to-deploy"
-        ],
-        "suggested_diff": {
-          "from": null,
-          "to": "1.1.3",
-          "diffstat": {
-            "insertions": 2578,
-            "deletions": 0,
-            "files_changed": 10
-          }
-        }
-      },
-      {
-        "name": "os_str_bytes",
-        "notable_parents": "clap",
-        "suggested_criteria": [
-          "safe-to-deploy"
-        ],
-        "suggested_diff": {
-          "from": null,
-          "to": "6.0.0",
-          "diffstat": {
-            "insertions": 2911,
-            "deletions": 0,
-            "files_changed": 21
-          }
-        }
-      },
-      {
-        "name": "textwrap",
-        "notable_parents": "clap",
-        "suggested_criteria": [
-          "safe-to-deploy"
-        ],
-        "suggested_diff": {
-          "from": null,
-          "to": "0.15.0",
-          "diffstat": {
-            "insertions": 5196,
-            "deletions": 0,
-            "files_changed": 15
-          }
-        }
-      },
-      {
-        "name": "proc-macro2",
-        "notable_parents": "test-project",
-        "suggested_criteria": [
-          "safe-to-deploy"
-        ],
-        "suggested_diff": {
-          "from": null,
-          "to": "1.0.37@git:4445659b0f753a928059244c875a58bb12f791e9",
-          "diffstat": {
-            "insertions": 5691,
-            "deletions": 0,
-            "files_changed": 19
-          }
-        }
-      },
-      {
-        "name": "indexmap",
-        "notable_parents": "h2, clap",
-        "suggested_criteria": [
-          "safe-to-deploy"
-        ],
-        "suggested_diff": {
-          "from": null,
-          "to": "1.8.1",
-          "diffstat": {
-            "insertions": 9445,
-            "deletions": 0,
-            "files_changed": 31
-          }
-        }
-      },
-      {
-        "name": "reqwest",
-        "notable_parents": "test-project",
-        "suggested_criteria": [
-          "safe-to-deploy"
-        ],
-        "suggested_diff": {
-          "from": null,
-          "to": "0.11.10",
-          "diffstat": {
-            "insertions": 19797,
-            "deletions": 0,
-            "files_changed": 60
-          }
-        }
-      },
-      {
-        "name": "serde_json",
-        "notable_parents": "reqwest, test-project",
-        "suggested_criteria": [
-          "safe-to-deploy"
-        ],
-        "suggested_diff": {
-          "from": null,
-          "to": "1.0.79",
-          "diffstat": {
-            "insertions": 22848,
-            "deletions": 0,
-            "files_changed": 86
-          }
-        }
-      },
-      {
-        "name": "tokio",
-        "notable_parents": "h2, hyper, reqwest, and 4 others",
-        "suggested_criteria": [
-          "safe-to-deploy"
-        ],
-        "suggested_diff": {
-          "from": null,
-          "to": "1.17.0",
-          "diffstat": {
-            "insertions": 91271,
-            "deletions": 0,
-            "files_changed": 403
-          }
-        }
-      },
-      {
-        "name": "libc",
-        "notable_parents": "mio, atty, tokio, openssl, and 8 others",
-        "suggested_criteria": [
-          "safe-to-deploy"
-        ],
-        "suggested_diff": {
-          "from": null,
-          "to": "0.2.123",
-          "diffstat": {
-            "insertions": 94060,
-            "deletions": 0,
-            "files_changed": 215
-          }
-        }
-      },
-      {
-        "name": "winapi",
-        "notable_parents": "mio, atty, miow, ntapi, and 7 others",
-        "suggested_criteria": [
-          "safe-to-deploy"
-        ],
-        "suggested_diff": {
-          "from": null,
-          "to": "0.3.9",
-          "diffstat": {
-            "insertions": 181323,
-            "deletions": 0,
-            "files_changed": 410
-          }
-        }
-      },
       {
         "name": "tinyvec_macros",
         "notable_parents": "tinyvec",
@@ -1198,6 +1022,22 @@ stdout:
           }
         }
       },
+      {
+        "name": "hermit-abi",
+        "notable_parents": "atty",
+        "suggested_criteria": [
+          "safe-to-run"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.1.19",
+          "diffstat": {
+            "insertions": 932,
+            "deletions": 0,
+            "files_changed": 9
+          }
+        }
+      },
       {
         "name": "httpdate",
         "notable_parents": "hyper",
@@ -1486,6 +1326,22 @@ stdout:
           }
         }
       },
+      {
+        "name": "termcolor",
+        "notable_parents": "clap",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "1.1.3",
+          "diffstat": {
+            "insertions": 2578,
+            "deletions": 0,
+            "files_changed": 10
+          }
+        }
+      },
       {
         "name": "slab",
         "notable_parents": "h2",
@@ -1502,6 +1358,22 @@ stdout:
           }
         }
       },
+      {
+        "name": "os_str_bytes",
+        "notable_parents": "clap",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "6.0.0",
+          "diffstat": {
+            "insertions": 2911,
+            "deletions": 0,
+            "files_changed": 21
+          }
+        }
+      },
       {
         "name": "wasm-bindgen-backend",
         "notable_parents": "wasm-bindgen-macro-support",
@@ -1758,6 +1630,22 @@ stdout:
           }
         }
       },
+      {
+        "name": "textwrap",
+        "notable_parents": "clap",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.15.0",
+          "diffstat": {
+            "insertions": 5196,
+            "deletions": 0,
+            "files_changed": 15
+          }
+        }
+      },
       {
         "name": "log",
         "notable_parents": "mio, want, reqwest, and 2 others",
@@ -1774,6 +1662,22 @@ stdout:
           }
         }
       },
+      {
+        "name": "proc-macro2",
+        "notable_parents": "test-project",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "1.0.37@git:4445659b0f753a928059244c875a58bb12f791e9",
+          "diffstat": {
+            "insertions": 5691,
+            "deletions": 0,
+            "files_changed": 19
+          }
+        }
+      },
       {
         "name": "proc-macro2",
         "notable_parents": "syn, quote, and 3 others",
@@ -1918,6 +1822,22 @@ stdout:
           }
         }
       },
+      {
+        "name": "indexmap",
+        "notable_parents": "h2, clap",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "1.8.1",
+          "diffstat": {
+            "insertions": 9445,
+            "deletions": 0,
+            "files_changed": 31
+          }
+        }
+      },
       {
         "name": "openssl-sys",
         "notable_parents": "openssl, native-tls",
@@ -2094,6 +2014,22 @@ stdout:
           }
         }
       },
+      {
+        "name": "reqwest",
+        "notable_parents": "test-project",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.11.10",
+          "diffstat": {
+            "insertions": 19797,
+            "deletions": 0,
+            "files_changed": 60
+          }
+        }
+      },
       {
         "name": "wasm-bindgen",
         "notable_parents": "js-sys, reqwest, web-sys, wasm-bindgen-futures",
@@ -2126,6 +2062,22 @@ stdout:
           }
         }
       },
+      {
+        "name": "serde_json",
+        "notable_parents": "reqwest, test-project",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "1.0.79",
+          "diffstat": {
+            "insertions": 22848,
+            "deletions": 0,
+            "files_changed": 86
+          }
+        }
+      },
       {
         "name": "hyper",
         "notable_parents": "reqwest, hyper-tls",
@@ -2255,200 +2207,88 @@ stdout:
         }
       },
       {
-        "name": "web-sys",
-        "notable_parents": "reqwest, wasm-bindgen-futures",
+        "name": "tokio",
+        "notable_parents": "h2, hyper, reqwest, and 4 others",
         "suggested_criteria": [
           "safe-to-deploy"
         ],
         "suggested_diff": {
           "from": null,
-          "to": "0.3.57",
+          "to": "1.17.0",
           "diffstat": {
-            "insertions": 197013,
+            "insertions": 91271,
             "deletions": 0,
-            "files_changed": 2202
+            "files_changed": 403
           }
         }
       },
       {
-        "name": "encoding_rs",
-        "notable_parents": "reqwest",
+        "name": "libc",
+        "notable_parents": "mio, atty, tokio, openssl, and 8 others",
         "suggested_criteria": [
           "safe-to-deploy"
         ],
         "suggested_diff": {
           "from": null,
-          "to": "0.8.31",
+          "to": "0.2.123",
           "diffstat": {
-            "insertions": 507245,
+            "insertions": 94060,
             "deletions": 0,
-            "files_changed": 102
-          }
-        }
-      }
-    ],
-    "suggest_by_criteria": {
-      "safe-to-deploy": [
-        {
-          "name": "termcolor",
-          "notable_parents": "clap",
-          "suggested_criteria": [
-            "safe-to-deploy"
-          ],
-          "suggested_diff": {
-            "from": null,
-            "to": "1.1.3",
-            "diffstat": {
-              "insertions": 2578,
-              "deletions": 0,
-              "files_changed": 10
-            }
-          }
-        },
-        {
-          "name": "os_str_bytes",
-          "notable_parents": "clap",
-          "suggested_criteria": [
-            "safe-to-deploy"
-          ],
-          "suggested_diff": {
-            "from": null,
-            "to": "6.0.0",
-            "diffstat": {
-              "insertions": 2911,
-              "deletions": 0,
-              "files_changed": 21
-            }
-          }
-        },
-        {
-          "name": "textwrap",
-          "notable_parents": "clap",
-          "suggested_criteria": [
-            "safe-to-deploy"
-          ],
-          "suggested_diff": {
-            "from": null,
-            "to": "0.15.0",
-            "diffstat": {
-              "insertions": 5196,
-              "deletions": 0,
-              "files_changed": 15
-            }
-          }
-        },
-        {
-          "name": "proc-macro2",
-          "notable_parents": "test-project",
-          "suggested_criteria": [
-            "safe-to-deploy"
-          ],
-          "suggested_diff": {
-            "from": null,
-            "to": "1.0.37@git:4445659b0f753a928059244c875a58bb12f791e9",
-            "diffstat": {
-              "insertions": 5691,
-              "deletions": 0,
-              "files_changed": 19
-            }
-          }
-        },
-        {
-          "name": "indexmap",
-          "notable_parents": "h2, clap",
-          "suggested_criteria": [
-            "safe-to-deploy"
-          ],
-          "suggested_diff": {
-            "from": null,
-            "to": "1.8.1",
-            "diffstat": {
-              "insertions": 9445,
-              "deletions": 0,
-              "files_changed": 31
-            }
-          }
-        },
-        {
-          "name": "reqwest",
-          "notable_parents": "test-project",
-          "suggested_criteria": [
-            "safe-to-deploy"
-          ],
-          "suggested_diff": {
-            "from": null,
-            "to": "0.11.10",
-            "diffstat": {
-              "insertions": 19797,
-              "deletions": 0,
-              "files_changed": 60
-            }
-          }
-        },
-        {
-          "name": "serde_json",
-          "notable_parents": "reqwest, test-project",
-          "suggested_criteria": [
-            "safe-to-deploy"
-          ],
-          "suggested_diff": {
-            "from": null,
-            "to": "1.0.79",
-            "diffstat": {
-              "insertions": 22848,
-              "deletions": 0,
-              "files_changed": 86
-            }
+            "files_changed": 215
           }
-        },
-        {
-          "name": "tokio",
-          "notable_parents": "h2, hyper, reqwest, and 4 others",
-          "suggested_criteria": [
-            "safe-to-deploy"
-          ],
-          "suggested_diff": {
-            "from": null,
-            "to": "1.17.0",
-            "diffstat": {
-              "insertions": 91271,
-              "deletions": 0,
-              "files_changed": 403
-            }
+        }
+      },
+      {
+        "name": "winapi",
+        "notable_parents": "mio, atty, miow, ntapi, and 7 others",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.3.9",
+          "diffstat": {
+            "insertions": 181323,
+            "deletions": 0,
+            "files_changed": 410
           }
-        },
-        {
-          "name": "libc",
-          "notable_parents": "mio, atty, tokio, openssl, and 8 others",
-          "suggested_criteria": [
-            "safe-to-deploy"
-          ],
-          "suggested_diff": {
-            "from": null,
-            "to": "0.2.123",
-            "diffstat": {
-              "insertions": 94060,
-              "deletions": 0,
-              "files_changed": 215
-            }
+        }
+      },
+      {
+        "name": "web-sys",
+        "notable_parents": "reqwest, wasm-bindgen-futures",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.3.57",
+          "diffstat": {
+            "insertions": 197013,
+            "deletions": 0,
+            "files_changed": 2202
           }
-        },
-        {
-          "name": "winapi",
-          "notable_parents": "mio, atty, miow, ntapi, and 7 others",
-          "suggested_criteria": [
-            "safe-to-deploy"
-          ],
-          "suggested_diff": {
-            "from": null,
-            "to": "0.3.9",
-            "diffstat": {
-              "insertions": 181323,
-              "deletions": 0,
-              "files_changed": 410
-            }
+        }
+      },
+      {
+        "name": "encoding_rs",
+        "notable_parents": "reqwest",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.8.31",
+          "diffstat": {
+            "insertions": 507245,
+            "deletions": 0,
+            "files_changed": 102
           }
-        },
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "safe-to-deploy": [
         {
           "name": "tinyvec_macros",
           "notable_parents": "tinyvec",
@@ -3057,6 +2897,22 @@ stdout:
             }
           }
         },
+        {
+          "name": "termcolor",
+          "notable_parents": "clap",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "1.1.3",
+            "diffstat": {
+              "insertions": 2578,
+              "deletions": 0,
+              "files_changed": 10
+            }
+          }
+        },
         {
           "name": "slab",
           "notable_parents": "h2",
@@ -3073,6 +2929,22 @@ stdout:
             }
           }
         },
+        {
+          "name": "os_str_bytes",
+          "notable_parents": "clap",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "6.0.0",
+            "diffstat": {
+              "insertions": 2911,
+              "deletions": 0,
+              "files_changed": 21
+            }
+          }
+        },
         {
           "name": "wasm-bindgen-backend",
           "notable_parents": "wasm-bindgen-macro-support",
@@ -3329,6 +3201,22 @@ stdout:
             }
           }
         },
+        {
+          "name": "textwrap",
+          "notable_parents": "clap",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.15.0",
+            "diffstat": {
+              "insertions": 5196,
+              "deletions": 0,
+              "files_changed": 15
+            }
+          }
+        },
         {
           "name": "log",
           "notable_parents": "mio, want, reqwest, and 2 others",
@@ -3345,6 +3233,22 @@ stdout:
             }
           }
         },
+        {
+          "name": "proc-macro2",
+          "notable_parents": "test-project",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "1.0.37@git:4445659b0f753a928059244c875a58bb12f791e9",
+            "diffstat": {
+              "insertions": 5691,
+              "deletions": 0,
+              "files_changed": 19
+            }
+          }
+        },
         {
           "name": "proc-macro2",
           "notable_parents": "syn, quote, and 3 others",
@@ -3489,6 +3393,22 @@ stdout:
             }
           }
         },
+        {
+          "name": "indexmap",
+          "notable_parents": "h2, clap",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "1.8.1",
+            "diffstat": {
+              "insertions": 9445,
+              "deletions": 0,
+              "files_changed": 31
+            }
+          }
+        },
         {
           "name": "openssl-sys",
           "notable_parents": "openssl, native-tls",
@@ -3665,6 +3585,22 @@ stdout:
             }
           }
         },
+        {
+          "name": "reqwest",
+          "notable_parents": "test-project",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.11.10",
+            "diffstat": {
+              "insertions": 19797,
+              "deletions": 0,
+              "files_changed": 60
+            }
+          }
+        },
         {
           "name": "wasm-bindgen",
           "notable_parents": "js-sys, reqwest, web-sys, wasm-bindgen-futures",
@@ -3697,6 +3633,22 @@ stdout:
             }
           }
         },
+        {
+          "name": "serde_json",
+          "notable_parents": "reqwest, test-project",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "1.0.79",
+            "diffstat": {
+              "insertions": 22848,
+              "deletions": 0,
+              "files_changed": 86
+            }
+          }
+        },
         {
           "name": "hyper",
           "notable_parents": "reqwest, hyper-tls",
@@ -3825,6 +3777,54 @@ stdout:
             }
           }
         },
+        {
+          "name": "tokio",
+          "notable_parents": "h2, hyper, reqwest, and 4 others",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "1.17.0",
+            "diffstat": {
+              "insertions": 91271,
+              "deletions": 0,
+              "files_changed": 403
+            }
+          }
+        },
+        {
+          "name": "libc",
+          "notable_parents": "mio, atty, tokio, openssl, and 8 others",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.2.123",
+            "diffstat": {
+              "insertions": 94060,
+              "deletions": 0,
+              "files_changed": 215
+            }
+          }
+        },
+        {
+          "name": "winapi",
+          "notable_parents": "mio, atty, miow, ntapi, and 7 others",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.3.9",
+            "diffstat": {
+              "insertions": 181323,
+              "deletions": 0,
+              "files_changed": 410
+            }
+          }
+        },
         {
           "name": "web-sys",
           "notable_parents": "reqwest, wasm-bindgen-futures",
diff --git a/tests/snapshots/test_cli__test-project-suggest.snap b/tests/snapshots/test_cli__test-project-suggest.snap
index d7e7265..903dd3a 100644
--- a/tests/snapshots/test_cli__test-project-suggest.snap
+++ b/tests/snapshots/test_cli__test-project-suggest.snap
@@ -4,16 +4,6 @@ expression: format_outputs(&output)
 ---
 stdout:
 recommended audits for safe-to-deploy:
-    cargo vet inspect termcolor 1.1.3                                                  (used by clap)                                              (2578 lines)
-    cargo vet inspect os_str_bytes 6.0.0                                               (used by clap)                                              (2911 lines)
-    cargo vet inspect textwrap 0.15.0                                                  (used by clap)                                              (5196 lines)
-    cargo vet inspect proc-macro2 1.0.37@git:4445659b0f753a928059244c875a58bb12f791e9  (used by test-project)                                      (5691 lines)
-    cargo vet inspect indexmap 1.8.1                                                   (used by h2, clap)                                          (9445 lines)
-    cargo vet inspect reqwest 0.11.10                                                  (used by test-project)                                      (19797 lines)
-    cargo vet inspect serde_json 1.0.79                                                (used by reqwest, test-project)                             (22848 lines)
-    cargo vet inspect tokio 1.17.0                                                     (used by h2, hyper, reqwest, and 4 others)                  (91271 lines)
-    cargo vet inspect libc 0.2.123                                                     (used by mio, atty, tokio, openssl, and 8 others)           (94060 lines)
-    cargo vet inspect winapi 0.3.9                                                     (used by mio, atty, miow, ntapi, and 7 others)              (181323 lines)
     cargo vet inspect tinyvec_macros 0.1.0                                             (used by tinyvec)                                           (81 lines)
     cargo vet inspect matches 0.1.9                                                    (used by url, idna, form_urlencoded)                        (210 lines)
     cargo vet inspect foreign-types-shared 0.1.1                                       (used by foreign-types)                                     (302 lines)
@@ -52,7 +42,9 @@ recommended audits for safe-to-deploy:
     cargo vet inspect security-framework-sys 2.6.1                                     (used by native-tls, security-framework)                    (2016 lines)
     cargo vet inspect unicode-xid 0.2.2                                                (used by syn, proc-macro2, proc-macro2)                     (2044 lines)
     cargo vet inspect serde_urlencoded 0.7.1                                           (used by reqwest)                                           (2120 lines)
+    cargo vet inspect termcolor 1.1.3                                                  (used by clap)                                              (2578 lines)
     cargo vet inspect slab 0.4.6                                                       (used by h2)                                                (2615 lines)
+    cargo vet inspect os_str_bytes 6.0.0                                               (used by clap)                                              (2911 lines)
     cargo vet inspect wasm-bindgen-backend 0.2.80                                      (used by wasm-bindgen-macro-support)                        (3018 lines)
     cargo vet inspect miow 0.3.7                                                       (used by mio)                                               (3129 lines)
     cargo vet inspect unicode-bidi 0.3.7                                               (used by idna)                                              (3217 lines)
@@ -69,7 +61,9 @@ recommended audits for safe-to-deploy:
     cargo vet inspect tempfile 3.3.0                                                   (used by native-tls)                                        (4059 lines)
     cargo vet inspect ryu 1.0.9                                                        (used by serde_json, serde_urlencoded)                      (4362 lines)
     cargo vet inspect schannel 0.1.19                                                  (used by native-tls)                                        (4601 lines)
+    cargo vet inspect textwrap 0.15.0                                                  (used by clap)                                              (5196 lines)
     cargo vet inspect log 0.4.16                                                       (used by mio, want, reqwest, and 2 others)                  (5625 lines)
+    cargo vet inspect proc-macro2 1.0.37@git:4445659b0f753a928059244c875a58bb12f791e9  (used by test-project)                                      (5691 lines)
     cargo vet inspect proc-macro2 1.0.37                                               (used by syn, quote, and 3 others)                          (5695 lines)
     cargo vet inspect socket2 0.4.4                                                    (used by hyper, tokio)                                      (6011 lines)
     cargo vet inspect pin-project-lite 0.2.8                                           (used by hyper, tokio, reqwest, and 4 others)               (6100 lines)
@@ -79,6 +73,7 @@ recommended audits for safe-to-deploy:
     cargo vet inspect memchr 2.4.1                                                     (used by tokio, os_str_bytes)                               (8712 lines)
     cargo vet inspect bytes 1.1.0                                                      (used by h2, http, hyper, tokio, and 4 others)              (9207 lines)
     cargo vet inspect security-framework 2.6.1                                         (used by native-tls)                                        (9316 lines)
+    cargo vet inspect indexmap 1.8.1                                                   (used by h2, clap)                                          (9445 lines)
     cargo vet inspect openssl-sys 0.9.72                                               (used by openssl, native-tls)                               (9477 lines)
     cargo vet inspect bumpalo 3.9.1                                                    (used by wasm-bindgen-backend)                              (10099 lines)
     cargo vet inspect tracing 0.1.33                                                   (used by h2, hyper, tokio-util)                             (10950 lines)
@@ -90,8 +85,10 @@ recommended audits for safe-to-deploy:
     cargo vet inspect url 2.2.2                                                        (used by reqwest)                                           (16344 lines)
     cargo vet inspect tinyvec 1.5.1                                                    (used by unicode-normalization)                             (16751 lines)
     cargo vet inspect http 0.2.6                                                       (used by h2, hyper, reqwest, http-body)                     (16819 lines)
+    cargo vet inspect reqwest 0.11.10                                                  (used by test-project)                                      (19797 lines)
     cargo vet inspect wasm-bindgen 0.2.80                                              (used by js-sys, reqwest, web-sys, wasm-bindgen-futures)    (20977 lines)
     cargo vet inspect ntapi 0.3.7                                                      (used by mio)                                               (21227 lines)
+    cargo vet inspect serde_json 1.0.79                                                (used by reqwest, test-project)                             (22848 lines)
     cargo vet inspect hyper 0.14.18                                                    (used by reqwest, hyper-tls)                                (24841 lines)
     cargo vet inspect futures-util 0.3.21                                              (used by h2, hyper, reqwest)                                (25067 lines)
     cargo vet inspect h2 0.3.13                                                        (used by hyper, reqwest)                                    (25417 lines)
@@ -100,6 +97,9 @@ recommended audits for safe-to-deploy:
     cargo vet inspect unicode-normalization 0.1.19                                     (used by idna)                                              (28622 lines)
     cargo vet inspect idna 0.2.3                                                       (used by url)                                               (32532 lines)
     cargo vet inspect vcpkg 0.2.15                                                     (used by openssl-sys)                                       (42642 lines)
+    cargo vet inspect tokio 1.17.0                                                     (used by h2, hyper, reqwest, and 4 others)                  (91271 lines)
+    cargo vet inspect libc 0.2.123                                                     (used by mio, atty, tokio, openssl, and 8 others)           (94060 lines)
+    cargo vet inspect winapi 0.3.9                                                     (used by mio, atty, miow, ntapi, and 7 others)              (181323 lines)
     cargo vet inspect web-sys 0.3.57                                                   (used by reqwest, wasm-bindgen-futures)                     (197013 lines)
     cargo vet inspect encoding_rs 0.8.31                                               (used by reqwest)                                           (507245 lines)