Merge 'aosp/upstream-main'

Bug: 292125255
Change-Id: If09cd0261d0db866c758f6c6db840edcc6ec8d2d
diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml
new file mode 100644
index 0000000..13b2d4f
--- /dev/null
+++ b/.github/dependabot.yaml
@@ -0,0 +1,8 @@
+version: 2
+updates:
+- package-ecosystem: cargo
+  directory: "/"
+  schedule:
+    interval: daily
+    time: "10:00"
+  open-pull-requests-limit: 10
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
new file mode 100644
index 0000000..9c406a7
--- /dev/null
+++ b/.github/workflows/deploy.yml
@@ -0,0 +1,33 @@
+name: Deploy
+on:
+  push:
+    branches:
+      - main
+
+jobs:
+  deploy:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v2
+      with:
+        fetch-depth: 0
+    - name: Install mdbook
+      run: |
+        mkdir mdbook
+        curl -sSL https://github.com/rust-lang/mdBook/releases/download/v0.4.14/mdbook-v0.4.14-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=./mdbook
+        echo `pwd`/mdbook >> $GITHUB_PATH
+    - name: Deploy GitHub Pages
+      run: |
+        cd book
+        mdbook build
+        git worktree add gh-pages gh-pages
+        git config user.name "Deploy from CI"
+        git config user.email ""
+        cd gh-pages
+        # Delete the ref to avoid keeping history.
+        git update-ref -d refs/heads/gh-pages
+        rm -rf *
+        mv ../book/* .
+        git add .
+        git commit -m "Deploy $GITHUB_SHA to gh-pages"
+        git push --force
diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
new file mode 100644
index 0000000..8fc1580
--- /dev/null
+++ b/.github/workflows/rust.yml
@@ -0,0 +1,71 @@
+name: Rust CI
+
+on:
+  pull_request:
+  push:
+    branches:
+      - main
+  schedule:
+    - cron: '11 7 * * 1,4'
+
+env:
+  RUSTFLAGS: -Dwarnings
+
+jobs:
+
+  fmt:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - name: Run cargo fmt
+        run: |
+          cargo fmt --all -- --check
+
+  clippy:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - name: Run cargo clippy
+        run: |
+          cargo clippy --workspace --tests --examples
+
+  docs:
+    runs-on: ubuntu-latest
+    env:
+      RUSTDOCFLAGS: -Dwarnings
+    steps:
+      - uses: actions/checkout@v2
+      - name: Run cargo doc
+        run: |
+          cargo doc --workspace --no-deps
+
+  test:
+    runs-on: ${{ matrix.os }}
+    strategy:
+      matrix:
+        os: [ubuntu-latest, windows-latest, macOS-latest]
+    steps:
+      - uses: actions/checkout@v2
+      - name: Run cargo test
+        run: |
+          cargo test --workspace --all-targets
+
+  msrv-check:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - uses: actions-rs/toolchain@v1
+        with:
+          toolchain: 1.65.0
+          override: true
+      - name: Run cargo check
+        run: |
+          cargo check
+
+  vet:
+    runs-on: ubuntu-latest
+    steps:
+     - uses: actions/checkout@v2
+     - name: Run cargo vet
+       run: |
+        cargo run -- vet --locked
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d217983
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+/target
+tests/test-project/target
\ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..1a3aa1c
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,2053 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "addr2line"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "aho-corasick"
+version = "0.7.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "ansi_term"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi 0.1.19",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "backtrace"
+version = "0.3.65"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61"
+dependencies = [
+ "addr2line",
+ "cc",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+]
+
+[[package]]
+name = "backtrace-ext"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50"
+dependencies = [
+ "backtrace",
+]
+
+[[package]]
+name = "base64"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
+
+[[package]]
+name = "base64-stream"
+version = "1.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4896f7f4cd81cc2610cf11f682cd562e9e4e24ddc597a9b5d1bf1c5e6bb3ddfb"
+dependencies = [
+ "base64",
+ "educe",
+ "generic-array",
+]
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bumpalo"
+version = "3.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3"
+
+[[package]]
+name = "bytes"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
+
+[[package]]
+name = "camino"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "869119e97797867fd90f5e22af7d0bd274bd4635ebb9eb68c04f3f513ae6c412"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "cargo-platform"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "cargo-vet"
+version = "0.8.0"
+dependencies = [
+ "base64-stream",
+ "bytes",
+ "cargo_metadata",
+ "chrono",
+ "clap",
+ "clap-cargo",
+ "console",
+ "crates-index",
+ "dirs",
+ "filetime",
+ "flate2",
+ "futures-util",
+ "home",
+ "indicatif",
+ "insta",
+ "lazy_static",
+ "libc",
+ "miette",
+ "nom",
+ "open",
+ "reqwest",
+ "serde",
+ "serde_json",
+ "similar",
+ "tar",
+ "tempfile",
+ "textwrap",
+ "thiserror",
+ "tokio",
+ "toml",
+ "toml_edit",
+ "tracing",
+ "tracing-subscriber",
+ "url",
+ "winapi",
+]
+
+[[package]]
+name = "cargo_metadata"
+version = "0.15.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "982a0cf6a99c350d7246035613882e376d58cebe571785abc5da4f648d53ac0a"
+dependencies = [
+ "camino",
+ "cargo-platform",
+ "semver",
+ "serde",
+ "serde_json",
+ "thiserror",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
+dependencies = [
+ "jobserver",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
+dependencies = [
+ "num-integer",
+ "num-traits",
+ "serde",
+]
+
+[[package]]
+name = "clap"
+version = "3.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f1fe12880bae935d142c8702d500c63a4e8634b6c3c57ad72bf978fc7b6249a"
+dependencies = [
+ "atty",
+ "bitflags",
+ "clap_derive",
+ "clap_lex",
+ "indexmap",
+ "once_cell",
+ "strsim",
+ "termcolor",
+ "textwrap",
+]
+
+[[package]]
+name = "clap-cargo"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "841b17c26cdae63b80cd9014f4fb779b761c388908bdf58e15223a08d65e9b08"
+dependencies = [
+ "clap",
+ "doc-comment",
+]
+
+[[package]]
+name = "clap_derive"
+version = "3.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed6db9e867166a43a53f7199b5e4d1f522a1e5bd626654be263c999ce59df39a"
+dependencies = [
+ "heck",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.96",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5538cd660450ebeb4234cfecf8f2284b844ffc4c50531e66d584ad5b91293613"
+dependencies = [
+ "os_str_bytes",
+]
+
+[[package]]
+name = "combine"
+version = "4.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a604e93b79d1808327a6fca85a6f2d69de66461e7620f5a4cbf5fb4d1d7c948"
+dependencies = [
+ "bytes",
+ "memchr",
+]
+
+[[package]]
+name = "console"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a28b32d32ca44b70c3e4acd7db1babf555fa026e385fb95f18028f88848b3c31"
+dependencies = [
+ "encode_unicode",
+ "libc",
+ "once_cell",
+ "regex",
+ "terminal_size",
+ "unicode-width",
+ "winapi",
+]
+
+[[package]]
+name = "crates-index"
+version = "0.18.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2519c91ad7a6e3250a64fb71162d2db1afe7bcf826a465f84d2052fd69639b7a"
+dependencies = [
+ "git2",
+ "hex",
+ "home",
+ "memchr",
+ "num_cpus",
+ "rustc-hash",
+ "semver",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "smartstring",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "dirs"
+version = "4.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
+dependencies = [
+ "dirs-sys",
+]
+
+[[package]]
+name = "dirs-sys"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
+dependencies = [
+ "libc",
+ "redox_users",
+ "winapi",
+]
+
+[[package]]
+name = "doc-comment"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
+
+[[package]]
+name = "educe"
+version = "0.4.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb0188e3c3ba8df5753894d54461f0e39bc91741dc5b22e1c46999ec2c71f4e4"
+dependencies = [
+ "enum-ordinalize",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.96",
+]
+
+[[package]]
+name = "either"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
+
+[[package]]
+name = "encode_unicode"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
+
+[[package]]
+name = "encoding_rs"
+version = "0.8.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "enum-ordinalize"
+version = "3.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a62bb1df8b45ecb7ffa78dca1c17a438fb193eb083db0b1b494d2a61bcb5096a"
+dependencies = [
+ "num-bigint",
+ "num-traits",
+ "proc-macro2",
+ "quote",
+ "rustc_version",
+ "syn 1.0.96",
+]
+
+[[package]]
+name = "errno"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
+dependencies = [
+ "errno-dragonfly",
+ "libc",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "errno-dragonfly"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
+dependencies = [
+ "cc",
+ "libc",
+]
+
+[[package]]
+name = "fastrand"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf"
+dependencies = [
+ "instant",
+]
+
+[[package]]
+name = "filetime"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0408e2626025178a6a7f7ffc05a25bc47103229f19c113755de7bf63816290c"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "winapi",
+]
+
+[[package]]
+name = "flate2"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6"
+dependencies = [
+ "crc32fast",
+ "libz-sys",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
+dependencies = [
+ "matches",
+ "percent-encoding",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3"
+
+[[package]]
+name = "futures-sink"
+version = "0.3.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868"
+
+[[package]]
+name = "futures-task"
+version = "0.3.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a"
+
+[[package]]
+name = "futures-util"
+version = "0.3.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "gimli"
+version = "0.26.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4"
+
+[[package]]
+name = "git2"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0155506aab710a86160ddb504a480d2964d7ab5b9e62419be69e0032bc5931c"
+dependencies = [
+ "bitflags",
+ "libc",
+ "libgit2-sys",
+ "log",
+ "url",
+]
+
+[[package]]
+name = "h2"
+version = "0.3.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57"
+dependencies = [
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "futures-util",
+ "http",
+ "indexmap",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
+
+[[package]]
+name = "heck"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "home"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2456aef2e6b6a9784192ae780c0f15bc57df0e918585282325e8c8ac27737654"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "http"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
+dependencies = [
+ "bytes",
+ "http",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c"
+
+[[package]]
+name = "httpdate"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
+
+[[package]]
+name = "hyper"
+version = "0.14.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42dc3c131584288d375f2d07f822b0cb012d8c6fb899a5b9fdb3cb7eb9b6004f"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "httparse",
+ "httpdate",
+ "itoa",
+ "pin-project-lite",
+ "socket2",
+ "tokio",
+ "tower-service",
+ "tracing",
+ "want",
+]
+
+[[package]]
+name = "hyper-rustls"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac"
+dependencies = [
+ "http",
+ "hyper",
+ "rustls",
+ "tokio",
+ "tokio-rustls",
+]
+
+[[package]]
+name = "idna"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
+dependencies = [
+ "matches",
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6012d540c5baa3589337a98ce73408de9b5a25ec9fc2c6fd6be8f0d39e0ca5a"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+]
+
+[[package]]
+name = "indicatif"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcc42b206e70d86ec03285b123e65a5458c92027d1fb2ae3555878b8113b3ddf"
+dependencies = [
+ "console",
+ "number_prefix",
+ "unicode-width",
+]
+
+[[package]]
+name = "insta"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36fb7ec420af04ce7d1a422945cd19c52bf01772ead45934cee77f056dca1081"
+dependencies = [
+ "console",
+ "once_cell",
+ "serde",
+ "serde_json",
+ "serde_yaml",
+ "similar",
+]
+
+[[package]]
+name = "instant"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "io-lifetimes"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
+dependencies = [
+ "hermit-abi 0.3.1",
+ "libc",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "ipnet"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b"
+
+[[package]]
+name = "is-terminal"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f"
+dependencies = [
+ "hermit-abi 0.3.1",
+ "io-lifetimes",
+ "rustix",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "is_ci"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb"
+
+[[package]]
+name = "itertools"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d"
+
+[[package]]
+name = "jobserver"
+version = "0.1.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.57"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.146"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b"
+
+[[package]]
+name = "libgit2-sys"
+version = "0.13.4+1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0fa6563431ede25f5cc7f6d803c6afbc1c5d3ad3d4925d12c882bf2b526f5d1"
+dependencies = [
+ "cc",
+ "libc",
+ "libz-sys",
+ "pkg-config",
+]
+
+[[package]]
+name = "libz-sys"
+version = "1.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "linked-hash-map"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
+
+[[package]]
+name = "log"
+version = "0.4.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "matches"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
+
+[[package]]
+name = "memchr"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
+
+[[package]]
+name = "miette"
+version = "5.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a236ff270093b0b67451bc50a509bd1bad302cb1d3c7d37d5efe931238581fa9"
+dependencies = [
+ "backtrace",
+ "backtrace-ext",
+ "is-terminal",
+ "miette-derive",
+ "once_cell",
+ "owo-colors",
+ "supports-color",
+ "supports-hyperlinks",
+ "supports-unicode",
+ "terminal_size",
+ "textwrap",
+ "thiserror",
+ "unicode-width",
+]
+
+[[package]]
+name = "miette-derive"
+version = "5.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4901771e1d44ddb37964565c654a3223ba41a594d02b8da471cc4464912b5cfa"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.18",
+]
+
+[[package]]
+name = "mime"
+version = "0.3.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
+
+[[package]]
+name = "minimal-lexical"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "mio"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799"
+dependencies = [
+ "libc",
+ "log",
+ "wasi",
+ "windows-sys 0.36.1",
+]
+
+[[package]]
+name = "nom"
+version = "7.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+]
+
+[[package]]
+name = "num-bigint"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
+dependencies = [
+ "hermit-abi 0.1.19",
+ "libc",
+]
+
+[[package]]
+name = "number_prefix"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
+
+[[package]]
+name = "object"
+version = "0.28.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225"
+
+[[package]]
+name = "open"
+version = "3.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f23a407004a1033f53e93f9b45580d14de23928faad187384f891507c9b0c045"
+dependencies = [
+ "pathdiff",
+ "windows-sys 0.36.1",
+]
+
+[[package]]
+name = "os_str_bytes"
+version = "6.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa"
+
+[[package]]
+name = "owo-colors"
+version = "3.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "decf7381921fea4dcb2549c5667eda59b3ec297ab7e2b5fc33eac69d2e7da87b"
+
+[[package]]
+name = "pathdiff"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd"
+
+[[package]]
+name = "percent-encoding"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
+
+[[package]]
+name = "proc-macro-error"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+dependencies = [
+ "proc-macro-error-attr",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.96",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-error-attr"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "redox_users"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
+dependencies = [
+ "getrandom",
+ "redox_syscall",
+ "thiserror",
+]
+
+[[package]]
+name = "regex"
+version = "1.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64"
+
+[[package]]
+name = "remove_dir_all"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "reqwest"
+version = "0.11.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b75aa69a3f06bbcc66ede33af2af253c6f7a86b1ca0033f60c580a27074fbf92"
+dependencies = [
+ "base64",
+ "bytes",
+ "encoding_rs",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "hyper",
+ "hyper-rustls",
+ "ipnet",
+ "js-sys",
+ "lazy_static",
+ "log",
+ "mime",
+ "percent-encoding",
+ "pin-project-lite",
+ "rustls",
+ "rustls-pemfile",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "tokio",
+ "tokio-rustls",
+ "tower-service",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "webpki-roots",
+ "winreg",
+]
+
+[[package]]
+name = "ring"
+version = "0.16.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
+dependencies = [
+ "cc",
+ "libc",
+ "once_cell",
+ "spin",
+ "untrusted",
+ "web-sys",
+ "winapi",
+]
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
+
+[[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
+[[package]]
+name = "rustc_version"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rustix"
+version = "0.37.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0"
+dependencies = [
+ "bitflags",
+ "errno",
+ "io-lifetimes",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
+name = "rustls"
+version = "0.20.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033"
+dependencies = [
+ "log",
+ "ring",
+ "sct",
+ "webpki",
+]
+
+[[package]]
+name = "rustls-pemfile"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7522c9de787ff061458fe9a829dc790a3f5b22dc571694fc5883f448b94d9a9"
+dependencies = [
+ "base64",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695"
+
+[[package]]
+name = "sct"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
+[[package]]
+name = "semver"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a41d061efea015927ac527063765e73601444cdc344ba855bc7bd44578b25e1c"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.137"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.137"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.96",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.82"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_yaml"
+version = "0.8.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "707d15895415db6628332b737c838b88c598522e4dc70647e59b72312924aebc"
+dependencies = [
+ "indexmap",
+ "ryu",
+ "serde",
+ "yaml-rust",
+]
+
+[[package]]
+name = "sharded-slab"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "similar"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62ac7f900db32bf3fd12e0117dd3dc4da74bc52ebaac97f39668446d89694803"
+
+[[package]]
+name = "slab"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32"
+
+[[package]]
+name = "smallvec"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
+
+[[package]]
+name = "smartstring"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29"
+dependencies = [
+ "autocfg",
+ "serde",
+ "static_assertions",
+ "version_check",
+]
+
+[[package]]
+name = "smawk"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
+
+[[package]]
+name = "socket2"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "spin"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
+name = "supports-color"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4950e7174bffabe99455511c39707310e7e9b440364a2fcb1cc21521be57b354"
+dependencies = [
+ "is-terminal",
+ "is_ci",
+]
+
+[[package]]
+name = "supports-hyperlinks"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f84231692eb0d4d41e4cdd0cabfdd2e6cd9e255e65f80c9aa7c98dd502b4233d"
+dependencies = [
+ "is-terminal",
+]
+
+[[package]]
+name = "supports-unicode"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b6c2cb240ab5dd21ed4906895ee23fe5a48acdbd15a3ce388e7b62a9b66baf7"
+dependencies = [
+ "is-terminal",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.96"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "tar"
+version = "0.4.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6"
+dependencies = [
+ "filetime",
+ "libc",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
+dependencies = [
+ "cfg-if",
+ "fastrand",
+ "libc",
+ "redox_syscall",
+ "remove_dir_all",
+ "winapi",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "terminal_size"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
+dependencies = [
+ "smawk",
+ "unicode-linebreak",
+ "unicode-width",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.18",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
+
+[[package]]
+name = "tokio"
+version = "1.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581"
+dependencies = [
+ "autocfg",
+ "bytes",
+ "libc",
+ "memchr",
+ "mio",
+ "num_cpus",
+ "once_cell",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "socket2",
+ "tokio-macros",
+ "winapi",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.96",
+]
+
+[[package]]
+name = "tokio-rustls"
+version = "0.23.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59"
+dependencies = [
+ "rustls",
+ "tokio",
+ "webpki",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+ "tracing",
+]
+
+[[package]]
+name = "toml"
+version = "0.5.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.14.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5376256e44f2443f8896ac012507c19a012df0fe8758b55246ae51a2279db51f"
+dependencies = [
+ "combine",
+ "indexmap",
+ "itertools",
+ "serde",
+]
+
+[[package]]
+name = "tower-service"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6"
+
+[[package]]
+name = "tracing"
+version = "0.1.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160"
+dependencies = [
+ "cfg-if",
+ "log",
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.96",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7709595b8878a4965ce5e87ebf880a7d39c9afc6837721b21a5a816a8117d921"
+dependencies = [
+ "once_cell",
+ "valuable",
+]
+
+[[package]]
+name = "tracing-log"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922"
+dependencies = [
+ "lazy_static",
+ "log",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4bc28f93baff38037f64e6f43d34cfa1605f27a49c34e8a04c5e78b0babf2596"
+dependencies = [
+ "ansi_term",
+ "sharded-slab",
+ "smallvec",
+ "thread_local",
+ "tracing-core",
+ "tracing-log",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
+
+[[package]]
+name = "typenum"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c"
+
+[[package]]
+name = "unicode-linebreak"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a52dcaab0c48d931f7cc8ef826fa51690a08e1ea55117ef26f89864f532383f"
+dependencies = [
+ "regex",
+]
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-width"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
+
+[[package]]
+name = "untrusted"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
+
+[[package]]
+name = "url"
+version = "2.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "matches",
+ "percent-encoding",
+]
+
+[[package]]
+name = "valuable"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
+
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "want"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
+dependencies = [
+ "log",
+ "try-lock",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.80"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.80"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4"
+dependencies = [
+ "bumpalo",
+ "lazy_static",
+ "log",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.96",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f741de44b75e14c35df886aff5f1eb73aa114fa5d4d00dcd37b5e01259bf3b2"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.80"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.80"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.96",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.80"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744"
+
+[[package]]
+name = "web-sys"
+version = "0.3.57"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "webpki"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
+[[package]]
+name = "webpki-roots"
+version = "0.22.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44d8de8415c823c8abd270ad483c6feeac771fad964890779f9a8cb24fbbc1bf"
+dependencies = [
+ "webpki",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-sys"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
+dependencies = [
+ "windows_aarch64_msvc 0.36.1",
+ "windows_i686_gnu 0.36.1",
+ "windows_i686_msvc 0.36.1",
+ "windows_x86_64_gnu 0.36.1",
+ "windows_x86_64_msvc 0.36.1",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc 0.48.0",
+ "windows_i686_gnu 0.48.0",
+ "windows_i686_msvc 0.48.0",
+ "windows_x86_64_gnu 0.48.0",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc 0.48.0",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
+
+[[package]]
+name = "winreg"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "yaml-rust"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
+dependencies = [
+ "linked-hash-map",
+]
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..5a39c60
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,64 @@
+[package]
+name = "cargo-vet"
+version = "0.8.0"
+edition = "2021"
+authors = ["Bobby Holley <bobbyholley@gmail.com>"]
+license = "Apache-2.0/MIT"
+repository = "https://github.com/bholley/cargo-vet"
+homepage = "https://bholley.net/cargo-vet"
+description = "Supply-chain security for Rust"
+exclude = [
+  "book/*",
+  "src/snapshots/*",
+  "src/tests/",
+  "tests/",
+]
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+base64-stream = "1.2.7"
+bytes = "1.1.0"
+cargo_metadata = "0.15.2"
+chrono = { version = "0.4.23", default-features = false, features = ["alloc", "std", "serde"] }
+clap = { version = "3.2.6", features = ["derive"] }
+clap-cargo = "0.9.1"
+console = "0.15.0"
+crates-index = { version = "0.18.8", default-features = false }
+dirs = "4.0.0"
+filetime = "0.2.16"
+flate2 = { version = "1.0.3", default-features = false, features = ["zlib"] }
+futures-util = { version = "0.3.21", default-features = false, features = ["std"] }
+home = "0.5.3"
+indicatif = "0.17.0"
+lazy_static = "1.4.0"
+libc = "0.2"
+nom = "7.1.1"
+reqwest = { version = "0.11.10", default-features = false, features = ["rustls-tls"] }
+serde = "1.0.136"
+serde_json = "1.0.82"
+similar = "2.2.0"
+tar = { version = "0.4.26", default-features = false }
+tempfile = "3.3.0"
+textwrap = { version = "0.15", default-features = false }
+toml_edit = { version = "0.14.4", features = ["serde"] }
+tokio = { version = "1.20.1", features = ["fs", "macros", "process", "rt-multi-thread"] }
+tracing = { version = "0.1.34", features = ["log"] }
+tracing-subscriber = "0.3.11"
+miette = { version = "5.9.0", features = ["fancy"] }
+thiserror = "1.0.31"
+url = "2.2.2"
+toml = "0.5.9"
+open = "3.0.2"
+
+[target.'cfg(windows)'.dependencies.winapi]
+version = "0.3"
+features = [
+  "minwindef",
+  "winerror",
+  "fileapi",
+  "minwinbase",
+]
+
+[dev-dependencies]
+insta = "1.16.0"
diff --git a/LICENSE-APACHE b/LICENSE-APACHE
new file mode 100644
index 0000000..261eeb9
--- /dev/null
+++ b/LICENSE-APACHE
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/LICENSE-MIT b/LICENSE-MIT
new file mode 100644
index 0000000..bfc9986
--- /dev/null
+++ b/LICENSE-MIT
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 Bobby Holley
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..87c9df6
--- /dev/null
+++ b/README.md
@@ -0,0 +1,23 @@
+# cargo-vet

+

+[![crates.io](https://img.shields.io/crates/v/cargo-vet.svg)](https://crates.io/crates/cargo-vet)

+![Rust CI](https://github.com/mozilla/cargo-vet/workflows/Rust%20CI/badge.svg?branch=main)

+

+The `cargo vet` subcommand is a tool to help projects ensure that third-party Rust dependencies have been audited by a trusted entity. It strives to be lightweight and easy to integrate.

+

+More details available in the [book](https://mozilla.github.io/cargo-vet/).

+

+## License

+

+Licensed under either of

+

+ * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)

+ * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)

+

+at your option.

+

+### Contribution

+

+Unless you explicitly state otherwise, any contribution intentionally submitted

+for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any

+additional terms or conditions.

diff --git a/book/book.toml b/book/book.toml
new file mode 100644
index 0000000..cefb64c
--- /dev/null
+++ b/book/book.toml
@@ -0,0 +1,8 @@
+[book]
+authors = ["Bobby Holley"]
+multilingual = false
+src = "src"
+title = "Cargo Vet"
+
+[output.html]
+additional-css = ["src/custom.css"]
diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md
new file mode 100644
index 0000000..895c9fd
--- /dev/null
+++ b/book/src/SUMMARY.md
@@ -0,0 +1,27 @@
+# Summary
+
+- [Introduction](./index.md)
+  - [Motivation](./motivation.md)
+  - [How it Works](./how-it-works.md)
+- [Tutorial](./tutorial.md)
+  - [Installation](./install.md)
+  - [Setup](./setup.md)
+  - [Audit Criteria](./audit-criteria.md)
+  - [Importing Audits](./importing-audits.md)
+  - [Recording Audits](./recording-audits.md)
+  - [Performing Audits](./performing-audits.md)
+  - [Trusting Publishers](./trusting-publishers.md)
+  - [Specifying Policies](./specifying-policies.md)
+  - [Multiple Repositories](./multiple-repositories.md)
+  - [Configuring CI](./configuring-ci.md)
+  - [Curating Your Audit Set](./curating-your-audit-set.md)
+- [Reference](./reference.md)
+  - [Configuration](./config.md)
+  - [Audit Entries](./audit-entries.md)
+  - [Wildcard Audit Entries](./wildcard-audit-entries.md)
+  - [Trusted Entries](./trusted-entries.md)
+  - [Built-In Criteria](./built-in-criteria.md)
+  - [First-Party Code](./first-party-code.md)
+  - [FAQ](./faq.md)
+  - [Commands](./commands.md)
+  - [The Algorithm](./algorithm.md)
diff --git a/book/src/algorithm.md b/book/src/algorithm.md
new file mode 100644
index 0000000..8fa5a27
--- /dev/null
+++ b/book/src/algorithm.md
@@ -0,0 +1,608 @@
+# The Cargo Vet Algorithm

+

+The heart of `vet` is the "[resolver](https://github.com/mozilla/cargo-vet/blob/main/src/resolver.rs)" which takes in your build graph and your supply_chain dir, and determines whether `vet check` should pass.

+

+If `check` fails, it tries to determine the reason for that failure (which as we'll see is a non-trivial question). If you request a `suggest` it will then try to suggest "good" audits that will definitely satisfy `check` (which is again non-trivial).

+

+These results are a basic building block that most other commands will defer to:

+

+* `vet check` (the command run with bare `vet`) is just this operation

+* `vet suggest` is this operation with all suggestable exemptions deleted

+* `vet certify` fills in any unspecified information using this operation

+* `vet regenerate` generally uses this operation to know what to do

+

+For the sake of clarity, this chapter will also include some discussion of "initialization" which gathers up the input state that the resolver needs.

+

+## Initialization Steps

+

+This phase is generally just a bunch of loading, parsing, and validating. Different commands

+may vary slightly in how they do these steps, as they may implicitly be --locked or --frozen,

+or want to query hypothetical states.

+

+1. Acquire the build graph ([cargo metadata][] via the [cargo_metadata][] crate)

+2. Acquire the store (`supply_chain`) (load, parse, validate)

+3. Update the imports (fetch, parse, validate)

+4. Check `audit-as-crates-io` (check against local cargo registry)

+

+

+## Resolve Steps

+

+These are the logical steps of the resolver, although they are more interleaved than this

+initial summary implies:

+

+1. Build data structures

+    1. Construct the `DepGraph`

+    2. Construct the `CriteriaMapper`

+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

+abbreviated pseudo-rust. Each of these steps will be elaborated on in the

+subsequent sections.

+

+```rust ,ignore

+// 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(..);

+

+// Step 3: Resolve the validated criteria for each third-party package

+for package in &graph.nodes {

+    if !package.is_third_party {

+        continue;

+    }

+

+    // 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 4: Check if the crate validates for the required criteria

+    for criteria in requirements[package] {

+        match &search_results[criteria] {

+            ..

+        }

+    }

+}

+

+// If there were any conflicts with violation entries, bail!

+if !violations.is_empty() {

+    return ResolveReport { conclusion: Conclusion::FailForViolationConflict(..), .. };

+}

+

+// If there were no failures, we're done!

+if failures.is_empty() {

+    return ResolveReport { conclusion: Conclusion::Success(..), .. };

+}

+

+// Step 5: Suggest time! Compute the simplest audits to fix the failures!

+let suggest = compute_suggest(..);

+

+return ResolveReport { conclusion: Conclusion::FailForVet(..), .. };

+```

+

+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.

+

+

+

+

+# Step 1a: The DepGraph (Processing Cargo Metadata)

+

+All of our analysis derives from the output of [cargo metadata][] and our

+interpretation of that, so it's worth discussing how we use it, and what we

+believe to be true of its output.

+

+Our interpretation of the metadata is the DepGraph. You can dump the DepGraph with

+`cargo vet dump-graph`. Most commands take a `--filter-graph` argument which will

+force us to discard certain parts of the DepGraph before performing the operation

+of the command. This can be useful for debugging issues, but we recommend only doing

+this while `--locked` to avoid corrupting your store.

+

+By default we run `cargo metadata --locked --all-features`. If you pass `--locked` to vet,

+we will instead pass `--frozen` to `cargo metadata`. `--all-features` can be negated

+by passing `--no-all-features` to vet. We otherwise expose the usual feature flags of

+cargo directly.

+

+The reason we pass `--all-features` is because we want the "maximal" build graph, which

+all "real" builds are simply a subset of. Cargo metadata in general provides this, but

+will omit optional dependencies that are locked behind disabled features. By enabling them all,

+we should get every possible dependency for every possible feature and platform.

+

+By validating that the maximal build graph is vetted, all possible builds should in turn

+be vetted, because they are simply subsets of that graph.

+

+Cargo metadata produces the build graph in a kind of awkward way where some information

+for the packages is in `"packages"` and some information is in  `"resolve"`, and we need

+to manually compute lots of facts like "roots", "only for tests", and "[topological sort][]"

+(metadata has a notion of roots, but it's not what you think, and mostly reflects an 

+internal concept of cargo that isn't useful to us).

+

+If we knew about it at the time we might have used [guppy][] to handle interpretting

+cargo metadata's results. As it stands, we've hand-rolled all that stuff.

+

+Cargo metadata largely uses [PackageId][]s as primary keys for identifying a package

+in your build, and we largely agree with that internally, but some human-facing interfaces

+like audits also treat (PackageName, [Version][]) as a valid key. This is a true

+statement on crates.io itself, but may not hold when you include unpublished packages,

+patches/renames(?), or third party registries. We don't really have a solid disambiguation

+strategy at the moment, we just assume it doesn't happen and don't worry about it.

+

+The resolver primarily use a PackageIdx as a primary key for packages, which is an interned PackageId.

+The DepGraph holds this interner.

+

+

+

+## Dealing With Cycles From Tests

+

+The resolver assumes the maximal graph is a [DAG][], which is an almost true statement

+that we can make true with a minor desugaring of the graph. There is only one situation

+where the cargo build graph is not a DAG: the tests for a crate. This can happen very

+easily, and is kind of natural, but also very evil when you first learn about it.

+

+As a concrete example, there is kind of a conceptual cycle between [serde](https://github.com/serde-rs/serde/blob/master/serde/Cargo.toml) and [serde_derive](https://github.com/serde-rs/serde/blob/master/serde_derive/Cargo.toml). However serde_derive is a standalone crate, and serde (optionally)

+pulls in serde_derive as a dependency... unless you're testing serde_derive, and then serde_derive

+quite reasonably depends on serde to test its output, creating a cyclic dependency on itself!

+

+The way to resolve this monstrosity is to realize that the *tests* for serde_derive are actually

+a different package from serde_derive, which we call serde_derive_dev (because cargo calls test

+edges "dev dependencies"). So although the graph reported by cargo_metadata looks like a cycle:

+

+```

+serde <-----+

+  |         |

+  |         |

+  +--> serde_derive

+```

+

+In actuality, serde_derive_dev breaks the cycle and creates a nice clean DAG:

+

+```

+  +--serde_derive_dev ---+

+  |          |           |

+  v          |           v

+serde        |     test_only_dep

+  |          |           |

+  |          v          ...

+  +--> serde_derive

+```

+

+There is a subtle distinction to be made here for packages *only* used for tests:

+these wouldn't be part of the build graph without dev-dependencies (dev edges) but

+they are still "real" nodes, and all of their dependencies are "real" and still

+must form a proper DAG. The only packages which can have cycle-causing dev-dependencies,

+and therefore require a desugaring to produce "fake" nodes, are *workspace members*.

+These are the packages that will be tested if you run `cargo test --workspace`.

+

+Actually doing this desugaring is really messy, because a lot of things about the "real"

+node are still true about the "fake" node, and we generally want to talk about the "real"

+node and the "fake" node as if they were one thing. So we actually just analyze the build graph

+in two steps. To understand how this works, we need to first look at how DAGs are analyzed.

+

+Any analysis on a [DAG][] generally starts with a [topological sort][], which is just a fancy way of saying you do depth-first-search ([DFS][]) on every root and only use a node only after you've searched all its children (this is the post-order, for graph people). Note that each iteration of DFS reuses the

+"visited" from the previous iterations, because we only want to visit each node once.

+

+Also note that knowing the roots is simply an optimization, you can just run DFS on every node and you will get a valid topological order -- we run it for all the workspace members, which includes all of

+the roots, but none of the test-only packages, which will be useful for identifying test-only packages

+when we get to our desugaring. (You may have workspace members which in fact are only for testing,

+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!

+

+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:

+

+1. analyze the build graph without dev deps (edges), which is definitely a DAG

+2. add back the dev deps and reprocess all the nodes as if they were the "fake" node

+

+The key insight to this approach is that the implicit dev nodes are all roots -- nothing

+depends on them. As a result, adding these nodes can't change which packages the "real"

+nodes depend on, and any analysis done on them is valid without the dev edges!

+

+When doing the topological sort, because we only run DFS from workspace members,

+the result of this is that we will visit all the nodes that are part of a "real" build

+in the first pass, and then the test-only packages in the second pass. This makes computing

+"test only" packages a convenient side-effect of the topological sort. Hopefully it's clear

+to you that the resulting ordering functions as a topological sort as long as our recrusive

+analyses take the form of two loops as so:

+

+```

+for node in topological_sort:

+    analysis_that_DOESNT_query_dev_dependencies(node)

+for node in topological_sort:

+    analysis_that_CAN_query_dev_dependencies(node)

+```

+

+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

+

+The hardest task of the DepGraph is computing the topological sort of the packages as

+described in the previous section, but it also computes the following facts for each package

+(node):

+

+* [PackageId][] (primary key)

+* [Version][]

+* name

+* is_third_party (is_crates_io)

+* is_root

+* is_workspace_member

+* is_dev_only

+* normal_deps

+* build_deps

+* dev_deps

+* reverse_deps

+

+Whether a package is third party is deferred to [cargo_metadata][]'s [is_crates_io][] method

+but overrideable by `audit-as-crates-io` in config.toml. This completely changes how the

+resolver handles validating criteria for that package. Packages which aren't third party

+are referred to as "first party".

+

+Roots are simply packages which have no reverse-deps, which matters because those will

+implicitly be required to pass the default root policy (safe-to-deploy) if no other policy

+is specified for them.

+

+Workspace members must pass a dev-policy check, which is the only place where

+we query dev-dependencies (in the fabled "second pass" from the previous section).

+

+Dev-only packages are only used in tests, and therefore will only by queried in

+dev-policy checks (and so by default only need to be safe-to-run).

+

+

+

+

+

+# Step 1b: The CriteriaMapper

+

+The CriteriaMapper handles the process of converting between criteria names and

+CriteriaIndices. It's basically an interner, but made more complicated by the existence

+of builtins, imports, and "implies" relationships.

+

+The resolver primarily operates on CriteriaSets, which are sets of CriteriaIndices.

+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

+"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 efficient (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).

+

+Imported criteria are pre-mapped onto local criteria while acquiring the store,

+by using a CriteriaMapper in the imported namespace to determine implied

+criteria, and then applying the mappings specified in the `criteria-map` to

+determine the corresponding local criteria. This avoids worrying about imported

+namespaces when running the actual resolver, and helps avoid potential issues

+with large numbers of criteria.

+

+The biggest complexity of this process is handling "implies".  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.

+

+When mapping a criteria set to a list of criteria names, we will elide implied criteria

+(so a `["safe-to-deploy", "safe-to-run"]` will just be `["safe-to-deploy"]`).

+

+

+

+## Computing The Transitive Closure of Criteria

+

+The [transitive closure][] of a criteria is the CriteriaSet that would result if you

+add the criteria itself, and every criteria that implies, and every criteria THEY imply,

+and so on. This resulting CriteriaSet is effectively the "true" value of a criteria.

+

+We do this by constructing a directed "criteria graph" where an "implies" is an edge.

+The transitive closure for each criteria can then be computed by running depth-first-search

+([DFS][]) on that node, and adding every reachable node to the CriteriaSet.

+

+That's it!

+

+Being able to precompute the transitive closure massively simplifies the resolver,

+as it means we never have to re-evaulate the implies relationships when unioning

+CriteriaSets, making potentially O(n<sup>3</sup>) operations into constant time ones,

+where n is the number of criteria (the criteria graph can have O(n<sup>2</sup>) criteria,

+and a criteria set can have O(n) criteria, and we might have to look at every edge of

+the graph for every criteria whenever we add one).

+

+The *existence* of the transitive closure is however not a fundamental truth. It

+exists because we have artifically limited what import maps and implies is allowed to

+do. In particular, if you ever allowed an implies relationship that requires

+*two different criteria* to imply another, the transitive closure would not be

+a useful concept, and we'd be forced to re-check every implies rule whenever

+a criteria got added to a criteria set (which is happening constantly in the resolver).

+

+[See this issue for a detailed example demonstrating this problem](https://github.com/mozilla/cargo-vet/issues/240).

+

+

+

+

+

+

+# 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`).

+Each edge has a list of criteria it claims to certify, and dependency criteria that the

+dependencies of this package must satisfy for the edge to be considered "valid" (see

+the next section for details).

+

+There is an implicit Root Version which represents an empty package, meaning that throughout

+much of the audit graph, versions are represented as `Option<Version>`.

+

+When trying to validate whether a particular version of a package is audited, we also add

+a Target Version to the graph (if it doesn't exist already).

+

+Full audits are desugarred to delta audits from the Root Version (so an audit for `0.2.0` would

+be lowered to a delta audit from `Root -> 0.2.0`).

+

+Exemptions are desugared to full audits (and therefore deltas) with a special

+DeltaEdgeOrigin indicating their origin.  This is used to deprioritize the edges

+so that we can more easily detect exemptions that aren't needed anymore.

+

+Imported audits are lowered in the exact same way as local criteria, but with

+special DeltaEdgeOrigin to indicate their origin, to allow us to deprioritize

+imported audits, and determine exactly which audits are needed.

+

+A special DeltaEdgeOrigin is also used for imported wildcard criteria,

+indicating both which wildcard audit is responsible, as well as which publisher

+information is being used.

+

+With all of this established. the problem of determining whether a package is audited for a given

+criteria can be reduced to determining if there *exists* a path from the Root Version to the

+Target Version along edges that certify that criteria. Suggesting an audit similarly becomes

+finding the "best" edge to add to make the Root and Target connected for the desired criteria.

+

+

+## Checking Violations

+

+During AuditGraph construction violations are also checked. Violations have a [VersionReq][] and

+a list of violated criteria. They claim that, for all versions covered by the VersionReq, you believe

+that the listed criteria are explicitly violated. An error is produced if any edge is

+added to the AuditGraph where *either* endpoint matches the VersionReq and *any* criteria

+it claims to be an audit for is listed by the violation.

+

+This is an extremely complicated statement to parse, so let's look at some examples:

+

+```

+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!

+```

+

+One very notable implication of this is that a violation for `["safe-to-run", "safe-to-deploy"]`

+is actually equivalent to `["safe-to-run"]`, not `["safe-to-deploy"]`! This means that the normal

+way of handling things, turning the violation's criteria into one CriteriaSet and checking

+if `audit.contains(violation)` is incorrect!

+

+We must instead do this check for each individual item in the violation:

+

+```rust

+let has_violation = violation.iter().any(|item| audit.contains(item));

+```

+

+It may seem a bit strange to produce an error if *any* audit is in any way contradicted

+by *any* violation. Is that necessary? Is that sufficient?

+

+It's definitely sufficient: it's impossible to validate a version without having an audit edge

+with an end-point in that version.

+

+I would argue that it's also *necessary*: the existence of any audit (or exemption)

+that is directly contradicted by a violation is essentially an integrity error on the

+claims that we are working with. Even if you don't even use the audit for anything

+anymore, people who are peering with you and importing your audits might be, so you

+should do something about those audits as soon as you find out they might be wrong!

+

+There is currently no mechanism for mechanically dealing with such an integrity error,

+even if the audit or violation comes from a foreign import. Such a situation is serious

+enough that it merits direct discussion between humans. That said, if this becomes

+enough of a problem we may eventually add such a feature.

+

+

+

+# Step 3b: Searching for paths in the `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

+step we validate each criteria individually (so everything I'm about to

+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).

+

+If it does, we've validated the criteria for the Target Version. If it doesn't,

+then we haven't.

+

+But things are much more complicated because we want to provide more feedback

+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?)

+* 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

+using a stack, such that we only try to use the "best" edges first, and can

+be certain that we don't try to use a "worse" edge until we've tried all of the

+paths using better edges.

+

+The best edge of all is a local audit. If we can find a path using only

+those edges, then we're fully audited, we don't need any exemptions we

+might have for this package (a lot of caveats to this, so we don't really

+make that conclusion reliably), and the imports.lock doesn't need to be updated.

+

+If we need to add back in exemptions to find a path, then the exemptions

+were necessary to validate this criteria.

+

+If we need to add back in new imports to find a path, then we need to update

+imports.lock to cache necessary audits for --locked executions. (The fact

+that this comes after exemptions means we may be slightly imprecise about

+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 Ok(..), communicating both that the

+package validates this criteria, plus any caveats back to the caller.

+

+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 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 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 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.

+

+It's worth noting here that delta audits can "go backwards" (i.e. `1.0.1 -> 1.0.0`),

+and all of this code handles that perfectly fine without any special cases.

+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).

+

+

+

+# Step 4: Checking if each crate validates for the required criteria

+

+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.

+

+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 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 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

+sets, we are guaranteed that the intersections are non-empty.

+

+So how do we pick the *best* diff? Well, we straight up download every version of the package that

+we have audits for and diff-stat all the combinations. Smallest diff wins! Does that sound horrible

+and slow? It is! That's why we have a secret global diff-stat cache on your system.

+

+Also we don't *literally* diff every combination. We turn the O(n<sup>2</sup>) diffs

+into only O(n) diffs with a simple heuristic: for each Target Reachable Node,

+we find the package closest version *smaller* than that version and the closest version

+*bigger* than that version. We then diff that version against only those two versions.

+This may potentially miss some magical diff where a big change is made and then reverted,

+but this diffing stuff needs some amount of taming!

+

+It's worth noting that [Version]s don't form a proper metric space: We cannot compute

+the "distance" between two Versions in the abstract, and then compare that to the "distance"

+between two other versions. Versions *do* however have a total ordering, so we *can*

+compute minimum and maximum versions, and say whether a version is bigger or smaller

+than another. As a result it's possible to compute "the largest version that's smaller than X"

+and "the smallest version that's larger than X", which is what we use. There is however

+no way to say whether the smaller-maximum or the bigger-minimum is closer to X, so we must

+try both.

+

+It's also worth reiterating here that diffs *can* go backwards. If you're on 1.0.0 and

+have an audit for 1.0.1, we will happily recommend the reverse-diff from `1.0.1 -> 1.0.0`.

+This is slightly brain melty at first but nothing really needs to specially handle this,

+it Just Works.

+

+Any diff we recommend from the Root Version is "resugared" into recommending a full audit,

+(and is also computed by diffing against an empty directory). It is impossible

+to recommend a diff to the Root Version, because there cannot be audits of the

+Root Version.

+

+

+

+

+

+

+[cargo metadata]: https://doc.rust-lang.org/cargo/commands/cargo-metadata.html

+[cargo_metadata]: https://docs.rs/cargo_metadata/latest/cargo_metadata/

+[is_crates_io]: https://docs.rs/cargo_metadata/latest/cargo_metadata/struct.Source.html#method.is_crates_io

+[DAG]: https://en.wikipedia.org/wiki/Directed_acyclic_graph

+[PackageId]: https://docs.rs/cargo_metadata/latest/cargo_metadata/struct.PackageId.html

+[Version]: https://docs.rs/semver/latest/semver/struct.Version.html

+[VersionReq]: https://docs.rs/semver/latest/semver/struct.VersionReq.html

+[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

diff --git a/book/src/audit-criteria.md b/book/src/audit-criteria.md
new file mode 100644
index 0000000..7498428
--- /dev/null
+++ b/book/src/audit-criteria.md
@@ -0,0 +1,41 @@
+# Audit Criteria
+
+Before you can go about auditing code, you need to decide what you want the
+audits to entail. This is expressed with "audit criteria", which are just labels
+corresponding to human-readable descriptions of what to check for.
+
+`cargo vet` comes pre-equipped with two built-in criteria:
+[safe-to-run](built-in-criteria.md#safe-to-run) and
+[safe-to-deploy](built-in-criteria.md#safe-to-deploy). You can use these without
+any additional configuration.
+
+## Custom Criteria
+
+You can also specify arbitrary custom criteria in `audits.toml`. For example:
+
+```
+[criteria.crypto-reviewed]
+description = '''
+The cryptographic code in this crate has been reviewed for correctness by a
+member of a designated set of cryptography experts within the project.
+'''
+```
+
+The full feature set is documented [here](config.md#the-criteria-table).
+
+## Multiple Sets of Criteria
+
+There are a number of reasons you might wish to operate with multiple sets of
+criteria:
+* **Applying extra checks to some crates:** For example, you might define
+  `crypto-reviewed` criteria and require them for audits of crates which
+  implement cryptographic algorithms that your application depends on.
+* **Relaxing your audit requirements for some crates:** For example, you might
+  decide that crates not exposed in production can just be `safe-to-run`
+  rather than `safe-to-deploy`, since they don't need to be audited for handling
+  adversarial input.
+* **Improving Sharing:** If one project wants to audit for issues A and B, and
+  another project wants to audit for B and C, defining separate sets of criteria
+  for A, B, and C allows the two projects to partially share work.
+
+You can define and use as many separate sets of criteria as you like.
diff --git a/book/src/audit-entries.md b/book/src/audit-entries.md
new file mode 100644
index 0000000..4d2155c
--- /dev/null
+++ b/book/src/audit-entries.md
@@ -0,0 +1,54 @@
+# Audit Entries
+
+This section defines the semantics of the various keys that may be specified in
+audit table entries.
+
+## `version`
+
+Specifies that this audit entry corresponds to an absolute version that was
+audited for the relevant criteria in its entirety.
+
+## `delta`
+
+Specifies that this audit entry certifies that the delta between two absolute
+versions preserves the relevant criteria. Deltas can go both forward and
+backward in the version sequence.
+
+The syntax is `version_a -> version_b`, where the diff between version_a and
+version_b was audited.
+
+Note that it's not always possible to conclude that a diff preserves certain
+properties without also inspecting some portion of the base version. The
+standard here is that the properties are actually preserved, not merely that
+that the diff doesn't obviously violate them. It is the responsibility of the
+auditor to acquire sufficient context to certify the former.
+
+## `violation`
+
+Specifies that the given versions do not meet the associated criteria. Because a
+range of versions is usually required, this field uses Cargo's standard
+[VersionReq](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html)
+syntax.
+
+If a `violation` entry exists for a given crate version, `cargo vet` will reject
+the dependency even if it's listed in the `exemptions` table.
+
+## `criteria`
+
+Specifies the relevant criteria for this audit. This field is required.
+
+## `who`
+
+A string identifying the auditor. When invoking `cargo vet certify`, the
+value is auto-populated from the git config.
+
+This field is optional, but encouraged for two reasons:
+* It makes it easier to attribute audits at a glance, particularly for
+  remotely-hosted audit files.
+* It emphasizes to the author that they are signing off on having performed the
+  audit.
+
+## `notes`
+
+An optional free-form string containing any information the auditor may wish to
+record.
diff --git a/book/src/built-in-criteria.md b/book/src/built-in-criteria.md
new file mode 100644
index 0000000..daca2e3
--- /dev/null
+++ b/book/src/built-in-criteria.md
@@ -0,0 +1,19 @@
+# Built-In Criteria
+
+While you can define whatever criteria you like, `cargo vet` includes two
+commonly-used audit criteria out of the box. These criteria are automatically
+mapped across projects.
+
+## safe-to-run
+
+```
+{{#include ../../src/criteria/safe-to-run.txt}}
+```
+
+## safe-to-deploy
+
+```
+{{#include ../../src/criteria/safe-to-deploy.txt}}
+```
+
+This implies `safe-to-run`.
diff --git a/book/src/commands.md b/book/src/commands.md
new file mode 100644
index 0000000..366e050
--- /dev/null
+++ b/book/src/commands.md
@@ -0,0 +1,8 @@
+# Commands
+
+This section documents the command-line interface of `cargo vet`. The
+documentation is automatically generated from the implementation, and
+so it may be incomplete in some areas where the code remains under
+development.
+
+{{#include ../../tests/snapshots/test_cli__markdown-help.snap:14:}}
diff --git a/book/src/config.md b/book/src/config.md
new file mode 100644
index 0000000..4fe0019
--- /dev/null
+++ b/book/src/config.md
@@ -0,0 +1,234 @@
+# Configuration
+
+This section describes the structure and semantics of the various configuration
+files used by `cargo vet`.
+
+## Location
+
+By default, `cargo vet` data lives in a `supply-chain` directory next to
+`Cargo.lock`. This location is configurable via the `[package.metadata.vet]`
+directive in Cargo.toml, as well as via `[workspace.metadata.vet]` when using a
+workspace with a virtual root.
+
+The default configuration is equivalent to the following:
+
+```toml
+[package.metadata.vet]
+store = { path = './supply-chain' }
+```
+
+## `audits.toml`
+
+This file contains the audits performed by the project members and descriptions
+of the audit criteria. The information in this file can be imported by other
+projects.
+
+### The `criteria` Table
+
+This table defines different sets of custom criteria. Entries have several
+potential fields:
+
+#### `description`
+
+A concise description of the criteria. This field (or `description-url`) is
+required.
+
+#### `description-url`
+
+An alternative to `description` which locates the criteria text at a
+publicly-accessible URL. This can be useful for sharing criteria descriptions
+across multiple repositories.
+
+#### `implies`
+
+An optional string or array of other criteria that are subsumed by this entry.
+Audit entries that are certified with these criteria are also implicitly
+certified with any implied criteria.
+
+For example, specifying the [built-in criteria](built-in-criteria.md) as custom
+criteria would look like this:
+
+```
+[criteria.safe-to-run]
+description = '...'
+
+[criteria.safe-to-deploy]
+description = '...'
+implies = 'safe-to-run'
+```
+
+### The `audits` Table
+
+This table contains the audit entries, indexed by crate name. Because there are
+often multiple audits per crate (different versions, delta audits, etc), audit
+entries are specified as table arrays, i.e. `[[audits.foo]]`.
+
+The semantics of the various audit entries keys are described
+[here](audit-entries.md).
+
+### The `trusted` Table
+
+This table contains the trusted publisher entries, indexed by crate name. Because there may be
+multiple publishers per crate, trusted entries are specified as table arrays, i.e.
+`[[trusted.foo]]`.
+
+The semantics of the various trusted entries keys are described [here](trusted-entries.md).
+
+## `config.toml`
+
+This file contains configuration information for this specific project. This
+file cannot be imported by other projects.
+
+### `default-criteria`
+
+This top-level key specifies the default criteria that `cargo vet certify` will
+use when recording audits. If unspecified, this defaults to `safe-to-deploy`.
+
+### The `cargo-vet` Table
+
+This table contains metadata used to track the version of cargo-vet used to
+create the store, and may be used in the future to allow other global
+configuration details to be specified.
+
+### The `imports` Table
+
+This table enumerates the external audit sets that are imported into this
+project. The key is a user-defined nickname, so entries are specified as
+`[imports.foo]`.
+
+#### `url`
+
+Specifies an HTTPS url from which the remote `audits.toml` can be fetched. This
+field is required.
+
+#### `criteria-map`
+
+A table specifying mappings from the imported audit set to local criteria. Each
+imported audit's criteria is mapped through these import maps, considering the
+peer's `implies` relationships, and transformed into a set of local criteria
+when importing.
+
+```
+[imports.peer.criteria-map]
+peer-criteria = "local-criteria"
+their-super-audited = ["safe-to-deploy", "audited"]
+```
+
+Unless otherwise specified, the peer's `safe-to-run` and `safe-to-deploy`
+criteria will be implicitly mapped to the local `safe-to-run` and
+`safe-to-deploy` criteria. This can be overridden by specifying the mapping for
+`safe-to-run` or `safe-to-deploy` in the criteria map.
+
+```
+[imports.peer.criteria-map]
+safe-to-run = []
+safe-to-deploy = "safe-to-run"
+```
+
+Other unmapped criteria will be discarded when importing.
+
+#### `exclude`
+
+A list of crates whose audit entries should not be imported from this source.
+This can be used as a last resort to resolve disagreements over the suitability
+of a given crate.
+
+### The `policy` Table
+
+This table allows projects to configure the audit requirements that `cargo vet`
+should enforce on various dependencies. When unspecified, non-top-level crates
+inherit most policy attributes from their parents, whereas top-level crates get
+the defaults described below.
+
+In this context, "top-level" generally refers to crates with no
+reverse-dependencies — except when evaluating dev-dependencies, in which case
+every workspace member is considered a root.
+
+Keys of this table can be crate names (in which case the policy is applied to
+_all_ versions of the crate) or strings of the form `"CRATE:VERSION"` (you'll
+more than likely need to add quotes in TOML because the version string will have
+periods). If you specify versions, they may only refer to crate versions which
+are in the graph.
+
+#### `criteria`
+
+A string or array of strings specifying the criteria that should be enforced for
+this crate and its dependency subtree.
+
+This may only be specified for first-party crates. Requirements for third-party
+crates should be applied via inheritance or `dependency-criteria`.
+
+For top-level crates, defaults to `safe-to-deploy`.
+
+#### `dev-criteria`
+
+Same as the above, but applied to dev-dependencies.
+
+For top-level crates, defaults to `safe-to-run`.
+
+#### `dependency-criteria`
+
+Allows overriding the above values on a per-dependency basis.
+
+```
+[policy.foo]
+dependency-criteria = { bar = [] }
+notes = "bar is only used to implement a foo feature we never plan to enable."
+```
+
+Unlike `criteria` and `dev-criteria`, `dependency-criteria` may apply directly
+to third-party crates (both `foo` and `bar` may be third-party in the above
+example). Specifying `criteria` is disallowed for third-party crates because a
+given third-party crate can often be used in multiple unrelated places in a
+project's dependency graph. So in the above example, we want to exempt `bar`
+from auditing insofar as it's used by `foo`, but not necessarily if it crops up
+somewhere else.
+
+Third-party crates with `dependency-criteria` must be associated with specific
+versions in the policy table (see the description of policy table keys above).
+Additionally, if a crate has any `dependency-criteria` specified and any version
+exists as a third-party crate in the graph, all versions of the crate must be
+explicitly specified in the policy table keys.
+
+Defaults to the empty set and is not inherited.
+
+#### `audit-as-crates-io`
+
+Specifies whether first-party packages with this crate name should receive audit
+enforcement as if they were fetched from crates.io. See [First-Party
+Code](first-party-code.md) for more details.
+
+#### `notes`
+
+Free-form string for recording rationale or other relevant information.
+
+### The `exemptions` Table
+
+This table enumerates the set of crates which are being used despite missing the
+required audits. It has a similar structure to the `audits` table in
+`audits.toml`, but each entry has fewer supported fields.
+
+#### `version`
+
+Specifies the exact version which should be exempted.
+
+#### `criteria`
+
+Specifies the criteria covered by the exemption.
+
+#### `notes`
+
+Free-form string for recording rationale or other relevant information.
+
+#### `suggest`
+
+A boolean indicating whether this entry is eligible to be surfaced by `cargo vet
+suggest`.
+
+Defaults to true. This exists to allow you silence certain suggestions that, for
+whatever reason, you don't plan to act on in the immediate future.
+
+## `imports.lock`
+
+This file is auto-generated by `cargo vet` and its format should be treated as
+an implementation detail.
diff --git a/book/src/configuring-ci.md b/book/src/configuring-ci.md
new file mode 100644
index 0000000..1566600
--- /dev/null
+++ b/book/src/configuring-ci.md
@@ -0,0 +1,39 @@
+# Configuring CI
+
+As a final step in setting up a project, you should enable verification to run
+as part of your project's continuous integration system.
+
+If your project is hosted on GitHub, you can accomplish this by adding the
+following to a new or existing `.yml` file in `.github/workflows` (with `X.Y.Z`
+replaced with your desired version):
+
+```yml
+name: CI
+on: [push, pull_request]
+jobs:
+  cargo-vet:
+    name: Vet Dependencies
+    runs-on: ubuntu-latest
+    env:
+      CARGO_VET_VERSION: X.Y.Z
+    steps:
+    - uses: actions/checkout@master
+    - name: Install Rust
+      run: rustup update stable && rustup default stable
+    - uses: actions/cache@v2
+      with:
+        path: ${{ runner.tool_cache }}/cargo-vet
+        key: cargo-vet-bin-${{ env.CARGO_VET_VERSION }}
+    - name: Add the tool cache directory to the search path
+      run: echo "${{ runner.tool_cache }}/cargo-vet/bin" >> $GITHUB_PATH
+    - name: Ensure that the tool cache is populated with the cargo-vet binary
+      run: cargo install --root ${{ runner.tool_cache }}/cargo-vet --version ${{ env.CARGO_VET_VERSION }} cargo-vet
+    - name: Invoke cargo-vet
+      run: cargo vet --locked
+```
+
+This will ensure that that all changes made to your repository, either via a PR
+or a direct push, have a fully-vetted dependency set. The extra logic around the
+tool cache allows GitHub to persist a copy of the cargo-vet binary rather than
+compiling it from scratch each time, enabling results to be displayed within a
+few seconds rather than several minutes.
diff --git a/book/src/curating-your-audit-set.md b/book/src/curating-your-audit-set.md
new file mode 100644
index 0000000..112bd07
--- /dev/null
+++ b/book/src/curating-your-audit-set.md
@@ -0,0 +1,63 @@
+# Curating Your Audit Set
+
+Each entry in your `audits.toml` represents your organization's seal of
+approval. What that means is ultimately up to you, but you should be mindful of
+the trust that others may be placing in you and the consequences for your brand
+if that trust is broken.
+
+This section outlines some norms and best-practices for responsible
+participation in the cargo-vet ecosystem.
+
+## Oversight and Enforcement
+
+The most essential step is to ensure that you have adequate access controls on
+your `supply-chain` directory (specifically `audits.toml`). For small projects
+where a handful of maintainers review every change, the repository's ordinary
+controls may be sufficient. But as the set of maintainers grows, there is an
+increasing risk that someone unfamiliar with the significance of `audits.toml`
+will approve an audit without appropriate scrutiny.
+
+For projects where more than five individuals can approve changes, we recommend
+designating a small group of individuals to oversee the audit set and ensure
+that all submissions meet the organization's standards
+([example](https://groups.google.com/a/mozilla.org/g/governance/c/wMWBqkCnR34)).
+GitHub-hosted projects can use the
+[CODEOWNERS](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners)
+file to ensure that all submissions are approved by a member of that group.
+
+## Evaluating Submissions
+
+When someone submits an audit, there is no real way to check their work. So
+while code submissions from anonymous contributors can often be quite valuable,
+audits need to come from a known individual who you trust to represent your
+organization. Such a person should have the technical proficiency to reliably
+identify problems, the professionalism to do a good job, and the integrity to be
+truthful about their findings.
+
+A good litmus test is whether you would permit this individual to single-handedly
+review and accept a patch from an anonymous contributor. The simplest approach
+is just to restrict audit submissions to that set of people. However, there may
+be situations where you find it reasonable to widen the set — such as former
+maintainers who depart on good terms, or individuals at other organizations with
+whom you have extensive relationships and wouldn't hesitate to bring on board if
+the opportunity arose.
+
+## Self-Certification
+
+A natural consequence of the above is that there is no general prohibition
+against organizations certifying crates that they themselves published. The
+purpose of auditing is to extend an organization's seal of approval to code they
+didn't write. The purpose is not to add additional layers of review to code that
+they did write, which carries that seal by default.
+
+Self-certified crates should meet an organization's own standards for first-party
+code, which generally involves every line having undergone proper code review.
+This "second set of eyes" principle is important, it's just not one that
+cargo-vet can mechanically enforce in this context. In the future, cargo-vet may
+add support for requiring that crates have been audited by N organizations,
+which would provide stronger guarantees about independent review.
+
+For crates with frequent updates, self-certifying each individual release can be
+a chore. The [wildcard audit](./wildcard-audit-entries.md) feature is designed
+to address this by allowing organizations to self-certify any release of a crate
+published by a given account within a specified time interval.
diff --git a/book/src/custom.css b/book/src/custom.css
new file mode 100644
index 0000000..f6d1410
--- /dev/null
+++ b/book/src/custom.css
@@ -0,0 +1,3 @@
+img {
+  margin: 30px 0px 30px 0px;
+}
diff --git a/book/src/faq.md b/book/src/faq.md
new file mode 100644
index 0000000..729ee35
--- /dev/null
+++ b/book/src/faq.md
@@ -0,0 +1,35 @@
+# FAQ
+
+This section aims to address a few frequently-asked questions whose answers
+don't quite fit elsewhere in the book.
+
+
+## Why does `cargo vet init` automatically exempt all existing dependencies?
+
+A key goal of `cargo vet` is to make it very easy to go from first learning
+about the tool to having it running on CI. Having an open-ended task — like
+auditing one or more crates — on that critical path increases the chance that
+the developer gets side-tracked and never completes the setup. So the idea is to
+enable developers to quickly get to a green state, and then use `cargo vet
+suggest` to ratchet down the set of exemptions at their own pace.
+
+
+## How does this relate to `cargo crev`?
+
+This work was partially inspired by `cargo crev`, and borrows some aspects
+from its design. We are grateful for its existence and the hard work behind it.
+`cargo vet` makes a few design choices that differ from `cargo crev`:
+* **Project-Oriented:** `cargo vet` is geared towards usage by organizations,
+  and therefore does not separate audits by individual developer. Consequently,
+  it does not have a separate identity and authentication layer.
+* **No Web-of-Trust:** there is no notion of transitive trust. The decision to
+  trust audits performed by another party is independent of that party's trust
+  choices, which might be rooted in a different threat model.
+* **Automated Enforcement:** `cargo vet` is designed to be run as an enforcement
+  tool for projects to manage (rather than just inspect) their supply chains,
+  and consequently has a number of affordances in this direction.
+* **Audit Criteria:** `cargo vet` supports recording
+  [multiple kinds of audits](audit-criteria.md).
+
+Eventually, it could make sense to implement some form of bridging between the
+two systems.
diff --git a/book/src/first-party-code.md b/book/src/first-party-code.md
new file mode 100644
index 0000000..73e702d
--- /dev/null
+++ b/book/src/first-party-code.md
@@ -0,0 +1,66 @@
+# First-Party Code
+
+When run, `cargo vet` invokes the `cargo metadata` subcommand to learn about the
+crate graph. When traversing the graph, `cargo vet` enforces audits for all
+crates.io dependencies.
+
+Generally speaking, all other nodes in the graph are considered trusted and
+therefore non-auditable. This includes root crates, path dependencies, git
+dependencies, and custom (non-crates.io) registry dependencies.
+
+However, there are some situations which blur the line between first- and
+third-party code. This can occur, for example, when the `[patch]` table is used
+to replace the contents of a crates.io package with a locally-modified version.
+Sometimes the replacement is rewritten from scratch, but often it's derived from
+the original, sometimes just with a single modification. Insofar as the package
+you're using is still primarily third-party code, you'll want to audit it like
+anything else — but cargo-vet has no foolproof way to mechanically deduce whether
+the replacement is a derived work.
+
+To ensure the right thing happens, cargo-vet detects these ambiguous situations
+and requires the user to specify the intended behavior. Specifically, if there
+exists a public crate with the same name and version as a given first-party
+crate, cargo-vet will require a policy entry for that crate specifying
+`audit-as-crates-io` as either true or false[^1]. If it's set to true, cargo-vet
+will perform audit enforcement.
+
+When enabled for a git dependency, this enforcement is precise. It requires an
+audit for the base published version that exists on crates.io, and then one or
+more delta audits from that base version to the specific git commit used by the
+build graph. Git commits are identified with an extended `x.y.z@git:SHA` syntax.
+They may only appear in delta audits and should be performed relative to the
+nearest published version, which ensures that audit information is recorded in
+terms of published versions wherever possible for the sake of reusability by
+others.
+
+When enabled for a path dependency, this enforcement is not precise, because
+cargo-vet lacks a hash by which to uniquely identify the actual package
+contents. In this case, only an audit for the base published version is required.
+It's important to note that any audits for such crates always correspond to the
+original crates.io version. This is what `inspect` and `certify` will display,
+and this is what you should review before certifying, since others in the
+ecosystem may rely on your audits when using the original crate without your
+particular modifications.
+
+If audit-as-crates-io is enabled for a path dependency with a version which has
+not been published on crates.io, cargo-vet will instead require an audit of the
+latest published version before the local version, ensuring all audits
+correspond to a crate on crates.io[^2]. If the local version is later published,
+`cargo vet` will warn you, allowing you to update your audits.
+
+## Footnotes
+
+[^1]: To enable an easy setup experience, `cargo vet init` will attempt to guess the
+value of `audit-as-crates-io` for pre-existing packages during initialization, and
+generate exemptions for the packages for which the generated value is `true`. At
+present it will guess `true` if either the `description` or `repository` fields in
+`Cargo.toml` are non-empty and match the current values on crates.io. This behavior
+can also be triggered for newly-added dependencies with `cargo vet regenerate
+audit-as-crates-io`, but you should verify the results.
+
+[^2]: Which version is used for an unpublished crate will be recorded in
+imports.lock to ensure that `cargo vet` will continue to pass as new versions
+are published. Stale `unpublished` entries will be cleaned up by `prune` when
+they are no longer required for `cargo vet` to pass, and can also be regenerated
+using `cargo vet regenerate unpublished`, though this may cause `cargo vet` to
+start failing.
diff --git a/book/src/how-it-works.md b/book/src/how-it-works.md
new file mode 100644
index 0000000..02fae5f
--- /dev/null
+++ b/book/src/how-it-works.md
@@ -0,0 +1,114 @@
+# How it Works
+
+Most developers are busy people with limited energy to devote to supply-chain
+integrity. Therefore, the driving principle behind cargo-vet is to minimize
+friction and make it as easy as possible to do the right thing. It aims to be
+trivial to set up, fit unobtrusively into existing workflows, guide people
+through each step, and allow the entire ecosystem to share the work of auditing
+widely-used packages.
+
+This section provides a high-level overview of how the system operates to
+achieve these goals.
+
+## Setup
+
+<!-- diagrams: https://docs.google.com/presentation/d/18svkEsm9K5gLQeJLfILGdMUTsujiDgzecrswcOAdceQ/edit -->
+
+Cargo-vet is easy to set up. Most users will already have a repository with some
+pre-existing third-party dependencies:
+
+![Existing Repository](images/existing_repo.png)
+
+Cargo-vet can be enabled by adding the tool as a linter and running `cargo vet
+init`, which creates some metadata in the repository:
+
+![Repository with Metadata](images/with_metadata.png)
+
+This takes about five minutes, and crucially, does not require auditing the
+existing dependencies. These are automatically added to the exemptions list:
+
+![Exemptions](images/exemptions.png)
+
+This makes it low-effort to get started, and facilitates tackling the backlog
+incrementally from an approved state.
+
+## Adding New Third-Party Code
+
+Sometime later, a developer attempts to pull new third-party code into the
+project. This might be a new dependency, or an update to an existing one:
+
+![Changeset](images/changeset.png)
+
+As part of continuous integration, cargo-vet analyzes the updated build graph to
+verify that the new code has been audited by a trusted organization. If not, the
+patch is refused:
+
+![Refusal](images/refusal.png)
+
+Next, cargo-vet assists the developer in resolving the situation.  First, it
+scans the registry to see if any well-known organizations have audited that
+package before:
+
+![Potential Imports](images/potential_imports.png)
+
+If there’s a match, cargo-vet informs the developer and offers the option to add
+that organization to the project’s trusted imports:
+
+![Import](images/import.png)
+
+This enables projects to lazily build up an increasingly wide set of approved
+crates. Approval of both import and audit submissions automatically falls to the
+code owners of the `supply-chain/` directory, which should consist of either
+project leadership or a dedicated security team.
+
+## Auditing Workflow
+
+It may of course be the case that the developer needs to perform the audit
+themselves, and cargo-vet streamlines this process. Often someone will have
+already audited a different version of the same crate, in which case cargo-vet
+computes the relevant diffs and identifies the smallest one[^1]. After walking
+the developer through the process of determining what to audit, it then presents
+the relevant artifacts for inspection, either locally or on
+[Sourcegraph](https://sourcegraph.com).
+
+Cargo-vet minimizes developer friction by storing audits in-tree. This means
+that developers don’t need to navigate or authenticate with an external system.
+Interactions with cargo-vet are generally triggered when a developer creates a
+changeset adding new third-party code, and this design allows them to simply
+submit the relevant audits as part of that changeset:
+
+![Audit Submission](images/audit_submission.png)
+
+## Sharing the Work
+
+Cargo-vet’s mechanisms for sharing and discovery are built on top of this
+decentralized storage. Imports are implemented by pointing directly to the
+audit files in external repositories, and the registry is merely an index of
+such files from well-known organizations:
+
+![Registry](images/registry.png)
+
+This also means there’s no central infrastructure for an attacker to compromise.
+Imports used to vet the dependency graph are always fetched directly from the
+relevant organization, and only after explicitly adding that organization to the
+trusted set.
+
+Audit sharing is a key force-multiplier behind `cargo vet`, but it is not
+essential. Projects can of course decline to add any imports and perform all
+audits themselves.
+
+## Additional Features
+
+Cargo-vet has a number of advanced features under the hood — it supports custom
+audit criteria, configurable policies for different subtrees in the build graph,
+and filtering out platform-specific code. These features are all completely
+optional, and the baseline experience is designed to be simple and require
+minimal onboarding. You can learn more about them in the subsequent chapters of
+this book.
+
+## Footnotes
+
+[^1]: Differential audits work even for crates in the exemptions list. While it
+  might seem counter-intuitive to perform a relative security audit against an
+  unknown base, doing so still provides meaningful protection against future
+  supply-chain attacks.
diff --git a/book/src/images/audit_submission.png b/book/src/images/audit_submission.png
new file mode 100644
index 0000000..e681d50
--- /dev/null
+++ b/book/src/images/audit_submission.png
Binary files differ
diff --git a/book/src/images/changeset.png b/book/src/images/changeset.png
new file mode 100644
index 0000000..8f7c7b2
--- /dev/null
+++ b/book/src/images/changeset.png
Binary files differ
diff --git a/book/src/images/exemptions.png b/book/src/images/exemptions.png
new file mode 100644
index 0000000..f1cb25e
--- /dev/null
+++ b/book/src/images/exemptions.png
Binary files differ
diff --git a/book/src/images/existing_repo.png b/book/src/images/existing_repo.png
new file mode 100644
index 0000000..86b9952
--- /dev/null
+++ b/book/src/images/existing_repo.png
Binary files differ
diff --git a/book/src/images/import.png b/book/src/images/import.png
new file mode 100644
index 0000000..d3d48a6
--- /dev/null
+++ b/book/src/images/import.png
Binary files differ
diff --git a/book/src/images/potential_imports.png b/book/src/images/potential_imports.png
new file mode 100644
index 0000000..ccbba71
--- /dev/null
+++ b/book/src/images/potential_imports.png
Binary files differ
diff --git a/book/src/images/refusal.png b/book/src/images/refusal.png
new file mode 100644
index 0000000..51347e9
--- /dev/null
+++ b/book/src/images/refusal.png
Binary files differ
diff --git a/book/src/images/registry.png b/book/src/images/registry.png
new file mode 100644
index 0000000..15eaf7b
--- /dev/null
+++ b/book/src/images/registry.png
Binary files differ
diff --git a/book/src/images/with_metadata.png b/book/src/images/with_metadata.png
new file mode 100644
index 0000000..b69de1c
--- /dev/null
+++ b/book/src/images/with_metadata.png
Binary files differ
diff --git a/book/src/importing-audits.md b/book/src/importing-audits.md
new file mode 100644
index 0000000..d1bb501
--- /dev/null
+++ b/book/src/importing-audits.md
@@ -0,0 +1,45 @@
+# Importing Audits
+
+The fastest way to shrink the `exemptions` list is to pull in the audit sets from
+other projects that you trust via `imports` directives in `config.toml`.  This
+directive allows you to virtually merge audit lists from other projects into
+your own:
+
+```
+[imports.foo]
+url = "https://raw.githubusercontent.com/foo-team/foo/main/supply-chain/audits.toml"
+
+[imports.bar]
+url = "https://hg.bar.org/repo/raw-file/tip/supply-chain/audits.toml"
+```
+Upon invocation, `cargo vet` will fetch each url, extract the relevant data, and
+store the information in `imports.lock`. Similar to `cargo vendor`, passing
+`--locked` will skip the fetch.
+
+Note that this mechanism is not transitive — you can't directly import someone
+else's list of imports. This is an intentional limitation which keeps trust
+relationships direct and easy to reason about. That said, you can always inspect
+the `config.toml` of other projects for inspiration, and explicitly adopt any
+`imports` entries that meet your requirements.
+
+The [built-in criteria](built-in-criteria.md) have the same meaning across all
+projects, so importing an audit for `safe-to-run` has the same effect as
+appending that same audit to your own `audits.toml`. By default, custom criteria
+defined in a foreign audit file exist in a private namespace and have no meaning
+in the local project. However, they can be [mapped](config.md#criteria-map) as
+desired to locally-defined criteria.
+
+## The Registry
+
+To ease discovery, `cargo vet` maintains a central registry of the audit sets
+published by well-known organizations. This information is stored in the
+[`registry.toml`](https://raw.githubusercontent.com/bholley/cargo-vet/main/registry.toml)
+file alongside the source code in the `cargo vet`
+[repository](https://github.com/bholley/cargo-vet). You can request the
+inclusion of your audit set in the registry by submitting a pull request.
+
+You can inspect the registry directly to find audit sets you wish to import.
+Moreover, when suggesting audits, `cargo vet` will fetch the sets listed in the
+registry and surface any entries that could be imported to address the
+identified gaps. This is described later [in more
+detail](performing-audits.md#suggestions-from-the-registry).
diff --git a/book/src/index.md b/book/src/index.md
new file mode 100644
index 0000000..f11020d
--- /dev/null
+++ b/book/src/index.md
@@ -0,0 +1,31 @@
+# Cargo Vet
+
+The `cargo vet` subcommand is a tool to help projects ensure that third-party
+Rust dependencies have been audited by a trusted entity. It strives to be
+lightweight and easy to integrate.
+
+When run, `cargo vet` matches all of a project's third-party dependencies
+against a set of audits performed by the project authors or entities they trust.
+If there are any gaps, the tool provides mechanical assistance in performing and
+documenting the audit.
+
+The primary reason that people do not ordinarily audit open-source dependencies
+is that it is too much work. There are a few key ways that `cargo vet` aims to
+reduce developer effort to a manageable level:
+
+* **Sharing**: Public crates are often used by many projects. These projects can
+share their findings with each other to avoid duplicating work.
+
+* **Relative Audits**: Different versions of the same crate are often quite similar
+to each other. Developers can inspect the difference between two versions, and record
+that if the first version was vetted, the second can be considered vetted as well.
+
+* **Deferred Audits**: It is not always practical to achieve full coverage.
+Dependencies can be added to a list of exceptions which can be ratcheted down
+over time. This makes it trivial to introduce `cargo vet` to a new project and
+guard against future vulnerabilities while vetting the pre-existing code
+gradually as time permits.
+
+> **Note**: `cargo vet` is under active development. If you're interested in
+> deploying it, [get in touch](mailto:bholley@mozilla.com).
+
diff --git a/book/src/install.md b/book/src/install.md
new file mode 100644
index 0000000..1d8fb68
--- /dev/null
+++ b/book/src/install.md
@@ -0,0 +1,13 @@
+# Installation
+
+Installing `cargo vet` can be done through Cargo:
+
+```
+$ cargo install --locked cargo-vet
+```
+
+Afterwards you can confirm that it's installed via:
+
+```
+$ cargo vet --version
+```
diff --git a/book/src/motivation.md b/book/src/motivation.md
new file mode 100644
index 0000000..35737b3
--- /dev/null
+++ b/book/src/motivation.md
@@ -0,0 +1,67 @@
+# Motivation
+
+The discussion below covers the high-level motivation for building this system. If
+you're just interested in how it works, you can skip to the next section.
+
+### Security Risks of Third-Party Code
+Low-friction reuse of third-party components — via systems like crates.io or npm — is
+an essential element of modern software development. Unfortunately, it also
+widens the set of actors who can introduce a security vulnerability into the final
+product.
+
+These defects can be honest mistakes, or intentional supply-chain attacks. They
+can exist in the initial version, or be introduced later as an update. They can
+be introduced by the original author, or by a new maintainer
+who acquires control over the release of subsequent versions.
+Taken together, these avenues constitute a demonstrated and growing
+risk to software security.
+
+Ideally, the composition model would include technical guarantees to isolate
+components from each other and prevent a defect in one component from compromising
+the security of the entire program (e.g. [WebAssembly nanoprocesses](https://bytecodealliance.org/articles/announcing-the-bytecode-alliance)).
+However, that is often not a realistic solution for many projects today. In the absence
+of technical guarantees, the responsibility for ensuring software integrity falls to
+humans. But reviewing every line of third-party code can be very time-consuming and
+difficult, and undermines the original premise of low-friction code reuse. Practically
+speaking, it often just doesn't happen — even at large well-resourced companies.
+
+### Tackling This in Rust
+There are two properties of Rust that make this problem easier to solve.
+
+First, it's relatively easy to audit Rust code. Unlike C/C++, Rust code is
+memory-safe by default, and unlike JavaScript, there is no highly-dynamic shared
+global environment. This means that you can often reason at a high level about
+the range of a module's potential behavior without carefully studying all of its
+internal invariants. For example, a complicated string parser with a narrow
+interface, no unsafe code, and no powerful imports has limited means to
+compromise the rest of the program. This also makes it easier to conclude that a
+new version is safe based on a diff from a prior trusted version.
+
+Second, nearly everyone in the Rust ecosystem relies on the same set of basic tooling
+— Cargo and crates.io — to import and manage third-party components, and there is high
+overlap in the dependency sets. For example, at the time of writing,
+[Firefox](https://hg.mozilla.org/mozilla-central/file/add572d6012047244d022436e0b5c578b3dd7cf7/Cargo.lock),
+[wasmtime](https://github.com/bytecodealliance/wasmtime/blob/49c2b1e60a87623796046176500bed6afa956d2f/Cargo.lock),
+and [the Rust compiler](https://github.com/rust-lang/rust/blob/532d3cda90b8a729cd982548649d32803d265052/Cargo.lock)
+specified 406, 310, and 357 crates.io dependencies respectively[^1]. Ignoring
+version, each project shares about half of its dependencies with at least one of
+the other two projects, and 107 dependencies are common across all three.
+
+This creates opportunities to share the analysis burden in an systematic way. If you're able to
+discover that a trusted party has already audited the exact crate release you're using,
+you can gain quite a bit of confidence in its integrity with no additional effort. If
+that party has audited a different version, you could consider either switching to it, or
+merely auditing the diff between the two. Not every organization
+and project share the same level of risk tolerance, but there is a lot of common
+ground, and substantial room for improvement beyond no sharing at all.
+
+
+## Footnotes
+
+[^1]: The following command string computes the names of the crates.io packages
+  specified in `Cargo.lock`. Note the filtering for path and git dependencies,
+  along with removing duplicates due to different versions of the same crate:
+
+```
+grep -e "name = " -e "source = \"registry" Cargo.lock | awk '/source =/ { print prv_line; next } { prv_line = $0 }' | sort -u
+```
diff --git a/book/src/multiple-repositories.md b/book/src/multiple-repositories.md
new file mode 100644
index 0000000..dbdbf02
--- /dev/null
+++ b/book/src/multiple-repositories.md
@@ -0,0 +1,68 @@
+# Multiple Repositories
+
+The discussion thus far assumes the project exists in a single repository, but
+it's common for organizations to manage code across multiple repositories. At
+first glance this presents a dilemma as to whether to centralize or distribute
+the audit records. Putting them all in one place makes them easier to consume,
+but more cumbersome to produce, since updating a package in one repository may
+require a developer to record a new audit in another repository.
+
+The `cargo vet aggregate` subcommand resolves this tension. The command itself
+simply takes a list of audit file URLs, and produces a single merged file[^1].
+The recommended workflow is as follows:
+1. Create a dedicated repository to host the merged audits ([example](https://github.com/mozilla/supply-chain)).
+2. Add a file called `sources.list` to this repository, which contains a plain
+   list of URLs for the audit files in each project.
+3. Create a recurring task on that repository to invoke `cargo vet aggregate
+   sources.list > audits.toml` and commit the result if changed[^2].
+4. Add the aggregated audit file to the `imports` table of each individual
+   repository.
+
+Beyond streamlining the workflow within the project, this approach also makes it
+easy for others to import the full audit set without needing to navigate the
+details of various source repositories.
+
+[^1]: The entries in the new file have an additional `aggregated-from` field
+      which points to their original location.
+
+[^2]: On GitHub, this can be accomplished by adding the following to
+    `.github/workflows/aggregate.yml`:
+```yml
+name: CI
+on:
+  schedule:
+    # Every five minutes (maximum frequency allowed by GitHub)
+    - cron:  '*/5 * * * *'
+
+permissions:
+  contents: write
+
+jobs:
+  aggregate:
+    name: Aggregate Dependencies
+    runs-on: ubuntu-latest
+    env:
+      CARGO_VET_VERSION: X.Y.Z
+    steps:
+    - uses: actions/checkout@master
+    - name: Install Rust
+      run: rustup update stable && rustup default stable
+    - uses: actions/cache@v2
+      with:
+        path: ${{ runner.tool_cache }}/cargo-vet
+        key: cargo-vet-bin-${{ env.CARGO_VET_VERSION }}
+    - name: Add the tool cache directory to the search path
+      run: echo "${{ runner.tool_cache }}/cargo-vet/bin" >> $GITHUB_PATH
+    - name: Ensure that the tool cache is populated with the cargo-vet binary
+      run: cargo install --root ${{ runner.tool_cache }}/cargo-vet --version ${{ env.CARGO_VET_VERSION }} cargo-vet
+    - name: Invoke cargo-vet aggregate
+      run: cargo vet aggregate --output-file audits.toml sources.list
+    - name: Commit changes (if any)
+      run: |
+        git config --global user.name "cargo-vet[bot]"
+        git config --global user.email "cargo-vet-aggregate@invalid"
+        git add audits.toml
+        git commit -m "Aggregate new audits" || true
+    - name: Push changes (if any)
+      run: git push origin main
+```
diff --git a/book/src/performing-audits.md b/book/src/performing-audits.md
new file mode 100644
index 0000000..b8225ed
--- /dev/null
+++ b/book/src/performing-audits.md
@@ -0,0 +1,147 @@
+# Performing Audits
+
+Human attention is a precious resource, so `cargo vet` provides several features
+to spend that attention as efficiently as possible.
+
+## Managing Dependency Changes
+
+When you run `cargo update`, you generally pull in new crates or new versions of
+existing crates, which may cause `cargo vet` to fail. In this situation,
+`cargo vet` identifies the relevant crates and recommends how to audit them:
+
+```
+$ cargo update
+  ....
+
+$ cargo vet
+  Vetting Failed!
+
+  3 unvetted dependencies:
+      bar:1.5 missing ["safe-to-deploy"]
+      baz:1.3 missing ["safe-to-deploy"]
+      foo:1.2.1 missing ["safe-to-deploy"]
+
+  recommended audits for safe-to-deploy:
+      cargo vet diff foo 1.2 1.2.1  (10 lines)
+      cargo vet diff bar 2.1.1 1.5  (253 lines)
+      cargo vet inspect baz 1.3     (2033 lines)
+
+  estimated audit backlog: 2296 lines
+
+  Use |cargo vet certify| to record the audits.
+```
+
+Note that if other versions of a given crate have already been verified, there
+will be multiple ways to perform the review: either from scratch, or relative to
+one or more already-audited versions. In these cases, `cargo vet`
+computes all the possible approaches and selects the smallest one.
+
+You can, of course, choose to add one or more unvetted dependencies to the
+`exemptions` list instead of auditing them. This may be expedient in some
+situations, though doing so frequently undermines the value provided by the
+tool.
+
+## Inspecting Crates
+
+Once you've identified the audit you wish to perform, the next step is to
+produce the artifacts for inspection. This is less trivial than it might sound:
+even if the project is hosted somewhere like GitHub, there's no guarantee that
+the code in the repository matches the bits submitted to crates.io. And the
+packages on crates.io aren't easy to download manually.
+
+To make this easy, the `cargo vet inspect` subcommand will give you a link to
+the exact version of the crate hosted on [Sourcegraph](https://about.sourcegraph.com/).
+
+When you finish the audit, you can use `cargo vet certify` to add the entry to
+`audits.toml`:
+
+```
+$ cargo vet inspect baz 1.3
+You are about to inspect version 1.3 of 'baz', likely to certify it for "safe-to-deploy", which means:
+
+   ...
+
+You can inspect the crate here: https://sourcegraph.com/crates/baz@v1.3
+
+(press ENTER to open in your browser, or re-run with --mode=local)
+
+$ cargo vet certify baz 1.3
+
+  I, Alice, certify that I have audited version 1.3 of baz in accordance with
+  the following criteria:
+
+  ...
+
+ (type "yes" to certify): yes
+
+  Recorded full audit of baz version 1.3
+```
+
+You can also use the `--mode=local` flag to have `inspect` download the crate
+source code and drop you into a nested shell to inspect it.
+
+Similarly, `cargo vet diff` will give you a [Sourcegraph](https://about.sourcegraph.com/)
+link that will display the diff between the two versions.
+
+```
+$ cargo vet diff foo 1.2 1.2.1
+
+You are about to diff versions 1.2 and 1.2.1 of 'foo', likely to certify it for "safe-to-deploy", which means:
+
+   ...
+
+You can inspect the diff here: https://sourcegraph.com/crates/foo/-/compare/v1.2...v1.2.1
+
+$ cargo vet certify foo 1.2 1.2.1
+
+  I, Alice, certify that I have audited the changes between versions 1.2 and
+  1.2.1 of baz in accordance with the following criteria:
+
+  ...
+
+  (type "yes" to certify): yes
+
+  Recorded relative audit between foo versions 1.2 and 1.2.1
+```
+
+You can also use `--mode=local` flag to have `diff` download the two crates and display a
+git-compatible diff between the two.
+
+## Shrinking the `exemptions` Table
+
+Even when your project is passing `cargo vet`, lingering entries in `exemptions`
+could still leave you vulnerable. As such, shrinking it is a worthwhile endeavor.
+
+Any malicious crate can compromise your program, but not every crate requires
+the same amount of effort to verify. Some crates are larger than others, and
+different versions of the same crate are usually quite similar. To take
+advantage of this, `cargo vet suggest` can estimate the lowest-effort audits
+you can perform to reduce the number of entries in `exemptions`, and
+consequently, your attack surface.
+
+More precisely, `cargo vet suggest` computes the number of lines that would need
+to be reviewed for each exemptions dependency, and displays them in order. This
+is the same information you'd get if you emptied out `exemptions` and re-ran
+`cargo vet`.
+
+## Suggestions from the Registry
+
+When `cargo vet` suggests audits — either after a failed vet or during `cargo
+vet suggest` — it also fetches the contents of the
+[registry](importing-audits.md#the-registry) and checks whether any of the
+available sets contain audits which would fill some or all of the gap. If so, it
+enumerates them so that the developer can consider importing them in lieu of
+performing the entire audit themselves:
+
+```
+$ cargo vet suggest
+  recommended audits for safe-to-deploy:
+      cargo vet inspect baz 1.3   (used by mycrate)  (2033 lines)
+        NOTE: cargo vet import mozilla would reduce this to a 17-line diff
+      cargo vet inspect quxx 2.0  (used by baz)      (1000 lines)
+        NOTE: cargo vet import mozilla would eliminate this
+
+  estimated audit backlog: 3033 lines
+
+  Use |cargo vet certify| to record the audits.
+```
diff --git a/book/src/recording-audits.md b/book/src/recording-audits.md
new file mode 100644
index 0000000..ef7acc9
--- /dev/null
+++ b/book/src/recording-audits.md
@@ -0,0 +1,81 @@
+# Recording Audits
+
+Audits of your project's dependencies performed by you or your teammates are
+recorded in `audits.toml`. Note that these dependencies may have their own
+`audits.toml` files if they also happen to use `cargo vet`, but these have no
+effect on your project unless you explicitly import them in `config.toml`.
+
+## `audits.toml`
+
+Listing a crate in `audits.toml` means that the you've inspected it and
+determined that it meets the specified criteria.
+
+Each crate can have one or more audit entries, which support various fields.
+Specifying a `version` means that the owner has audited that version in its
+entirety. Specifying a `delta` means that the owner has audited the diff between
+the two versions, and determined that the changes preserve the relevant
+properties.
+
+If, in the course of your auditing, you find a crate that does _not_ meet the
+criteria, you can note this as well with `violation`.
+
+A sample `audits.toml` looks like this:
+```
+[criteria]
+
+...
+
+[[audits.bar]]
+version = "1.2.3"
+who = "Alice Foo <alicefoo@example.com>"
+criteria = "safe-to-deploy"
+
+[[audits.bar]]
+delta = "1.2.3 -> 1.2.4"
+who = "Bob Bar <bobbar@example.com>""
+criteria = "safe-to-deploy"
+
+[[audits.bar]]
+version = "2.1.3"
+who = "Alice Foo <alicefoo@example.com>"
+criteria = "safe-to-deploy"
+
+[[audits.bar]]
+delta = "2.1.3 -> 2.1.1"
+who = "Alice Foo <alicefoo@example.com>"
+criteria = "safe-to-deploy"
+
+[[audits.baz]]
+version = "0.2"
+who = "Alice Foo <alicefoo@example.com>"
+criteria = "safe-to-run"
+
+[[audits.foo]]
+version = "0.2.1 -> 0.3.1"
+who = "Bob Bar <bobbar@example.com>""
+criteria = "safe-to-deploy"
+
+[[audits.malicious_crate]]
+violation = "*"
+who = "Bob Bar <bobbar@example.com>""
+criteria = "safe-to-run"
+
+[[audits.partially_vulnerable_crate]]
+violation = ">=2.0, <2.3"
+who = "Bob Bar <bobbar@example.com>""
+criteria = "safe-to-deploy"
+```
+
+Exactly one of `version`, `delta`, or `violation` must be specified for each
+entry.
+
+The expectation is that this file should never be pruned unless a
+previously-recorded entry is determined to have been erroneous. Even if the
+owner no longer uses the specified crates, the audit records can still prove
+useful to others in the ecosystem.
+
+## The `exemptions` table in `config.toml`
+
+This table enumerates the dependencies that have not been audited, but which the
+project is nonetheless using. The structure is generally the same as the
+`audits` table, with a [few differences](config.md#the-exemptions-table).
diff --git a/book/src/reference.md b/book/src/reference.md
new file mode 100644
index 0000000..d86b160
--- /dev/null
+++ b/book/src/reference.md
@@ -0,0 +1,4 @@
+# Reference
+
+This chapter of the book provides more detail and documentation about specific
+aspects of `cargo vet`.
diff --git a/book/src/setup.md b/book/src/setup.md
new file mode 100644
index 0000000..32d55d2
--- /dev/null
+++ b/book/src/setup.md
@@ -0,0 +1,38 @@
+# Setup
+
+Now that you've installed `cargo vet`, you're ready to set it up for your project. Move
+into the top-level project directory and execute the following:
+
+```
+$ cargo vet
+  error: cargo vet is not configured
+```
+
+To be useful, `cargo vet` needs to know which audits have been performed and
+what policy should be enforced. By default, this information is stored next to
+`Cargo.lock` in a directory called `supply-chain`. This location is
+[configurable](./config.md).
+
+To get started, you can invoke:
+
+```
+$ cargo vet init
+```
+
+This creates and populates the `supply-chain` directory. It contains two files:
+`audits.toml` and `config.toml`. The `exemptions` table of `config.toml` is
+populated with the full list of third-party crates currently used by the
+project. The files in this directory should be added to version control along
+with `Cargo.lock`.
+
+Now, try vetting again:
+
+```
+$ cargo vet
+  Vetting Succeeded (X exempted)
+```
+
+You're now up and running, though with an empty audit set: vetting only succeeds
+because your list of exceptions contains the exact set of current dependencies
+used in your project. Generally speaking, you should try to avoid more
+exceptions, and ideally seek to shrink the list over time.
diff --git a/book/src/specifying-policies.md b/book/src/specifying-policies.md
new file mode 100644
index 0000000..79823ee
--- /dev/null
+++ b/book/src/specifying-policies.md
@@ -0,0 +1,26 @@
+# Specifying Policies
+
+By default, `cargo vet` checks all transitive dependencies of all top-level
+crates against the following criteria on all-platforms:
+* For regular dependencies: `safe-to-deploy`
+* For dev-dependencies: `safe-to-run`
+* For build-dependencies[^1]: `safe-to-deploy`
+
+In some situations, you may be able to reduce your workload by encoding your
+requirements more precisely. For example, your workspace might contain both a
+production product and an internal tool, and you might decide that the
+dependencies of the latter need only be `safe-to-run`.
+
+If the default behavior works for you, there's no need to specify anything. If
+you wish to encode policies such as the above, you can do so in
+[config.toml](config.md#the-policy-table).
+
+## Footnotes
+
+[^1]: Strictly speaking, we want the build-dependencies themselves to be `safe-to-run`
+and their contribution to the build (e.g., generated code) to be safe-to-deploy.
+Rather than introduce separate criteria to handle this nuance explicitly,
+cargo-vet bundles it into the [definition](built-in-criteria.md#safe-to-deploy)
+of `safe-to-deploy`. This keeps things more simple and intuitive without
+sacrificing much precision, since in practice it's generally quite clear whether
+a crate is intended to operate at build time or at run time.
diff --git a/book/src/trusted-entries.md b/book/src/trusted-entries.md
new file mode 100644
index 0000000..dd2ca32
--- /dev/null
+++ b/book/src/trusted-entries.md
@@ -0,0 +1,33 @@
+# Trusted Package Entries
+
+This section defines the semantics of the various keys that may be specified in trusted table
+entries.
+
+## `criteria`
+
+Specifies the relevant criteria under which the crate and publisher is trusted. This field is
+required. This may be a single criteria or an array of criteria.
+
+## `user-id`
+
+Specified the user id of the user which is trusted. Note that this is the `crates.io` user id, not
+the user ame.
+
+## `start`
+
+Earliest day of publication which should be considered trusted for the crate and user. Crates
+published by the user before this date will not be considered as certified. This field is required.
+
+Note that publication dates use UTC rather than local time.
+
+## `end`
+
+Latest day of publication which should be considered trusted for the crate and user. Crates
+published by the user after this date will not be considered as certified. This date may be at most
+1 year in the future. This field is required.
+
+Note that publication dates use UTC rather than local time.
+
+## `notes`
+
+An optional free-form string containing any information regarding the trust of this crate and user.
diff --git a/book/src/trusting-publishers.md b/book/src/trusting-publishers.md
new file mode 100644
index 0000000..7cdf2f6
--- /dev/null
+++ b/book/src/trusting-publishers.md
@@ -0,0 +1,51 @@
+## Trusting Publishers
+
+In addition to audits, `cargo vet` also supports trusting releases of a given
+crate by a specific publisher.
+
+### Motivation
+
+The core purpose of `cargo vet` is to assign trust to the contents of each crate
+you use. The tool is audit-oriented because the crates in the ecosystem are very
+heterogeneous in origin: it's usually impractical to require that every
+dependency was _developed_ by a trusted source, so the next best thing is to
+ensure that everything has been _audited_ by a trusted source.
+
+However, there are cases where you do trust the developer.  Rather than
+requiring an additional audit record for these crates, `cargo vet` allows you to
+declare that you trust the developer of a given crate to always release code
+which meets the desired criteria.
+
+### Mechanics
+
+Trusted publishers may be added with `cargo vet trust`. Entries require a trust
+expiration date, which ensures that the judgment is revisited periodically.
+
+The trust relationships are recorded in the `trusted` section of `audits.toml`:
+```
+[[trusted.baz]]
+criteria = "safe-to-deploy"
+user-id = 5555 // Alice Jones
+start = ...
+end = ...
+notes = "Alice is an excellent developer and super-trustworthy."
+```
+
+### Suggestions
+
+When there is an existing trust entry for a given publisher in your audit set or
+that of your imports, `cargo vet suggest` will suggest that you consider adding
+trust entries for a new unaudited crate by the same publisher:
+
+```
+$ cargo vet suggest
+  recommended audits for safe-to-deploy:
+      cargo vet inspect baz 1.3   (used by mycrate)  (2033 lines)
+        NOTE: mozilla trusts Alice Jones (ajones) - consider cargo vet trust baz or cargo vet trust --all ajones
+```
+
+Trust entries are fundamentally a heuristic. The trusted publisher is not
+consulted and may or may not have personally authored or reviewed all the code.
+Thus it is important to assess the risk and potentially do some investigation on
+the development and release process before trusting a crate.
+
diff --git a/book/src/tutorial.md b/book/src/tutorial.md
new file mode 100644
index 0000000..f39aa71
--- /dev/null
+++ b/book/src/tutorial.md
@@ -0,0 +1,4 @@
+# Tutorial
+
+This chapter walks through the steps of deploying and using `cargo vet`, with
+a survey of its key features.
diff --git a/book/src/wildcard-audit-entries.md b/book/src/wildcard-audit-entries.md
new file mode 100644
index 0000000..cee3d46
--- /dev/null
+++ b/book/src/wildcard-audit-entries.md
@@ -0,0 +1,87 @@
+# Wildcard Audit Entries
+
+Wildcard audits are a special type of audit intended as a convenience mechanism
+for organizations that
+[self-certify](curating-your-audit-set.md#self-certification) their own crates.
+Using this feature, an organization can publish an audit which applies to all
+versions published by a given account, avoiding the need to add a new entry to
+`audits.toml` for each new version of the package.
+
+Wildcard audits live at the top of `audits.toml` and look like this:
+
+```
+[[wildcard-audits.foo]]
+who = ...
+criteria = ...
+user-id = ...
+start = ...
+end = ...
+renew = ...
+notes = ...
+```
+
+Whereas a regular audit certifies that the individual has verified that the
+crate contents meet the criteria, a wildcard audit certifies that _any_ version
+of the crate published by the given account will meet the criteria. In effect,
+the author is vouching for the integrity of the entire release process, i.e.
+that releases are always cut from a branch for which every change has been
+approved by a trusted individual who will enforce the criteria.
+
+Wildcard audits can be added with `cargo vet certify` using the `--wildcard`
+option. By default, this sets the `end` date to one year in the future. Once
+added (whether manually or by `cargo vet certify --wildcard`), the `end` date
+can be updated to one year in the future using the `cargo vet renew CRATE`
+command. `cargo vet renew --expiring` can be used to automatically update _all_
+audits which would expire in the next six weeks or have already expired, and
+don't have `renew = false` specified.
+
+## `user-id`
+
+Specifies the crates.io user-id of the user who's published versions should be
+audited. This ID is unfortunately not exposed on the crates.io website, but will
+be filled based on username if using the `cargo vet certify --wildcard $USER`
+command. This field is required.
+
+## `start`
+
+Earliest day of publication which should be considered certified by the wildcard
+audit. Crates published by the user before this date will not be considered as
+certified. This field is required.
+
+Note that publication dates use UTC rather than local time.
+
+## `end`
+
+Latest day of publication which should be considered certified by the wildcard
+audit. Crates published by the user after this date will not be considered as
+certified. This date may be at most 1 year in the future. This field is
+required.
+
+Note that publication dates use UTC rather than local time.
+
+## `renew`
+
+Specifies whether `cargo vet check` should suggest renewal for this audit if the
+`end` date is going to expire within the next six weeks (or has already
+expired), and whether `cargo vet renew --expiring` should renew this audit.
+
+## `criteria`
+
+Specifies the relevant criteria for this wildcard audit. This field is required.
+
+## `who`
+
+A string identifying the auditor. When invoking `cargo vet certify`, the
+value is auto-populated from the git config.
+
+See the documentation for [Audit Entries](./audit-entries.md#who) for more
+details.
+
+Note that while the `who` user may be different than crates.io user specified by
+`user-id`, they should generally either be the same person, or have a close
+relationship (e.g. a team lead certifying a shared publishing account).
+
+## `notes`
+
+An optional free-form string containing any information the auditor may wish to
+record.
diff --git a/registry.toml b/registry.toml
new file mode 100644
index 0000000..f02f472
--- /dev/null
+++ b/registry.toml
@@ -0,0 +1,20 @@
+[registry.bytecode-alliance]
+url = "https://raw.githubusercontent.com/bytecodealliance/wasmtime/main/supply-chain/audits.toml"
+
+[registry.embark-studios]
+url = "https://raw.githubusercontent.com/EmbarkStudios/rust-ecosystem/main/audits.toml"
+
+[registry.fermyon]
+url = "https://raw.githubusercontent.com/fermyon/spin/main/supply-chain/audits.toml"
+
+[registry.google]
+url = "https://raw.githubusercontent.com/google/supply-chain/main/audits.toml"
+
+[registry.isrg]
+url = "https://raw.githubusercontent.com/divviup/libprio-rs/main/supply-chain/audits.toml"
+
+[registry.mozilla]
+url = "https://raw.githubusercontent.com/mozilla/supply-chain/main/audits.toml"
+
+[registry.zcash]
+url = "https://raw.githubusercontent.com/zcash/rust-ecosystem/main/supply-chain/audits.toml"
diff --git a/src/cli.rs b/src/cli.rs
new file mode 100644
index 0000000..de6a492
--- /dev/null
+++ b/src/cli.rs
@@ -0,0 +1,962 @@
+use std::{path::PathBuf, str::FromStr};
+
+use clap::{Parser, Subcommand, ValueEnum};
+use tracing::level_filters::LevelFilter;
+
+use crate::format::{CriteriaName, ImportName, PackageName, VersionReq, VetVersion};
+
+#[derive(Parser)]
+#[clap(version, about, long_about = None)]
+#[clap(propagate_version = true)]
+#[clap(bin_name = "cargo")]
+pub enum FakeCli {
+    Vet(Cli),
+}
+
+#[derive(clap::Args)]
+#[clap(version)]
+#[clap(bin_name = "cargo vet")]
+#[clap(args_conflicts_with_subcommands = true)]
+#[clap(global_setting(clap::AppSettings::DeriveDisplayOrder))]
+/// Supply-chain security for Rust
+///
+/// When run without a subcommand, `cargo vet` will invoke the `check`
+/// subcommand. See `cargo vet help check` for more details.
+pub struct Cli {
+    /// Subcommands ("no subcommand" defaults to `check`)
+    #[clap(subcommand)]
+    pub command: Option<Commands>,
+
+    // Top-level flags
+    /// Path to Cargo.toml
+    #[clap(long, name = "PATH", parse(from_os_str))]
+    #[clap(help_heading = "GLOBAL OPTIONS", global = true)]
+    pub manifest_path: Option<PathBuf>,
+
+    /// Path to the supply-chain directory
+    #[clap(long, name = "STORE_PATH", parse(from_os_str))]
+    #[clap(help_heading = "GLOBAL OPTIONS", global = true)]
+    pub store_path: Option<PathBuf>,
+
+    /// Don't use --all-features
+    ///
+    /// We default to passing --all-features to `cargo metadata`
+    /// because we want to analyze your full dependency tree
+    #[clap(long, action)]
+    #[clap(help_heading = "GLOBAL OPTIONS", global = true)]
+    pub no_all_features: bool,
+
+    /// Do not activate the `default` feature
+    #[clap(long, action)]
+    #[clap(help_heading = "GLOBAL OPTIONS", global = true)]
+    pub no_default_features: bool,
+
+    /// Space-separated list of features to activate
+    #[clap(long, action, require_value_delimiter = true, value_delimiter = ' ')]
+    #[clap(help_heading = "GLOBAL OPTIONS", global = true)]
+    pub features: Vec<String>,
+
+    /// Do not fetch new imported audits.
+    #[clap(long, action)]
+    #[clap(help_heading = "GLOBAL OPTIONS", global = true)]
+    pub locked: bool,
+
+    /// Avoid the network entirely, requiring either that the cargo cache is
+    /// populated or the dependencies are vendored. Requires --locked.
+    #[clap(long, action)]
+    #[clap(requires = "locked")]
+    #[clap(help_heading = "GLOBAL OPTIONS", global = true)]
+    pub frozen: bool,
+
+    /// Prevent commands such as `check` and `certify` from automatically
+    /// cleaning up unused exemptions.
+    #[clap(long, action)]
+    #[clap(help_heading = "GLOBAL OPTIONS", global = true)]
+    pub no_minimize_exemptions: bool,
+
+    /// Prevent commands such as `check` and `suggest` from suggesting registry
+    /// imports.
+    #[clap(long, action)]
+    #[clap(help_heading = "GLOBAL OPTIONS", global = true)]
+    pub no_registry_suggestions: bool,
+
+    /// How verbose logging should be (log level)
+    #[clap(long, action)]
+    #[clap(default_value_t = LevelFilter::WARN)]
+    #[clap(possible_values = ["off", "error", "warn", "info", "debug", "trace"])]
+    #[clap(help_heading = "GLOBAL OPTIONS", global = true)]
+    pub verbose: LevelFilter,
+
+    /// Instead of stdout, write output to this file
+    #[clap(long, action)]
+    #[clap(help_heading = "GLOBAL OPTIONS", global = true)]
+    pub output_file: Option<PathBuf>,
+
+    /// Instead of stderr, write logs to this file (only used after successful CLI parsing)
+    #[clap(long, action)]
+    #[clap(help_heading = "GLOBAL OPTIONS", global = true)]
+    pub log_file: Option<PathBuf>,
+
+    /// The format of the output
+    #[clap(long, value_enum, action)]
+    #[clap(default_value_t = OutputFormat::Human)]
+    #[clap(help_heading = "GLOBAL OPTIONS", global = true)]
+    pub output_format: OutputFormat,
+
+    /// Use the following path instead of the global cache directory
+    ///
+    /// The cache stores information such as the summary results used by vet's
+    /// suggestion machinery, cached results from crates.io APIs, and checkouts
+    /// of crates from crates.io in some cases. This is generally automatically
+    /// managed in the system cache directory.
+    ///
+    /// This mostly exists for testing vet itself.
+    #[clap(long, action)]
+    #[clap(help_heading = "GLOBAL OPTIONS", global = true)]
+    pub cache_dir: Option<PathBuf>,
+
+    /// The date and time to use as now.
+    #[clap(long, action, hide = true)]
+    #[clap(help_heading = "GLOBAL OPTIONS", global = true)]
+    pub current_time: Option<chrono::DateTime<chrono::Utc>>,
+
+    /// Filter out different parts of the build graph and pretend that's the true graph
+    ///
+    /// Example: `--filter-graph="exclude(any(eq(is_dev_only(true)),eq(name(serde_derive))))"`
+    ///
+    /// This mostly exists to debug or reduce projects that cargo-vet is mishandling.
+    /// Combining this with `cargo vet --output-format=json dump-graph` can produce an
+    /// input that can be added to vet's test suite.
+    ///
+    ///
+    /// The resulting graph is computed as follows:
+    ///
+    /// 1. First compute the original graph
+    /// 2. Then apply the filters to find the new set of nodes
+    /// 3. Create a new empty graph
+    /// 4. For each workspace member that still exists, recursively add it and its dependencies
+    ///
+    /// This means that any non-workspace package that becomes "orphaned" by the filters will
+    /// be implicitly discarded even if it passes the filters.
+    ///
+    /// Possible filters:
+    ///
+    /// * `include($query)`: only include packages that match this filter
+    /// * `exclude($query)`: exclude packages that match this filter
+    ///
+    ///
+    /// Possible queries:
+    ///
+    /// * `any($query1, $query2, ...)`: true if any of the listed queries are true
+    /// * `all($query1, $query2, ...)`: true if all of the listed queries are true
+    /// * `not($query)`: true if the query is false
+    /// * `$property`: true if the package has this property
+    ///
+    ///
+    /// Possible properties:
+    ///
+    /// * `name($string)`: the package's name (i.e. `serde`)
+    /// * `version($version)`: the package's version (i.e. `1.2.0`)
+    /// * `is_root($bool)`: whether it's a root in the original graph (ignoring dev-deps)
+    /// * `is_workspace_member($bool)`: whether the package is a workspace-member (can be tested)
+    /// * `is_third_party($bool)`: whether the package is considered third-party by vet
+    /// * `is_dev_only($bool)`: whether it's only used by dev (test) builds in the original graph
+    #[clap(long, action)]
+    #[clap(verbatim_doc_comment)]
+    #[clap(help_heading = "GLOBAL OPTIONS", global = true)]
+    pub filter_graph: Option<Vec<GraphFilter>>,
+
+    // Args for `Check` when the subcommand is not explicitly specified.
+    //
+    // These are exclusive with specifying a subcommand due to
+    // `args_conflicts_with_subcommand`.
+    #[clap(flatten)]
+    pub check_args: CheckArgs,
+}
+
+#[derive(Subcommand)]
+pub enum Commands {
+    // Main commands:
+    /// \[default\] Check that the current project has been vetted
+    ///
+    /// This is the default behaviour if no subcommand is specified.
+    ///
+    /// If the check fails due to lack of audits, we will do our best to explain why
+    /// vetting failed, and what should be done to fix it. This can involve a certain
+    /// amount of guesswork, as there are many possible solutions and we only want to recommend
+    /// the "best" one to keep things simple.
+    ///
+    /// Failures and suggestions can either be "Certain" or "Speculative". Speculative items
+    /// are greyed out and sorted lower to indicate that the Certain entries should be looked
+    /// at first. Speculative items are for packages that probably need audits too, but
+    /// only appear as transitive dependencies of Certain items.
+    ///
+    /// During review of Certain issues you may take various actions that change what's needed
+    /// for the Speculative ones. For instance you may discover you're enabling a feature you
+    /// don't need, and that's the only reason the Speculative package is in your tree. Or you
+    /// may determine that the Certain package only needs to be safe-to-run, which may make
+    /// the Speculative requirements weaker or completely resolved. For these reasons we
+    /// recommend fixing problems "top down", and Certain items are The Top.
+    ///
+    /// Suggested fixes are grouped by the criteria they should be reviewed for and sorted by
+    /// how easy the review should be (in terms of lines of code). We only ever suggest audits
+    /// (and provide the command you need to run to do it), but there are other possible fixes
+    /// like an `exemption` or `policy` change.
+    ///
+    /// The most aggressive solution is to run `cargo vet regenerate exemptions` which will
+    /// add whatever exemptions necessary to make `check` pass (and remove uneeded ones).
+    /// Ideally you should avoid doing this and prefer adding audits, but if you've done all
+    /// the audits you plan on doing, that's the way to finish the job.
+    #[clap(disable_version_flag = true)]
+    Check(CheckArgs),
+
+    /// Suggest some low-hanging fruit to review
+    ///
+    /// This is essentially the same as `check` but with all your `exemptions` temporarily
+    /// removed as a way to inspect your "review backlog". As such, we recommend against
+    /// running this command while `check` is failing, because this will just give you worse
+    /// information.
+    ///
+    /// If you don't consider an exemption to be "backlog", add `suggest = false` to its
+    /// entry and we won't remove it while suggesting.
+    ///
+    /// See also `regenerate exemptions`, which can be used to "garbage collect"
+    /// your backlog (if you run it while `check` is passing).
+    #[clap(disable_version_flag = true)]
+    Suggest(SuggestArgs),
+
+    /// Initialize cargo-vet for your project
+    ///
+    /// This will add `exemptions` and `audit-as-crates-io = false` for all packages that
+    /// need it to make `check` pass immediately and make it easy to start using vet with
+    /// your project.
+    ///
+    /// At this point you can either configure your project further or start working on your
+    /// review backlog with `suggest`.
+    #[clap(disable_version_flag = true)]
+    Init(InitArgs),
+
+    // Fetch Commands
+    /// Fetch the source of a package
+    ///
+    /// We will attempt to guess what criteria you want to audit the package for
+    /// based on the current check/suggest status, and show you the meaning of
+    /// those criteria ahead of time.
+    #[clap(disable_version_flag = true)]
+    Inspect(InspectArgs),
+
+    /// Yield a diff against the last reviewed version
+    ///
+    /// We will attempt to guess what criteria you want to audit the package for
+    /// based on the current check/suggest status, and show you the meaning of
+    /// those criteria ahead of time.
+    #[clap(disable_version_flag = true)]
+    Diff(DiffArgs),
+
+    // Update State Commands
+    /// Mark a package as audited
+    ///
+    /// This command will do its best to guess what you want to be certifying.
+    ///
+    /// If invoked with no args, it will try to certify the last thing you looked at
+    /// with `inspect` or `diff`. Otherwise you must either supply the package name
+    /// and one version (for a full audit) or two versions (for a delta audit).
+    ///
+    /// Once the package+version(s) have been selected, we will try to guess what
+    /// criteria to certify it for. First we will `check`, and if the check fails
+    /// and your audit would seemingly fix this package, we will use the criteria
+    /// recommended for that fix. If `check` passes, we will assume you are working
+    /// on your backlog and instead use the recommendations of `suggest`.
+    ///
+    /// If this removes the need for an `exemption` will we automatically remove it.
+    #[clap(disable_version_flag = true)]
+    Certify(CertifyArgs),
+
+    /// Import a new peer's imports
+    ///
+    /// If invoked without a URL parameter, it will look up the named peer in
+    /// the cargo-vet registry, and import that peer.
+    #[clap(disable_version_flag = true)]
+    Import(ImportArgs),
+
+    /// Trust a given crate and publisher
+    #[clap(disable_version_flag = true)]
+    Trust(TrustArgs),
+
+    /// Explicitly regenerate various pieces of information
+    ///
+    /// There are several things that `cargo vet` *can* do for you automatically
+    /// but we choose to make manual just to keep a human in the loop of those
+    /// decisions. Some of these might one day become automatic if we agree they're
+    /// boring/reliable enough.
+    ///
+    /// See the subcommands for specifics.
+    #[clap(disable_version_flag = true)]
+    #[clap(subcommand)]
+    Regenerate(RegenerateSubcommands),
+
+    /// Mark a package as exempted from review
+    ///
+    /// Exemptions are *usually* just "backlog" and the expectation is that you will review
+    /// them "eventually". You should usually only be trying to remove them, but sometimes
+    /// additions are necessary to make progress.
+    ///
+    /// `regenerate exemptions` will do this for your automatically to make `check` pass
+    /// (and remove any unnecessary ones), so we recommend using that over `add-exemption`.
+    /// This command mostly exists as "plumbing" for building tools on top of `cargo vet`.
+    #[clap(disable_version_flag = true)]
+    AddExemption(AddExemptionArgs),
+
+    /// Declare that some versions of a package violate certain audit criteria
+    ///
+    /// **IMPORTANT**: violations take *VersionReqs* not *Versions*. This is the same
+    /// syntax used by Cargo.toml when specifying dependencies. A bare `1.0.0` actually
+    /// means `^1.0.0`. If you want to forbid a *specific* version, use `=1.0.0`.
+    /// This command can be a bit awkward because syntax like `*` has special meaning
+    /// in scripts and terminals. It's probably easier to just manually add the entry
+    /// to your audits.toml, but the command's here in case you want it.
+    ///
+    /// Violations are essentially treated as integrity constraints on your supply-chain,
+    /// and will only result in errors if you have `exemptions` or `audits` (including
+    /// imported ones) that claim criteria that are contradicted by the `violation`.
+    /// It is not inherently an error to depend on a package with a `violation`.
+    ///
+    /// For instance, someone may review a package and determine that it's horribly
+    /// unsound in the face of untrusted inputs, and therefore *un*safe-to-deploy. They
+    /// would then add a "safe-to-deploy" violation for whatever versions of that
+    /// package seem to have that problem. But if the package basically works fine
+    /// on trusted inputs, it might still be safe-to-run. So if you use it in your
+    /// tests and have an audit that only claims safe-to-run, we won't mention it.
+    ///
+    /// When a violation *does* cause an integrity error, it's up to you and your
+    /// peers to figure out what to do about it. There isn't yet a mechanism for
+    /// dealing with disagreements with a peer's published violations.
+    #[clap(disable_version_flag = true)]
+    RecordViolation(RecordViolationArgs),
+
+    // Plumbing/Debug Commands
+    /// Reformat all of vet's files (in case you hand-edited them)
+    ///
+    /// Most commands will implicitly do this, so this mostly exists as "plumbing"
+    /// for building tools on top of vet, or in case you don't want to run another command.
+    #[clap(disable_version_flag = true)]
+    Fmt(FmtArgs),
+
+    /// Prune unnecessary imports and exemptions
+    ///
+    /// This will fetch the updated state of imports, and attempt to remove any
+    /// now-unnecessary imports or exemptions from the supply-chain.
+    #[clap(disable_version_flag = true)]
+    Prune(PruneArgs),
+
+    /// Fetch and merge audits from multiple sources into a single `audits.toml`
+    /// file.
+    ///
+    /// Will fetch the audits from each URL in the provided file, combining them
+    /// into a single file. Custom criteria will be merged by-name, and must
+    /// have identical descriptions in each source audit file.
+    #[clap(disable_version_flag = true)]
+    Aggregate(AggregateArgs),
+
+    /// Print the cargo build graph as understood by `cargo vet`
+    ///
+    /// This is a debugging command, the output's format is not guaranteed.
+    /// Use `cargo metadata` to get a stable version of what *cargo* thinks the
+    /// build graph is. Our graph is based on that result.
+    ///
+    /// With `--output-format=human` (the default) this will print out mermaid-js
+    /// diagrams, which things like github natively support rendering of.
+    ///
+    /// With `--output-format=json` we will print out more raw statistics for you
+    /// to search/analyze.
+    ///
+    /// Most projects will have unreadably complex build graphs, so you may want to
+    /// use the global `--filter-graph` argument to narrow your focus on an interesting
+    /// subgraph. `--filter-graph` is applied *before* doing any semantic analysis,
+    /// so if you filter out a package and it was the problem, the problem will disappear.
+    /// This can be used to bisect a problem if you get ambitious enough with your filters.
+    #[clap(disable_version_flag = true)]
+    DumpGraph(DumpGraphArgs),
+
+    /// Print --help as markdown (for generating docs)
+    ///
+    /// The output of this is not stable or guaranteed.
+    #[clap(disable_version_flag = true)]
+    #[clap(hide = true)]
+    HelpMarkdown(HelpMarkdownArgs),
+
+    /// Clean up old packages from the vet cache
+    ///
+    /// Removes packages which haven't been accessed in a while, and deletes
+    /// any extra files which aren't recognized by cargo-vet.
+    ///
+    /// In the future, many cargo-vet subcommands will implicitly do this.
+    #[clap(disable_version_flag = true)]
+    Gc(GcArgs),
+
+    /// Renew wildcard audit expirations
+    ///
+    /// This will set a wildcard audit expiration to be one year in the future from when it is run.
+    /// It can optionally do this for all audits which are expiring soon.
+    #[clap(disable_version_flag = true)]
+    Renew(RenewArgs),
+}
+
+#[derive(Subcommand)]
+pub enum RegenerateSubcommands {
+    /// Regenerate your exemptions to make `check` pass minimally
+    ///
+    /// This command can be used for two purposes: to force your supply-chain to pass `check`
+    /// when it's currently failing, or to minimize/garbage-collect your exemptions when it's
+    /// already passing. These are ultimately the same operation.
+    ///
+    /// We will try our best to preserve existing exemptions, removing only those that
+    /// aren't needed, and adding only those that are needed. Exemptions that are overbroad
+    /// may also be weakened (i.e. safe-to-deploy may be reduced to safe-to-run).
+    #[clap(disable_version_flag = true)]
+    Exemptions(RegenerateExemptionsArgs),
+
+    /// Regenerate your imports and accept changes to criteria
+    ///
+    /// This is equivalent to `cargo vet fetch-imports` but it won't produce an error if
+    /// the descriptions of foreign criteria change.
+    #[clap(disable_version_flag = true)]
+    Imports(RegenerateImportsArgs),
+
+    /// Add `audit-as-crates-io` to the policy entry for all crates which require one.
+    ///
+    /// Crates which have a matching `description` and `repository` entry to a
+    /// published crate on crates.io will be marked as `audit-as-crates-io = true`.
+    #[clap(disable_version_flag = true)]
+    AuditAsCratesIo(RegenerateAuditAsCratesIoArgs),
+
+    /// Remove all outdated `unpublished` entries for crates which have since
+    /// been published, or should now be audited as a more-recent version.
+    ///
+    /// Unlike `cargo vet prune`, this will remove outdated `unpublished`
+    /// entries even if it will cause `check` to start failing.
+    #[clap(disable_version_flag = true)]
+    Unpublished(RegenerateUnpublishedArgs),
+}
+
+#[derive(clap::Args)]
+pub struct CheckArgs {}
+
+#[derive(clap::Args)]
+pub struct InitArgs {}
+
+/// Fetches the crate to a temp location and pushd's to it
+#[derive(clap::Args)]
+pub struct InspectArgs {
+    /// The package to inspect
+    #[clap(action)]
+    pub package: PackageName,
+    /// The version to inspect
+    #[clap(action)]
+    pub version: VetVersion,
+    /// How to inspect the source
+    #[clap(long, action, default_value = "sourcegraph")]
+    pub mode: FetchMode,
+}
+
+/// Emits a diff of the two versions
+#[derive(clap::Args)]
+pub struct DiffArgs {
+    /// The package to diff
+    #[clap(action)]
+    pub package: PackageName,
+    /// The base version to diff
+    #[clap(action)]
+    pub version1: VetVersion,
+    /// The target version to diff
+    #[clap(action)]
+    pub version2: VetVersion,
+    /// How to inspect the source
+    #[clap(long, action, default_value = "sourcegraph")]
+    pub mode: FetchMode,
+}
+
+/// Certifies a package as audited
+#[derive(clap::Args)]
+pub struct CertifyArgs {
+    /// The package to certify as audited
+    #[clap(action)]
+    pub package: Option<PackageName>,
+    /// The version to certify as audited
+    #[clap(action)]
+    pub version1: Option<VetVersion>,
+    /// If present, instead certify a diff from version1->version2
+    #[clap(action)]
+    pub version2: Option<VetVersion>,
+    /// If present, certify a wildcard audit for the user with the given username.
+    ///
+    /// Use the --start-date and --end-date options to specify the date range to
+    /// certify for.
+    #[clap(long, action, conflicts_with("version1"), requires("package"))]
+    pub wildcard: Option<String>,
+    /// The criteria to certify for this audit
+    ///
+    /// If not provided, we will prompt you for this information.
+    #[clap(long, action)]
+    pub criteria: Vec<CriteriaName>,
+    /// Who to name as the auditor
+    ///
+    /// If not provided, we will collect this information from the local git.
+    #[clap(long, action)]
+    pub who: Vec<String>,
+    /// A free-form string to include with the new audit entry
+    ///
+    /// If not provided, there will be no notes.
+    #[clap(long, action)]
+    pub notes: Option<String>,
+    /// Start date to create a wildcard audit from.
+    ///
+    /// Only valid with `--wildcard`.
+    ///
+    /// If not provided, will be the publication date of the first version
+    /// published by the given user.
+    #[clap(long, action, requires("wildcard"))]
+    pub start_date: Option<chrono::NaiveDate>,
+    /// End date to create a wildcard audit from. May be at most 1 year in the future.
+    ///
+    /// Only valid with `--wildcard`.
+    ///
+    /// If not provided, will be 1 year from the current date.
+    #[clap(long, action, requires("wildcard"))]
+    pub end_date: Option<chrono::NaiveDate>,
+    /// Accept all criteria without an interactive prompt
+    #[clap(long, action)]
+    pub accept_all: bool,
+    /// Force the command to ignore whether the package/version makes sense
+    ///
+    /// To catch typos/mistakes, we check if the thing you're trying to
+    /// talk about is part of your current build, but this flag disables that.
+    #[clap(long, action)]
+    pub force: bool,
+}
+
+/// Import a new peer
+#[derive(clap::Args)]
+pub struct ImportArgs {
+    /// The name of the peer to import
+    #[clap(action)]
+    pub name: ImportName,
+    /// The URL(s) of the peer's audits.toml file(s).
+    ///
+    /// If a URL is not provided, a peer with the given name will be looked up
+    /// in the cargo-vet registry to determine the import URL(s).
+    #[clap(action)]
+    pub url: Vec<String>,
+}
+
+/// Trust a crate's publisher
+#[derive(clap::Args)]
+pub struct TrustArgs {
+    /// The package to trust
+    ///
+    /// Must be specified unless --all has been specified.
+    #[clap(action, required_unless_present("all"))]
+    pub package: Option<PackageName>,
+    /// The username of the publisher to trust
+    ///
+    /// If not provided, will be inferred to be the sole known publisher of the
+    /// given crate. If there is more than one publisher for the given crate,
+    /// the login must be provided explicitly.
+    #[clap(action)]
+    pub publisher_login: Option<String>,
+    /// The criteria to certify for this trust entry
+    ///
+    /// If not provided, we will prompt you for this information.
+    #[clap(long, action)]
+    pub criteria: Vec<CriteriaName>,
+    /// Start date to create the trust entry from.
+    ///
+    /// If not provided, will be the publication date of the first version
+    /// published by the given user.
+    #[clap(long, action)]
+    pub start_date: Option<chrono::NaiveDate>,
+    /// End date to create the trust entry from. May be at most 1 year in the future.
+    ///
+    /// If not provided, will be 1 year from the current date.
+    #[clap(long, action)]
+    pub end_date: Option<chrono::NaiveDate>,
+    /// A free-form string to include with the new audit entry
+    ///
+    /// If not provided, there will be no notes.
+    #[clap(long, action)]
+    pub notes: Option<String>,
+    /// If specified, trusts all packages with exemptions or failures which are
+    /// solely published by the given user.
+    #[clap(long, action, conflicts_with("package"))]
+    pub all: Option<String>,
+    /// If specified along with --all, also trusts packages with multiple
+    /// publishers, so long as at least one version was published by the given
+    /// user.
+    #[clap(long, action, requires("all"))]
+    pub allow_multiple_publishers: bool,
+}
+
+/// Forbids the given version
+#[derive(clap::Args)]
+pub struct RecordViolationArgs {
+    /// The package to forbid
+    #[clap(action)]
+    pub package: PackageName,
+    /// The versions to forbid
+    #[clap(action)]
+    pub versions: VersionReq,
+    /// The criteria that have failed to be satisfied.
+    ///
+    /// If not provided, we will prompt you for this information(?)
+    #[clap(long, action)]
+    pub criteria: Vec<CriteriaName>,
+    /// Who to name as the auditor
+    ///
+    /// If not provided, we will collect this information from the local git.
+    #[clap(long, action)]
+    pub who: Vec<String>,
+    /// A free-form string to include with the new forbid entry
+    ///
+    /// If not provided, there will be no notes.
+    #[clap(long, action)]
+    pub notes: Option<String>,
+    /// Force the command to ignore whether the package/version makes sense
+    ///
+    /// To catch typos/mistakes, we check if the thing you're trying to
+    /// talk about is part of your current build, but this flag disables that.
+    #[clap(long, action)]
+    pub force: bool,
+}
+
+/// Certifies the given version
+#[derive(clap::Args)]
+pub struct AddExemptionArgs {
+    /// The package to mark as exempted
+    #[clap(action)]
+    pub package: PackageName,
+    /// The version to mark as exempted
+    #[clap(action)]
+    pub version: VetVersion,
+    /// The criteria to assume (trust)
+    ///
+    /// If not provided, we will prompt you for this information.
+    #[clap(long, action)]
+    pub criteria: Vec<CriteriaName>,
+    /// A free-form string to include with the new forbid entry
+    ///
+    /// If not provided, there will be no notes.
+    #[clap(long, action)]
+    pub notes: Option<String>,
+    /// Suppress suggesting this exemption for review
+    #[clap(long, action)]
+    pub no_suggest: bool,
+    /// Force the command to ignore whether the package/version makes sense
+    ///
+    /// To catch typos/mistakes, we check if the thing you're trying to
+    /// talk about is part of your current build, but this flag disables that.
+    #[clap(long, action)]
+    pub force: bool,
+}
+
+#[derive(clap::Args)]
+pub struct SuggestArgs {}
+
+#[derive(clap::Args)]
+pub struct FmtArgs {}
+
+#[derive(clap::Args)]
+pub struct PruneArgs {
+    /// Don't prune unused imports
+    #[clap(long, action)]
+    pub no_imports: bool,
+    /// Don't prune unused exemptions
+    #[clap(long, action)]
+    pub no_exemptions: bool,
+}
+
+#[derive(clap::Args)]
+pub struct RegenerateExemptionsArgs {}
+
+#[derive(clap::Args)]
+pub struct RegenerateImportsArgs {}
+
+#[derive(clap::Args)]
+pub struct RegenerateAuditAsCratesIoArgs {}
+
+#[derive(clap::Args)]
+pub struct RegenerateUnpublishedArgs {}
+
+#[derive(clap::Args)]
+pub struct AggregateArgs {
+    /// Path to a file containing a list of URLs to aggregate the audits from.
+    #[clap(action)]
+    pub sources: PathBuf,
+}
+
+#[derive(clap::Args)]
+pub struct HelpMarkdownArgs {}
+
+#[derive(clap::Args)]
+pub struct GcArgs {
+    /// Packages in the vet cache which haven't been used for this many days
+    /// will be removed.
+    #[clap(long, action)]
+    #[clap(default_value_t = 30.0)]
+    pub max_package_age_days: f64,
+
+    /// Remove the entire cache directory, forcing it to be regenerated next
+    /// time you use cargo vet.
+    #[clap(long, action)]
+    pub clean: bool,
+}
+
+#[derive(clap::Args)]
+pub struct RenewArgs {
+    // Change this doc string if the WILDCARD_AUDIT_EXPIRATION_STRING changes.
+    /// Renew all wildcard audits which will have expired six weeks from now.
+    #[clap(long, action, conflicts_with("crate-name"))]
+    pub expiring: bool,
+
+    /// The name of a crate to renew.
+    #[clap(value_name("CRATE"), action, required_unless_present("expiring"))]
+    pub crate_name: Option<String>,
+}
+
+#[derive(clap::Args)]
+pub struct DumpGraphArgs {
+    /// The depth of the graph to print (for a large project, the full graph is a HUGE MESS).
+    #[clap(long, value_enum, action)]
+    #[clap(default_value_t = DumpGraphDepth::FirstParty)]
+    pub depth: DumpGraphDepth,
+}
+
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
+pub enum DumpGraphDepth {
+    Roots,
+    Workspace,
+    FirstParty,
+    FirstPartyAndDirects,
+    Full,
+}
+
+/// Logging verbosity levels
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
+pub enum Verbose {
+    Off,
+    Error,
+    Warn,
+    Info,
+    Debug,
+    Trace,
+}
+
+#[derive(Clone, Debug)]
+pub struct DependencyCriteriaArg {
+    pub dependency: PackageName,
+    pub criteria: CriteriaName,
+}
+
+impl FromStr for DependencyCriteriaArg {
+    // the error must be owned as well
+    type Err = String;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        use nom::{
+            bytes::complete::{is_not, tag},
+            combinator::all_consuming,
+            error::{convert_error, VerboseError},
+            sequence::tuple,
+            Finish, IResult,
+        };
+        type ParseResult<I, O> = IResult<I, O, VerboseError<I>>;
+
+        fn parse(input: &str) -> ParseResult<&str, DependencyCriteriaArg> {
+            let (rest, (dependency, _, criteria)) =
+                all_consuming(tuple((is_not(":"), tag(":"), is_not(":"))))(input)?;
+            Ok((
+                rest,
+                DependencyCriteriaArg {
+                    dependency: dependency.to_string(),
+                    criteria: criteria.to_string(),
+                },
+            ))
+        }
+
+        match parse(s).finish() {
+            Ok((_remaining, val)) => Ok(val),
+            Err(e) => Err(convert_error(s, e)),
+        }
+    }
+}
+
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
+pub enum FetchMode {
+    Local,
+    Sourcegraph,
+}
+
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
+pub enum OutputFormat {
+    /// Print output in a human-readable form.
+    Human,
+    /// Print output in a machine-readable form with minimal extra context.
+    Json,
+}
+
+#[derive(Clone, Debug)]
+pub enum GraphFilter {
+    Include(GraphFilterQuery),
+    Exclude(GraphFilterQuery),
+}
+
+#[derive(Clone, Debug)]
+pub enum GraphFilterQuery {
+    Any(Vec<GraphFilterQuery>),
+    All(Vec<GraphFilterQuery>),
+    Not(Box<GraphFilterQuery>),
+    Prop(GraphFilterProperty),
+}
+
+#[derive(Clone, Debug)]
+pub enum GraphFilterProperty {
+    Name(PackageName),
+    Version(VetVersion),
+    IsRoot(bool),
+    IsWorkspaceMember(bool),
+    IsThirdParty(bool),
+    IsDevOnly(bool),
+}
+
+impl FromStr for GraphFilter {
+    type Err = String;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        use nom::{
+            branch::alt,
+            bytes::complete::{is_not, tag},
+            character::complete::multispace0,
+            combinator::{all_consuming, cut},
+            error::{convert_error, ParseError, VerboseError, VerboseErrorKind},
+            multi::separated_list1,
+            sequence::delimited,
+            Finish, IResult,
+        };
+        type ParseResult<I, O> = IResult<I, O, VerboseError<I>>;
+
+        fn parse(input: &str) -> ParseResult<&str, GraphFilter> {
+            all_consuming(alt((include_filter, exclude_filter)))(input)
+        }
+        fn include_filter(input: &str) -> ParseResult<&str, GraphFilter> {
+            let (rest, val) =
+                delimited(ws(tag("include(")), cut(filter_query), ws(tag(")")))(input)?;
+            Ok((rest, GraphFilter::Include(val)))
+        }
+        fn exclude_filter(input: &str) -> ParseResult<&str, GraphFilter> {
+            let (rest, val) =
+                delimited(ws(tag("exclude(")), cut(filter_query), ws(tag(")")))(input)?;
+            Ok((rest, GraphFilter::Exclude(val)))
+        }
+        fn filter_query(input: &str) -> ParseResult<&str, GraphFilterQuery> {
+            alt((any_query, all_query, not_query, prop_query))(input)
+        }
+        fn any_query(input: &str) -> ParseResult<&str, GraphFilterQuery> {
+            let (rest, val) = delimited(
+                ws(tag("any(")),
+                cut(separated_list1(tag(","), cut(filter_query))),
+                ws(tag(")")),
+            )(input)?;
+            Ok((rest, GraphFilterQuery::Any(val)))
+        }
+        fn all_query(input: &str) -> ParseResult<&str, GraphFilterQuery> {
+            let (rest, val) = delimited(
+                ws(tag("all(")),
+                cut(separated_list1(tag(","), cut(filter_query))),
+                ws(tag(")")),
+            )(input)?;
+            Ok((rest, GraphFilterQuery::All(val)))
+        }
+        fn not_query(input: &str) -> ParseResult<&str, GraphFilterQuery> {
+            let (rest, val) = delimited(ws(tag("not(")), cut(filter_query), ws(tag(")")))(input)?;
+            Ok((rest, GraphFilterQuery::Not(Box::new(val))))
+        }
+        fn prop_query(input: &str) -> ParseResult<&str, GraphFilterQuery> {
+            let (rest, val) = filter_property(input)?;
+            Ok((rest, GraphFilterQuery::Prop(val)))
+        }
+        fn filter_property(input: &str) -> ParseResult<&str, GraphFilterProperty> {
+            alt((
+                prop_name,
+                prop_version,
+                prop_is_root,
+                prop_is_workspace_member,
+                prop_is_third_party,
+                prop_is_dev_only,
+            ))(input)
+        }
+        fn prop_name(input: &str) -> ParseResult<&str, GraphFilterProperty> {
+            let (rest, val) =
+                delimited(ws(tag("name(")), cut(val_package_name), ws(tag(")")))(input)?;
+            Ok((rest, GraphFilterProperty::Name(val.to_string())))
+        }
+        fn prop_version(input: &str) -> ParseResult<&str, GraphFilterProperty> {
+            let (rest, val) =
+                delimited(ws(tag("version(")), cut(val_version), ws(tag(")")))(input)?;
+            Ok((rest, GraphFilterProperty::Version(val)))
+        }
+        fn prop_is_root(input: &str) -> ParseResult<&str, GraphFilterProperty> {
+            let (rest, val) = delimited(ws(tag("is_root(")), cut(val_bool), ws(tag(")")))(input)?;
+            Ok((rest, GraphFilterProperty::IsRoot(val)))
+        }
+        fn prop_is_workspace_member(input: &str) -> ParseResult<&str, GraphFilterProperty> {
+            let (rest, val) =
+                delimited(ws(tag("is_workspace_member(")), cut(val_bool), ws(tag(")")))(input)?;
+            Ok((rest, GraphFilterProperty::IsWorkspaceMember(val)))
+        }
+        fn prop_is_third_party(input: &str) -> ParseResult<&str, GraphFilterProperty> {
+            let (rest, val) =
+                delimited(ws(tag("is_third_party(")), cut(val_bool), ws(tag(")")))(input)?;
+            Ok((rest, GraphFilterProperty::IsThirdParty(val)))
+        }
+        fn prop_is_dev_only(input: &str) -> ParseResult<&str, GraphFilterProperty> {
+            let (rest, val) =
+                delimited(ws(tag("is_dev_only(")), cut(val_bool), ws(tag(")")))(input)?;
+            Ok((rest, GraphFilterProperty::IsDevOnly(val)))
+        }
+        fn val_bool(input: &str) -> ParseResult<&str, bool> {
+            alt((val_true, val_false))(input)
+        }
+        fn val_true(input: &str) -> ParseResult<&str, bool> {
+            let (rest, _val) = ws(tag("true"))(input)?;
+            Ok((rest, true))
+        }
+        fn val_false(input: &str) -> ParseResult<&str, bool> {
+            let (rest, _val) = ws(tag("false"))(input)?;
+            Ok((rest, false))
+        }
+        fn val_package_name(input: &str) -> ParseResult<&str, &str> {
+            is_not(") ")(input)
+        }
+        fn val_version(input: &str) -> ParseResult<&str, VetVersion> {
+            let (rest, val) = is_not(") ")(input)?;
+            let val = VetVersion::from_str(val).map_err(|_e| {
+                nom::Err::Failure(VerboseError {
+                    errors: vec![(val, VerboseErrorKind::Context("version parse error"))],
+                })
+            })?;
+            Ok((rest, val))
+        }
+        fn ws<'a, F: 'a, O, E: ParseError<&'a str>>(
+            inner: F,
+        ) -> impl FnMut(&'a str) -> IResult<&'a str, O, E>
+        where
+            F: Fn(&'a str) -> IResult<&'a str, O, E>,
+        {
+            delimited(multispace0, inner, multispace0)
+        }
+
+        match parse(s).finish() {
+            Ok((_remaining, val)) => Ok(val),
+            Err(e) => Err(convert_error(s, e)),
+        }
+    }
+}
diff --git a/src/criteria.rs b/src/criteria.rs
new file mode 100644
index 0000000..ac08ee5
--- /dev/null
+++ b/src/criteria.rs
@@ -0,0 +1,237 @@
+//! Helper types for working with criteria and criteria sets.
+
+use std::fmt;
+
+use crate::format::{
+    CriteriaEntry, CriteriaName, CriteriaStr, FastMap, SortedMap, SAFE_TO_DEPLOY, SAFE_TO_RUN,
+};
+
+/// Set of booleans, 64 should be Enough For Anyone (but abstracting in case not).
+///
+/// Note that this intentionally doesn't implement Default to allow the implementation
+/// to require the CriteriaMapper to provide the count of items at construction time.
+/// Which will be useful if we ever decide to give it ~infinite capacity and wrap
+/// a BitSet.
+#[derive(Clone)]
+pub struct CriteriaSet(u64);
+const MAX_CRITERIA: usize = u64::BITS as usize; // funnier this way
+
+/// A processed version of config.toml's criteria definitions, for mapping
+/// lists of criteria names to CriteriaSets.
+#[derive(Debug, Clone)]
+pub struct CriteriaMapper {
+    /// name -> index in all lists
+    index: FastMap<CriteriaName, usize>,
+    /// Names for every criteria
+    names: Vec<CriteriaName>,
+    /// The transitive closure of all criteria implied by each criteria (including self)
+    implied_criteria: Vec<CriteriaSet>,
+}
+
+impl CriteriaMapper {
+    pub fn new(criteria: &SortedMap<CriteriaName, CriteriaEntry>) -> CriteriaMapper {
+        // Fixed indices for built-in criteria
+        const SAFE_TO_RUN_IDX: usize = 0;
+        const SAFE_TO_DEPLOY_IDX: usize = 1;
+
+        // Build the list of possible criteria
+        let names: Vec<CriteriaName> = [SAFE_TO_RUN.to_owned(), SAFE_TO_DEPLOY.to_owned()]
+            .into_iter()
+            .chain(criteria.keys().cloned())
+            .collect();
+        assert_eq!(names[SAFE_TO_RUN_IDX], SAFE_TO_RUN);
+        assert_eq!(names[SAFE_TO_DEPLOY_IDX], SAFE_TO_DEPLOY);
+
+        // Populate the index from the list to allow fast by-name look-ups
+        let mut index = FastMap::with_capacity(names.len());
+        for (idx, name) in names.iter().enumerate() {
+            if index.insert(name.clone(), idx).is_some() {
+                // XXX: Consider producing a better error here?
+                panic!("Cannot specify multiple criteria with the name '{name}'");
+            }
+        }
+
+        // Create the list containing implied criteria and pre-populate it with
+        // the SAFE_TO_DEPLOY->SAFE_TO_RUN imply.
+        let mut direct_implies = vec![CriteriaSet::none(names.len()); names.len()];
+        direct_implies[SAFE_TO_DEPLOY_IDX].set_criteria(SAFE_TO_RUN_IDX);
+        for (name, entry) in criteria {
+            let idx = index[name];
+            for implied in &entry.implies {
+                direct_implies[idx].set_criteria(index[&**implied]);
+            }
+        }
+
+        let implied_criteria = (0..names.len())
+            .map(|idx| {
+                // Helper to recursively add all criteria implied by the given
+                // criteria to the CriteriaSet.
+                fn recurse_implies(
+                    result: &mut CriteriaSet,
+                    direct_implies: &[CriteriaSet],
+                    cur_idx: usize,
+                ) {
+                    for idx in direct_implies[cur_idx].indices() {
+                        if !result.has_criteria(idx) {
+                            result.set_criteria(idx);
+                            recurse_implies(result, direct_implies, idx);
+                        }
+                    }
+                }
+
+                // Determine all criteria implied by each index, ensure each
+                // criteria does not imply itself, and then complete the set.
+                let mut implied = CriteriaSet::none(names.len());
+                recurse_implies(&mut implied, &direct_implies, idx);
+                if implied.has_criteria(idx) {
+                    // XXX: Consider producing a better error here?
+                    panic!("criteria '{}' implies itself", names[idx]);
+                }
+                implied.set_criteria(idx);
+                implied
+            })
+            .collect();
+
+        CriteriaMapper {
+            index,
+            names,
+            implied_criteria,
+        }
+    }
+
+    /// Builds a CriteriaSet from a list of criteria.
+    pub fn criteria_from_list<'b, S: AsRef<str> + 'b + ?Sized>(
+        &self,
+        list: impl IntoIterator<Item = &'b S>,
+    ) -> CriteriaSet {
+        let mut result = self.no_criteria();
+        for criteria in list {
+            self.set_criteria(&mut result, criteria.as_ref());
+        }
+        result
+    }
+
+    /// Set the given named criteria and all criteria implied by it within the
+    /// given CriteriaSet.
+    pub fn set_criteria(&self, set: &mut CriteriaSet, criteria: CriteriaStr) {
+        set.unioned_with(&self.implied_criteria[self.index[criteria]])
+    }
+
+    /// An iterator over every criteria in order, with 'implies' fully applied.
+    pub fn all_criteria_iter(&self) -> impl Iterator<Item = &CriteriaSet> {
+        self.implied_criteria.iter()
+    }
+
+    /// Get the total number of criteria.
+    pub fn len(&self) -> usize {
+        self.names.len()
+    }
+
+    /// Get a CriteriaSet of the correct size for this CriteriaMap containing no criteria
+    pub fn no_criteria(&self) -> CriteriaSet {
+        CriteriaSet::none(self.len())
+    }
+
+    /// Get a CriteriaSet of the correct size for this CriteriaMap containing all criteria
+    pub fn all_criteria(&self) -> CriteriaSet {
+        CriteriaSet::all(self.len())
+    }
+
+    /// Like [`CriteriaSet::indices`] but uses knowledge of things like
+    /// `implies` relationships to remove redundant information. For
+    /// instance, if safe-to-deploy is set, we don't also yield safe-to-run.
+    pub fn minimal_indices<'a>(
+        &'a self,
+        criteria: &'a CriteriaSet,
+    ) -> impl Iterator<Item = usize> + 'a {
+        criteria.indices().filter(|&cur_idx| {
+            criteria.indices().all(|other_idx| {
+                // Ignore our own index
+                let is_identity = cur_idx == other_idx;
+                // Discard this criteria if it's implied by another
+                let isnt_implied = !self.implied_criteria[other_idx].has_criteria(cur_idx);
+                is_identity || isnt_implied
+            })
+        })
+    }
+
+    /// Yields all the names of the set criteria with implied members filtered out.
+    pub fn criteria_names<'a>(
+        &'a self,
+        criteria: &'a CriteriaSet,
+    ) -> impl Iterator<Item = CriteriaStr<'a>> + 'a {
+        self.minimal_indices(criteria).map(|idx| &*self.names[idx])
+    }
+
+    /// Yields the names for all criteria
+    pub fn all_criteria_names(&self) -> impl Iterator<Item = CriteriaStr<'_>> + '_ {
+        self.names.iter().map(|s| &s[..])
+    }
+
+    /// Yields the name of a specific criteria by index.
+    pub fn criteria_name(&self, criteria_idx: usize) -> CriteriaStr<'_> {
+        &self.names[criteria_idx][..]
+    }
+
+    /// Yields the index for the given criteria
+    pub fn criteria_index(&self, criteria_name: CriteriaStr<'_>) -> usize {
+        self.index[criteria_name]
+    }
+}
+
+impl CriteriaSet {
+    pub fn none(count: usize) -> Self {
+        assert!(
+            count <= MAX_CRITERIA,
+            "{MAX_CRITERIA} was not Enough For Everyone ({count} criteria)"
+        );
+        CriteriaSet(0)
+    }
+    pub fn all(count: usize) -> Self {
+        assert!(
+            count <= MAX_CRITERIA,
+            "{MAX_CRITERIA} was not Enough For Everyone ({count} criteria)"
+        );
+        CriteriaSet((1u64 << count).wrapping_sub(1))
+    }
+    pub fn set_criteria(&mut self, idx: usize) {
+        self.0 |= 1 << idx;
+    }
+    pub fn clear_criteria(&mut self, other: &CriteriaSet) {
+        self.0 &= !other.0;
+    }
+    pub fn has_criteria(&self, idx: usize) -> bool {
+        (self.0 & (1 << idx)) != 0
+    }
+    pub fn _intersected_with(&mut self, other: &CriteriaSet) {
+        self.0 &= other.0;
+    }
+    pub fn unioned_with(&mut self, other: &CriteriaSet) {
+        self.0 |= other.0;
+    }
+    pub fn contains(&self, other: &CriteriaSet) -> bool {
+        (self.0 & other.0) == other.0
+    }
+    pub fn is_empty(&self) -> bool {
+        self.0 == 0
+    }
+    pub fn indices(&self) -> impl Iterator<Item = usize> + '_ {
+        // Yield all the offsets that are set by repeatedly getting the lowest 1 and clearing it
+        let mut raw = self.0;
+        std::iter::from_fn(move || {
+            if raw == 0 {
+                None
+            } else {
+                let next = raw.trailing_zeros() as usize;
+                raw &= !(1 << next);
+                Some(next)
+            }
+        })
+    }
+}
+
+impl fmt::Debug for CriteriaSet {
+    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
+        write!(fmt, "{:08b}", self.0)
+    }
+}
diff --git a/src/criteria/safe-to-deploy.txt b/src/criteria/safe-to-deploy.txt
new file mode 100644
index 0000000..5dd6e5b
--- /dev/null
+++ b/src/criteria/safe-to-deploy.txt
@@ -0,0 +1,18 @@
+This crate will not introduce a serious security vulnerability to production
+software exposed to untrusted input.
+
+Auditors are not required to perform a full logic review of the entire crate.
+Rather, they must review enough to fully reason about the behavior of all unsafe
+blocks and usage of powerful imports. For any reasonable usage of the crate in
+real-world software, an attacker must not be able to manipulate the runtime
+behavior of these sections in an exploitable or surprising way.
+
+Ideally, all unsafe code is fully sound, and ambient capabilities (e.g.
+filesystem access) are hardened against manipulation and consistent with the
+advertised behavior of the crate. However, some discretion is permitted. In such
+cases, the nature of the discretion should be recorded in the `notes` field of
+the audit record.
+
+For crates which generate deployed code (e.g. build dependencies or procedural
+macros), reasonable usage of the crate should output code which meets the above
+criteria.
diff --git a/src/criteria/safe-to-run.txt b/src/criteria/safe-to-run.txt
new file mode 100644
index 0000000..dd9beaa
--- /dev/null
+++ b/src/criteria/safe-to-run.txt
@@ -0,0 +1,6 @@
+This crate can be compiled, run, and tested on a local workstation or in
+controlled automation without surprising consequences, such as:
+* Reading or writing data from sensitive or unrelated parts of the filesystem.
+* Installing software or reconfiguring the device.
+* Connecting to untrusted network endpoints.
+* Misuse of system resources (e.g. cryptocurrency mining).
diff --git a/src/errors.rs b/src/errors.rs
new file mode 100644
index 0000000..e046efa
--- /dev/null
+++ b/src/errors.rs
@@ -0,0 +1,1025 @@
+use std::{
+    ffi::OsString,
+    fmt::{Debug, Display},
+    num::ParseIntError,
+    path::{PathBuf, StripPrefixError},
+    string::FromUtf8Error,
+    sync::Arc,
+};
+
+use cargo_metadata::semver;
+use miette::{Diagnostic, MietteSpanContents, SourceCode, SourceOffset, SourceSpan};
+use thiserror::Error;
+
+use crate::{
+    format::{
+        CriteriaName, ForeignCriteriaName, ImportName, PackageName, StoreVersion, VetVersion,
+    },
+    network::PayloadEncoding,
+    serialization::spanned::Spanned,
+};
+
+#[derive(Eq, PartialEq)]
+struct SourceFileInner {
+    name: String,
+    source: String,
+}
+
+#[derive(Clone, Eq, PartialEq)]
+pub struct SourceFile {
+    inner: Arc<SourceFileInner>,
+}
+
+impl SourceFile {
+    pub fn new_empty(name: &str) -> Self {
+        Self::new(name, String::new())
+    }
+    pub fn new(name: &str, source: String) -> Self {
+        SourceFile {
+            inner: Arc::new(SourceFileInner {
+                name: name.to_owned(),
+                source,
+            }),
+        }
+    }
+    pub fn name(&self) -> &str {
+        &self.inner.name
+    }
+    pub fn source(&self) -> &str {
+        &self.inner.source
+    }
+}
+
+impl SourceCode for SourceFile {
+    fn read_span<'a>(
+        &'a self,
+        span: &SourceSpan,
+        context_lines_before: usize,
+        context_lines_after: usize,
+    ) -> Result<Box<dyn miette::SpanContents<'a> + 'a>, miette::MietteError> {
+        let contents = self
+            .source()
+            .read_span(span, context_lines_before, context_lines_after)?;
+        Ok(Box::new(MietteSpanContents::new_named(
+            self.name().to_owned(),
+            contents.data(),
+            *contents.span(),
+            contents.line(),
+            contents.column(),
+            contents.line_count(),
+        )))
+    }
+}
+
+impl Debug for SourceFile {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("SourceFile")
+            .field("name", &self.name())
+            .field("source", &self.source())
+            .finish()
+    }
+}
+
+//////////////////////////////////////////////////////////
+// VersionParseError
+//////////////////////////////////////////////////////////
+
+#[derive(Debug, Error)]
+#[non_exhaustive]
+pub enum VersionParseError {
+    #[error(transparent)]
+    Semver(#[from] semver::Error),
+    #[error("unrecognized revision type, expected 'git:' prefix")]
+    UnknownRevision,
+    #[error("unrecognized git hash, expected 40 hex digits")]
+    InvalidGitHash,
+}
+
+#[derive(Debug, Error)]
+#[non_exhaustive]
+pub enum StoreVersionParseError {
+    #[error("error parsing version component: {0}")]
+    ParseInt(#[from] ParseIntError),
+    #[error("missing '.' separator")]
+    MissingSeparator,
+}
+
+///////////////////////////////////////////////////////////
+// MetadataAcquireError
+///////////////////////////////////////////////////////////
+
+#[derive(Debug, Error, Diagnostic)]
+pub enum MetadataAcquireError {
+    #[error("`cargo metadata` exited with an error:\n{stderr}")]
+    #[diagnostic(help("You may need to run `cargo generate-lockfile` to create a Cargo.lock"))]
+    MetadataError {
+        /// Stderr returned by the `cargo metadata` command
+        stderr: String,
+    },
+    #[error(transparent)]
+    Other(cargo_metadata::Error),
+}
+
+impl From<cargo_metadata::Error> for MetadataAcquireError {
+    fn from(value: cargo_metadata::Error) -> Self {
+        match value {
+            // Wrap normal errors to provide extra help information.
+            cargo_metadata::Error::CargoMetadata { stderr } => {
+                MetadataAcquireError::MetadataError { stderr }
+            }
+            // Other errors couldn't be caused by that problem, so are
+            // unchanged.
+            other => MetadataAcquireError::Other(other),
+        }
+    }
+}
+
+///////////////////////////////////////////////////////////
+// AuditAsErrors
+///////////////////////////////////////////////////////////
+
+#[derive(Debug, Error, Diagnostic)]
+#[error("There are some issues with your policy.audit-as-crates-io entries")]
+#[diagnostic()]
+pub struct AuditAsErrors {
+    #[related]
+    pub errors: Vec<AuditAsError>,
+}
+
+#[derive(Debug, Error, Diagnostic)]
+#[non_exhaustive]
+pub enum AuditAsError {
+    #[error(transparent)]
+    #[diagnostic(transparent)]
+    NeedsAuditAs(NeedsAuditAsErrors),
+    #[error(transparent)]
+    #[diagnostic(transparent)]
+    ShouldntBeAuditAs(ShouldntBeAuditAsErrors),
+    #[error(transparent)]
+    #[diagnostic(transparent)]
+    UnusedAuditAs(UnusedAuditAsErrors),
+}
+
+#[derive(Debug, Error, Diagnostic)]
+#[diagnostic(help("Add a `policy.*.audit-as-crates-io` entry for them"))]
+pub struct NeedsAuditAsErrors {
+    pub errors: Vec<PackageError>,
+}
+
+impl Display for NeedsAuditAsErrors {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.write_str("Some non-crates.io-fetched packages match published crates.io versions")?;
+        for e in &self.errors {
+            f.write_fmt(format_args!("\n  {e}"))?
+        }
+        Ok(())
+    }
+}
+
+#[derive(Debug, Error, Diagnostic)]
+#[diagnostic(help("Remove the audit-as-crates-io entries or make them `false`"))]
+pub struct ShouldntBeAuditAsErrors {
+    pub errors: Vec<PackageError>,
+}
+
+impl Display for ShouldntBeAuditAsErrors {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.write_str("some audit-as-crates-io packages don't match published crates.io versions")?;
+        for e in &self.errors {
+            f.write_fmt(format_args!("\n  {e}"))?
+        }
+        Ok(())
+    }
+}
+
+#[derive(Debug, Error, Diagnostic)]
+#[diagnostic(help("Remove the audit-as-crates-io entries"))]
+pub struct UnusedAuditAsErrors {
+    pub errors: Vec<PackageError>,
+}
+
+impl Display for UnusedAuditAsErrors {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.write_str("some audit-as-crates-io policies don't match first-party crates")?;
+        for e in &self.errors {
+            f.write_fmt(format_args!("\n  {e}"))?
+        }
+        Ok(())
+    }
+}
+
+#[derive(Debug, Error, PartialEq, Eq)]
+#[error("{package}{}", .version.as_ref().map(|v| format!(":{v}")).unwrap_or_default())]
+pub struct PackageError {
+    pub package: PackageName,
+    pub version: Option<VetVersion>,
+}
+
+///////////////////////////////////////////////////////////
+// CratePolicyErrors
+///////////////////////////////////////////////////////////
+#[derive(Debug, Error, Diagnostic, PartialEq, Eq)]
+#[error("There are some issues with your third-party policy entries")]
+#[diagnostic()]
+pub struct CratePolicyErrors {
+    #[related]
+    pub errors: Vec<CratePolicyError>,
+}
+
+#[derive(Debug, Error, Diagnostic, PartialEq, Eq)]
+#[non_exhaustive]
+pub enum CratePolicyError {
+    #[error(transparent)]
+    #[diagnostic(transparent)]
+    NeedsVersion(NeedsPolicyVersionErrors),
+    #[error(transparent)]
+    #[diagnostic(transparent)]
+    UnusedVersion(UnusedPolicyVersionErrors),
+}
+
+#[derive(Debug, Error, Diagnostic, PartialEq, Eq)]
+#[diagnostic(help(
+    "Specifing `dependency-criteria` requires explicit policies for each version of \
+     a crate. Add a `policy.\"<crate>:<version>\"` entry for them."
+))]
+pub struct NeedsPolicyVersionErrors {
+    pub errors: Vec<PackageError>,
+}
+
+impl Display for NeedsPolicyVersionErrors {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.write_str("some crates have policies that are missing an associated version")?;
+        for e in &self.errors {
+            f.write_fmt(format_args!("\n  {e}"))?
+        }
+        Ok(())
+    }
+}
+
+#[derive(Debug, Error, Diagnostic, PartialEq, Eq)]
+#[diagnostic(help("Remove the `policy` entries"))]
+pub struct UnusedPolicyVersionErrors {
+    pub errors: Vec<PackageError>,
+}
+
+impl Display for UnusedPolicyVersionErrors {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.write_str("some versioned policy entries don't correspond to crates being used")?;
+        for e in &self.errors {
+            f.write_fmt(format_args!("\n  {e}"))?
+        }
+        Ok(())
+    }
+}
+
+///////////////////////////////////////////////////////////
+// CertifyError
+///////////////////////////////////////////////////////////
+
+#[derive(Debug, Error, Diagnostic)]
+#[non_exhaustive]
+pub enum CertifyError {
+    #[error("no criteria chosen, aborting")]
+    NoCriteriaChosen,
+    #[error("couldn't guess what version of {0} to certify, please specify")]
+    CouldntGuessVersion(PackageName),
+    #[error("couldn't guess what package to certify, please specify")]
+    CouldntGuessPackage,
+    #[error("couldn't find uncommented certify statement")]
+    CouldntFindCertifyStatement,
+    #[error("'{0}' isn't one of your foreign packages")]
+    #[diagnostic(help("use --force to ignore this error"))]
+    NotAPackage(PackageName),
+    #[error("'{0}' has not published any relevant version of '{1}'")]
+    #[diagnostic(help("please specify a user who has published a version of '{1}'"))]
+    NotAPublisher(String, PackageName),
+    #[error("end date of {0} is too far in the future")]
+    #[diagnostic(help("wildcard audit end dates may be at most 1 year in the future"))]
+    BadWildcardEndDate(chrono::NaiveDate),
+    #[error(transparent)]
+    IoError(#[from] std::io::Error),
+    #[error(transparent)]
+    EditError(#[from] EditError),
+    #[error(transparent)]
+    UserInfoError(#[from] UserInfoError),
+    #[error(transparent)]
+    FetchAuditError(#[from] FetchAuditError),
+    #[error(transparent)]
+    GetPublishersError(#[from] CrateInfoError),
+    #[error(transparent)]
+    CacheAcquire(#[from] CacheAcquireError),
+}
+
+///////////////////////////////////////////////////////////
+// EditError
+///////////////////////////////////////////////////////////
+
+#[derive(Debug, Error, Diagnostic)]
+#[non_exhaustive]
+pub enum EditError {
+    #[error("Failed to launch editor")]
+    CouldntLaunch(#[source] std::io::Error),
+    #[error("Failed to open result of editor")]
+    CouldntOpen(#[source] std::io::Error),
+    #[error("Failed to read result of editor")]
+    CouldntRead(#[source] std::io::Error),
+}
+
+///////////////////////////////////////////////////////////
+// InitErrors
+///////////////////////////////////////////////////////////
+
+#[derive(Debug, Error, Diagnostic)]
+#[error("Failed to initialize the cargo-vet store (supply-chain)")]
+#[non_exhaustive]
+pub enum InitError {
+    StoreCreate(#[source] StoreCreateError),
+    StoreCommit(#[source] StoreCommitError),
+}
+
+///////////////////////////////////////////////////////////
+// StoreErrors
+///////////////////////////////////////////////////////////
+
+#[derive(Debug, Error, Diagnostic)]
+#[non_exhaustive]
+#[error("Couldn't create the store (supply-chain)")]
+pub enum StoreCreateError {
+    CouldntCreate(#[source] std::io::Error),
+    CouldntAcquire(
+        #[from]
+        #[source]
+        FlockError,
+    ),
+}
+
+#[derive(Debug, Error, Diagnostic)]
+#[non_exhaustive]
+pub enum StoreAcquireError {
+    #[error("Couldn't acquire the store's (supply-chain's) lock")]
+    CouldntLock(
+        #[from]
+        #[source]
+        FlockError,
+    ),
+    #[error("The supply-chain store was created with an incompatible version of cargo-vet ({0})")]
+    #[help("Run cargo vet without --locked to update the store to this version")]
+    OutdatedStore(StoreVersion),
+    #[error("The supply-chain store was created with a newer version of cargo-vet ({0})")]
+    #[help("Update to the latest version using `cargo install cargo-vet`")]
+    NewerStore(StoreVersion),
+    #[error(transparent)]
+    #[diagnostic(transparent)]
+    LoadToml(#[from] LoadTomlError),
+    #[error("Couldn't acquire the store")]
+    IoError(
+        #[from]
+        #[source]
+        std::io::Error,
+    ),
+    #[error(transparent)]
+    #[diagnostic(transparent)]
+    Validate(#[from] StoreValidateErrors),
+    #[error(transparent)]
+    #[diagnostic(transparent)]
+    FetchAuditError(#[from] Box<FetchAuditError>),
+    #[error(transparent)]
+    #[diagnostic(transparent)]
+    GetPublishersError(#[from] Box<CrateInfoError>),
+    #[diagnostic(transparent)]
+    #[error(transparent)]
+    CriteriaChange(#[from] CriteriaChangeErrors),
+    #[diagnostic(transparent)]
+    #[error(transparent)]
+    CacheAcquire(#[from] Box<CacheAcquireError>),
+}
+
+#[derive(Debug, Error, Diagnostic)]
+#[non_exhaustive]
+#[error("Failed to commit store")]
+pub enum StoreCommitError {
+    IoError(
+        #[from]
+        #[source]
+        std::io::Error,
+    ),
+    StoreToml(
+        #[from]
+        #[source]
+        StoreTomlError,
+    ),
+}
+
+#[derive(Debug, Error, Diagnostic)]
+#[error("Some of your imported audits changed their criteria descriptions")]
+#[diagnostic()]
+pub struct CriteriaChangeErrors {
+    #[related]
+    pub errors: Vec<CriteriaChangeError>,
+}
+#[derive(Debug, Error, Diagnostic)]
+#[error("{import_name}'s '{criteria_name}' criteria changed:\n\n{unified_diff}")]
+#[diagnostic(help("Run `cargo vet regenerate imports` to accept this new definition"))]
+pub struct CriteriaChangeError {
+    pub import_name: ImportName,
+    pub criteria_name: ForeignCriteriaName,
+    pub unified_diff: String,
+}
+
+////////////////////////////////////////////////////////////
+// StoreValidateErrors
+////////////////////////////////////////////////////////////
+
+#[derive(Debug, Error, Diagnostic)]
+#[error("Your cargo-vet store (supply-chain) has consistency errors")]
+pub struct StoreValidateErrors {
+    #[related]
+    pub errors: Vec<StoreValidateError>,
+}
+
+#[derive(Debug, Error, Diagnostic)]
+#[non_exhaustive]
+pub enum StoreValidateError {
+    #[diagnostic(transparent)]
+    #[error(transparent)]
+    InvalidCriteria(InvalidCriteriaError),
+    #[diagnostic(transparent)]
+    #[error(transparent)]
+    BadFormat(BadFormatError),
+    #[diagnostic(transparent)]
+    #[error(transparent)]
+    BadWildcardEndDate(BadWildcardEndDateError),
+    #[error("imports.lock is out-of-date with respect to configuration")]
+    #[diagnostic(help("run `cargo vet` without --locked to update imports"))]
+    ImportsLockOutdated,
+}
+
+#[derive(Debug, Error, Diagnostic)]
+#[error("'{invalid}' is not a valid criteria name")]
+#[diagnostic(help("the possible criteria are {:?}", valid_names))]
+pub struct InvalidCriteriaError {
+    #[source_code]
+    pub source_code: SourceFile,
+    #[label]
+    pub span: SourceSpan,
+    pub invalid: String,
+    pub valid_names: Arc<Vec<String>>,
+}
+
+#[derive(Debug, Error, Diagnostic)]
+#[error("A file in the store is not correctly formatted:\n\n{unified_diff}")]
+#[diagnostic(help("run `cargo vet` without --locked to reformat files in the store"))]
+pub struct BadFormatError {
+    pub unified_diff: String,
+}
+
+#[derive(Debug, Error, Diagnostic)]
+#[error("'{date}' is more than a year in the future")]
+#[diagnostic(help("wildcard audits must end at most a year in the future ({max})"))]
+pub struct BadWildcardEndDateError {
+    #[source_code]
+    pub source_code: SourceFile,
+    #[label]
+    pub span: SourceSpan,
+    pub date: chrono::NaiveDate,
+    pub max: chrono::NaiveDate,
+}
+
+//////////////////////////////////////////////////////////
+// CacheErrors
+/////////////////////////////////////////////////////////
+
+#[derive(Debug, Error, Diagnostic)]
+#[non_exhaustive]
+pub enum CacheAcquireError {
+    #[error("Couldn't acquire cache")]
+    IoError(
+        #[from]
+        #[source]
+        std::io::Error,
+    ),
+    #[error("Failed to create cache root dir: {}", target.display())]
+    Root {
+        target: PathBuf,
+        #[source]
+        error: std::io::Error,
+    },
+    #[error("Failed to create cache src dir: {}", target.display())]
+    Src {
+        target: PathBuf,
+        #[source]
+        error: std::io::Error,
+    },
+    #[error("Failed to create cache empty dir: {}", target.display())]
+    Empty {
+        target: PathBuf,
+        #[source]
+        error: std::io::Error,
+    },
+    #[error("Failed to create cache package dir: {}", target.display())]
+    Cache {
+        target: PathBuf,
+        #[source]
+        error: std::io::Error,
+    },
+    #[error("Couldn't acquire the cache's lock")]
+    CouldntLock(
+        #[from]
+        #[source]
+        FlockError,
+    ),
+}
+
+#[derive(Debug, Error, Diagnostic)]
+#[non_exhaustive]
+#[error("Failed to commit cache")]
+pub enum CacheCommitError {
+    IoError(
+        #[from]
+        #[source]
+        std::io::Error,
+    ),
+    StoreToml(
+        #[from]
+        #[source]
+        StoreTomlError,
+    ),
+    StoreJson(
+        #[from]
+        #[source]
+        StoreJsonError,
+    ),
+}
+
+//////////////////////////////////////////////////////////
+/// CommandError
+//////////////////////////////////////////////////////////
+
+#[derive(Debug, Error, Diagnostic)]
+#[non_exhaustive]
+pub enum CommandError {
+    #[error("Command failed")]
+    CommandFailed(#[source] std::io::Error),
+    #[error("Bad status {0}")]
+    BadStatus(i32),
+    #[error("Wasn't UTF-8")]
+    BadOutput(#[source] FromUtf8Error),
+}
+
+//////////////////////////////////////////////////////////
+// FetchAndDiffError
+//////////////////////////////////////////////////////////
+
+#[derive(Debug, Error, Diagnostic)]
+#[non_exhaustive]
+pub enum FetchAndDiffError {
+    #[error(transparent)]
+    #[diagnostic(transparent)]
+    Diff(#[from] DiffError),
+    #[error(transparent)]
+    #[diagnostic(transparent)]
+    Fetch(#[from] FetchError),
+}
+
+//////////////////////////////////////////////////////////
+// DiffError
+//////////////////////////////////////////////////////////
+
+#[derive(Debug, Error, Diagnostic)]
+#[non_exhaustive]
+pub enum DiffError {
+    #[error("Failed to diff package")]
+    CommandError(
+        #[from]
+        #[source]
+        CommandError,
+    ),
+    #[error("Diff command produced an unexpected path")]
+    UnexpectedPath(#[source] StripPrefixError),
+    #[error("Diff command produced invalid output")]
+    InvalidOutput,
+}
+
+//////////////////////////////////////////////////////////
+// UserInfoError
+//////////////////////////////////////////////////////////
+
+#[derive(Debug, Error, Diagnostic)]
+#[non_exhaustive]
+pub enum UserInfoError {
+    #[error("Failed to get user.name")]
+    UserCommandFailed(#[source] CommandError),
+    #[error("Failed to get user.email")]
+    EmailCommandFailed(#[source] CommandError),
+}
+
+//////////////////////////////////////////////////////////
+// FetchError
+//////////////////////////////////////////////////////////
+
+#[derive(Debug, Error, Diagnostic)]
+#[non_exhaustive]
+pub enum FetchError {
+    #[error("Invalid URL for package: {url}")]
+    InvalidUrl {
+        url: String,
+        #[source]
+        error: url::ParseError,
+    },
+    #[error("Running as --frozen but needed to fetch {package}:{version}")]
+    Frozen {
+        package: PackageName,
+        version: semver::Version,
+    },
+    #[error("Failed to unpack .crate at {}", src.display())]
+    Unpack {
+        src: PathBuf,
+        #[source]
+        error: UnpackError,
+    },
+    #[error("failed to open cached .crate at {}", target.display())]
+    OpenCached {
+        target: std::path::PathBuf,
+        #[source]
+        error: std::io::Error,
+    },
+    #[error("Failed to unpack checkout at {}", src.display())]
+    UnpackCheckout {
+        src: PathBuf,
+        #[source]
+        error: UnpackCheckoutError,
+    },
+    #[error("Cannot get source for unknown git commit {git_rev} of {package}")]
+    #[help("Only revisions actively used in the dependency graph can be located")]
+    UnknownGitRevision {
+        package: PackageName,
+        git_rev: String,
+    },
+    #[error(transparent)]
+    #[diagnostic(transparent)]
+    Download(#[from] DownloadError),
+}
+
+#[derive(Debug, Error, Diagnostic)]
+#[non_exhaustive]
+pub enum UnpackError {
+    #[error("Failed to iterate archive")]
+    ArchiveIterate(#[source] std::io::Error),
+    #[error("Failed to read archive entry")]
+    ArchiveEntry(#[source] std::io::Error),
+    #[error("Invalid archive, {} wasn't under {}", entry_path.display(), prefix.to_string_lossy())]
+    InvalidPaths {
+        entry_path: PathBuf,
+        prefix: OsString,
+    },
+    #[error("Failed to unpack archive entry {}", entry_path.display())]
+    Unpack {
+        entry_path: PathBuf,
+        #[source]
+        error: std::io::Error,
+    },
+    #[error("Failed to finalize unpack to {}", target.display())]
+    LockCreate {
+        target: std::path::PathBuf,
+        #[source]
+        error: std::io::Error,
+    },
+    #[error(transparent)]
+    IoError(#[from] std::io::Error),
+}
+
+#[derive(Debug, Error, Diagnostic)]
+#[non_exhaustive]
+pub enum UnpackCheckoutError {
+    #[error("Failed to run 'cargo package --list'")]
+    CommandError(
+        #[from]
+        #[source]
+        CommandError,
+    ),
+    #[error("Failed to create directory {path}")]
+    CreateDirError {
+        path: std::path::PathBuf,
+        #[source]
+        error: std::io::Error,
+    },
+    #[error("Failed to copy file contents for {target}")]
+    CopyError {
+        target: std::path::PathBuf,
+        #[source]
+        error: std::io::Error,
+    },
+    #[error("Failed to finalize checkout unpack")]
+    LockCreate(#[source] std::io::Error),
+}
+
+//////////////////////////////////////////////////////////
+// FetchAuditError
+//////////////////////////////////////////////////////////
+
+#[derive(Debug, Error, Diagnostic)]
+#[non_exhaustive]
+pub enum FetchAuditError {
+    // FIXME: would have to explicitly import URL for this error
+    #[error("invalid URL for foreign import {import_name} @ {import_url}")]
+    InvalidUrl {
+        import_name: ImportName,
+        import_url: String,
+        #[source]
+        error: url::ParseError,
+    },
+    #[error("{import_name}'s '{criteria_name}' criteria is missing a description")]
+    MissingCriteriaDescription {
+        import_name: ImportName,
+        criteria_name: ForeignCriteriaName,
+    },
+    #[error("{import_name}'s '{criteria_name}' criteria description URI is invalid: '{url}'")]
+    InvalidCriteriaDescriptionUrl {
+        import_name: ImportName,
+        criteria_name: ForeignCriteriaName,
+        url: String,
+        #[source]
+        error: url::ParseError,
+    },
+    #[error("error when aggregating multiple sources for {import_name}")]
+    #[diagnostic(help("all sources for mapped custom criteria must have identical descriptions"))]
+    Aggregate {
+        import_name: ImportName,
+        #[related]
+        errors: Vec<FetchAuditAggregateError>,
+    },
+    #[diagnostic(transparent)]
+    #[error(transparent)]
+    Download(#[from] DownloadError),
+    #[diagnostic(transparent)]
+    #[error(transparent)]
+    Toml(#[from] LoadTomlError),
+    #[diagnostic(transparent)]
+    #[error(transparent)]
+    Json(#[from] LoadJsonError),
+}
+
+#[derive(Debug, Error, Diagnostic)]
+#[error("criteria description mismatch for {criteria_name}\n{first}\n{second}")]
+#[diagnostic(help("{criteria_name} is mapped to the local criteria {mapped_to:?}"))]
+pub struct FetchAuditAggregateError {
+    pub criteria_name: CriteriaName,
+    pub mapped_to: Vec<Spanned<CriteriaName>>,
+    pub first: AggregateCriteriaDescription,
+    pub second: AggregateCriteriaDescription,
+}
+
+//////////////////////////////////////////////////////////
+// CrateInfoError
+//////////////////////////////////////////////////////////
+
+#[derive(Debug, Error, Diagnostic)]
+#[non_exhaustive]
+pub enum CrateInfoError {
+    #[diagnostic(transparent)]
+    #[error(transparent)]
+    Download(#[from] DownloadError),
+    #[diagnostic(transparent)]
+    #[error(transparent)]
+    Json(#[from] LoadJsonError),
+    #[error("Cannot fetch crate information, '{name}' does not exist.")]
+    DoesNotExist { name: PackageName },
+}
+
+//////////////////////////////////////////////////////////
+// FetchRegistryError
+//////////////////////////////////////////////////////////
+
+#[derive(Debug, Error, Diagnostic)]
+#[non_exhaustive]
+pub enum FetchRegistryError {
+    #[error("Encountered an error fetching the cargo-vet registry.")]
+    Download(#[from] DownloadError),
+    #[error("Import suggestions are disabled due to an incompatible registry. Consider upgrading to the most recent release of cargo-vet.")]
+    Toml(#[from] LoadTomlError),
+    #[error("Error when fetching crate information. Registry suggestions may be incomplete.")]
+    CrateInfo(#[from] CrateInfoError),
+}
+
+//////////////////////////////////////////////////////////
+// DownloadError
+//////////////////////////////////////////////////////////
+
+#[derive(Debug, Error, Diagnostic)]
+#[non_exhaustive]
+pub enum DownloadError {
+    #[error("failed to start download of {url}")]
+    FailedToStartDownload {
+        url: Box<reqwest::Url>,
+        #[source]
+        error: reqwest::Error,
+    },
+    #[error("failed to create file for download to {}", target.display())]
+    FailedToCreateDownload {
+        target: std::path::PathBuf,
+        #[source]
+        error: std::io::Error,
+    },
+    #[error("failed to read download from {url}")]
+    FailedToReadDownload {
+        url: Box<reqwest::Url>,
+        #[source]
+        error: reqwest::Error,
+    },
+    #[error("failed to write download to {}", target.display())]
+    FailedToWriteDownload {
+        target: std::path::PathBuf,
+        #[source]
+        error: std::io::Error,
+    },
+    #[error("failed to rename download to final location {}", target.display())]
+    FailedToFinalizeDownload {
+        target: std::path::PathBuf,
+        #[source]
+        error: std::io::Error,
+    },
+    #[error("download wasn't valid utf8: {url}")]
+    InvalidText {
+        url: Box<reqwest::Url>,
+        #[source]
+        error: FromUtf8Error,
+    },
+    #[error("download encoding ({encoding}) error")]
+    InvalidEncoding {
+        encoding: PayloadEncoding,
+        #[source]
+        error: std::io::Error,
+    },
+}
+
+//////////////////////////////////////////////////////////
+// SuggestError
+//////////////////////////////////////////////////////////
+
+#[derive(Debug, Error, Diagnostic)]
+#[non_exhaustive]
+pub enum SuggestError {
+    #[error(transparent)]
+    #[diagnostic(transparent)]
+    CacheAcquire(#[from] CacheAcquireError),
+    #[error(transparent)]
+    #[diagnostic(transparent)]
+    FetchAudit(#[from] FetchAuditError),
+}
+
+//////////////////////////////////////////////////////////
+// FlockError
+//////////////////////////////////////////////////////////
+
+#[derive(Debug, Error, Diagnostic)]
+#[non_exhaustive]
+pub enum FlockError {
+    #[error("couldn't acquire file lock")]
+    IoError(
+        #[from]
+        #[source]
+        std::io::Error,
+    ),
+}
+
+//////////////////////////////////////////////////////////
+// AggregateErrors
+//////////////////////////////////////////////////////////
+
+#[derive(Debug, Error, Diagnostic)]
+#[error("there were errors aggregating source audit files")]
+#[diagnostic()]
+pub struct AggregateErrors {
+    #[related]
+    pub errors: Vec<AggregateError>,
+}
+
+#[derive(Debug, Error, Diagnostic)]
+#[non_exhaustive]
+pub enum AggregateError {
+    #[error(transparent)]
+    #[diagnostic(transparent)]
+    CriteriaDescriptionMismatch(AggregateCriteriaDescriptionMismatchError),
+    #[error(transparent)]
+    #[diagnostic(transparent)]
+    ImpliesMismatch(AggregateImpliesMismatchError),
+}
+
+#[derive(Debug, Error, Diagnostic)]
+#[non_exhaustive]
+#[error("criteria description mismatch for {criteria_name}\n{first}\n{second}")]
+pub struct AggregateCriteriaDescriptionMismatchError {
+    pub criteria_name: CriteriaName,
+    pub first: AggregateCriteriaDescription,
+    pub second: AggregateCriteriaDescription,
+}
+
+#[derive(Debug)]
+pub struct AggregateCriteriaDescription {
+    pub source: String,
+    pub description: Option<String>,
+    pub description_url: Option<String>,
+}
+
+impl Display for AggregateCriteriaDescription {
+    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+        if let Some(description) = &self.description {
+            write!(f, "{}:\n{}", self.source, description)
+        } else if let Some(description_url) = &self.description_url {
+            write!(f, "{}:\n(URL) {}", self.source, description_url)
+        } else {
+            write!(f, "{}:\n(no description)", self.source)
+        }
+    }
+}
+
+#[derive(Debug, Error, Diagnostic)]
+#[non_exhaustive]
+#[error("implied criteria mismatch for {criteria_name}\n{first}\n{second}")]
+pub struct AggregateImpliesMismatchError {
+    pub criteria_name: CriteriaName,
+    pub first: AggregateCriteriaImplies,
+    pub second: AggregateCriteriaImplies,
+}
+
+#[derive(Debug)]
+pub struct AggregateCriteriaImplies {
+    pub source: String,
+    pub implies: Vec<CriteriaName>,
+}
+
+impl Display for AggregateCriteriaImplies {
+    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+        write!(f, "{}:", self.source)?;
+        for implied in &self.implies {
+            write!(f, "\n - {implied}")?;
+        }
+        Ok(())
+    }
+}
+
+//////////////////////////////////////////////////////////
+// TomlError/JsonError
+//////////////////////////////////////////////////////////
+
+#[derive(Debug, Error, Diagnostic)]
+#[error("Failed to parse toml file")]
+pub struct TomlParseError {
+    #[source_code]
+    pub source_code: SourceFile,
+    #[label("here")]
+    pub span: SourceOffset,
+    #[source]
+    pub error: toml::de::Error,
+}
+
+#[derive(Debug, Error, Diagnostic)]
+#[error("Failed to parse json file")]
+pub struct JsonParseError {
+    // #[source_code]
+    // input: SourceFile,
+    // #[label("here")]
+    // span: SourceOffset,
+    #[source]
+    pub error: serde_json::Error,
+}
+
+#[derive(Debug, Error, Diagnostic)]
+#[non_exhaustive]
+pub enum LoadTomlError {
+    #[error(transparent)]
+    #[diagnostic(transparent)]
+    TomlParse(#[from] TomlParseError),
+
+    #[error("TOML wasn't valid utf8")]
+    InvalidText {
+        #[source]
+        #[from]
+        error: FromUtf8Error,
+    },
+
+    #[error("couldn't load toml")]
+    IoError(
+        #[from]
+        #[source]
+        std::io::Error,
+    ),
+}
+
+#[derive(Debug, Error, Diagnostic)]
+#[non_exhaustive]
+pub enum LoadJsonError {
+    #[error(transparent)]
+    #[diagnostic(transparent)]
+    JsonParse(#[from] JsonParseError),
+    #[error("couldn't load json")]
+    IoError(
+        #[from]
+        #[source]
+        std::io::Error,
+    ),
+}
+
+pub type StoreJsonError = serde_json::Error;
+
+pub type StoreTomlError = toml_edit::ser::Error;
diff --git a/src/flock.rs b/src/flock.rs
new file mode 100644
index 0000000..4268c09
--- /dev/null
+++ b/src/flock.rs
@@ -0,0 +1,432 @@
+//! This is an almost-direct port of the `flock.rs` module from cargo, adapted
+//! to build within cargo-vet.
+
+// FIXME: Consider moving this to an external library so it's easier to share
+// with others in the future. This will require some minor changes to the
+// interface to make it less dependent on internal error handling and logging.
+
+use std::fs::{self, File, OpenOptions};
+use std::io;
+use std::io::{Read, Seek, SeekFrom, Write};
+use std::path::{Display, Path, PathBuf};
+
+use sys::*;
+
+use crate::errors::FlockError;
+use crate::out::indeterminate_spinner;
+
+#[derive(Debug)]
+pub struct FileLock {
+    f: Option<File>,
+    path: PathBuf,
+    state: State,
+}
+
+#[derive(PartialEq, Debug)]
+enum State {
+    Unlocked,
+    Shared,
+    Exclusive,
+}
+
+impl FileLock {
+    /// Returns the underlying file handle of this lock.
+    pub fn file(&self) -> &File {
+        self.f.as_ref().unwrap()
+    }
+
+    /// Returns the underlying path that this lock points to.
+    ///
+    /// Note that special care must be taken to ensure that the path is not
+    /// referenced outside the lifetime of this lock.
+    pub fn path(&self) -> &Path {
+        assert_ne!(self.state, State::Unlocked);
+        &self.path
+    }
+
+    /// Returns the parent path containing this file
+    pub fn parent(&self) -> &Path {
+        assert_ne!(self.state, State::Unlocked);
+        self.path.parent().unwrap()
+    }
+}
+
+impl Read for FileLock {
+    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+        self.file().read(buf)
+    }
+}
+
+impl Seek for FileLock {
+    fn seek(&mut self, to: SeekFrom) -> io::Result<u64> {
+        self.file().seek(to)
+    }
+}
+
+impl Write for FileLock {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        self.file().write(buf)
+    }
+
+    fn flush(&mut self) -> io::Result<()> {
+        self.file().flush()
+    }
+}
+
+impl Drop for FileLock {
+    fn drop(&mut self) {
+        if self.state != State::Unlocked {
+            if let Some(f) = self.f.take() {
+                let _ = unlock(&f);
+            }
+        }
+    }
+}
+
+/// A "filesystem" is intended to be a globally shared, hence locked, resource.
+///
+/// The `Path` of a filesystem cannot be learned unless it's done in a locked
+/// fashion, and otherwise functions on this structure are prepared to handle
+/// concurrent invocations across multiple instances.
+#[derive(Clone, Debug)]
+pub struct Filesystem {
+    root: PathBuf,
+}
+
+impl Filesystem {
+    /// Creates a new filesystem to be rooted at the given path.
+    pub fn new(path: PathBuf) -> Filesystem {
+        Filesystem { root: path }
+    }
+
+    /// Like `Path::join`, creates a new filesystem rooted at this filesystem
+    /// joined with the given path.
+    pub fn join<T: AsRef<Path>>(&self, other: T) -> Filesystem {
+        Filesystem::new(self.root.join(other))
+    }
+
+    /// Like `Path::push`, pushes a new path component onto this filesystem.
+    pub fn push<T: AsRef<Path>>(&mut self, other: T) {
+        self.root.push(other);
+    }
+
+    /// Consumes this filesystem and returns the underlying `PathBuf`.
+    ///
+    /// Note that this is a relatively dangerous operation and should be used
+    /// with great caution!.
+    pub fn into_path_unlocked(self) -> PathBuf {
+        self.root
+    }
+
+    /// Returns the underlying `Path`.
+    ///
+    /// Note that this is a relatively dangerous operation and should be used
+    /// with great caution!.
+    pub fn as_path_unlocked(&self) -> &Path {
+        &self.root
+    }
+
+    /// Creates the directory pointed to by this filesystem.
+    ///
+    /// Handles errors where other processes are also attempting to concurrently
+    /// create this directory.
+    pub fn create_dir(&self) -> Result<(), std::io::Error> {
+        fs::create_dir_all(&self.root)
+    }
+
+    /// Returns an adaptor that can be used to print the path of this
+    /// filesystem.
+    pub fn display(&self) -> Display<'_> {
+        self.root.display()
+    }
+
+    /// Opens exclusive access to a file, returning the locked version of a
+    /// file.
+    ///
+    /// This function will create a file at `path` if it doesn't already exist
+    /// (including intermediate directories), and then it will acquire an
+    /// exclusive lock on `path`. If the process must block waiting for the
+    /// lock, the `msg` is printed to `config`.
+    ///
+    /// The returned file can be accessed to look at the path and also has
+    /// read/write access to the underlying file.
+    pub fn open_rw<P>(&self, path: P, msg: &str) -> Result<FileLock, FlockError>
+    where
+        P: AsRef<Path>,
+    {
+        self.open(
+            path.as_ref(),
+            OpenOptions::new().read(true).write(true).create(true),
+            State::Exclusive,
+            msg,
+        )
+    }
+
+    /// Opens shared access to a file, returning the locked version of a file.
+    ///
+    /// This function will fail if `path` doesn't already exist, but if it does
+    /// then it will acquire a shared lock on `path`. If the process must block
+    /// waiting for the lock, the `msg` is printed to `config`.
+    ///
+    /// The returned file can be accessed to look at the path and also has read
+    /// access to the underlying file. Any writes to the file will return an
+    /// error.
+    pub fn open_ro<P>(&self, path: P, msg: &str) -> Result<FileLock, FlockError>
+    where
+        P: AsRef<Path>,
+    {
+        self.open(
+            path.as_ref(),
+            OpenOptions::new().read(true),
+            State::Shared,
+            msg,
+        )
+    }
+
+    fn open(
+        &self,
+        path: &Path,
+        opts: &OpenOptions,
+        state: State,
+        msg: &str,
+    ) -> Result<FileLock, FlockError> {
+        let path = self.root.join(path);
+
+        // If we want an exclusive lock then if we fail because of NotFound it's
+        // likely because an intermediate directory didn't exist, so try to
+        // create the directory and then continue.
+        let f = opts.open(&path).or_else(|e| {
+            if e.kind() == io::ErrorKind::NotFound && state == State::Exclusive {
+                let parent = path.parent().unwrap();
+                fs::create_dir_all(parent)?;
+                Ok(opts.open(&path)?)
+            } else {
+                Err(e)
+            }
+        })?;
+        match state {
+            State::Exclusive => {
+                acquire(msg, &path, &|| try_lock_exclusive(&f), &|| {
+                    lock_exclusive(&f)
+                })?;
+            }
+            State::Shared => {
+                acquire(msg, &path, &|| try_lock_shared(&f), &|| lock_shared(&f))?;
+            }
+            State::Unlocked => {}
+        }
+        Ok(FileLock {
+            f: Some(f),
+            path,
+            state,
+        })
+    }
+}
+
+impl PartialEq<Path> for Filesystem {
+    fn eq(&self, other: &Path) -> bool {
+        self.root == other
+    }
+}
+
+impl PartialEq<Filesystem> for Path {
+    fn eq(&self, other: &Filesystem) -> bool {
+        self == other.root
+    }
+}
+
+/// Acquires a lock on a file in a "nice" manner.
+///
+/// This function will acquire the lock on a `path`, printing out a message to
+/// the console if we have to wait for it. It will first attempt to use `try` to
+/// acquire a lock on the crate, and in the case of contention it will emit a
+/// status message based on `msg` to `config`'s shell, and then use `block` to
+/// block waiting to acquire a lock.
+///
+/// Returns an error if the lock could not be acquired or if any error other
+/// than a contention error happens.
+fn acquire(
+    msg: &str,
+    path: &Path,
+    lock_try: &dyn Fn() -> io::Result<()>,
+    lock_block: &dyn Fn() -> io::Result<()>,
+) -> Result<(), FlockError> {
+    // File locking on Unix is currently implemented via `flock`, which is known
+    // to be broken on NFS. We could in theory just ignore errors that happen on
+    // NFS, but apparently the failure mode [1] for `flock` on NFS is **blocking
+    // forever**, even if the "non-blocking" flag is passed!
+    //
+    // As a result, we just skip all file locks entirely on NFS mounts. That
+    // should avoid calling any `flock` functions at all, and it wouldn't work
+    // there anyway.
+    //
+    // [1]: https://github.com/rust-lang/cargo/issues/2615
+    if is_on_nfs_mount(path) {
+        return Ok(());
+    }
+
+    match lock_try() {
+        Ok(()) => return Ok(()),
+
+        // In addition to ignoring NFS which is commonly not working we also
+        // just ignore locking on filesystems that look like they don't
+        // implement file locking.
+        Err(e) if error_unsupported(&e) => return Ok(()),
+
+        Err(e) => {
+            if !error_contended(&e) {
+                return Err(e.into());
+            }
+        }
+    }
+
+    let _spinner = indeterminate_spinner("Blocking", format!("waiting for file lock on {msg}"));
+
+    lock_block()?;
+    return Ok(());
+
+    #[cfg(all(target_os = "linux", not(target_env = "musl")))]
+    fn is_on_nfs_mount(path: &Path) -> bool {
+        use std::ffi::CString;
+        use std::mem;
+        use std::os::unix::prelude::*;
+
+        let path = match CString::new(path.as_os_str().as_bytes()) {
+            Ok(path) => path,
+            Err(_) => return false,
+        };
+
+        unsafe {
+            let mut buf: libc::statfs = mem::zeroed();
+            let r = libc::statfs(path.as_ptr(), &mut buf);
+
+            r == 0 && buf.f_type as u32 == libc::NFS_SUPER_MAGIC as u32
+        }
+    }
+
+    #[cfg(any(not(target_os = "linux"), target_env = "musl"))]
+    fn is_on_nfs_mount(_path: &Path) -> bool {
+        false
+    }
+}
+
+#[cfg(unix)]
+mod sys {
+    use std::fs::File;
+    use std::io::{Error, Result};
+    use std::os::unix::io::AsRawFd;
+
+    pub(super) fn lock_shared(file: &File) -> Result<()> {
+        flock(file, libc::LOCK_SH)
+    }
+
+    pub(super) fn lock_exclusive(file: &File) -> Result<()> {
+        flock(file, libc::LOCK_EX)
+    }
+
+    pub(super) fn try_lock_shared(file: &File) -> Result<()> {
+        flock(file, libc::LOCK_SH | libc::LOCK_NB)
+    }
+
+    pub(super) fn try_lock_exclusive(file: &File) -> Result<()> {
+        flock(file, libc::LOCK_EX | libc::LOCK_NB)
+    }
+
+    pub(super) fn unlock(file: &File) -> Result<()> {
+        flock(file, libc::LOCK_UN)
+    }
+
+    pub(super) fn error_contended(err: &Error) -> bool {
+        err.raw_os_error().map_or(false, |x| x == libc::EWOULDBLOCK)
+    }
+
+    pub(super) fn error_unsupported(err: &Error) -> bool {
+        match err.raw_os_error() {
+            // Unfortunately, depending on the target, these may or may not be the same.
+            // For targets in which they are the same, the duplicate pattern causes a warning.
+            #[allow(unreachable_patterns)]
+            Some(libc::ENOTSUP | libc::EOPNOTSUPP) => true,
+            #[cfg(target_os = "linux")]
+            Some(libc::ENOSYS) => true,
+            _ => false,
+        }
+    }
+
+    #[cfg(not(target_os = "solaris"))]
+    fn flock(file: &File, flag: libc::c_int) -> Result<()> {
+        let ret = unsafe { libc::flock(file.as_raw_fd(), flag) };
+        if ret < 0 {
+            Err(Error::last_os_error())
+        } else {
+            Ok(())
+        }
+    }
+
+    #[cfg(target_os = "solaris")]
+    fn flock(file: &File, flag: libc::c_int) -> Result<()> {
+        // Solaris lacks flock(), so simply succeed with a no-op
+        Ok(())
+    }
+}
+
+#[cfg(windows)]
+mod sys {
+    use std::fs::File;
+    use std::io::{Error, Result};
+    use std::mem;
+    use std::os::windows::io::AsRawHandle;
+
+    use winapi::shared::minwindef::DWORD;
+    use winapi::shared::winerror::{ERROR_INVALID_FUNCTION, ERROR_LOCK_VIOLATION};
+    use winapi::um::fileapi::{LockFileEx, UnlockFile};
+    use winapi::um::minwinbase::{LOCKFILE_EXCLUSIVE_LOCK, LOCKFILE_FAIL_IMMEDIATELY};
+
+    pub(super) fn lock_shared(file: &File) -> Result<()> {
+        lock_file(file, 0)
+    }
+
+    pub(super) fn lock_exclusive(file: &File) -> Result<()> {
+        lock_file(file, LOCKFILE_EXCLUSIVE_LOCK)
+    }
+
+    pub(super) fn try_lock_shared(file: &File) -> Result<()> {
+        lock_file(file, LOCKFILE_FAIL_IMMEDIATELY)
+    }
+
+    pub(super) fn try_lock_exclusive(file: &File) -> Result<()> {
+        lock_file(file, LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY)
+    }
+
+    pub(super) fn error_contended(err: &Error) -> bool {
+        err.raw_os_error()
+            .map_or(false, |x| x == ERROR_LOCK_VIOLATION as i32)
+    }
+
+    pub(super) fn error_unsupported(err: &Error) -> bool {
+        err.raw_os_error()
+            .map_or(false, |x| x == ERROR_INVALID_FUNCTION as i32)
+    }
+
+    pub(super) fn unlock(file: &File) -> Result<()> {
+        unsafe {
+            let ret = UnlockFile(file.as_raw_handle(), 0, 0, !0, !0);
+            if ret == 0 {
+                Err(Error::last_os_error())
+            } else {
+                Ok(())
+            }
+        }
+    }
+
+    fn lock_file(file: &File, flags: DWORD) -> Result<()> {
+        unsafe {
+            let mut overlapped = mem::zeroed();
+            let ret = LockFileEx(file.as_raw_handle(), flags, 0, !0, !0, &mut overlapped);
+            if ret == 0 {
+                Err(Error::last_os_error())
+            } else {
+                Ok(())
+            }
+        }
+    }
+}
diff --git a/src/format.rs b/src/format.rs
new file mode 100644
index 0000000..612e02a
--- /dev/null
+++ b/src/format.rs
@@ -0,0 +1,1351 @@
+//! Details of the file formats used by cargo vet
+
+use crate::errors::{StoreVersionParseError, VersionParseError};
+use crate::resolver::{DiffRecommendation, ViolationConflict};
+use crate::serialization::spanned::Spanned;
+use crate::{flock::Filesystem, serialization};
+use core::{cmp, fmt};
+use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
+use std::path::PathBuf;
+use std::str::FromStr;
+
+use cargo_metadata::{semver, Package};
+use serde::{de, de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
+
+// Collections based on how we're using, so it's easier to swap them out.
+pub type FastMap<K, V> = HashMap<K, V>;
+pub type FastSet<T> = HashSet<T>;
+pub type SortedMap<K, V> = BTreeMap<K, V>;
+pub type SortedSet<T> = BTreeSet<T>;
+
+pub type CriteriaName = String;
+pub type CriteriaStr<'a> = &'a str;
+pub type ForeignCriteriaName = String;
+pub type PackageName = String;
+pub type PackageStr<'a> = &'a str;
+pub type ImportName = String;
+pub type ImportStr<'a> = &'a str;
+pub type CratesUserId = u64;
+
+// newtype VersionReq so that we can implement PartialOrd on it.
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
+pub struct VersionReq(pub semver::VersionReq);
+impl fmt::Display for VersionReq {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        self.0.fmt(f)
+    }
+}
+impl FromStr for VersionReq {
+    type Err = <semver::VersionReq as FromStr>::Err;
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        semver::VersionReq::from_str(s).map(VersionReq)
+    }
+}
+impl core::ops::Deref for VersionReq {
+    type Target = semver::VersionReq;
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+impl cmp::PartialOrd for VersionReq {
+    fn partial_cmp(&self, other: &VersionReq) -> Option<cmp::Ordering> {
+        format!("{self}").partial_cmp(&format!("{other}"))
+    }
+}
+impl VersionReq {
+    pub fn parse(text: &str) -> Result<Self, <Self as FromStr>::Err> {
+        cargo_metadata::semver::VersionReq::parse(text).map(VersionReq)
+    }
+    pub fn matches(&self, version: &VetVersion) -> bool {
+        self.0.matches(&version.semver)
+    }
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct VetVersion {
+    pub semver: semver::Version,
+    pub git_rev: Option<String>,
+}
+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:") {
+                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)
+            }
+        } else {
+            Ok(VetVersion {
+                semver: s.parse()?,
+                git_rev: None,
+            })
+        }
+    }
+
+    /// Check if this VetVersion exactly matches the given semver version with
+    /// no git revision metadata.
+    pub fn equals_semver(&self, semver: &semver::Version) -> bool {
+        self.git_rev.is_none() && &self.semver == semver
+    }
+}
+impl fmt::Display for VetVersion {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match &self.git_rev {
+            Some(hash) => write!(f, "{}@git:{}", self.semver, hash),
+            None => self.semver.fmt(f),
+        }
+    }
+}
+impl FromStr for VetVersion {
+    type Err = VersionParseError;
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        Self::parse(s)
+    }
+}
+impl Serialize for VetVersion {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        self.to_string().serialize(serializer)
+    }
+}
+impl<'de> Deserialize<'de> for VetVersion {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        struct VersionVisitor;
+
+        impl<'de> Visitor<'de> for VersionVisitor {
+            type Value = VetVersion;
+
+            fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
+                f.write_str("semver version")
+            }
+            fn visit_str<E>(self, string: &str) -> Result<Self::Value, E>
+            where
+                E: de::Error,
+            {
+                VetVersion::parse(string).map_err(de::Error::custom)
+            }
+        }
+
+        deserializer.deserialize_str(VersionVisitor)
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+//                                                                                //
+//                                                                                //
+//                                                                                //
+//                 Metaconfigs (found in Cargo.tomls)                             //
+//                                                                                //
+//                                                                                //
+//                                                                                //
+////////////////////////////////////////////////////////////////////////////////////
+
+/// A `[*.metadata.vet]` table in a Cargo.toml, configuring our behaviour
+#[derive(serde::Deserialize)]
+pub struct MetaConfigInstance {
+    // Reserved for future use, if not present version=1 assumed.
+    // (not sure whether this versions the format, or semantics, or...
+    // for now assuming this species global semantics of some kind.
+    pub version: Option<u64>,
+    pub store: Option<StoreInfo>,
+}
+#[derive(serde::Deserialize)]
+pub struct StoreInfo {
+    pub path: Option<PathBuf>,
+}
+
+// FIXME: It's *possible* for someone to have a workspace but not have a
+// global `vet` instance for the whole workspace. In this case they *could*
+// have individual `vet` instances for each subcrate they care about.
+// This is... Weird, and it's unclear what that *means*... but maybe it's valid?
+// Either way, we definitely don't support it right now!
+
+/// All available configuration files, overlaying each other.
+/// Generally contains: `[Default, Workspace, Package]`
+pub struct MetaConfig(pub Vec<MetaConfigInstance>);
+
+impl MetaConfig {
+    pub fn store_path(&self) -> Filesystem {
+        // Last config gets priority to set this
+        for config in self.0.iter().rev() {
+            if let Some(store) = &config.store {
+                if let Some(path) = &store.path {
+                    return Filesystem::new(path.into());
+                }
+            }
+        }
+        unreachable!("Default config didn't define store.path???");
+    }
+    pub fn version(&self) -> u64 {
+        // Last config gets priority to set this
+        for config in self.0.iter().rev() {
+            if let Some(ver) = config.version {
+                return ver;
+            }
+        }
+        unreachable!("Default config didn't define version???");
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+//                                                                                //
+//                                                                                //
+//                                                                                //
+//                                audits.toml                                     //
+//                                                                                //
+//                                                                                //
+//                                                                                //
+////////////////////////////////////////////////////////////////////////////////////
+
+pub type WildcardAudits = SortedMap<PackageName, Vec<WildcardEntry>>;
+
+pub type AuditedDependencies = SortedMap<PackageName, Vec<AuditEntry>>;
+
+pub type TrustedPackages = SortedMap<PackageName, Vec<TrustEntry>>;
+
+/// audits.toml
+#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq, Default)]
+pub struct AuditsFile {
+    /// A map of criteria_name to details on that criteria.
+    #[serde(skip_serializing_if = "SortedMap::is_empty")]
+    #[serde(default)]
+    pub criteria: SortedMap<CriteriaName, CriteriaEntry>,
+    /// Wildcard audits
+    #[serde(rename = "wildcard-audits")]
+    #[serde(skip_serializing_if = "SortedMap::is_empty")]
+    #[serde(default)]
+    pub wildcard_audits: WildcardAudits,
+    /// Actual audits.
+    pub audits: AuditedDependencies,
+    /// Trusted packages
+    #[serde(skip_serializing_if = "SortedMap::is_empty")]
+    #[serde(default)]
+    pub trusted: TrustedPackages,
+}
+
+/// Foreign audits.toml with unparsed entries and audits. Should have the same
+/// structure as `AuditsFile`, but with individual audits and criteria unparsed.
+#[derive(serde::Deserialize, Clone, Debug)]
+pub struct ForeignAuditsFile {
+    #[serde(default)]
+    pub criteria: SortedMap<CriteriaName, toml::Value>,
+    #[serde(default)]
+    #[serde(rename = "wildcard-audits")]
+    pub wildcard_audits: SortedMap<PackageName, Vec<toml::Value>>,
+    #[serde(default)]
+    pub audits: SortedMap<PackageName, Vec<toml::Value>>,
+    #[serde(default)]
+    pub trusted: SortedMap<PackageName, Vec<toml::Value>>,
+}
+
+/// Information on a Criteria
+#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
+#[serde(deny_unknown_fields)]
+pub struct CriteriaEntry {
+    /// Summary of how you evaluate something by this criteria.
+    pub description: Option<String>,
+    /// An alternative to description which locates the criteria text at a publicly-accessible URL.
+    /// This can be useful for sharing criteria descriptions across multiple repositories.
+    #[serde(rename = "description-url")]
+    pub description_url: Option<String>,
+    /// Criteria that this one implies
+    #[serde(skip_serializing_if = "Vec::is_empty")]
+    #[serde(default)]
+    #[serde(with = "serialization::string_or_vec")]
+    pub implies: Vec<Spanned<CriteriaName>>,
+    /// Chain of sources this criteria was aggregated from, most recent last.
+    #[serde(rename = "aggregated-from")]
+    #[serde(skip_serializing_if = "Vec::is_empty")]
+    #[serde(default)]
+    #[serde(with = "serialization::string_or_vec")]
+    pub aggregated_from: Vec<Spanned<String>>,
+}
+
+/// This is conceptually an enum
+#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
+#[serde(try_from = "serialization::audit::AuditEntryAll")]
+#[serde(into = "serialization::audit::AuditEntryAll")]
+pub struct AuditEntry {
+    pub who: Vec<Spanned<String>>,
+    pub criteria: Vec<Spanned<CriteriaName>>,
+    pub kind: AuditKind,
+    pub notes: Option<String>,
+    /// Chain of sources this audit was aggregated from, most recent last.
+    pub aggregated_from: Vec<Spanned<String>>,
+    /// A non-serialized member which indicates whether this audit is a "fresh"
+    /// audit. This will be set for all audits imported found in the remote
+    /// audits file which aren't also found in the local `imports.lock` cache.
+    ///
+    /// This should almost always be `false`, and only set to `true` by the
+    /// import handling code.
+    #[serde(skip)]
+    pub is_fresh_import: bool,
+}
+
+impl AuditEntry {
+    /// Should `self` be considered to be the same audit as `other`, e.g. for
+    /// the purposes of `is_fresh_import` checks?
+    pub fn same_audit_as(&self, other: &AuditEntry) -> bool {
+        // Ignore `who` and `notes` for comparison, as they are not relevant
+        // semantically and might have been updated uneventfully.
+        self.kind == other.kind && self.criteria == other.criteria
+    }
+}
+
+/// Implement PartialOrd manually because the order we want for sorting is
+/// different than the order we want for serialization.
+impl cmp::PartialOrd for AuditEntry {
+    fn partial_cmp<'a>(&'a self, other: &'a AuditEntry) -> Option<cmp::Ordering> {
+        let tuple = |x: &'a AuditEntry| (&x.kind, &x.criteria, &x.who, &x.notes);
+        tuple(self).partial_cmp(&tuple(other))
+    }
+}
+
+impl cmp::Ord for AuditEntry {
+    fn cmp(&self, other: &AuditEntry) -> cmp::Ordering {
+        self.partial_cmp(other).unwrap()
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd)]
+pub enum AuditKind {
+    Full { version: VetVersion },
+    Delta { from: VetVersion, to: VetVersion },
+    Violation { violation: VersionReq },
+}
+
+/// A "VERSION" or "VERSION -> VERSION"
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct Delta {
+    pub from: Option<VetVersion>,
+    pub to: VetVersion,
+}
+
+impl<'de> Deserialize<'de> for Delta {
+    fn deserialize<D>(deserializer: D) -> Result<Delta, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        struct DeltaVisitor;
+
+        impl<'de> Visitor<'de> for DeltaVisitor {
+            type Value = Delta;
+
+            fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
+                f.write_str("version -> version delta")
+            }
+
+            fn visit_str<E>(self, string: &str) -> Result<Self::Value, E>
+            where
+                E: de::Error,
+            {
+                if let Some((from, to)) = string.split_once("->") {
+                    Ok(Delta {
+                        from: Some(VetVersion::parse(from.trim()).map_err(de::Error::custom)?),
+                        to: VetVersion::parse(to.trim()).map_err(de::Error::custom)?,
+                    })
+                } else {
+                    Ok(Delta {
+                        from: None,
+                        to: VetVersion::parse(string.trim()).map_err(de::Error::custom)?,
+                    })
+                }
+            }
+        }
+
+        deserializer.deserialize_str(DeltaVisitor)
+    }
+}
+
+impl Serialize for Delta {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        match &self.from {
+            Some(from) => format!("{} -> {}", from, self.to).serialize(serializer),
+            None => self.to.serialize(serializer),
+        }
+    }
+}
+
+impl fmt::Display for Delta {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match &self.from {
+            Some(from) => writeln!(f, "{} -> {}", from, self.to),
+            None => self.to.fmt(f),
+        }
+    }
+}
+
+/// An entry specifying a wildcard audit for a specific crate based on crates.io
+/// publication time and user-id.
+///
+/// These audits will be reified in the imports.lock file when unlocked.
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct WildcardEntry {
+    #[serde(default)]
+    #[serde(skip_serializing_if = "Vec::is_empty")]
+    #[serde(with = "serialization::string_or_vec")]
+    pub who: Vec<Spanned<String>>,
+    #[serde(with = "serialization::string_or_vec")]
+    pub criteria: Vec<Spanned<CriteriaName>>,
+    #[serde(rename = "user-id")]
+    pub user_id: CratesUserId,
+    pub start: Spanned<chrono::NaiveDate>,
+    pub end: Spanned<chrono::NaiveDate>,
+    pub renew: Option<bool>,
+    pub notes: Option<String>,
+    #[serde(rename = "aggregated-from")]
+    #[serde(skip_serializing_if = "Vec::is_empty")]
+    #[serde(with = "serialization::string_or_vec")]
+    #[serde(default)]
+    pub aggregated_from: Vec<Spanned<String>>,
+    /// See `AuditEntry::is_fresh_import`.
+    #[serde(skip)]
+    pub is_fresh_import: bool,
+}
+
+impl WildcardEntry {
+    /// Should `self` be considered to be the same audit as `other`, e.g. for
+    /// the purposes of `is_fresh_import` checks?
+    pub fn same_audit_as(&self, other: &WildcardEntry) -> bool {
+        // Ignore `who` and `notes` for comparison, as they are not relevant
+        // semantically and might have been updated uneventfully.
+        self.user_id == other.user_id
+            && self.start == other.start
+            && self.end == other.end
+            && self.criteria == other.criteria
+    }
+
+    /// Whether a renewal should be suggested for the entry.
+    ///
+    /// If the entry expires before `date` (and `renew` isn't `false`) a renewal will be
+    /// suggested.
+    pub fn should_renew(&self, date: chrono::NaiveDate) -> bool {
+        self.renew.unwrap_or(true) && self.end < date
+    }
+}
+
+/// An entry specifying a trusted publisher for a specific crate based on
+/// crates.io publication time and user-id.
+///
+/// Trusted crates will be reified in the imports.lock file when unlocked.
+#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct TrustEntry {
+    #[serde(with = "serialization::string_or_vec")]
+    pub criteria: Vec<Spanned<CriteriaName>>,
+    #[serde(rename = "user-id")]
+    pub user_id: CratesUserId,
+    pub start: Spanned<chrono::NaiveDate>,
+    pub end: Spanned<chrono::NaiveDate>,
+    pub notes: Option<String>,
+    #[serde(rename = "aggregated-from")]
+    #[serde(skip_serializing_if = "Vec::is_empty")]
+    #[serde(with = "serialization::string_or_vec")]
+    #[serde(default)]
+    pub aggregated_from: Vec<Spanned<String>>,
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+//                                                                                //
+//                                                                                //
+//                                                                                //
+//                                config.toml                                     //
+//                                                                                //
+//                                                                                //
+//                                                                                //
+////////////////////////////////////////////////////////////////////////////////////
+
+/// config.toml
+#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
+pub struct ConfigFile {
+    #[serde(rename = "cargo-vet")]
+    #[serde(default = "CargoVetConfig::missing")]
+    pub cargo_vet: CargoVetConfig,
+
+    /// This top-level key specifies the default criteria that cargo vet certify will use
+    /// when recording audits. If unspecified, this defaults to "safe-to-deploy".
+    #[serde(rename = "default-criteria")]
+    #[serde(default = "get_default_criteria")]
+    #[serde(skip_serializing_if = "is_default_criteria")]
+    pub default_criteria: CriteriaName,
+
+    /// Remote audits.toml's that we trust and want to import.
+    #[serde(skip_serializing_if = "SortedMap::is_empty")]
+    #[serde(default)]
+    pub imports: SortedMap<ImportName, RemoteImport>,
+
+    /// A table of policies for crates.
+    #[serde(skip_serializing_if = "Policy::is_empty")]
+    #[serde(default)]
+    pub policy: Policy,
+
+    /// All of the "foreign" dependencies that we rely on but haven't audited yet.
+    /// Foreign dependencies are just "things on crates.io", everything else
+    /// (paths, git, etc) is assumed to be "under your control" and therefore implicitly trusted.
+    #[serde(skip_serializing_if = "SortedMap::is_empty")]
+    #[serde(default)]
+    #[serde(alias = "unaudited")]
+    pub exemptions: SortedMap<PackageName, Vec<ExemptedDependency>>,
+}
+
+pub static SAFE_TO_DEPLOY: CriteriaStr = "safe-to-deploy";
+pub static SAFE_TO_RUN: CriteriaStr = "safe-to-run";
+pub static DEFAULT_CRITERIA: CriteriaStr = SAFE_TO_DEPLOY;
+
+pub fn get_default_criteria() -> CriteriaName {
+    CriteriaName::from(DEFAULT_CRITERIA)
+}
+fn is_default_criteria(val: &CriteriaName) -> bool {
+    val == DEFAULT_CRITERIA
+}
+
+/// The table of crate policies.
+#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, Default)]
+#[serde(try_from = "serialization::policy::AllPolicies")]
+#[serde(into = "serialization::policy::AllPolicies")]
+pub struct Policy {
+    pub package: SortedMap<PackageName, PackagePolicyEntry>,
+}
+
+impl Policy {
+    /// Get the policy entry for the given crate, if any.
+    pub fn get(&self, name: PackageStr, version: &VetVersion) -> Option<&PolicyEntry> {
+        self.package
+            .get(name)
+            .and_then(|pkg_policy| match pkg_policy {
+                PackagePolicyEntry::Unversioned(e) => Some(e),
+                PackagePolicyEntry::Versioned { version: v } => v.get(version),
+            })
+    }
+
+    /// Get the mutable policy entry for the given crate, if any.
+    pub fn get_mut(
+        &mut self,
+        name: PackageStr,
+        version: Option<&VetVersion>,
+    ) -> Option<&mut PolicyEntry> {
+        self.package
+            .get_mut(name)
+            .and_then(|pkg_policy| match pkg_policy {
+                PackagePolicyEntry::Unversioned(e) => Some(e),
+                PackagePolicyEntry::Versioned { version: v } => {
+                    version.and_then(|version| v.get_mut(version))
+                }
+            })
+    }
+
+    /// Get the mutable policy entry for the given crate, creating a default if none exists.
+    ///
+    /// Unlike `get_mut`, this guarantees that the policy is represented as versioned or
+    /// unversioned based on the whether the `version` is provided. If the `version` passed is
+    /// incompatible with the current policy, None is returned.
+    ///
+    /// `all_versions` is required to maintain proper structure of the policy map if the entry is
+    /// missing: if one policy version is provided, they all must be.
+    pub fn get_mut_or_default<F: FnOnce() -> Vec<VetVersion>>(
+        &mut self,
+        name: PackageName,
+        version: Option<&VetVersion>,
+        all_versions: F,
+    ) -> Option<&mut PolicyEntry> {
+        let pkg_policy = self.package.entry(name).or_insert_with(|| {
+            if version.is_none() {
+                PackagePolicyEntry::Unversioned(Default::default())
+            } else {
+                PackagePolicyEntry::Versioned {
+                    version: all_versions()
+                        .into_iter()
+                        .map(|v| (v, Default::default()))
+                        .collect(),
+                }
+            }
+        });
+
+        match (pkg_policy, version) {
+            (PackagePolicyEntry::Unversioned(e), None) => Some(e),
+            (PackagePolicyEntry::Versioned { version }, Some(v)) => version.get_mut(v),
+            _ => None,
+        }
+    }
+
+    /// Insert a new package policy entry.
+    pub fn insert(
+        &mut self,
+        name: PackageName,
+        entry: PackagePolicyEntry,
+    ) -> Option<PackagePolicyEntry> {
+        self.package.insert(name, entry)
+    }
+
+    /// Return whether there are no policies defined.
+    pub fn is_empty(&self) -> bool {
+        self.package.is_empty()
+    }
+
+    /// Return an iterator over defined policies.
+    pub fn iter(&self) -> PolicyIter {
+        PolicyIter {
+            iter: self.package.iter(),
+            versioned: None,
+        }
+    }
+}
+
+pub struct PolicyIter<'a> {
+    iter: <&'a SortedMap<PackageName, PackagePolicyEntry> as IntoIterator>::IntoIter,
+    versioned: Option<(
+        &'a PackageName,
+        <&'a SortedMap<VetVersion, PolicyEntry> as IntoIterator>::IntoIter,
+    )>,
+}
+
+impl<'a> Iterator for PolicyIter<'a> {
+    type Item = (&'a PackageName, Option<&'a VetVersion>, &'a PolicyEntry);
+
+    fn next(&mut self) -> Option<Self::Item> {
+        match &mut self.versioned {
+            Some((name, versioned)) => match versioned.next() {
+                Some((v, p)) => Some((name, Some(v), p)),
+                None => {
+                    self.versioned = None;
+                    self.next()
+                }
+            },
+            None => {
+                let (name, ppe) = self.iter.next()?;
+                match ppe {
+                    PackagePolicyEntry::Versioned { version } => {
+                        self.versioned = Some((name, version.iter()));
+                        self.next()
+                    }
+                    PackagePolicyEntry::Unversioned(p) => Some((name, None, p)),
+                }
+            }
+        }
+    }
+}
+
+impl<'a> IntoIterator for &'a Policy {
+    type IntoIter = PolicyIter<'a>;
+    type Item = <PolicyIter<'a> as Iterator>::Item;
+
+    fn into_iter(self) -> Self::IntoIter {
+        self.iter()
+    }
+}
+
+/// Policies for a particular package (crate).
+///
+/// If the crate exists as a third-party crate anywhere in the dependency tree, crate versions for
+/// _all_ and _only_ the versions present in the dependency tree must be provided to set policies.
+/// Otherwise, versions may be omitted.
+#[derive(Debug, Clone)]
+// We have to use a slightly different serialization than than `serde(untagged)`, because toml only
+// parses `Spanned` elements (as contained in `PolicyEntry`) through their own Deseralizer, and
+// `serde(untagged)` deserializes everything into a buffer first to try different deserialization
+// branches (which will use an internal `serde` Deserializer rather than the `toml` Deserializer).
+pub enum PackagePolicyEntry {
+    Versioned {
+        version: SortedMap<VetVersion, PolicyEntry>,
+    },
+    Unversioned(PolicyEntry),
+}
+
+/// Policies that crates must pass.
+///
+/// Policy settings here are basically the equivalent of audits.toml, which is separated out
+/// because it's not supposed to be shared (or, doesn't really make sense to share, since
+/// first-party crates are defined by "not on crates.io").
+///
+/// Because first-party crates are implicitly trusted, the only purpose of this table is to define
+/// the boundary between first-party and third-party ones.  More specifically, the criteria of the
+/// dependency edges between a first-party crate and its direct third-party dependencies.
+///
+/// If this sounds overwhelming, don't worry, everything defaults to "nothing special"
+/// and an empty PolicyTable basically just means "everything should satisfy the
+/// default criteria in audits.toml".
+#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, Default)]
+pub struct PolicyEntry {
+    /// Whether this nominally-first-party crate should actually be subject to audits
+    /// as-if it was third-party, based on matches to crates.io packages with the same
+    /// name and version. This field is optional for any package that *doesn't* have
+    /// such a match, and mandatory for all others (None == Some(false)).
+    ///
+    /// If true, this package will be handled like a third-party package and require
+    /// audits. If the package is not in the crates.io registry, it will be an error
+    /// and you should either make sure the current version is published or flip
+    /// this back to false.
+    ///
+    /// Setting this value to true is intended for actual externally developed projects
+    /// that you are importing into your project in a weird way with minimal modifications.
+    /// For instance, if you manually vendor the package in, or maintain a small patchset
+    /// on top of the currently published version.
+    ///
+    /// It should not be used for packages that are directly developed in this project
+    /// (a project shouldn't publish audits for its own code) or for non-trivial forks.
+    ///
+    /// Audits you *do* perform should be for the actual version published to crates.io,
+    /// which are the versions `cargo vet diff` and `cargo vet inspect` will fetch.
+    #[serde(rename = "audit-as-crates-io")]
+    pub audit_as_crates_io: Option<bool>,
+
+    /// Default criteria that must be satisfied by all *direct* third-party (foreign) dependencies
+    /// of the crate. If satisfied, the crate is set to satisfying all criteria.
+    ///
+    /// If not present, this defaults to the default criteria in the audits table.
+    #[serde(default)]
+    #[serde(with = "serialization::string_or_vec_or_none")]
+    pub criteria: Option<Vec<Spanned<CriteriaName>>>,
+
+    /// Same as `criteria`, but for crates that are only used as dev-dependencies.
+    #[serde(rename = "dev-criteria")]
+    #[serde(default)]
+    #[serde(with = "serialization::string_or_vec_or_none")]
+    pub dev_criteria: Option<Vec<Spanned<CriteriaName>>>,
+
+    /// Custom criteria for a specific crate's dependencies.
+    ///
+    /// Any dependency edge that isn't explicitly specified defaults to `criteria`.
+    #[serde(rename = "dependency-criteria")]
+    #[serde(skip_serializing_if = "CriteriaMap::is_empty")]
+    #[serde(with = "serialization::criteria_map")]
+    #[serde(default)]
+    pub dependency_criteria: CriteriaMap,
+
+    /// Freeform notes
+    pub notes: Option<String>,
+}
+
+/// Helper type for managing a mapping from a string to a set of criteria. This
+/// is used for dependency-criteria to specify the criteria that transitive
+/// dependencies must satisfy, as well as for criteria-maps when specifying the
+/// criteria implied by foreign criteria.
+///
+/// Example:
+///
+/// ```toml
+/// dependency_criteria = { hmac = ['secure', 'crypto_reviewed'] }
+/// ```
+///
+/// ```toml
+/// criteria-map = { fuzzed = 'safe-to-deploy' }
+/// ```
+pub type CriteriaMap = SortedMap<Spanned<String>, Vec<Spanned<CriteriaName>>>;
+
+pub static DEFAULT_POLICY_CRITERIA: CriteriaStr = SAFE_TO_DEPLOY;
+pub static DEFAULT_POLICY_DEV_CRITERIA: CriteriaStr = SAFE_TO_RUN;
+
+/// A remote audits.toml that we trust the contents of (by virtue of trusting the maintainer).
+#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, Default)]
+pub struct RemoteImport {
+    /// URL(s) of the foreign audits.toml
+    #[serde(with = "serialization::string_or_vec")]
+    pub url: Vec<String>,
+    /// A list of crates for which no audits or violations should be imported.
+    #[serde(skip_serializing_if = "Vec::is_empty")]
+    #[serde(default)]
+    pub exclude: Vec<PackageName>,
+    /// A list of criteria that are implied by foreign criteria
+    #[serde(rename = "criteria-map")]
+    #[serde(skip_serializing_if = "CriteriaMap::is_empty")]
+    #[serde(with = "serialization::criteria_map")]
+    #[serde(default)]
+    pub criteria_map: CriteriaMap,
+}
+
+/// Translations of foreign criteria to local criteria.
+#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
+pub struct CriteriaMapping {
+    /// This local criteria is implied...
+    pub ours: CriteriaName,
+    /// If this foreign criteria applies
+    pub theirs: Spanned<ForeignCriteriaName>,
+}
+
+/// Semantically identical to a 'full audit' entry, but private to our project
+/// and tracked as less-good than a proper audit, so that you try to get rid of it.
+#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, PartialOrd, Ord)]
+pub struct ExemptedDependency {
+    /// The version of the crate that we are currently "fine" with leaving unaudited.
+    pub version: VetVersion,
+    /// Criteria that we're willing to handwave for this version (assuming our dependencies
+    /// satisfy this criteria). This isn't defaulted, 'vet init' and similar commands will
+    /// pick a "good" initial value.
+    #[serde(default)]
+    #[serde(with = "serialization::string_or_vec")]
+    pub criteria: Vec<Spanned<CriteriaName>>,
+    /// Whether 'suggest' should bother mentioning this (defaults true).
+    #[serde(default = "get_default_exemptions_suggest")]
+    #[serde(skip_serializing_if = "is_default_exemptions_suggest")]
+    pub suggest: bool,
+    /// Freeform notes, put whatever you want here. Just more stable/reliable than comments.
+    pub notes: Option<String>,
+}
+
+static DEFAULT_EXEMPTIONS_SUGGEST: bool = true;
+pub fn get_default_exemptions_suggest() -> bool {
+    DEFAULT_EXEMPTIONS_SUGGEST
+}
+fn is_default_exemptions_suggest(val: &bool) -> bool {
+    val == &DEFAULT_EXEMPTIONS_SUGGEST
+}
+
+/// Special version type used for store versions. Only contains two components
+/// (major/minor) to avoid patch version changes from causing changes to the
+/// store.
+#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
+pub struct StoreVersion {
+    pub major: u64,
+    pub minor: u64,
+}
+
+impl StoreVersion {
+    #[cfg(not(test))]
+    pub fn current() -> Self {
+        StoreVersion {
+            major: env!("CARGO_PKG_VERSION_MAJOR").parse().unwrap(),
+            minor: env!("CARGO_PKG_VERSION_MINOR").parse().unwrap(),
+        }
+    }
+
+    // To keep output from tests stable, when running unit tests we always
+    // pretend we're version 1.0
+    #[cfg(test)]
+    pub fn current() -> Self {
+        StoreVersion { major: 1, minor: 0 }
+    }
+}
+
+impl FromStr for StoreVersion {
+    type Err = StoreVersionParseError;
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        match s.split_once('.') {
+            Some((major, minor)) => Ok(StoreVersion {
+                major: major.parse()?,
+                minor: minor.parse()?,
+            }),
+            None => Err(StoreVersionParseError::MissingSeparator),
+        }
+    }
+}
+
+impl fmt::Display for StoreVersion {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "{}.{}", self.major, self.minor)
+    }
+}
+
+impl Serialize for StoreVersion {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        self.to_string().serialize(serializer)
+    }
+}
+
+impl<'de> Deserialize<'de> for StoreVersion {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        struct VersionVisitor;
+
+        impl<'de> Visitor<'de> for VersionVisitor {
+            type Value = StoreVersion;
+
+            fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
+                f.write_str("store version")
+            }
+            fn visit_str<E>(self, string: &str) -> Result<Self::Value, E>
+            where
+                E: de::Error,
+            {
+                StoreVersion::from_str(string).map_err(de::Error::custom)
+            }
+        }
+
+        deserializer.deserialize_str(VersionVisitor)
+    }
+}
+
+/// Cargo vet config metadata field for the store's config file.
+#[derive(Debug, Serialize, Deserialize, Clone)]
+pub struct CargoVetConfig {
+    pub version: StoreVersion,
+}
+
+impl CargoVetConfig {
+    /// Pretend that any store which was created without a version specified is
+    /// from version 0.4.
+    fn missing() -> Self {
+        Self {
+            version: StoreVersion { major: 0, minor: 4 },
+        }
+    }
+}
+
+impl Default for CargoVetConfig {
+    fn default() -> Self {
+        Self {
+            version: StoreVersion::current(),
+        }
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+//                                                                                //
+//                                                                                //
+//                                                                                //
+//                                imports.lock                                    //
+//                                                                                //
+//                                                                                //
+//                                                                                //
+////////////////////////////////////////////////////////////////////////////////////
+
+#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, PartialEq, Eq)]
+pub struct ImportsFile {
+    #[serde(default)]
+    #[serde(skip_serializing_if = "SortedMap::is_empty")]
+    pub unpublished: SortedMap<PackageName, Vec<UnpublishedEntry>>,
+    #[serde(default)]
+    #[serde(skip_serializing_if = "SortedMap::is_empty")]
+    pub publisher: SortedMap<PackageName, Vec<CratesPublisher>>,
+    #[serde(default)]
+    #[serde(skip_serializing_if = "SortedMap::is_empty")]
+    pub audits: SortedMap<ImportName, AuditsFile>,
+}
+
+/// Information about who published a specific version of a crate to be cached
+/// in imports.lock.
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
+pub struct CratesPublisher {
+    // NOTE: This will only ever be a `semver::Version`, however the resolver
+    // code works on borrowed `VetVersion` instances, so we use one here so it
+    // is easier to use within the resolver.
+    pub version: VetVersion,
+    pub when: chrono::NaiveDate,
+    #[serde(rename = "user-id")]
+    pub user_id: CratesUserId,
+    #[serde(rename = "user-login")]
+    pub user_login: String,
+    #[serde(rename = "user-name")]
+    pub user_name: Option<String>,
+    /// See `AuditEntry::is_fresh_import`.
+    #[serde(skip)]
+    pub is_fresh_import: bool,
+}
+
+// Information about a specific crate being unpublished
+#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
+pub struct UnpublishedEntry {
+    // NOTE: This will only ever be a `semver::Version`, however the resolver
+    // code works on borrowed `VetVersion` instances, so we use one here so it
+    // is easier to use within the resolver.
+    pub version: VetVersion,
+    pub audited_as: VetVersion,
+    /// Set to `true` if `version` was not published when acquiring the Store.
+    /// Always set to `false` when locked.
+    #[serde(skip)]
+    pub still_unpublished: bool,
+    /// See `AuditEntry::is_fresh_import`.
+    #[serde(skip)]
+    pub is_fresh_import: bool,
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+//                                                                                //
+//                                                                                //
+//                                                                                //
+//                               diffcache.toml                                   //
+//                                                                                //
+//                                                                                //
+//                                                                                //
+////////////////////////////////////////////////////////////////////////////////////
+
+/// The current DiffCache file format in a tagged enum.
+///
+/// If we fail to read the DiffCache it will be silently re-built, meaning that
+/// the version enum tag can be changed to force the DiffCache to be
+/// re-generated after a breaking change to the format, such as a change to how
+/// diffs are computed or identified.
+#[derive(Serialize, Deserialize, Clone)]
+#[serde(tag = "version")]
+pub enum DiffCache {
+    #[serde(rename = "2")]
+    V2 {
+        diffs: SortedMap<PackageName, SortedMap<Delta, DiffStat>>,
+    },
+}
+
+impl Default for DiffCache {
+    fn default() -> Self {
+        DiffCache::V2 {
+            diffs: SortedMap::new(),
+        }
+    }
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
+pub struct DiffStat {
+    pub insertions: u64,
+    pub deletions: u64,
+    pub files_changed: u64,
+}
+
+impl DiffStat {
+    pub fn count(&self) -> u64 {
+        self.insertions + self.deletions
+    }
+}
+
+impl fmt::Display for DiffStat {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "{} files changed", self.files_changed)?;
+        if self.insertions > 0 {
+            write!(f, ", {} insertions(+)", self.insertions)?;
+        }
+        if self.deletions > 0 {
+            write!(f, ", {} deletions(-)", self.deletions)?;
+        }
+        Ok(())
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+//                                                                                //
+//                                                                                //
+//                                                                                //
+//                             command-history.json                               //
+//                                                                                //
+//                                                                                //
+//                                                                                //
+////////////////////////////////////////////////////////////////////////////////////
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub enum FetchCommand {
+    Inspect {
+        package: PackageName,
+        version: VetVersion,
+    },
+    Diff {
+        package: PackageName,
+        version1: VetVersion,
+        version2: VetVersion,
+    },
+}
+
+impl FetchCommand {
+    pub fn package(&self) -> PackageStr {
+        match self {
+            FetchCommand::Inspect { package, .. } => package,
+            FetchCommand::Diff { package, .. } => package,
+        }
+    }
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone, Default)]
+pub struct CommandHistory {
+    #[serde(flatten)]
+    pub last_fetch: Option<FetchCommand>,
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+//                                                                                //
+//                                                                                //
+//                                                                                //
+//                             crates-io-cache.json                               //
+//                                                                                //
+//                                                                                //
+//                                                                                //
+////////////////////////////////////////////////////////////////////////////////////
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct CratesCacheUser {
+    pub login: String,
+    pub name: Option<String>,
+}
+impl fmt::Display for CratesCacheUser {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        if let Some(name) = &self.name {
+            write!(f, "{} ({})", name, &self.login)
+        } else {
+            write!(f, "{}", &self.login)
+        }
+    }
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct CratesCacheVersionDetails {
+    pub created_at: chrono::DateTime<chrono::Utc>,
+    pub published_by: Option<CratesUserId>,
+}
+
+#[derive(Serialize, Deserialize, Default, Debug, Clone)]
+pub struct CratesCacheEntry {
+    pub last_fetched: chrono::DateTime<chrono::Utc>,
+    /// If versions is empty, this indicates that we queried the info and found that the crate has
+    /// no published versions (and thus doesn't exist as of `last_fetched`).
+    ///
+    /// Versions are a sorted map because we sometimes need to iterate in order. We don't use a sorted
+    /// Vec because we may partially update the versions when we access the index (though technically
+    /// that update _should_ only have new versions which would append to a Vec if it were that).
+    pub versions: SortedMap<semver::Version, Option<CratesCacheVersionDetails>>,
+    pub metadata: Option<CratesAPICrateMetadata>,
+}
+
+#[derive(Serialize, Deserialize, Clone, Default)]
+pub struct CratesCache {
+    pub users: SortedMap<CratesUserId, CratesCacheUser>,
+    pub crates: SortedMap<PackageName, CratesCacheEntry>,
+}
+
+impl CratesCacheEntry {
+    /// Return whether the crate exists.
+    ///
+    /// The cache stores non-existent results when fetching.
+    pub fn exists(&self) -> bool {
+        !self.versions.is_empty()
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+//                                                                                //
+//                                                                                //
+//                                                                                //
+//                                 crates.io API                                  //
+//                                                                                //
+//                                                                                //
+//                                                                                //
+////////////////////////////////////////////////////////////////////////////////////
+
+// NOTE: This is a subset of the format returned from the crates.io v1 API.
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct CratesAPIUser {
+    pub id: CratesUserId,
+    pub login: String,
+    pub name: Option<String>,
+}
+
+// NOTE: This is a subset of the format returned from the crates.io v1 API.
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct CratesAPIVersion {
+    pub created_at: chrono::DateTime<chrono::Utc>,
+    pub num: semver::Version,
+    pub published_by: Option<CratesAPIUser>,
+}
+
+// NOTE: This is a subset of the format returned from the crates.io v1 API.
+#[derive(Serialize, Deserialize, Debug, Default, Clone)]
+pub struct CratesAPICrateMetadata {
+    pub description: Option<String>,
+    pub repository: Option<String>,
+}
+
+// NOTE: This is a subset of the format returned from the crates.io v1 API.
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct CratesAPICrate {
+    #[serde(rename = "crate")]
+    pub crate_data: CratesAPICrateMetadata,
+    pub versions: Vec<CratesAPIVersion>,
+}
+
+impl CratesAPICrateMetadata {
+    /// Whether this metadata is similar enough to that of the given package to be considered the
+    /// same.
+    pub fn consider_as_same(&self, p: &Package) -> bool {
+        (self.description.is_some() && p.description == self.description)
+            || (self.repository.is_some() && p.repository == self.repository)
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+//                                                                                //
+//                                                                                //
+//                                                                                //
+//                               registry.toml                                    //
+//                                                                                //
+//                                                                                //
+//                                                                                //
+////////////////////////////////////////////////////////////////////////////////////
+
+#[derive(Serialize, Deserialize, Debug, Clone, Default)]
+pub struct RegistryFile {
+    pub registry: SortedMap<ImportName, RegistryEntry>,
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone)]
+pub struct RegistryEntry {
+    #[serde(with = "serialization::string_or_vec")]
+    pub url: Vec<String>,
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+//                                                                                //
+//                                                                                //
+//                                                                                //
+//                             <json report output>                               //
+//                                                                                //
+//                                                                                //
+//                                                                                //
+////////////////////////////////////////////////////////////////////////////////////
+
+/// cargo-vet's `--output-format=json` for `check` and `suggest` on:
+///
+/// * success
+/// * audit failure
+/// * violation conflicts
+///
+/// Other errors like i/o or supply-chain integrity issues will show
+/// up as miette-style json errors.
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct JsonReport {
+    #[serde(flatten)]
+    pub conclusion: JsonReportConclusion,
+}
+
+/// The conclusion of running `check` or `suggest`
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(tag = "conclusion")]
+pub enum JsonReportConclusion {
+    /// Success! Everything's Good.
+    #[serde(rename = "success")]
+    Success(JsonReportSuccess),
+    /// The violations and audits/exemptions are contradictory!
+    #[serde(rename = "fail (violation)")]
+    FailForViolationConflict(JsonReportFailForViolationConflict),
+    /// The audit failed, here's why and what to do.
+    #[serde(rename = "fail (vetting)")]
+    FailForVet(JsonReportFailForVet),
+}
+
+/// Success! Everything is audited!
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct JsonReportSuccess {
+    /// These packages are fully vetted
+    pub vetted_fully: Vec<JsonPackage>,
+    /// These packages are partially vetted (some audits but relies on an `exemption`).
+    pub vetted_partially: Vec<JsonPackage>,
+    /// These packages are exempted
+    pub vetted_with_exemptions: Vec<JsonPackage>,
+}
+
+/// Failure! The violations and audits/exemptions are contradictory!
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct JsonReportFailForViolationConflict {
+    /// These packages have the following conflicts
+    // FIXME(SCHEMA): we probably shouldn't expose this internal type
+    pub violations: SortedMap<PackageAndVersion, Vec<ViolationConflict>>,
+}
+
+/// Failure! You need more audits!
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct JsonReportFailForVet {
+    /// Here are the problems we found
+    pub failures: Vec<JsonVetFailure>,
+    /// And here are the fixes we recommend
+    pub suggest: Option<JsonSuggest>,
+}
+
+/// Suggested fixes for a FailForVet
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct JsonSuggest {
+    /// Here are the suggestions sorted in the order of priority
+    pub suggestions: Vec<JsonSuggestItem>,
+    /// The same set of suggestions but grouped by the criteria (lists) needed to audit them
+    // FIXME(SCHEMA): this is kinda redundant? do consumers want this?
+    pub suggest_by_criteria: SortedMap<String, Vec<JsonSuggestItem>>,
+    /// The total number of lines you would need to review to resolve this
+    pub total_lines: u64,
+}
+
+/// This specific package needed the following criteria but doesn't have them!
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct JsonVetFailure {
+    /// The name of the package
+    pub name: PackageName,
+    /// The version of the package
+    pub version: VetVersion,
+    /// The missing criteria
+    pub missing_criteria: Vec<CriteriaName>,
+}
+
+/// We recommend auditing the following package
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct JsonSuggestItem {
+    /// The name of the package
+    pub name: PackageName,
+    /// Any notable parents the package has (can be helpful in giving context to the user)
+    // FIXME(SCHEMA): we probably shouldn't expose this as a String
+    pub notable_parents: String,
+    /// The criteria we recommend auditing the package for
+    pub suggested_criteria: Vec<CriteriaName>,
+    /// The diff (or full version) we recommend auditing
+    // FIXME(SCHEMA): we probably shouldn't expose this internal type
+    pub suggested_diff: DiffRecommendation,
+}
+
+/// A string of the form "package:version"
+pub type PackageAndVersion = String;
+
+/// A Package
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct JsonPackage {
+    /// Name of the package
+    pub name: PackageName,
+    /// 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/git_tool.rs b/src/git_tool.rs
new file mode 100644
index 0000000..08de400
--- /dev/null
+++ b/src/git_tool.rs
@@ -0,0 +1,360 @@
+//! Helper utilities for invoking git configured tools.
+
+use std::fs::File;
+use std::io::{self, BufRead, BufReader, Write};
+use std::path::{Path, PathBuf};
+use std::process::{Child, Command, ExitStatus, Stdio};
+use std::str;
+
+use tempfile::NamedTempFile;
+use tracing::warn;
+
+use crate::errors::EditError;
+use crate::out::Out;
+
+#[cfg(windows)]
+fn git_sh_path() -> Option<PathBuf> {
+    // Locate the `git` binary using the windows `where` command.
+    let output = Command::new("where").arg("git").output().ok()?;
+    if !output.status.success() {
+        return None;
+    }
+
+    // The git binary path should be either in the `cmd` or `bin` subdirectory
+    // of the git-for-windows install path, while the `sh.exe` binary is located
+    // in the `bin` subdirectory.
+    Path::new(str::from_utf8(&output.stdout).ok()?.trim())
+        .canonicalize()
+        .ok()?
+        .parent()?
+        .parent()?
+        .join(r"bin\sh.exe")
+        .canonicalize()
+        .ok()
+}
+
+#[cfg(not(windows))]
+fn git_sh_path() -> Option<PathBuf> {
+    Some("/bin/sh".into())
+}
+
+fn git_config(config: &str) -> Option<String> {
+    let output = Command::new("git")
+        .arg("config")
+        .arg(config)
+        .output()
+        .ok()?;
+    if !output.status.success() {
+        return None;
+    }
+    Some(str::from_utf8(&output.stdout).ok()?.trim().to_owned())
+}
+
+fn git_config_bool(config: &str) -> Option<bool> {
+    let value = git_config(config)?;
+    match &value[..] {
+        "true" | "yes" | "on" => Some(true),
+        "" | "false" | "no" | "off" => Some(false),
+        _ => Some(value.parse::<i64>().ok()? != 0),
+    }
+}
+
+/// Read the git configuration to determine the value for GIT_EDITOR.
+fn git_editor() -> Option<String> {
+    // Testing environment variable to force using the fallback editor instead
+    // of GIT_EDITOR.
+    if std::env::var("CARGO_VET_USE_FALLBACK_EDITOR").unwrap_or_default() == "1" {
+        return None;
+    }
+
+    let output = Command::new("git")
+        .arg("var")
+        .arg("GIT_EDITOR")
+        .output()
+        .ok()?;
+    if !output.status.success() {
+        return None;
+    }
+    Some(str::from_utf8(&output.stdout).ok()?.trim().to_owned())
+}
+
+#[cfg(windows)]
+const FALLBACK_EDITOR: &str = "notepad.exe";
+
+// NOTE: This is probably not as reliably available as `vi`, but is easier to
+// quit from for users who aren't familiar with vi.
+#[cfg(not(windows))]
+const FALLBACK_EDITOR: &str = "nano";
+
+/// Get a Command which can be used to invoke the user's EDITOR to edit a
+/// document when passed an argument. This will try to use the user's configured
+/// GIT_EDITOR when possible.
+pub fn editor_command() -> Command {
+    // Try to use the user's configured editor if we're able to locate their git
+    // install. If this fails, invoke the default editor instead.
+    //
+    // XXX: If we end up with commands which invoke the editor many times, it
+    // may eventually be worth adding some form of caching here.
+    match (git_sh_path(), git_editor()) {
+        (Some(git_sh), Some(git_editor)) => {
+            let mut cmd = Command::new(git_sh);
+            cmd.arg("-c")
+                .arg(format!("{git_editor} \"$@\""))
+                .arg(git_editor);
+            return cmd;
+        }
+        (_, None) => {
+            warn!("Unable to determine user's GIT_EDITOR");
+        }
+        (None, Some(_)) => {
+            warn!("Unable to locate user's git install to invoke GIT_EDITOR");
+        }
+    }
+    warn!("Falling back to running '{}' directly", FALLBACK_EDITOR);
+    Command::new(FALLBACK_EDITOR)
+}
+
+/// Run the default editor configured through git (GIT_EDITOR) and use it to
+/// edit the given file path.
+pub fn run_editor(path: &Path) -> io::Result<ExitStatus> {
+    editor_command().arg(path).status()
+}
+
+// On windows some editors (notably notepad pre-windows 11) don't handle
+// unix line endings very well, so make sure to give them windows line
+// endings.
+#[cfg(windows)]
+const LINE_ENDING: &str = "\r\n";
+
+#[cfg(not(windows))]
+const LINE_ENDING: &str = "\n";
+
+pub struct Editor<'a> {
+    tempfile: NamedTempFile,
+    comment_char: char,
+    #[allow(clippy::type_complexity)]
+    run_editor: Box<dyn FnOnce(&Path) -> io::Result<bool> + 'a>,
+}
+
+impl<'a> Editor<'a> {
+    /// Create a new editor for a temporary file.
+    pub fn new(name: &str) -> io::Result<Self> {
+        let tempfile = tempfile::Builder::new()
+            .prefix(&format!("{name}."))
+            .tempfile()?;
+        Ok(Editor {
+            tempfile,
+            comment_char: '#',
+            run_editor: Box::new(|p| run_editor(p).map(|s| s.success())),
+        })
+    }
+
+    /// Set the character to be used for comments.
+    pub fn set_comment_char(&mut self, comment_char: char) {
+        self.comment_char = comment_char;
+    }
+
+    /// Attempt to pick a comment character which does not appear at the start
+    /// of any line in the given text.
+    pub fn select_comment_char(&mut self, text: &str) {
+        let mut comment_chars = ['#', ';', '@', '!', '$', '%', '^', '&', '|', ':', '"', ';'];
+        for line in text.lines() {
+            for cc in &mut comment_chars {
+                if line.starts_with(*cc) {
+                    *cc = '\0';
+                    break;
+                }
+            }
+        }
+        self.set_comment_char(
+            comment_chars
+                .into_iter()
+                .find(|cc| *cc != '\0')
+                .expect("couldn't find a viable comment character"),
+        );
+    }
+
+    #[cfg(test)]
+    /// Test-only method to mock out the actual invocation of the editor.
+    pub fn set_run_editor(&mut self, run_editor: impl FnOnce(&Path) -> io::Result<bool> + 'a) {
+        self.run_editor = Box::new(run_editor);
+    }
+
+    /// Add comment lines to the editor. Any newlines in the input will be
+    /// normalized to the current platform, and a comment character will be
+    /// added.
+    pub fn add_comments(&mut self, text: &str) -> io::Result<()> {
+        let text = text.trim();
+        if text.is_empty() {
+            write!(self.tempfile, "{}{}", self.comment_char, LINE_ENDING)?;
+        }
+        for line in text.lines() {
+            if line.is_empty() {
+                write!(self.tempfile, "{}{}", self.comment_char, LINE_ENDING)?;
+            } else {
+                write!(
+                    self.tempfile,
+                    "{} {}{}",
+                    self.comment_char, line, LINE_ENDING
+                )?;
+            }
+        }
+        Ok(())
+    }
+
+    /// Add non-comment lines to the editor. These lines must not start with
+    /// comment_character.
+    pub fn add_text(&mut self, text: &str) -> io::Result<()> {
+        let text = text.trim();
+        if text.is_empty() {
+            write!(self.tempfile, "{LINE_ENDING}")?;
+        }
+        for line in text.lines() {
+            assert!(
+                !line.starts_with(self.comment_char),
+                "non-comment lines cannot start with a '{}' comment character",
+                self.comment_char
+            );
+            write!(self.tempfile, "{line}{LINE_ENDING}")?;
+        }
+        Ok(())
+    }
+
+    /// Run the editor, collecting and filtering the resulting file, and
+    /// returning it as a string.
+    pub fn edit(self) -> Result<String, EditError> {
+        // Close our handle on the file to allow other programs like the editor
+        // to modify it on Windows.
+        let path = self.tempfile.into_temp_path();
+        (self.run_editor)(&path).map_err(EditError::CouldntLaunch)?;
+
+        // Read in the result, filtering lines, and restoring unix line endings.
+        // This is roughly based on git's logic for cleaning up commit message
+        // files.
+        let mut lines: Vec<String> = Vec::new();
+        for line in BufReader::new(File::open(&path).map_err(EditError::CouldntOpen)?).lines() {
+            let line = line.map_err(EditError::CouldntRead)?;
+            // Ignore lines starting with a comment character.
+            if line.starts_with(self.comment_char) {
+                continue;
+            }
+            // Trim any trailing whitespace from each line, but leave leading
+            // whitespace untouched to avoid breaking formatted text.
+            let line = line.trim_end();
+            // Don't record 2 consecutive empty lines or empty lines at the
+            // start of the file.
+            if line.is_empty() && lines.last().map_or(true, |l| l.is_empty()) {
+                continue;
+            }
+            lines.push(line.to_owned());
+        }
+
+        // Ensure there's a trailing newline for non-empty files.
+        match lines.last() {
+            None => return Ok(String::new()),
+            Some(line) if !line.is_empty() => lines.push(String::new()),
+            _ => {}
+        }
+
+        Ok(lines.join("\n"))
+    }
+}
+
+/// Read the git configuration to determine the value for GIT_EDITOR.
+fn git_pager() -> Option<String> {
+    let output = Command::new("git")
+        .arg("var")
+        .arg("GIT_PAGER")
+        .output()
+        .ok()?;
+    if !output.status.success() {
+        return None;
+    }
+    let pager = str::from_utf8(&output.stdout).ok()?.trim().to_owned();
+    if pager == "cat" {
+        return None;
+    }
+    Some(pager)
+}
+
+/// Get a Command which can be used to invoke the user's EDITOR to edit a
+/// document when passed an argument. This will try to use the user's configured
+/// GIT_EDITOR when possible.
+fn pager_command(out: &dyn Out) -> Option<Command> {
+    if !out.is_term() {
+        return None;
+    }
+
+    // Try to use the user's configured pager if we're able to locate their git
+    // install. If this fails, we'll not use a pager. Don't bother warning in
+    // that case.
+    let git_sh = git_sh_path()?;
+    let git_pager = git_pager()?;
+
+    let mut cmd = Command::new(git_sh);
+    cmd.arg("-c")
+        .arg(format!("{git_pager} \"$@\""))
+        .arg(git_pager);
+    // These environment variables are hard-coded into `git` at build
+    // time, and are required to support colors in `less`.
+    cmd.env("LESS", "FRX").env("LV", "-c");
+    Some(cmd)
+}
+
+pub struct Pager<'a> {
+    out: &'a dyn Out,
+    child: Option<Child>,
+    use_color: bool,
+}
+
+impl<'a> Pager<'a> {
+    /// Create a new pager for the given output stream, or a dummy pager if no pager is available.
+    pub fn new(out: &'a dyn Out) -> Result<Self, io::Error> {
+        let child = if let Some(mut cmd) = pager_command(out) {
+            Some(cmd.stdin(Stdio::piped()).spawn()?)
+        } else {
+            None
+        };
+        let use_color = child.is_some()
+            && out.is_term()
+            && console::colors_enabled()
+            && git_config_bool("pager.color")
+                .or_else(|| git_config_bool("color.pager"))
+                .unwrap_or(true);
+        Ok(Pager {
+            out,
+            child,
+            use_color,
+        })
+    }
+
+    /// Should attempts to write to this pager include ANSI color codes?
+    pub fn use_color(&self) -> bool {
+        self.use_color
+    }
+
+    /// Wait for the pager to stop running, so it's OK to start writing to
+    /// output again.
+    pub fn wait(self) -> Result<(), io::Error> {
+        if let Some(mut child) = self.child {
+            child.wait()?;
+        }
+        Ok(())
+    }
+}
+
+impl<'a> io::Write for Pager<'a> {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        match &mut self.child {
+            Some(child) => child.stdin.as_mut().unwrap().write(buf),
+            None => self.out.write(buf),
+        }
+    }
+
+    fn flush(&mut self) -> io::Result<()> {
+        match &mut self.child {
+            Some(child) => child.stdin.as_mut().unwrap().flush(),
+            None => Ok(()),
+        }
+    }
+}
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..bbbaaf5
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,3003 @@
+use std::collections::HashMap;
+use std::io::{BufRead, BufReader};
+use std::ops::Deref;
+use std::panic::panic_any;
+use std::process::Stdio;
+use std::sync::{Arc, Mutex};
+use std::time::{Duration, SystemTime};
+use std::{fs::File, io, panic, path::PathBuf};
+
+use cargo_metadata::{Metadata, Package};
+use clap::{CommandFactory, Parser};
+use console::Term;
+use errors::{
+    AggregateCriteriaDescription, AggregateCriteriaDescriptionMismatchError,
+    AggregateCriteriaImplies, AggregateError, AggregateErrors, AggregateImpliesMismatchError,
+    AuditAsError, AuditAsErrors, CacheAcquireError, CertifyError, CratePolicyError,
+    CratePolicyErrors, FetchAuditError, LoadTomlError, NeedsAuditAsErrors,
+    NeedsPolicyVersionErrors, PackageError, ShouldntBeAuditAsErrors, TomlParseError,
+    UnusedAuditAsErrors, UnusedPolicyVersionErrors, UserInfoError,
+};
+use format::{CriteriaName, CriteriaStr, PackageName, Policy, PolicyEntry, SortedSet, VetVersion};
+use futures_util::future::{join_all, try_join_all};
+use indicatif::ProgressDrawTarget;
+use lazy_static::lazy_static;
+use miette::{miette, Context, Diagnostic, IntoDiagnostic, SourceOffset};
+use network::Network;
+use out::{progress_bar, IncProgressOnDrop};
+use reqwest::Url;
+use serde::de::Deserialize;
+use serialization::spanned::Spanned;
+use storage::fetch_registry;
+use thiserror::Error;
+use tracing::{error, info, trace, warn};
+
+use crate::cli::*;
+use crate::criteria::CriteriaMapper;
+use crate::errors::{
+    CommandError, DownloadError, FetchAndDiffError, FetchError, MetadataAcquireError, SourceFile,
+};
+use crate::format::{
+    AuditEntry, AuditKind, AuditsFile, ConfigFile, CratesUserId, CriteriaEntry, ExemptedDependency,
+    FetchCommand, MetaConfig, MetaConfigInstance, PackageStr, SortedMap, StoreInfo, TrustEntry,
+    WildcardEntry,
+};
+use crate::git_tool::Pager;
+use crate::out::{indeterminate_spinner, Out, StderrLogWriter, MULTIPROGRESS};
+use crate::storage::{Cache, Store};
+
+mod cli;
+mod criteria;
+pub mod errors;
+mod flock;
+pub mod format;
+mod git_tool;
+pub mod network;
+mod out;
+pub mod resolver;
+mod serialization;
+pub mod storage;
+mod string_format;
+#[cfg(test)]
+mod tests;
+
+/// Absolutely All The Global Configurations
+pub struct Config {
+    /// Cargo.toml `metadata.vet`
+    pub metacfg: MetaConfig,
+    /// `cargo metadata`
+    pub metadata: Metadata,
+    /// Freestanding configuration values
+    _rest: PartialConfig,
+}
+
+/// Configuration vars that are available in a free-standing situation
+/// (no actual cargo-vet instance to load/query).
+pub struct PartialConfig {
+    /// Details of the CLI invocation (args)
+    pub cli: Cli,
+    /// The date and time to use as the current time.
+    pub now: chrono::DateTime<chrono::Utc>,
+    /// Path to the cache directory we're using
+    pub cache_dir: PathBuf,
+    /// Whether we should mock the global cache (for unit testing)
+    pub mock_cache: bool,
+}
+
+impl PartialConfig {
+    pub fn today(&self) -> chrono::NaiveDate {
+        self.now.date_naive()
+    }
+}
+
+// Makes it a bit easier to have both a "partial" and "full" config
+impl Deref for Config {
+    type Target = PartialConfig;
+    fn deref(&self) -> &Self::Target {
+        &self._rest
+    }
+}
+
+pub trait PackageExt {
+    fn is_third_party(&self, policy: &Policy) -> bool;
+    fn is_crates_io(&self) -> bool;
+    fn policy_entry<'a>(&self, policy: &'a Policy) -> Option<&'a PolicyEntry>;
+    fn git_rev(&self) -> Option<String>;
+    fn vet_version(&self) -> VetVersion;
+}
+
+impl PackageExt for Package {
+    fn is_third_party(&self, policy: &Policy) -> bool {
+        let forced_third_party = self
+            .policy_entry(policy)
+            .and_then(|policy| policy.audit_as_crates_io)
+            .unwrap_or(false);
+
+        forced_third_party || self.is_crates_io()
+    }
+
+    fn is_crates_io(&self) -> bool {
+        self.source
+            .as_ref()
+            .map(|s| s.is_crates_io())
+            .unwrap_or(false)
+    }
+
+    fn policy_entry<'a>(&self, policy: &'a Policy) -> Option<&'a PolicyEntry> {
+        policy.get(&self.name, &self.vet_version())
+    }
+
+    fn git_rev(&self) -> Option<String> {
+        self.source.as_ref().and_then(|s| {
+            let git_source = s.repr.strip_prefix("git+")?;
+            let source_url = Url::parse(git_source).ok()?;
+            Some(source_url.fragment()?.to_owned())
+        })
+    }
+
+    fn vet_version(&self) -> VetVersion {
+        VetVersion {
+            semver: self.version.clone(),
+            git_rev: self.git_rev(),
+        }
+    }
+}
+
+const CACHE_DIR_SUFFIX: &str = "cargo-vet";
+const CARGO_ENV: &str = "CARGO";
+// package.metadata.vet
+const PACKAGE_VET_CONFIG: &str = "vet";
+// workspace.metadata.vet
+const WORKSPACE_VET_CONFIG: &str = "vet";
+
+const DURATION_DAY: Duration = Duration::from_secs(60 * 60 * 24);
+
+lazy_static! {
+    static ref WILDCARD_AUDIT_EXPIRATION_DURATION: chrono::Duration = chrono::Duration::weeks(6);
+}
+/// This string is always used in a context such as "in the next {STR}".
+const WILDCARD_AUDIT_EXPIRATION_STRING: &str = "six weeks";
+
+/// Trick to let us std::process::exit while still cleaning up
+/// by panicking with this type instead of a string.
+struct ExitPanic(i32);
+
+type ReportErrorFunc = dyn Fn(&miette::Report) + Send + Sync + 'static;
+
+// XXX: We might be able to get rid of this `lazy_static` after 1.63 due to
+// `const Mutex::new` being stabilized.
+lazy_static! {
+    static ref REPORT_ERROR: Mutex<Option<Box<ReportErrorFunc>>> = Mutex::new(None);
+}
+
+fn set_report_errors_as_json(out: Arc<dyn Out>) {
+    *REPORT_ERROR.lock().unwrap() = Some(Box::new(move |error| {
+        // Manually invoke JSONReportHandler to format the error as a report
+        // to out_.
+        let mut report = String::new();
+        miette::JSONReportHandler::new()
+            .render_report(&mut report, error.as_ref())
+            .unwrap();
+        writeln!(out, r#"{{"error": {report}}}"#);
+    }));
+}
+
+fn report_error(error: &miette::Report) {
+    {
+        let guard = REPORT_ERROR.lock().unwrap();
+        if let Some(do_report) = &*guard {
+            do_report(error);
+            return;
+        }
+    }
+    error!("{:?}", error);
+}
+
+fn main() -> Result<(), ()> {
+    // NOTE: Limit the maximum number of blocking threads to 128, rather than
+    // the default of 512.
+    // This may limit concurrency in some cases, but cargo-vet isn't running a
+    // server, and should avoid consuming all available resources.
+    let runtime = tokio::runtime::Builder::new_multi_thread()
+        .worker_threads(1)
+        .max_blocking_threads(128)
+        .enable_all()
+        .build()
+        .unwrap();
+    let _guard = runtime.enter();
+
+    // Wrap main up in a catch_panic so that we can use it to implement std::process::exit with
+    // unwinding, allowing us to silently exit the program while still cleaning up.
+    let panic_result = std::panic::catch_unwind(real_main);
+    let main_result = match panic_result {
+        Ok(main_result) => main_result,
+        Err(e) => {
+            if let Some(ExitPanic(code)) = e.downcast_ref::<ExitPanic>() {
+                // Exit panic, just silently exit with this status
+                std::process::exit(*code);
+            } else {
+                // Normal panic, let it ride
+                std::panic::resume_unwind(e);
+            }
+        }
+    };
+    main_result.map_err(|e| {
+        report_error(&e);
+        std::process::exit(-1);
+    })
+}
+
+fn real_main() -> Result<(), miette::Report> {
+    use cli::Commands::*;
+
+    let fake_cli = cli::FakeCli::parse();
+    let cli::FakeCli::Vet(cli) = fake_cli;
+
+    //////////////////////////////////////////////////////
+    // Setup logging / output
+    //////////////////////////////////////////////////////
+
+    // Init the logger (and make trace logging less noisy)
+    if let Some(log_path) = &cli.log_file {
+        let log_file = File::create(log_path).unwrap();
+        tracing_subscriber::fmt::fmt()
+            .with_max_level(cli.verbose)
+            .with_target(false)
+            .without_time()
+            .with_ansi(false)
+            .with_writer(log_file)
+            .init();
+    } else {
+        tracing_subscriber::fmt::fmt()
+            .with_max_level(cli.verbose)
+            .with_target(false)
+            .without_time()
+            .with_ansi(console::colors_enabled_stderr())
+            .with_writer(StderrLogWriter::new)
+            .init();
+    }
+
+    // Control how errors are formatted by setting the miette hook. This will
+    // only be used for errors presented to humans, when formatting an error as
+    // JSON, it will be handled by a custom `report_error` override, bypassing
+    // the hook.
+    let using_log_file = cli.log_file.is_some();
+    miette::set_hook(Box::new(move |_| {
+        let graphical_theme = if console::colors_enabled_stderr() && !using_log_file {
+            miette::GraphicalTheme::unicode()
+        } else {
+            miette::GraphicalTheme::unicode_nocolor()
+        };
+        Box::new(
+            miette::MietteHandlerOpts::new()
+                .graphical_theme(graphical_theme)
+                .build(),
+        )
+    }))
+    .expect("failed to initialize error handler");
+
+    // Now that miette is set up, use it to format panics.
+    panic::set_hook(Box::new(move |panic_info| {
+        if panic_info.payload().is::<ExitPanic>() {
+            return;
+        }
+
+        let payload = panic_info.payload();
+        let message = if let Some(msg) = payload.downcast_ref::<&str>() {
+            msg
+        } else if let Some(msg) = payload.downcast_ref::<String>() {
+            &msg[..]
+        } else {
+            "something went wrong"
+        };
+
+        #[derive(Debug, Error, Diagnostic)]
+        #[error("{message}")]
+        pub struct PanicError {
+            pub message: String,
+            #[help]
+            pub help: Option<String>,
+        }
+
+        report_error(
+            &miette::Report::from(PanicError {
+                message: message.to_owned(),
+                help: panic_info
+                    .location()
+                    .map(|loc| format!("at {}:{}:{}", loc.file(), loc.line(), loc.column())),
+            })
+            .wrap_err("cargo vet panicked"),
+        );
+    }));
+
+    // Initialize the MULTIPROGRESS's draw target, so that future progress
+    // events are rendered to stderr.
+    MULTIPROGRESS.set_draw_target(ProgressDrawTarget::stderr());
+
+    // Setup our output stream
+    let out: Arc<dyn Out> = if let Some(output_path) = &cli.output_file {
+        Arc::new(File::create(output_path).unwrap())
+    } else {
+        Arc::new(Term::stdout())
+    };
+
+    // If we're outputting JSON, replace the error report method such that it
+    // writes errors out to the normal output stream as JSON.
+    if cli.output_format == OutputFormat::Json {
+        set_report_errors_as_json(out.clone());
+    }
+
+    ////////////////////////////////////////////////////
+    // Potentially handle freestanding commands
+    ////////////////////////////////////////////////////
+
+    let cache_dir = cli.cache_dir.clone().unwrap_or_else(|| {
+        dirs::cache_dir()
+            .unwrap_or_else(std::env::temp_dir)
+            .join(CACHE_DIR_SUFFIX)
+    });
+    let now = cli
+        .current_time
+        .unwrap_or_else(|| chrono::DateTime::from(SystemTime::now()));
+    let partial_cfg = PartialConfig {
+        cli,
+        now,
+        cache_dir,
+        mock_cache: false,
+    };
+
+    match &partial_cfg.cli.command {
+        Some(Aggregate(sub_args)) => return cmd_aggregate(&out, &partial_cfg, sub_args),
+        Some(HelpMarkdown(sub_args)) => return cmd_help_md(&out, &partial_cfg, sub_args),
+        Some(Gc(sub_args)) => return cmd_gc(&out, &partial_cfg, sub_args),
+        _ => {
+            // Not a freestanding command, time to do full parsing and setup
+        }
+    }
+
+    ///////////////////////////////////////////////////
+    // Fetch cargo metadata
+    ///////////////////////////////////////////////////
+
+    let cli = &partial_cfg.cli;
+    let cargo_path = std::env::var_os(CARGO_ENV).expect("Cargo failed to set $CARGO, how?");
+
+    let mut cmd = cargo_metadata::MetadataCommand::new();
+    cmd.cargo_path(cargo_path);
+    if let Some(manifest_path) = &cli.manifest_path {
+        cmd.manifest_path(manifest_path);
+    }
+    if !cli.no_all_features {
+        cmd.features(cargo_metadata::CargoOpt::AllFeatures);
+    }
+    if cli.no_default_features {
+        cmd.features(cargo_metadata::CargoOpt::NoDefaultFeatures);
+    }
+    if !cli.features.is_empty() {
+        cmd.features(cargo_metadata::CargoOpt::SomeFeatures(cli.features.clone()));
+    }
+    // We never want cargo-vet to update the Cargo.lock.
+    // For frozen runs we also don't want to touch the network.
+    let mut other_options = Vec::new();
+    if cli.frozen {
+        other_options.push("--frozen".to_string());
+    } else {
+        other_options.push("--locked".to_string());
+    }
+    if !using_log_file
+        && cli.output_format == OutputFormat::Human
+        && console::colors_enabled_stderr()
+    {
+        other_options.push("--color=always".to_string());
+    }
+    cmd.other_options(other_options);
+
+    info!("Running: {:#?}", cmd.cargo_command());
+
+    // ERRORS: immediate fatal diagnostic
+    let metadata = {
+        let _spinner = indeterminate_spinner("Running", "`cargo metadata`");
+        cmd.exec().map_err(MetadataAcquireError::from)?
+    };
+
+    // trace!("Got Metadata! {:#?}", metadata);
+    trace!("Got Metadata!");
+
+    //////////////////////////////////////////////////////
+    // Parse out our own configuration
+    //////////////////////////////////////////////////////
+
+    let default_config = MetaConfigInstance {
+        version: Some(1),
+        store: Some(StoreInfo {
+            path: Some(
+                metadata
+                    .workspace_root
+                    .join(storage::DEFAULT_STORE)
+                    .into_std_path_buf(),
+            ),
+        }),
+    };
+
+    // FIXME: what is `store.path` relative to here?
+    let workspace_metacfg = metadata
+        .workspace_metadata
+        .get(WORKSPACE_VET_CONFIG)
+        .map(|cfg| {
+            // ERRORS: immediate fatal diagnostic
+            MetaConfigInstance::deserialize(cfg)
+                .into_diagnostic()
+                .wrap_err("Workspace had [{WORKSPACE_VET_CONFIG}] but it was malformed")
+        })
+        .transpose()?;
+
+    // FIXME: what is `store.path` relative to here?
+    let package_metacfg = metadata
+        .root_package()
+        .and_then(|r| r.metadata.get(PACKAGE_VET_CONFIG))
+        .map(|cfg| {
+            // ERRORS: immediate fatal diagnostic
+            MetaConfigInstance::deserialize(cfg)
+                .into_diagnostic()
+                .wrap_err("Root package had [{PACKAGE_VET_CONFIG}] but it was malformed")
+        })
+        .transpose()?;
+
+    let cli_metacfg = cli.store_path.as_ref().map(|path| MetaConfigInstance {
+        version: Some(1),
+        store: Some(StoreInfo {
+            path: Some(path.clone()),
+        }),
+    });
+
+    if workspace_metacfg.is_some() && package_metacfg.is_some() {
+        // ERRORS: immediate fatal diagnostic
+        return Err(miette!("Both a workspace and a package defined [metadata.vet]! We don't know what that means, if you do, let us know!"));
+    }
+
+    let mut metacfgs = vec![default_config];
+    if let Some(metacfg) = workspace_metacfg {
+        metacfgs.push(metacfg);
+    }
+    if let Some(metacfg) = package_metacfg {
+        metacfgs.push(metacfg);
+    }
+    if let Some(metacfg) = cli_metacfg {
+        metacfgs.push(metacfg);
+    }
+    let metacfg = MetaConfig(metacfgs);
+
+    info!("Final Metadata Config: ");
+    info!("  - version: {}", metacfg.version());
+    info!("  - store.path: {:#?}", metacfg.store_path());
+
+    //////////////////////////////////////////////////////
+    // Run the actual command
+    //////////////////////////////////////////////////////
+
+    let init = Store::is_init(&metacfg);
+    if matches!(cli.command, Some(Commands::Init { .. })) {
+        if init {
+            // ERRORS: immediate fatal diagnostic
+            return Err(miette!(
+                "'cargo vet' already initialized (store found at {})",
+                metacfg.store_path().display()
+            ));
+        }
+    } else if !init {
+        // ERRORS: immediate fatal diagnostic
+        return Err(miette!(
+            "You must run 'cargo vet init' (store not found at {})",
+            metacfg.store_path().display()
+        ));
+    }
+
+    let cfg = Config {
+        metacfg,
+        metadata,
+        _rest: partial_cfg,
+    };
+
+    use RegenerateSubcommands::*;
+    match &cfg.cli.command {
+        None => cmd_check(&out, &cfg, &cfg.cli.check_args),
+        Some(Check(sub_args)) => cmd_check(&out, &cfg, sub_args),
+        Some(Init(sub_args)) => cmd_init(&out, &cfg, sub_args),
+        Some(Certify(sub_args)) => cmd_certify(&out, &cfg, sub_args),
+        Some(Import(sub_args)) => cmd_import(&out, &cfg, sub_args),
+        Some(Trust(sub_args)) => cmd_trust(&out, &cfg, sub_args),
+        Some(AddExemption(sub_args)) => cmd_add_exemption(&out, &cfg, sub_args),
+        Some(RecordViolation(sub_args)) => cmd_record_violation(&out, &cfg, sub_args),
+        Some(Suggest(sub_args)) => cmd_suggest(&out, &cfg, sub_args),
+        Some(Fmt(sub_args)) => cmd_fmt(&out, &cfg, sub_args),
+        Some(Prune(sub_args)) => cmd_prune(&out, &cfg, sub_args),
+        Some(DumpGraph(sub_args)) => cmd_dump_graph(&out, &cfg, sub_args),
+        Some(Inspect(sub_args)) => cmd_inspect(&out, &cfg, sub_args),
+        Some(Diff(sub_args)) => cmd_diff(&out, &cfg, sub_args),
+        Some(Regenerate(Imports(sub_args))) => cmd_regenerate_imports(&out, &cfg, sub_args),
+        Some(Regenerate(Exemptions(sub_args))) => cmd_regenerate_exemptions(&out, &cfg, sub_args),
+        Some(Regenerate(AuditAsCratesIo(sub_args))) => {
+            cmd_regenerate_audit_as(&out, &cfg, sub_args)
+        }
+        Some(Regenerate(Unpublished(sub_args))) => cmd_regenerate_unpublished(&out, &cfg, sub_args),
+        Some(Renew(sub_args)) => cmd_renew(&out, &cfg, sub_args),
+        Some(Aggregate(_)) | Some(HelpMarkdown(_)) | Some(Gc(_)) => unreachable!("handled earlier"),
+    }
+}
+
+fn cmd_init(_out: &Arc<dyn Out>, cfg: &Config, _sub_args: &InitArgs) -> Result<(), miette::Report> {
+    // Initialize vet
+    trace!("initializing...");
+
+    let network = Network::acquire(cfg);
+    let mut store = Store::create(cfg)?;
+
+    check_crate_policies(cfg, &store)?;
+    tokio::runtime::Handle::current().block_on(fix_audit_as(cfg, network.as_ref(), &mut store))?;
+
+    // Run the resolver to regenerate exemptions, this will fill in exemptions
+    // such that the vet now passes.
+    resolver::update_store(cfg, &mut store, |_| resolver::UpdateMode {
+        search_mode: resolver::SearchMode::RegenerateExemptions,
+        prune_exemptions: true,
+        prune_imports: true,
+    });
+
+    store.commit()?;
+
+    Ok(())
+}
+
+fn cmd_inspect(
+    out: &Arc<dyn Out>,
+    cfg: &Config,
+    sub_args: &InspectArgs,
+) -> Result<(), miette::Report> {
+    let version = &sub_args.version;
+    let package = &*sub_args.package;
+
+    let fetched = {
+        let network = Network::acquire(cfg);
+        let store = Store::acquire(cfg, network.as_ref(), false)?;
+        let cache = Cache::acquire(cfg)?;
+
+        // Record this command for magic in `vet certify`
+        cache.set_last_fetch(FetchCommand::Inspect {
+            package: package.to_owned(),
+            version: version.clone(),
+        });
+
+        if sub_args.mode == FetchMode::Sourcegraph && version.git_rev.is_none() {
+            let url = format!("https://sourcegraph.com/crates/{package}@v{version}");
+            tokio::runtime::Handle::current()
+                .block_on(prompt_criteria_eulas(
+                    out,
+                    cfg,
+                    network.as_ref(),
+                    &store,
+                    package,
+                    None,
+                    version,
+                    Some(&url),
+                ))
+                .into_diagnostic()?;
+
+            open::that(&url).into_diagnostic().wrap_err_with(|| {
+                format!("Couldn't open {url} in your browser, try --mode=local?")
+            })?;
+
+            writeln!(out, "\nUse |cargo vet certify| to record your audit.");
+            return Ok(());
+        }
+
+        tokio::runtime::Handle::current().block_on(async {
+            let (pkg, eulas) = tokio::join!(
+                async {
+                    // If we're fetching a git revision for inspection, don't
+                    // use fetch_package, as we want to point the user at the
+                    // actual cargo checkout, rather than our repack, which may
+                    // be incomplete, and will be clobbered by GC.
+                    if let Some(git_rev) = &version.git_rev {
+                        storage::locate_local_checkout(&cfg.metadata, package, version).ok_or_else(
+                            || FetchError::UnknownGitRevision {
+                                package: package.to_owned(),
+                                git_rev: git_rev.to_owned(),
+                            },
+                        )
+                    } else {
+                        cache
+                            .fetch_package(&cfg.metadata, network.as_ref(), package, version)
+                            .await
+                    }
+                },
+                prompt_criteria_eulas(
+                    out,
+                    cfg,
+                    network.as_ref(),
+                    &store,
+                    package,
+                    None,
+                    version,
+                    None,
+                ),
+            );
+            eulas.into_diagnostic()?;
+            pkg.into_diagnostic()
+        })?
+    };
+
+    #[cfg(target_family = "unix")]
+    if let Some(shell) = std::env::var_os("SHELL") {
+        // Loosely borrowed from cargo crev.
+        writeln!(out, "Opening nested shell in: {fetched:#?}");
+        writeln!(out, "Use `exit` or Ctrl-D to finish.",);
+        let status = std::process::Command::new(shell)
+            .current_dir(fetched.clone())
+            .env("PWD", fetched)
+            .status()
+            .map_err(CommandError::CommandFailed)
+            .into_diagnostic()?;
+
+        writeln!(out, "\nUse |cargo vet certify| to record your audit.");
+
+        if let Some(code) = status.code() {
+            panic_any(ExitPanic(code));
+        }
+        return Ok(());
+    }
+
+    writeln!(out, "  fetched to {fetched:#?}");
+    writeln!(out, "\nUse |cargo vet certify| to record your audit.");
+    Ok(())
+}
+
+fn cmd_certify(
+    out: &Arc<dyn Out>,
+    cfg: &Config,
+    sub_args: &CertifyArgs,
+) -> Result<(), miette::Report> {
+    // Certify that you have reviewed a crate's source for some version / delta
+    let network = Network::acquire(cfg);
+    let mut store = Store::acquire(cfg, network.as_ref(), false)?;
+
+    // Grab the last fetch and immediately drop the cache
+    let last_fetch = Cache::acquire(cfg)?.get_last_fetch();
+
+    do_cmd_certify(out, cfg, sub_args, &mut store, network.as_ref(), last_fetch)?;
+
+    store.commit()?;
+    Ok(())
+}
+
+fn do_cmd_certify(
+    out: &Arc<dyn Out>,
+    cfg: &Config,
+    sub_args: &CertifyArgs,
+    store: &mut Store,
+    network: Option<&Network>,
+    last_fetch: Option<FetchCommand>,
+) -> Result<(), CertifyError> {
+    // Before setting up magic, we need to agree on a package
+    let package = if let Some(package) = &sub_args.package {
+        package.clone()
+    } else if let Some(last_fetch) = &last_fetch {
+        // If we just fetched a package, assume we want to certify it
+        last_fetch.package().to_owned()
+    } else {
+        return Err(CertifyError::CouldntGuessPackage);
+    };
+
+    // FIXME: can/should we check if the version makes sense..?
+    if !sub_args.force
+        && !foreign_packages(&cfg.metadata, &store.config).any(|pkg| pkg.name == *package)
+    {
+        return Err(CertifyError::NotAPackage(package));
+    }
+
+    #[derive(Debug)]
+    enum CertifyKind {
+        Delta {
+            from: VetVersion,
+            to: VetVersion,
+        },
+        Full {
+            version: VetVersion,
+        },
+        Wildcard {
+            user_login: String,
+            user_id: CratesUserId,
+            start: chrono::NaiveDate,
+            end: chrono::NaiveDate,
+            set_renew_false: bool,
+        },
+    }
+
+    let kind = if let Some(login) = &sub_args.wildcard {
+        // Fetch publisher information for relevant versions of `package`.
+        let publishers = store.ensure_publisher_versions(cfg, network, &package)?;
+        let published_versions = publishers
+            .iter()
+            .filter(|publisher| &publisher.user_login == login);
+
+        let earliest = published_versions
+            .min_by_key(|p| p.when)
+            .ok_or_else(|| CertifyError::NotAPublisher(login.to_owned(), package.to_owned()))?;
+
+        // Get the from and to dates, defaulting to a from date of the earliest
+        // published package by the user, and a to date of 12 months from today.
+        let start = sub_args.start_date.unwrap_or(earliest.when);
+
+        let max_end = cfg.today() + chrono::Months::new(12);
+        let end = sub_args.end_date.unwrap_or(max_end);
+        let set_renew_false = sub_args.end_date.is_some();
+        if end > max_end {
+            return Err(CertifyError::BadWildcardEndDate(end));
+        }
+
+        CertifyKind::Wildcard {
+            user_login: earliest.user_login.to_owned(),
+            user_id: earliest.user_id,
+            start,
+            end,
+            set_renew_false,
+        }
+    } else if let Some(v1) = &sub_args.version1 {
+        // If explicit versions were provided, use those
+        if let Some(v2) = &sub_args.version2 {
+            // This is a delta audit
+            CertifyKind::Delta {
+                from: v1.clone(),
+                to: v2.clone(),
+            }
+        } else {
+            // This is a full audit
+            CertifyKind::Full {
+                version: v1.clone(),
+            }
+        }
+    } else if let Some(fetch) = last_fetch.filter(|f| f.package() == package) {
+        // Otherwise, is we just fetched this package, use the version(s) we fetched
+        match fetch {
+            FetchCommand::Inspect { version, .. } => CertifyKind::Full { version },
+            FetchCommand::Diff {
+                version1, version2, ..
+            } => CertifyKind::Delta {
+                from: version1,
+                to: version2,
+            },
+        }
+    } else {
+        return Err(CertifyError::CouldntGuessVersion(package));
+    };
+
+    let (username, who) = if sub_args.who.is_empty() {
+        let user_info = get_user_info()?;
+        let who = format!("{} <{}>", user_info.username, user_info.email);
+        (user_info.username, vec![Spanned::from(who)])
+    } else {
+        (
+            sub_args.who.join(", "),
+            sub_args
+                .who
+                .iter()
+                .map(|w| Spanned::from(w.clone()))
+                .collect(),
+        )
+    };
+
+    let (criteria_guess, prompt) = if sub_args.criteria.is_empty() {
+        // If we don't have explicit cli criteria, guess the criteria
+        //
+        // * Check what would cause `cargo vet` to encounter fewer errors
+        // * Otherwise check what would cause `cargo vet suggest` to suggest fewer audits
+        // * Otherwise guess nothing
+        //
+        // Regardless of the guess, prompt the user to confirm (just needs to mash enter)
+        match &kind {
+            CertifyKind::Full { version } => (
+                guess_audit_criteria(cfg, store, &package, None, version),
+                Some(format!(
+                    "choose criteria to certify for {package}:{version}"
+                )),
+            ),
+            CertifyKind::Delta { from, to } => (
+                guess_audit_criteria(cfg, store, &package, Some(from), to),
+                Some(format!(
+                    "choose criteria to certify for {package}:{from} -> {to}"
+                )),
+            ),
+            CertifyKind::Wildcard { .. } => {
+                // FIXME: Consider predicting the criteria better for wildcard
+                // audits in the future.
+                (
+                    vec![format::SAFE_TO_DEPLOY.to_owned()],
+                    Some(format!("choose criteria to certify for {package}:*")),
+                )
+            }
+        }
+    } else {
+        // If we do have explcit criteria, don't prompt, but still pass through
+        // prompt_pick_criteria to simplify and validate.
+        (sub_args.criteria.clone(), None)
+    };
+    let criteria_names =
+        criteria_picker(out, &store.audits.criteria, criteria_guess, prompt.as_ref())?;
+
+    let statement = match &kind {
+        CertifyKind::Full { version } => {
+            format!(
+                    "I, {username}, certify that I have audited version {version} of {package} in accordance with the above criteria.",
+                )
+        }
+        CertifyKind::Delta { from, to } => {
+            format!(
+                    "I, {username}, certify that I have audited the changes from version {from} to {to} of {package} in accordance with the above criteria.",
+                )
+        }
+        CertifyKind::Wildcard {
+            user_login,
+            start,
+            end,
+            ..
+        } => {
+            format!(
+                    "I, {username}, certify that any version of {package} published by '{user_login}' between {start} and {end} will satisfy the above criteria.",
+                )
+        }
+    };
+
+    let mut notes = sub_args.notes.clone();
+    if !sub_args.accept_all {
+        // Get all the EULAs at once
+        let eulas = tokio::runtime::Handle::current().block_on(join_all(
+            criteria_names.iter().map(|criteria| async {
+                (
+                    &criteria[..],
+                    eula_for_criteria(network, &store.audits.criteria, criteria).await,
+                )
+            }),
+        ));
+
+        let mut editor = out.editor("VET_CERTIFY")?;
+        if let Some(notes) = &notes {
+            editor.select_comment_char(notes);
+        }
+
+        editor.add_comments(
+            "Please read the following criteria and then follow the instructions below:",
+        )?;
+        editor.add_text("")?;
+
+        for (criteria, eula) in &eulas {
+            editor.add_comments(&format!("=== BEGIN CRITERIA {criteria:?} ==="))?;
+            editor.add_comments("")?;
+            editor.add_comments(eula)?;
+            editor.add_comments("")?;
+            editor.add_comments("=== END CRITERIA ===")?;
+            editor.add_comments("")?;
+        }
+        editor.add_comments("Uncomment the following statement:")?;
+        editor.add_text("")?;
+        editor.add_comments(&statement)?;
+        editor.add_text("")?;
+        editor.add_comments("Add any notes about your audit below this line:")?;
+        editor.add_text("")?;
+        if let Some(notes) = &notes {
+            editor.add_text(notes)?;
+        }
+
+        let editor_result = editor.edit()?;
+
+        // Check to make sure that the statement was uncommented as the first
+        // line in the parsed file, and remove blank lines between the statement
+        // and notes.
+        let new_notes = match editor_result.trim_start().strip_prefix(&statement) {
+            Some(notes) => notes.trim_start_matches('\n'),
+            None => {
+                // FIXME: Might be nice to try to save any notes the user typed
+                // in and re-try the prompt if the user asks for it, in case
+                // they wrote some nice notes, but forgot to uncomment the
+                // statement.
+                return Err(CertifyError::CouldntFindCertifyStatement);
+            }
+        };
+
+        // Strip trailing newline if notes would otherwise contain no newlines.
+        let new_notes = new_notes
+            .strip_suffix('\n')
+            .filter(|s| !s.contains('\n'))
+            .unwrap_or(new_notes);
+
+        notes = if new_notes.is_empty() {
+            None
+        } else {
+            Some(new_notes.to_owned())
+        };
+    }
+
+    let criteria = criteria_names.into_iter().map(|s| s.into()).collect();
+    match kind {
+        CertifyKind::Full { version } => {
+            store
+                .audits
+                .audits
+                .entry(package.clone())
+                .or_default()
+                .push(AuditEntry {
+                    kind: AuditKind::Full { version },
+                    criteria,
+                    who,
+                    notes,
+                    aggregated_from: vec![],
+                    is_fresh_import: false,
+                });
+        }
+        CertifyKind::Delta { from, to } => {
+            store
+                .audits
+                .audits
+                .entry(package.clone())
+                .or_default()
+                .push(AuditEntry {
+                    kind: AuditKind::Delta { from, to },
+                    criteria,
+                    who,
+                    notes,
+                    aggregated_from: vec![],
+                    is_fresh_import: false,
+                });
+        }
+        CertifyKind::Wildcard {
+            user_id,
+            start,
+            end,
+            set_renew_false,
+            ..
+        } => {
+            store
+                .audits
+                .wildcard_audits
+                .entry(package.clone())
+                .or_default()
+                .push(WildcardEntry {
+                    who,
+                    criteria,
+                    user_id,
+                    start: start.into(),
+                    end: end.into(),
+                    renew: set_renew_false.then_some(false),
+                    notes,
+                    aggregated_from: vec![],
+                    is_fresh_import: false,
+                });
+        }
+    };
+
+    store
+        .validate(cfg.today(), false)
+        .expect("the new audit entry made the store invalid?");
+
+    // Minimize exemptions after adding the new audit. This will be used to
+    // potentially update imports, and remove now-unnecessary exemptions for the
+    // target package. We only prefer fresh imports and prune exemptions for the
+    // package we certified, to avoid unrelated changes.
+    resolver::update_store(cfg, store, |name| resolver::UpdateMode {
+        search_mode: if name == &package[..] {
+            resolver::SearchMode::PreferFreshImports
+        } else {
+            resolver::SearchMode::PreferExemptions
+        },
+        prune_exemptions: name == &package[..],
+        prune_imports: false,
+    });
+
+    Ok(())
+}
+
+fn criteria_picker(
+    out: &Arc<dyn Out>,
+    store_criteria: &SortedMap<CriteriaName, CriteriaEntry>,
+    criteria_guess: Vec<CriteriaName>,
+    prompt: Option<&impl AsRef<str>>,
+) -> Result<Vec<CriteriaName>, CertifyError> {
+    let criteria_mapper = CriteriaMapper::new(store_criteria);
+
+    let mut chosen_criteria = criteria_guess;
+    if let Some(prompt) = prompt {
+        // Prompt for criteria
+        loop {
+            out.clear_screen()?;
+            writeln!(out, "{}", prompt.as_ref());
+            for (criteria_idx, criteria_name) in criteria_mapper.all_criteria_names().enumerate() {
+                if chosen_criteria.iter().any(|s| s == criteria_name) {
+                    writeln!(
+                        out,
+                        "  {}. {}",
+                        criteria_idx + 1,
+                        out.style().green().bold().apply_to(criteria_name)
+                    );
+                } else {
+                    writeln!(
+                        out,
+                        "  {}. {}",
+                        criteria_idx + 1,
+                        out.style().bold().dim().apply_to(criteria_name)
+                    );
+                }
+            }
+
+            writeln!(out);
+            writeln!(out, "current selection: {:?}", chosen_criteria);
+            writeln!(out, "(press ENTER to accept the current criteria)");
+            let input = out.read_line_with_prompt("> ")?;
+            let input = input.trim();
+            if input.is_empty() {
+                if chosen_criteria.is_empty() {
+                    return Err(CertifyError::NoCriteriaChosen);
+                }
+                // User done selecting criteria
+                break;
+            }
+
+            // FIXME: these errors get cleared away right away
+            let answer = if let Ok(val) = input.parse::<usize>() {
+                val
+            } else {
+                // ERRORS: immediate error print to output for feedback, non-fatal
+                writeln!(out, "error: not a valid integer");
+                continue;
+            };
+            if answer == 0 || answer > criteria_mapper.len() {
+                // ERRORS: immediate error print to output for feedback, non-fatal
+                writeln!(out, "error: not a valid criteria");
+                continue;
+            }
+
+            let selection = criteria_mapper.criteria_name(answer - 1).to_owned();
+            if chosen_criteria.contains(&selection) {
+                chosen_criteria.retain(|x| x != &selection);
+            } else {
+                chosen_criteria.push(selection);
+            }
+        }
+    }
+
+    // Round-trip this through the criteria_mapper to clean up `implies` relationships
+    let criteria_set = criteria_mapper.criteria_from_list(&chosen_criteria);
+    Ok(criteria_mapper
+        .criteria_names(&criteria_set)
+        .map(|s| s.to_owned())
+        .collect::<Vec<_>>())
+}
+
+/// Attempt to guess which criteria are being certified for a given package and
+/// audit kind.
+///
+/// The logic which this method uses to guess the criteria to use is as follows:
+///
+/// * Check what would cause `cargo vet` to encounter fewer errors
+/// * Otherwise check what would cause `cargo vet suggest` to suggest fewer audits
+/// * Otherwise guess nothing
+fn guess_audit_criteria(
+    cfg: &Config,
+    store: &Store,
+    package: PackageStr<'_>,
+    from: Option<&VetVersion>,
+    to: &VetVersion,
+) -> Vec<String> {
+    // Attempt to resolve a normal `cargo vet`, and try to find criteria which
+    // would heal some errors in that result if it fails.
+    let criteria = resolver::resolve(&cfg.metadata, cfg.cli.filter_graph.as_ref(), store)
+        .compute_suggested_criteria(package, from, to);
+    if !criteria.is_empty() {
+        return criteria;
+    }
+
+    // If a normal `cargo vet` failed to turn up any criteria, try a more
+    // aggressive `cargo vet suggest`.
+    //
+    // This is as much as we can do, so just return the result whether or not we
+    // find anything.
+    resolver::resolve(
+        &cfg.metadata,
+        cfg.cli.filter_graph.as_ref(),
+        &store.clone_for_suggest(true),
+    )
+    .compute_suggested_criteria(package, from, to)
+}
+
+/// Prompt the user to read the EULAs for the expected criteria which they will
+/// be certifying for with this diff or inspect command.
+///
+/// This method is async so it can be performed concurrently with waiting for
+/// the downloads to complete.
+#[allow(clippy::too_many_arguments)]
+async fn prompt_criteria_eulas(
+    out: &Arc<dyn Out>,
+    cfg: &Config,
+    network: Option<&Network>,
+    store: &Store,
+    package: PackageStr<'_>,
+    from: Option<&VetVersion>,
+    to: &VetVersion,
+    url: Option<&str>,
+) -> Result<(), io::Error> {
+    let description = if let Some(from) = from {
+        format!("You are about to diff versions {from} and {to} of '{package}'")
+    } else {
+        format!("You are about to inspect version {to} of '{package}'")
+    };
+
+    // Guess which criteria the user is going to be auditing the package for.
+    let criteria_names = guess_audit_criteria(cfg, store, package, from, to);
+
+    // FIXME: These `writeln` calls can do blocking I/O, but they hopefully
+    // shouldn't block long enough for it interfere with downloading packages in
+    // the background. We do the `read_line_with_prompt` call async.
+    if criteria_names.is_empty() {
+        writeln!(out, "{}", out.style().bold().apply_to(description));
+        warn!("unable to determine likely criteria, this may not be a relevant audit for this project.");
+    } else {
+        let eulas = join_all(criteria_names.iter().map(|criteria| async {
+            (
+                &criteria[..],
+                eula_for_criteria(network, &store.audits.criteria, criteria).await,
+            )
+        }))
+        .await;
+
+        for (idx, (criteria, eula)) in eulas.into_iter().enumerate() {
+            let prompt = if idx == 0 {
+                format!("{description}, likely to certify it for {criteria:?}, which means:")
+            } else {
+                format!("... and for {criteria:?}, which means:")
+            };
+            writeln!(
+                out,
+                "{}\n\n  {}",
+                out.style().bold().apply_to(prompt),
+                eula.replace('\n', "\n  "),
+            );
+        }
+
+        writeln!(
+            out,
+            "{}",
+            out.style().bold().apply_to(
+                "Please read the above criteria and consider them when performing the audit."
+            )
+        );
+    }
+
+    writeln!(
+        out,
+        "{}",
+        out.style().bold().apply_to(
+            "Other software projects may rely on this audit. Ask for help if you're not sure.\n"
+        )
+    );
+
+    let final_prompt = if let Some(url) = url {
+        writeln!(
+            out,
+            "You can inspect the {} here: {}\n",
+            if from.is_some() { "diff" } else { "crate" },
+            url,
+        );
+        "(press ENTER to open in your browser, or re-run with --mode=local)"
+    } else {
+        "(press ENTER to inspect locally)"
+    };
+
+    let out_ = out.clone();
+    tokio::task::spawn_blocking(move || out_.read_line_with_prompt(final_prompt)).await??;
+    Ok(())
+}
+
+fn cmd_import(
+    _out: &Arc<dyn Out>,
+    cfg: &Config,
+    sub_args: &ImportArgs,
+) -> Result<(), miette::Report> {
+    let Some(network) = Network::acquire(cfg) else {
+        return Err(miette!("`cargo vet import` cannot be run while frozen"));
+    };
+
+    // Determine the URL for the import, potentially fetching the registry to
+    // find it.
+    let registry_file;
+    let import_urls = if sub_args.url.is_empty() {
+        registry_file = tokio::runtime::Handle::current().block_on(fetch_registry(&network))?;
+        registry_file
+            .registry
+            .get(&sub_args.name)
+            .ok_or_else(|| miette!("no peer named {} found in the registry", &sub_args.name))
+            .map(|entry| entry.url.clone())?
+    } else {
+        sub_args.url.clone()
+    };
+
+    let mut store = Store::acquire_offline(cfg)?;
+
+    // Insert a new entry for the new import, or update an existing entry to use
+    // the newly specified URLs.
+    store
+        .config
+        .imports
+        .entry(sub_args.name.clone())
+        .or_default()
+        .url = import_urls;
+
+    // After adding the new entry, go online, this will fetch the new import.
+    let cache = Cache::acquire(cfg)?;
+    tokio::runtime::Handle::current().block_on(store.go_online(cfg, &network, &cache, false))?;
+
+    // Update the store state, pruning unnecessary exemptions, and cleaning out
+    // unnecessary old imports.
+    resolver::update_store(cfg, &mut store, |_| resolver::UpdateMode {
+        search_mode: resolver::SearchMode::PreferFreshImports,
+        prune_exemptions: true,
+        prune_imports: true,
+    });
+
+    store.commit()?;
+
+    Ok(())
+}
+
+fn cmd_trust(out: &Arc<dyn Out>, cfg: &Config, sub_args: &TrustArgs) -> Result<(), miette::Report> {
+    // Certify that you have reviewed a crate's source for some version / delta
+    let network = Network::acquire(cfg);
+    let mut store = Store::acquire(cfg, network.as_ref(), false)?;
+
+    do_cmd_trust(out, cfg, sub_args, &mut store, network.as_ref())?;
+
+    store.commit()?;
+
+    Ok(())
+}
+
+fn do_cmd_trust(
+    out: &Arc<dyn Out>,
+    cfg: &Config,
+    sub_args: &TrustArgs,
+    store: &mut Store,
+    network: Option<&Network>,
+) -> Result<(), miette::Report> {
+    if let Some(package) = &sub_args.package {
+        // Fetch publisher information for relevant versions of `package`.
+        let publishers = store.ensure_publisher_versions(cfg, network, package)?;
+
+        let publisher_login = if let Some(login) = &sub_args.publisher_login {
+            login.clone()
+        } else if let Some(first) = publishers.first() {
+            if publishers
+                .iter()
+                .all(|publisher| publisher.user_id == first.user_id)
+            {
+                first.user_login.clone()
+            } else {
+                return Err(miette!(
+                    "The package '{}' has multiple known publishers, \
+                    please explicitly specify which publisher to trust",
+                    package
+                ));
+            }
+        } else {
+            return Err(miette!(
+                "The package '{}' has no known publishers, so cannot be trusted",
+                package
+            ));
+        };
+
+        apply_cmd_trust(
+            out,
+            cfg,
+            store,
+            network,
+            package,
+            &publisher_login,
+            sub_args.start_date,
+            sub_args.end_date,
+            &sub_args.criteria,
+            sub_args.notes.as_ref(),
+        )
+    } else if let Some(publisher_login) = &sub_args.all {
+        // Run the resolver against the store in "suggest" mode to discover the
+        // set of packages which either fail to audit or need exemptions.
+        let suggest_store = store.clone_for_suggest(true);
+        let report =
+            resolver::resolve(&cfg.metadata, cfg.cli.filter_graph.as_ref(), &suggest_store);
+        let resolver::Conclusion::FailForVet(fail) = &report.conclusion else {
+            return Err(miette!("No failing or exempted crates, trust --all will do nothing"));
+        };
+
+        // Enumerate the failed packages to collect the set of packages which
+        // will be trusted.
+        let mut failed_criteria = report.criteria_mapper.no_criteria();
+        let mut trust = Vec::new();
+        let mut skipped = Vec::new();
+        for (failure_idx, audit_failure) in &fail.failures {
+            let package = &report.graph.nodes[*failure_idx];
+
+            // Ensure the store has publisher information for this package. This
+            // is a no-op if called multiple times for the same package.
+            let publishers = store.ensure_publisher_versions(cfg, network, package.name)?;
+            let by_user = publishers
+                .iter()
+                .filter(|p| &p.user_login == publisher_login)
+                .count();
+            if by_user == 0 {
+                continue; // never published by this user
+            }
+
+            // Record if we're skipping this package due to multiple publishers.
+            if by_user != publishers.len() && !sub_args.allow_multiple_publishers {
+                skipped.push(package.name);
+            } else {
+                trust.push(package.name);
+                failed_criteria.unioned_with(&audit_failure.criteria_failures);
+            }
+        }
+        trust.sort();
+        trust.dedup();
+
+        // Delay warning about skipped entries until after `criteria_picker`, as
+        // that may clear the terminal.
+        let maybe_warn_skipped = || {
+            if !skipped.is_empty() {
+                skipped.sort();
+                skipped.dedup();
+                warn!(
+                    "Skipped {} due to multiple publishers",
+                    string_format::FormatShortList::new(skipped)
+                );
+                warn!("  Run with --allow-multiple-publishers to also trust these packages");
+            }
+        };
+
+        if trust.is_empty() {
+            maybe_warn_skipped();
+            return Err(miette!(
+                "No failing or exempted packages published by {publisher_login}"
+            ));
+        }
+
+        let criteria_names = criteria_picker(
+            out,
+            &store.audits.criteria,
+            if sub_args.criteria.is_empty() {
+                report
+                    .criteria_mapper
+                    .criteria_names(&failed_criteria)
+                    .map(|s| s.to_owned())
+                    .collect()
+            } else {
+                sub_args.criteria.clone()
+            },
+            if sub_args.criteria.is_empty() {
+                Some(format!(
+                    "choose trusted criteria for packages published by {publisher_login} ({})",
+                    string_format::FormatShortList::new(trust.clone())
+                ))
+            } else {
+                None
+            }
+            .as_ref(),
+        )?;
+
+        maybe_warn_skipped();
+
+        for package in &trust {
+            apply_cmd_trust(
+                out,
+                cfg,
+                store,
+                network,
+                package,
+                publisher_login,
+                sub_args.start_date,
+                sub_args.end_date,
+                &criteria_names,
+                sub_args.notes.as_ref(),
+            )?;
+        }
+
+        Ok(())
+    } else {
+        Err(miette!("Please specify either a package to trust or --all"))
+    }
+}
+
+#[allow(clippy::too_many_arguments)]
+fn apply_cmd_trust(
+    out: &Arc<dyn Out>,
+    cfg: &Config,
+    store: &mut Store,
+    network: Option<&Network>,
+    package: &str,
+    publisher_login: &str,
+    start_date: Option<chrono::NaiveDate>,
+    end_date: Option<chrono::NaiveDate>,
+    criteria: &[CriteriaName],
+    notes: Option<&String>,
+) -> Result<(), miette::Report> {
+    // Fetch publisher information for relevant versions of `package`.
+    let publishers = store.ensure_publisher_versions(cfg, network, package)?;
+
+    let published_versions = publishers
+        .iter()
+        .filter(|publisher| publisher.user_login == publisher_login);
+
+    let earliest = published_versions.min_by_key(|p| p.when).ok_or_else(|| {
+        CertifyError::NotAPublisher(publisher_login.to_owned(), package.to_owned())
+    })?;
+    let user_id = earliest.user_id;
+
+    // Get the from and to dates, defaulting to a from date of the earliest
+    // published package by the user, and a to date of 12 months from today.
+    let start = start_date.unwrap_or(earliest.when);
+
+    let end = end_date.unwrap_or(cfg.today() + chrono::Months::new(12));
+
+    let criteria_names = criteria_picker(
+        out,
+        &store.audits.criteria,
+        if criteria.is_empty() {
+            vec![format::SAFE_TO_DEPLOY.to_owned()]
+        } else {
+            criteria.to_owned()
+        },
+        if criteria.is_empty() {
+            Some(format!(
+                "choose trusted criteria for {package}:* published by {publisher_login}"
+            ))
+        } else {
+            None
+        }
+        .as_ref(),
+    )?;
+    let criteria = criteria_names.into_iter().map(Spanned::from).collect();
+
+    // Check if we have an existing trust entry which could be extended to
+    // handle a wider date range, and update that instead if possible.
+    let trust_entries = store.audits.trusted.entry(package.to_owned()).or_default();
+    if let Some(trust_entry) = trust_entries.iter_mut().find(|trust_entry| {
+        trust_entry.criteria == criteria
+            && trust_entry.user_id == user_id
+            && start <= *trust_entry.start
+            && *trust_entry.end <= end
+            && notes.is_none()
+    }) {
+        trust_entry.start = start.into();
+        trust_entry.end = end.into();
+    } else {
+        trust_entries.push(TrustEntry {
+            criteria,
+            user_id,
+            start: start.into(),
+            end: end.into(),
+            notes: notes.cloned(),
+            aggregated_from: vec![],
+        });
+    }
+
+    store
+        .validate(cfg.today(), false)
+        .expect("the new trusted entry made the store invalid?");
+
+    // Minimize exemptions after adding the new trust entry. This will be used
+    // to potentially update imports, and remove now-unnecessary exemptions for
+    // the target package. We only prefer fresh imports and prune exemptions for
+    // the package we trusted, to avoid unrelated changes.
+    resolver::update_store(cfg, store, |name| resolver::UpdateMode {
+        search_mode: if name == package {
+            resolver::SearchMode::PreferFreshImports
+        } else {
+            resolver::SearchMode::PreferExemptions
+        },
+        prune_exemptions: name == package,
+        prune_imports: false,
+    });
+    Ok(())
+}
+
+fn cmd_record_violation(
+    out: &Arc<dyn Out>,
+    cfg: &Config,
+    sub_args: &RecordViolationArgs,
+) -> Result<(), miette::Report> {
+    // Mark a package as a violation
+    let mut store = Store::acquire_offline(cfg)?;
+
+    let kind = AuditKind::Violation {
+        violation: sub_args.versions.clone(),
+    };
+
+    let (_username, who) = if sub_args.who.is_empty() {
+        let user_info = get_user_info()?;
+        let who = format!("{} <{}>", user_info.username, user_info.email);
+        (user_info.username, vec![Spanned::from(who)])
+    } else {
+        (
+            sub_args.who.join(", "),
+            sub_args
+                .who
+                .iter()
+                .map(|w| Spanned::from(w.clone()))
+                .collect(),
+        )
+    };
+
+    let notes = sub_args.notes.clone();
+
+    let criteria = if sub_args.criteria.is_empty() {
+        // TODO: provide an interactive prompt for this
+        vec![store.config.default_criteria.clone().into()]
+    } else {
+        sub_args
+            .criteria
+            .iter()
+            .map(|s| s.to_owned().into())
+            .collect()
+    };
+
+    // FIXME: can/should we check if the version makes sense..?
+    if !sub_args.force
+        && !foreign_packages(&cfg.metadata, &store.config).any(|pkg| pkg.name == sub_args.package)
+    {
+        // ERRORS: immediate fatal diagnostic? should we allow you to forbid random packages?
+        // You're definitely *allowed* to have unused audits, otherwise you'd be constantly deleting
+        // useful audits whenever you update your dependencies! But this might be a useful guard
+        // against typosquatting or other weird issues?
+        return Err(miette!(
+            "'{}' isn't one of your foreign packages",
+            sub_args.package
+        ));
+    }
+
+    // Ok! Ready to commit the audit!
+    let new_entry = AuditEntry {
+        kind,
+        criteria,
+        who,
+        notes,
+        aggregated_from: vec![],
+        is_fresh_import: false,
+    };
+
+    store
+        .audits
+        .audits
+        .entry(sub_args.package.clone())
+        .or_default()
+        .push(new_entry);
+
+    store.commit()?;
+
+    writeln!(out, "If you've identified a security vulnerability in {} please report it at https://github.com/rustsec/advisory-db#reporting-vulnerabilities", sub_args.package);
+
+    Ok(())
+}
+
+fn cmd_add_exemption(
+    _out: &Arc<dyn Out>,
+    cfg: &Config,
+    sub_args: &AddExemptionArgs,
+) -> Result<(), miette::Report> {
+    // Add an exemption entry
+    let mut store = Store::acquire_offline(cfg)?;
+
+    let notes = sub_args.notes.clone();
+
+    let criteria = if sub_args.criteria.is_empty() {
+        // TODO: provide an interactive prompt for this
+        vec![store.config.default_criteria.clone().into()]
+    } else {
+        sub_args
+            .criteria
+            .iter()
+            .map(|s| s.to_owned().into())
+            .collect()
+    };
+
+    let suggest = !sub_args.no_suggest;
+
+    // FIXME: can/should we check if the version makes sense..?
+    if !sub_args.force
+        && !foreign_packages(&cfg.metadata, &store.config).any(|pkg| pkg.name == sub_args.package)
+    {
+        // ERRORS: immediate fatal diagnostic? should we allow you to certify random packages?
+        // You're definitely *allowed* to have unused audits, otherwise you'd be constantly deleting
+        // useful audits whenever you update your dependencies! But this might be a useful guard
+        // against typosquatting or other weird issues?
+        return Err(miette!(
+            "'{}' isn't one of your foreign packages",
+            sub_args.package
+        ));
+    }
+
+    // Ok! Ready to commit the audit!
+    let new_entry = ExemptedDependency {
+        criteria,
+        notes,
+        version: sub_args.version.clone(),
+        suggest,
+    };
+
+    store
+        .config
+        .exemptions
+        .entry(sub_args.package.clone())
+        .or_default()
+        .push(new_entry);
+
+    store.commit()?;
+
+    Ok(())
+}
+
+fn cmd_suggest(
+    out: &Arc<dyn Out>,
+    cfg: &Config,
+    _sub_args: &SuggestArgs,
+) -> Result<(), miette::Report> {
+    // Run the checker to validate that the current set of deps is covered by the current cargo vet store
+    trace!("suggesting...");
+    let network = Network::acquire(cfg);
+    let suggest_store = Store::acquire(cfg, network.as_ref(), false)?.clone_for_suggest(true);
+
+    // DO THE THING!!!!
+    let report = resolver::resolve(&cfg.metadata, cfg.cli.filter_graph.as_ref(), &suggest_store);
+    let suggest = report.compute_suggest(cfg, &suggest_store, network.as_ref())?;
+    match cfg.cli.output_format {
+        OutputFormat::Human => report
+            .print_suggest_human(out, cfg, suggest.as_ref())
+            .into_diagnostic()?,
+        OutputFormat::Json => report.print_json(out, suggest.as_ref())?,
+    }
+
+    Ok(())
+}
+
+fn cmd_regenerate_imports(
+    out: &Arc<dyn Out>,
+    cfg: &Config,
+    _sub_args: &RegenerateImportsArgs,
+) -> Result<(), miette::Report> {
+    trace!("regenerating imports...");
+
+    if cfg.cli.locked {
+        // ERRORS: just a warning that you're holding it wrong, unclear if immediate or buffered,
+        // or if this should be a hard error, or if we should ignore the --locked flag and
+        // just do it anyway
+        writeln!(
+            out,
+            "warning: ran `regenerate imports` with --locked, this won't do anything!"
+        );
+        return Ok(());
+    }
+
+    let network = Network::acquire(cfg);
+    let mut store = Store::acquire(cfg, network.as_ref(), true)?;
+
+    // Update the store state, pruning unnecessary exemptions, and cleaning out
+    // unnecessary old imports.
+    resolver::update_store(cfg, &mut store, |_| resolver::UpdateMode {
+        search_mode: resolver::SearchMode::PreferFreshImports,
+        prune_exemptions: true,
+        prune_imports: true,
+    });
+
+    store.commit()?;
+    Ok(())
+}
+
+fn cmd_regenerate_audit_as(
+    _out: &Arc<dyn Out>,
+    cfg: &Config,
+    _sub_args: &RegenerateAuditAsCratesIoArgs,
+) -> Result<(), miette::Report> {
+    trace!("regenerating audit-as-crates-io...");
+    let network = Network::acquire(cfg);
+    let mut store = Store::acquire_offline(cfg)?;
+
+    tokio::runtime::Handle::current().block_on(fix_audit_as(cfg, network.as_ref(), &mut store))?;
+
+    // We were successful, commit the store
+    store.commit()?;
+
+    Ok(())
+}
+
+fn cmd_regenerate_unpublished(
+    out: &Arc<dyn Out>,
+    cfg: &Config,
+    _sub_args: &RegenerateUnpublishedArgs,
+) -> Result<(), miette::Report> {
+    trace!("regenerating unpublished entries...");
+
+    if cfg.cli.locked {
+        // ERRORS: just a warning that you're holding it wrong, unclear if immediate or buffered,
+        // or if this should be a hard error, or if we should ignore the --locked flag and
+        // just do it anyway
+        writeln!(
+            out,
+            "warning: ran `regenerate unpublished` with --locked, this won't do anything!"
+        );
+        return Ok(());
+    }
+
+    let network = Network::acquire(cfg);
+    let mut store = Store::acquire(cfg, network.as_ref(), false)?;
+
+    // Strip all non-fresh entries from the unpublished table, marking the
+    // previously fresh entries as non-fresh.
+    if let Some(live_imports) = &mut store.live_imports {
+        for unpublished in live_imports.unpublished.values_mut() {
+            unpublished.retain_mut(|u| std::mem::replace(&mut u.is_fresh_import, false));
+        }
+    }
+
+    // Run a minimal store update to import new entries which would now be
+    // required for `check` to pass. Note that this won't ensure `check`
+    // actually passes after the change.
+    resolver::update_store(cfg, &mut store, |_| resolver::UpdateMode {
+        search_mode: resolver::SearchMode::PreferExemptions,
+        prune_exemptions: false,
+        prune_imports: false,
+    });
+
+    store.commit()?;
+    Ok(())
+}
+
+fn cmd_renew(out: &Arc<dyn Out>, cfg: &Config, sub_args: &RenewArgs) -> Result<(), miette::Report> {
+    trace!("renewing wildcard audits");
+    let mut store = Store::acquire_offline(cfg)?;
+    do_cmd_renew(out, cfg, &mut store, sub_args);
+    store.commit()?;
+    Ok(())
+}
+
+fn do_cmd_renew(out: &Arc<dyn Out>, cfg: &Config, store: &mut Store, sub_args: &RenewArgs) {
+    assert!(sub_args.expiring ^ sub_args.crate_name.is_some());
+
+    // We need the cache to map user ids to user names, though we can work around it if there is an
+    // error.
+    let cache = Cache::acquire(cfg).ok();
+
+    let new_end_date = cfg.today() + chrono::Months::new(12);
+
+    let mut renewing: WildcardAuditRenewal;
+
+    if let Some(name) = &sub_args.crate_name {
+        match WildcardAuditRenewal::single_crate(name, store) {
+            Some(renewal) => {
+                renewing = renewal;
+                if renewing.is_empty() {
+                    info!("no wildcard audits for {name} are eligible for renewal (all have `renew = false`)");
+                    return;
+                }
+            }
+            None => {
+                warn!("ran `renew {name}`, but there are no wildcard audits for the crate");
+                return;
+            }
+        }
+    } else {
+        // Find and update all expiring crates.
+        assert!(sub_args.expiring);
+        renewing = WildcardAuditRenewal::expiring(cfg, store);
+
+        if renewing.is_empty() {
+            info!("no wildcard audits that are eligible for renewal have expired or are expiring in the next {WILDCARD_AUDIT_EXPIRATION_STRING}");
+            return;
+        }
+    }
+
+    renewing.renew(new_end_date);
+
+    writeln!(
+        out,
+        "Updated wildcard audits for the following crates and publishers to expire on {new_end_date}:"
+    );
+
+    let user_string = |user_id: u64| -> String {
+        cache
+            .as_ref()
+            .and_then(|c| c.get_crates_user_info(user_id))
+            .map(|n| n.to_string())
+            .unwrap_or_else(|| format!("id={}", user_id))
+    };
+    for (name, entries) in renewing.crates {
+        writeln!(
+            out,
+            "  {}: {:80}",
+            name,
+            string_format::FormatShortList::new(
+                entries
+                    .iter()
+                    .map(|(entry, _)| user_string(entry.user_id))
+                    .collect()
+            )
+        );
+    }
+}
+
+/// Adjust the store to satisfy audit-as-crates-io issues
+///
+/// Every reported issue will be resolved by just setting `audit-as-crates-io = Some(false)`,
+/// because that always works, no matter what the problem is.
+async fn fix_audit_as(
+    cfg: &Config,
+    network: Option<&Network>,
+    store: &mut Store,
+) -> Result<(), CacheAcquireError> {
+    let _spinner = indeterminate_spinner("Fetching", "crate metadata");
+
+    let mut cache = Cache::acquire(cfg)?;
+
+    let third_party_packages = foreign_packages_strict(&cfg.metadata, &store.config)
+        .map(|p| &p.name)
+        .collect::<SortedSet<_>>();
+
+    let issues = check_audit_as_crates_io(cfg, store, network, &mut cache).await;
+    if let Err(AuditAsErrors { errors }) = issues {
+        fn get_policy_entry<'a>(
+            store: &'a mut Store,
+            cfg: &Config,
+            third_party_packages: &SortedSet<&String>,
+            error: &PackageError,
+        ) -> &'a mut PolicyEntry {
+            let is_third_party = third_party_packages.contains(&error.package);
+            let all_versions = || {
+                cfg.metadata
+                    .packages
+                    .iter()
+                    .filter_map(|p| (p.name == error.package).then(|| p.vet_version()))
+                    .collect()
+            };
+            // This can only fail if there's a logical error in `check_audit_as_crates_io`.
+            store
+                .config
+                .policy
+                .get_mut_or_default(
+                    error.package.clone(),
+                    is_third_party.then_some(error.version.as_ref()).flatten(),
+                    all_versions,
+                )
+                .expect("unexpected crate policy state")
+        }
+
+        for error in errors {
+            match error {
+                AuditAsError::NeedsAuditAs(NeedsAuditAsErrors { errors }) => {
+                    for err in errors {
+                        // We'll default audit-as-crates-io to true if the
+                        // crate's description or repository matches an existing
+                        // package on crates.io.
+                        //
+                        // XXX: This is just indended to reduce the chance of
+                        // false positives, but is certainly a bit of a loose
+                        // comparison. If it turns out to be an issue we can
+                        // improve it in the future.
+                        let default_audit_as = if network.is_some() {
+                            // NOTE: Handle all errors silently here, as we can always recover by
+                            // setting `audit-as-crates-io = false`. The error cases below are very
+                            // unlikely to occur since information will be cached from the initial
+                            // checks which generated the NeedsAuditAsErrors.
+                            let crates_api_metadata =
+                                match cache.get_crate_metadata(network, &err.package).await {
+                                    Ok(v) => v,
+                                    Err(e) => {
+                                        warn!("crate metadata error for {}: {e}", &err.package);
+                                        Default::default()
+                                    }
+                                };
+
+                            cfg.metadata.packages.iter().any(|p| {
+                                p.name == err.package && crates_api_metadata.consider_as_same(p)
+                            })
+                        } else {
+                            false
+                        };
+
+                        get_policy_entry(store, cfg, &third_party_packages, &err)
+                            .audit_as_crates_io = Some(default_audit_as);
+                    }
+                }
+                AuditAsError::ShouldntBeAuditAs(ShouldntBeAuditAsErrors { errors }) => {
+                    for err in errors {
+                        get_policy_entry(store, cfg, &third_party_packages, &err)
+                            .audit_as_crates_io = Some(false);
+                    }
+                }
+                AuditAsError::UnusedAuditAs(unuseds) => {
+                    for err in unuseds.errors {
+                        // XXX: consider removing the policy completely if
+                        // there's nothing left in it anymore?
+                        if let Some(policy) = store
+                            .config
+                            .policy
+                            .get_mut(&err.package, err.version.as_ref())
+                        {
+                            policy.audit_as_crates_io = None;
+                        }
+                    }
+                }
+            }
+        }
+    }
+    Ok(())
+}
+
+fn cmd_regenerate_exemptions(
+    _out: &Arc<dyn Out>,
+    cfg: &Config,
+    _sub_args: &RegenerateExemptionsArgs,
+) -> Result<(), miette::Report> {
+    trace!("regenerating exemptions...");
+    let network = Network::acquire(cfg);
+    let mut store = Store::acquire(cfg, network.as_ref(), false)?;
+
+    // Update the store using a full RegenerateExemptions search.
+    resolver::update_store(cfg, &mut store, |_| resolver::UpdateMode {
+        search_mode: resolver::SearchMode::RegenerateExemptions,
+        prune_exemptions: true,
+        prune_imports: true,
+    });
+
+    // We were successful, commit the store
+    store.commit()?;
+
+    Ok(())
+}
+
+fn cmd_diff(out: &Arc<dyn Out>, cfg: &Config, sub_args: &DiffArgs) -> Result<(), miette::Report> {
+    let version1 = &sub_args.version1;
+    let version2 = &sub_args.version2;
+    let package = &*sub_args.package;
+
+    let to_compare = {
+        let network = Network::acquire(cfg);
+        let store = Store::acquire(cfg, network.as_ref(), false)?;
+        let cache = Cache::acquire(cfg)?;
+
+        // Record this command for magic in `vet certify`
+        cache.set_last_fetch(FetchCommand::Diff {
+            package: package.to_owned(),
+            version1: version1.clone(),
+            version2: version2.clone(),
+        });
+
+        if sub_args.mode == FetchMode::Sourcegraph
+            && version1.git_rev.is_none()
+            && version2.git_rev.is_none()
+        {
+            let url = format!(
+                "https://sourcegraph.com/crates/{package}/-/compare/v{version1}...v{version2}?visible=1000000"
+            );
+            tokio::runtime::Handle::current()
+                .block_on(prompt_criteria_eulas(
+                    out,
+                    cfg,
+                    network.as_ref(),
+                    &store,
+                    package,
+                    Some(version1),
+                    version2,
+                    Some(&url),
+                ))
+                .into_diagnostic()?;
+
+            open::that(&url).into_diagnostic().wrap_err_with(|| {
+                format!("Couldn't open {url} in your browser, try --mode=local?")
+            })?;
+
+            writeln!(out, "\nUse |cargo vet certify| to record your audit.");
+
+            return Ok(());
+        }
+
+        tokio::runtime::Handle::current().block_on(async {
+            // NOTE: don't `try_join` everything as we don't want to abort the
+            // prompt to the user if the download fails while it is being shown, as
+            // that could be disorienting.
+            let (to_compare, eulas) = tokio::join!(
+                async {
+                    let (pkg1, pkg2) = tokio::try_join!(
+                        cache.fetch_package(&cfg.metadata, network.as_ref(), package, version1),
+                        cache.fetch_package(&cfg.metadata, network.as_ref(), package, version2)
+                    )?;
+                    let (_, to_compare) = cache
+                        .diffstat_package(
+                            &pkg1,
+                            &pkg2,
+                            version1.git_rev.is_some() || version2.git_rev.is_some(),
+                        )
+                        .await?;
+                    Ok::<_, FetchAndDiffError>(to_compare)
+                },
+                prompt_criteria_eulas(
+                    out,
+                    cfg,
+                    network.as_ref(),
+                    &store,
+                    package,
+                    Some(version1),
+                    version2,
+                    None,
+                )
+            );
+            eulas.into_diagnostic()?;
+            to_compare.into_diagnostic()
+        })?
+    };
+
+    writeln!(out);
+
+    // Start a pager to show the output from our diff invocations. This will
+    // fall back to just printing to `stdout` if no pager is available or we're
+    // not piped to a terminal.
+    let mut pager = Pager::new(&**out).into_diagnostic()?;
+
+    for (from, to) in to_compare {
+        let output = std::process::Command::new("git")
+            .arg("-c")
+            .arg("core.safecrlf=false")
+            .arg("diff")
+            .arg(if pager.use_color() {
+                "--color=always"
+            } else {
+                "--color=never"
+            })
+            .arg("--no-index")
+            .arg("--ignore-cr-at-eol")
+            .arg(&from)
+            .arg(&to)
+            .stdout(Stdio::piped())
+            .output()
+            .map_err(CommandError::CommandFailed)
+            .into_diagnostic()?;
+        io::Write::write_all(&mut pager, &output.stdout).into_diagnostic()?;
+    }
+
+    pager.wait().into_diagnostic()?;
+
+    writeln!(out, "\nUse |cargo vet certify| to record your audit.");
+
+    Ok(())
+}
+
+fn cmd_check(
+    out: &Arc<dyn Out>,
+    cfg: &Config,
+    _sub_args: &CheckArgs,
+) -> Result<(), miette::Report> {
+    // Run the checker to validate that the current set of deps is covered by the current cargo vet store
+    trace!("vetting...");
+
+    let network = Network::acquire(cfg);
+    let mut store = Store::acquire(cfg, network.as_ref(), false)?;
+
+    if !cfg.cli.locked {
+        // Check if any of our first-parties are in the crates.io registry
+        let mut cache = Cache::acquire(cfg).into_diagnostic()?;
+        // Check crate policies prior to audit_as_crates_io because the suggestions of
+        // check_audit_as_crates_io will rely on the correct structure of crate policies.
+        check_crate_policies(cfg, &store)?;
+        tokio::runtime::Handle::current().block_on(check_audit_as_crates_io(
+            cfg,
+            &store,
+            network.as_ref(),
+            &mut cache,
+        ))?;
+    }
+
+    // DO THE THING!!!!
+    let report = resolver::resolve(&cfg.metadata, cfg.cli.filter_graph.as_ref(), &store);
+
+    // Bare `cargo vet` shouldn't suggest in CI
+    let suggest = if !cfg.cli.locked {
+        report.compute_suggest(cfg, &store, network.as_ref())?
+    } else {
+        None
+    };
+
+    match cfg.cli.output_format {
+        OutputFormat::Human => report
+            .print_human(out, cfg, suggest.as_ref())
+            .into_diagnostic()?,
+        OutputFormat::Json => report.print_json(out, suggest.as_ref())?,
+    }
+
+    // Only save imports if we succeeded, to avoid any modifications on error.
+    if report.has_errors() {
+        // ERRORS: immediate fatal diagnostic? Arguably should be silent.
+        // Err(eyre!("report contains errors"))?;
+        panic_any(ExitPanic(-1));
+    } else {
+        if !cfg.cli.locked {
+            // Simulate a full `fetch-imports` run, and record the potential
+            // pruned imports and exemptions.
+            let (pruned_imports, pruned_exemptions) =
+                resolver::get_store_updates(cfg, &store, |_| resolver::UpdateMode {
+                    search_mode: resolver::SearchMode::PreferFreshImports,
+                    prune_exemptions: true,
+                    prune_imports: true,
+                });
+
+            // Perform a minimal store update to pull in necessary imports,
+            // while avoiding any other changes to exemptions or imports.
+            resolver::update_store(cfg, &mut store, |_| resolver::UpdateMode {
+                search_mode: resolver::SearchMode::PreferExemptions,
+                prune_exemptions: false,
+                prune_imports: false,
+            });
+
+            // XXX: Consider trying to be more precise here? Would require some
+            // more clever comparisons.
+            if store.config.exemptions != pruned_exemptions {
+                warn!("Your supply-chain has unnecessary exemptions which could be relaxed or pruned.");
+                warn!("  Consider running `cargo vet prune` to prune unnecessary exemptions and imports.");
+            } else if store.imports != pruned_imports {
+                warn!("Your supply-chain has unnecessary imports which could be pruned.");
+                warn!("  Consider running `cargo vet prune` to prune unnecessary imports.");
+            }
+
+            // Check if we have `unpublished` entries for crates which have since been published.
+            let since_published: Vec<_> = pruned_imports
+                .unpublished
+                .iter()
+                .filter(|(_, unpublished)| unpublished.iter().any(|u| !u.still_unpublished))
+                .map(|(package, _)| package)
+                .collect();
+            if !since_published.is_empty() {
+                let published = string_format::FormatShortList::new(since_published);
+                warn!("Your supply-chain depends on previously unpublished versions of {published} which have since been published.");
+                warn!("  Consider running `cargo vet regenerate unpublished` to remove these entries.");
+            }
+
+            // Warn about wildcard audits which will be expiring soon or have expired.
+            let expiry = WildcardAuditRenewal::expiring(cfg, &mut store);
+
+            if !expiry.is_empty() {
+                let expired = expiry.expired_crates();
+                let expiring_soon = expiry.expiring_crates();
+                if !expired.is_empty() {
+                    let expired = string_format::FormatShortList::new(expired);
+                    warn!(
+                        "Your audit set contains wildcard audits for {expired} which have expired."
+                    );
+                }
+                if !expiring_soon.is_empty() {
+                    let expiring = string_format::FormatShortList::new(expiring_soon);
+                    warn!("Your audit set contains wildcard audits for {expiring} which expire within the next {WILDCARD_AUDIT_EXPIRATION_STRING}.");
+                }
+                warn!("  Consider running `cargo vet renew --expiring` or adding `renew = false` to the wildcard entries in audits.toml.");
+            }
+        }
+
+        store.commit()?;
+    }
+
+    Ok(())
+}
+
+#[derive(Default)]
+struct WildcardAuditRenewal<'a> {
+    // the bool indicates whether the entry for that user id is already expired (true) or will
+    // expire soon (false)
+    pub crates: SortedMap<PackageStr<'a>, Vec<(&'a mut WildcardEntry, bool)>>,
+}
+
+impl<'a> WildcardAuditRenewal<'a> {
+    /// Get all wildcard audit entries which have expired or will expire soon.
+    ///
+    /// This function _does not_ modify the store, but since the mutable references to the entries
+    /// are stored (for potential use by `renew`), it must take a mutable Store.
+    pub fn expiring(cfg: &Config, store: &'a mut Store) -> Self {
+        let expire_date = cfg.today() + *WILDCARD_AUDIT_EXPIRATION_DURATION;
+
+        let mut crates: SortedMap<PackageStr<'a>, Vec<(&'a mut WildcardEntry, bool)>> =
+            Default::default();
+        for (name, audits) in store.audits.wildcard_audits.iter_mut() {
+            // Check whether there are any audits expiring by the expiration date. Of those
+            // audits, check whether all of them are already expired (to change the warning
+            // message to be more informative).
+            for entry in audits.iter_mut().filter(|e| e.should_renew(expire_date)) {
+                let expired = entry.should_renew(cfg.today());
+                crates.entry(name).or_default().push((entry, expired));
+            }
+        }
+
+        WildcardAuditRenewal { crates }
+    }
+
+    /// Create a renewal with a single crate explicitly provided.
+    ///
+    /// This will renew all eligible audits, regardless of expiration. Thus `expired_crates` and
+    /// `expiring_crates` should not be used.
+    pub fn single_crate(name: PackageStr<'a>, store: &'a mut Store) -> Option<Self> {
+        let mut crates: SortedMap<PackageStr<'a>, Vec<(&'a mut WildcardEntry, bool)>> =
+            Default::default();
+        let audits = store.audits.wildcard_audits.get_mut(name)?;
+        for entry in audits {
+            if entry.renew.unwrap_or(true) {
+                // We don't care about the expiring/expired, so insert with false.
+                crates.entry(name).or_default().push((entry, false));
+            }
+        }
+        Some(WildcardAuditRenewal { crates })
+    }
+
+    /// Whether there are no wildcard audits expiring or expired.
+    pub fn is_empty(&self) -> bool {
+        self.crates.is_empty()
+    }
+
+    /// Get the crate names for which wildcard audits have expired.
+    pub fn expired_crates(&'a self) -> Vec<PackageStr<'a>> {
+        self.crates
+            .iter()
+            .filter_map(|(name, ids)| ids.iter().any(|(_, expired)| *expired).then_some(*name))
+            .collect()
+    }
+
+    /// Get the crate names for which wildcard audits will expire soon.
+    pub fn expiring_crates(&'a self) -> Vec<PackageStr<'a>> {
+        self.crates
+            .iter()
+            .filter_map(|(name, ids)| ids.iter().any(|(_, expired)| !*expired).then_some(*name))
+            .collect()
+    }
+
+    /// Renew all stored entries.
+    pub fn renew(&mut self, new_end_date: chrono::NaiveDate) {
+        for entry in self
+            .crates
+            .values_mut()
+            .flat_map(|v| v.iter_mut().map(|t| &mut t.0))
+        {
+            entry.end = new_end_date.into();
+        }
+    }
+}
+
+fn cmd_prune(
+    _out: &Arc<dyn Out>,
+    cfg: &Config,
+    sub_args: &PruneArgs,
+) -> Result<(), miette::Report> {
+    let network = Network::acquire(cfg);
+    let mut store = Store::acquire(cfg, network.as_ref(), false)?;
+
+    let _spinner = indeterminate_spinner("Pruning", "unnecessary imports and exemptions");
+
+    // Update the store with the live state, pruning unnecessary exemptions and
+    // imports.
+    resolver::update_store(cfg, &mut store, |_| resolver::UpdateMode {
+        search_mode: if sub_args.no_exemptions {
+            resolver::SearchMode::PreferExemptions
+        } else {
+            resolver::SearchMode::PreferFreshImports
+        },
+        prune_exemptions: !sub_args.no_exemptions,
+        prune_imports: !sub_args.no_imports,
+    });
+
+    store.commit()?;
+
+    Ok(())
+}
+
+fn cmd_aggregate(
+    out: &Arc<dyn Out>,
+    cfg: &PartialConfig,
+    sub_args: &AggregateArgs,
+) -> Result<(), miette::Report> {
+    let network =
+        Network::acquire(cfg).ok_or_else(|| miette!("cannot aggregate imports when --frozen"))?;
+
+    let mut urls = Vec::new();
+    {
+        let sources_file = BufReader::new(
+            File::open(&sub_args.sources)
+                .into_diagnostic()
+                .wrap_err("failed to open sources file")?,
+        );
+        for line_result in sources_file.lines() {
+            let line = line_result
+                .into_diagnostic()
+                .wrap_err("failed to read sources file")?;
+            let trimmed = line.trim();
+            if trimmed.is_empty() || trimmed.starts_with('#') {
+                // Ignore comment and empty lines.
+                continue;
+            }
+            urls.push(
+                Url::parse(trimmed)
+                    .into_diagnostic()
+                    .wrap_err_with(|| format!("failed to parse url: {trimmed:?}"))?,
+            );
+        }
+    }
+
+    let progress_bar = progress_bar("Fetching", "source audits", urls.len() as u64);
+    let sources = tokio::runtime::Handle::current()
+        .block_on(try_join_all(urls.into_iter().map(|url| async {
+            let _guard = IncProgressOnDrop(&progress_bar, 1);
+            let url_string = url.to_string();
+            let audit_bytes = network.download(url).await?;
+            let audit_string = String::from_utf8(audit_bytes).map_err(LoadTomlError::from)?;
+            let audit_source = SourceFile::new(&url_string, audit_string.clone());
+            let audit_file: AuditsFile = toml::de::from_str(&audit_string)
+                .map_err(|error| {
+                    let (line, col) = error.line_col().unwrap_or((0, 0));
+                    TomlParseError {
+                        source_code: audit_source,
+                        span: SourceOffset::from_location(&audit_string, line + 1, col + 1),
+                        error,
+                    }
+                })
+                .map_err(LoadTomlError::from)?;
+            Ok::<_, FetchAuditError>((url_string, audit_file))
+        })))
+        .into_diagnostic()?;
+
+    let merged_audits = do_aggregate_audits(sources).into_diagnostic()?;
+    let document = serialization::to_formatted_toml(merged_audits, None).into_diagnostic()?;
+    write!(out, "{document}");
+    Ok(())
+}
+
+fn do_aggregate_audits(sources: Vec<(String, AuditsFile)>) -> Result<AuditsFile, AggregateErrors> {
+    let mut errors = Vec::new();
+    let mut aggregate = AuditsFile {
+        criteria: SortedMap::new(),
+        wildcard_audits: SortedMap::new(),
+        audits: SortedMap::new(),
+        // FIXME: How should we handle aggregating trusted entries? Should we do
+        // any form of de-duplication?
+        trusted: SortedMap::new(),
+    };
+
+    for (source, audit_file) in sources {
+        // Add each criteria from the original source, managing duplicates by
+        // ensuring that their descriptions map 1:1.
+        for (criteria_name, mut criteria_entry) in audit_file.criteria {
+            match aggregate.criteria.entry(criteria_name) {
+                std::collections::btree_map::Entry::Vacant(vacant) => {
+                    criteria_entry.aggregated_from.push(source.clone().into());
+                    vacant.insert(criteria_entry);
+                }
+                std::collections::btree_map::Entry::Occupied(occupied) => {
+                    let prev_source = occupied
+                        .get()
+                        .aggregated_from
+                        .last()
+                        .map(|s| s.to_string())
+                        .unwrap_or_default();
+                    // NOTE: We don't record the new `aggregated_from` chain in
+                    // this case, as we already have a chain for the existing
+                    // entry which we don't want to clobber. This means that
+                    // source order in the `sources.list` file can impact where
+                    // your criteria are credited to originate from.
+                    if occupied.get().description != criteria_entry.description
+                        || occupied.get().description_url != criteria_entry.description_url
+                    {
+                        errors.push(AggregateError::CriteriaDescriptionMismatch(
+                            AggregateCriteriaDescriptionMismatchError {
+                                criteria_name: occupied.key().to_owned(),
+                                first: AggregateCriteriaDescription {
+                                    source: prev_source.clone(),
+                                    description: occupied.get().description.clone(),
+                                    description_url: occupied.get().description_url.clone(),
+                                },
+                                second: AggregateCriteriaDescription {
+                                    source: source.clone(),
+                                    description: criteria_entry.description.clone(),
+                                    description_url: criteria_entry.description_url.clone(),
+                                },
+                            },
+                        ))
+                    }
+                    if occupied.get().implies != criteria_entry.implies {
+                        errors.push(AggregateError::ImpliesMismatch(
+                            AggregateImpliesMismatchError {
+                                criteria_name: occupied.key().to_owned(),
+                                first: AggregateCriteriaImplies {
+                                    source: prev_source.clone(),
+                                    implies: occupied
+                                        .get()
+                                        .implies
+                                        .iter()
+                                        .map(|c| c.to_string())
+                                        .collect(),
+                                },
+                                second: AggregateCriteriaImplies {
+                                    source: source.clone(),
+                                    implies: criteria_entry
+                                        .implies
+                                        .iter()
+                                        .map(|c| c.to_string())
+                                        .collect(),
+                                },
+                            },
+                        ));
+                    }
+                }
+            }
+        }
+        for (package_name, entries) in audit_file.audits {
+            aggregate
+                .audits
+                .entry(package_name)
+                .or_default()
+                .extend(entries.into_iter().map(|mut audit_entry| {
+                    audit_entry.aggregated_from.push(source.clone().into());
+                    audit_entry
+                }));
+        }
+        for (package_name, entries) in audit_file.wildcard_audits {
+            aggregate
+                .wildcard_audits
+                .entry(package_name)
+                .or_default()
+                .extend(entries.into_iter().map(|mut wildcard_entry| {
+                    wildcard_entry.aggregated_from.push(source.clone().into());
+                    wildcard_entry
+                }));
+        }
+        for (package_name, entries) in audit_file.trusted {
+            aggregate
+                .trusted
+                .entry(package_name)
+                .or_default()
+                .extend(entries.into_iter().map(|mut trusted_entry| {
+                    trusted_entry.aggregated_from.push(source.clone().into());
+                    trusted_entry
+                }));
+        }
+    }
+    if errors.is_empty() {
+        Ok(aggregate)
+    } else {
+        Err(AggregateErrors { errors })
+    }
+}
+
+fn cmd_dump_graph(
+    out: &Arc<dyn Out>,
+    cfg: &Config,
+    sub_args: &DumpGraphArgs,
+) -> Result<(), miette::Report> {
+    // Dump a mermaid-js graph
+    trace!("dumping...");
+
+    let graph = resolver::DepGraph::new(&cfg.metadata, cfg.cli.filter_graph.as_ref(), None);
+    match cfg.cli.output_format {
+        OutputFormat::Human => graph.print_mermaid(out, sub_args).into_diagnostic()?,
+        OutputFormat::Json => {
+            serde_json::to_writer_pretty(&**out, &graph.nodes).into_diagnostic()?
+        }
+    }
+
+    Ok(())
+}
+
+fn cmd_fmt(_out: &Arc<dyn Out>, cfg: &Config, _sub_args: &FmtArgs) -> Result<(), miette::Report> {
+    // Reformat all the files (just load and store them, formatting is implicit).
+    trace!("formatting...");
+    // We don't need to fetch foreign audits to format files
+    let store = Store::acquire_offline(cfg)?;
+    store.commit()?;
+    Ok(())
+}
+
+/// Perform crimes on clap long_help to generate markdown docs
+fn cmd_help_md(
+    out: &Arc<dyn Out>,
+    _cfg: &PartialConfig,
+    _sub_args: &HelpMarkdownArgs,
+) -> Result<(), miette::Report> {
+    let app_name = "cargo-vet";
+    let pretty_app_name = "cargo vet";
+    // Make a new App to get the help message this time.
+
+    writeln!(out, "# {pretty_app_name} CLI manual");
+    writeln!(out);
+    writeln!(
+        out,
+        "> This manual can be regenerated with `{pretty_app_name} help-markdown`"
+    );
+    writeln!(out);
+
+    let mut fake_cli = FakeCli::command().term_width(0);
+    let full_command = fake_cli.get_subcommands_mut().next().unwrap();
+    full_command.build();
+    let mut todo = vec![full_command];
+    let mut is_full_command = true;
+
+    while let Some(command) = todo.pop() {
+        let mut help_buf = Vec::new();
+        command.write_long_help(&mut help_buf).unwrap();
+        let help = String::from_utf8(help_buf).unwrap();
+
+        // First line is --version
+        let mut lines = help.lines();
+        let version_line = lines.next().unwrap();
+        let subcommand_name = command.get_name();
+
+        if is_full_command {
+            writeln!(out, "Version: `{version_line}`");
+            writeln!(out);
+        } else {
+            // Give subcommands some breathing room
+            writeln!(out, "<br><br><br>");
+            writeln!(out, "## {pretty_app_name} {subcommand_name}");
+        }
+
+        let mut in_subcommands_listing = false;
+        let mut in_usage = false;
+        let mut in_global_options = false;
+        for line in lines {
+            // Use a trailing colon to indicate a heading
+            if let Some(heading) = line.strip_suffix(':') {
+                if !line.starts_with(' ') {
+                    // SCREAMING headers are Main headings
+                    if heading.to_ascii_uppercase() == heading {
+                        in_subcommands_listing = heading == "SUBCOMMANDS";
+                        in_usage = heading == "USAGE";
+                        in_global_options = heading == "GLOBAL OPTIONS";
+
+                        writeln!(out, "### {heading}");
+
+                        if in_global_options && !is_full_command {
+                            writeln!(
+                                out,
+                                "This subcommand accepts all the [global options](#global-options)"
+                            );
+                        }
+                    } else {
+                        writeln!(out, "### {heading}");
+                    }
+                    continue;
+                }
+            }
+
+            if in_global_options && !is_full_command {
+                // Skip global options for non-primary commands
+                continue;
+            }
+
+            if in_subcommands_listing && !line.starts_with("     ") {
+                // subcommand names are list items
+                let own_subcommand_name = line.trim();
+                write!(
+                    out,
+                    "* [{own_subcommand_name}](#{app_name}-{own_subcommand_name}): "
+                );
+                continue;
+            }
+            // The rest is indented, get rid of that
+            let line = line.trim();
+
+            // Usage strings get wrapped in full code blocks
+            if in_usage && line.starts_with(pretty_app_name) {
+                writeln!(out, "```");
+                writeln!(out, "{line}");
+                writeln!(out, "```");
+                continue;
+            }
+
+            // argument names are subheadings
+            if line.starts_with('-') || line.starts_with('<') {
+                writeln!(out, "#### `{line}`");
+                continue;
+            }
+
+            // escape default/value strings
+            if line.starts_with('[') {
+                writeln!(out, "\\{line}  ");
+                continue;
+            }
+
+            // Normal paragraph text
+            writeln!(out, "{line}");
+        }
+        writeln!(out);
+
+        // The todo list is a stack, and processed in reverse-order, append
+        // these commands to the end in reverse-order so the first command is
+        // processed first (i.e. at the end of the list).
+        todo.extend(
+            command
+                .get_subcommands_mut()
+                .filter(|cmd| !cmd.is_hide_set())
+                .collect::<Vec<_>>()
+                .into_iter()
+                .rev(),
+        );
+        is_full_command = false;
+    }
+
+    Ok(())
+}
+
+fn cmd_gc(
+    out: &Arc<dyn Out>,
+    cfg: &PartialConfig,
+    sub_args: &GcArgs,
+) -> Result<(), miette::Report> {
+    let cache = Cache::acquire(cfg)?;
+
+    if sub_args.clean {
+        writeln!(
+            out,
+            "cleaning entire contents of cache directory: {}",
+            cfg.cache_dir.display()
+        );
+        cache.clean_sync().into_diagnostic()?;
+        return Ok(());
+    }
+
+    if sub_args.max_package_age_days.is_nan() {
+        return Err(miette!("max package age cannot be NaN"));
+    }
+    if sub_args.max_package_age_days < 0.0 {
+        return Err(miette!("max package age cannot be negative"));
+    }
+
+    cache.gc_sync(DURATION_DAY.mul_f64(sub_args.max_package_age_days));
+    Ok(())
+}
+
+// Utils
+
+struct UserInfo {
+    username: String,
+    email: String,
+}
+
+fn get_user_info() -> Result<UserInfo, UserInfoError> {
+    fn get_git_config(value_name: &str) -> Result<String, CommandError> {
+        let out = std::process::Command::new("git")
+            .arg("config")
+            .arg("--get")
+            .arg(value_name)
+            .output()
+            .map_err(CommandError::CommandFailed)?;
+
+        if !out.status.success() {
+            return Err(CommandError::BadStatus(out.status.code().unwrap()));
+        }
+        String::from_utf8(out.stdout)
+            .map(|s| s.trim().to_string())
+            .map_err(CommandError::BadOutput)
+    }
+
+    let username = get_git_config("user.name").map_err(UserInfoError::UserCommandFailed)?;
+    let email = get_git_config("user.email").map_err(UserInfoError::EmailCommandFailed)?;
+
+    Ok(UserInfo { username, email })
+}
+
+async fn eula_for_criteria(
+    network: Option<&Network>,
+    criteria_map: &SortedMap<CriteriaName, CriteriaEntry>,
+    criteria: CriteriaStr<'_>,
+) -> String {
+    let builtin_eulas = [
+        (
+            format::SAFE_TO_DEPLOY,
+            include_str!("criteria/safe-to-deploy.txt"),
+        ),
+        (
+            format::SAFE_TO_RUN,
+            include_str!("criteria/safe-to-run.txt"),
+        ),
+    ]
+    .into_iter()
+    .collect::<HashMap<_, _>>();
+
+    // Several fallbacks
+    // * Try to get the builtin criteria
+    // * Try to get the criteria's description
+    // * Try to fetch the criteria's url
+    // * Just display the url
+
+    // First try the builtins
+    let builtin = builtin_eulas.get(criteria).map(|s| s.to_string());
+    if let Some(eula) = builtin {
+        return eula;
+    }
+
+    // ERRORS: the caller should have verified this entry already!
+    let criteria_entry = criteria_map
+        .get(criteria)
+        .unwrap_or_else(|| panic!("no entry for the criteria {criteria}"));
+    assert!(
+        criteria_entry.description.is_some() || criteria_entry.description_url.is_some(),
+        "entry for criteria {criteria} is corrupt!"
+    );
+
+    // Now try the description
+    if let Some(eula) = criteria_entry.description.clone() {
+        return eula;
+    }
+
+    // If we get here then there must be a URL, try to fetch it. If it fails, just print the URL
+    let url = Url::parse(criteria_entry.description_url.as_ref().unwrap()).unwrap();
+    if let Some(network) = network {
+        if let Ok(eula) = network.download(url.clone()).await.and_then(|bytes| {
+            String::from_utf8(bytes).map_err(|error| DownloadError::InvalidText {
+                url: Box::new(url.clone()),
+                error,
+            })
+        }) {
+            return eula;
+        }
+    }
+
+    // If we get here then the download failed, just print the URL
+    format!("Could not download criteria description, it should be available at {url}")
+}
+
+/// All third-party packages, with the audit-as-crates-io policy applied
+fn foreign_packages<'a>(
+    metadata: &'a Metadata,
+    config: &'a ConfigFile,
+) -> impl Iterator<Item = &'a Package> + 'a {
+    // Only analyze things from crates.io (no source = path-dep / workspace-member)
+    metadata
+        .packages
+        .iter()
+        .filter(|package| package.is_third_party(&config.policy))
+}
+
+/// All first-party packages, **without** the audit-as-crates-io policy applied
+/// (because it's used for validating that field's value).
+fn first_party_packages_strict<'a>(
+    metadata: &'a Metadata,
+    _config: &'a ConfigFile,
+) -> impl Iterator<Item = &'a Package> + 'a {
+    metadata
+        .packages
+        .iter()
+        .filter(move |package| !package.is_crates_io())
+}
+
+/// All third-party packages, **without** the audit-as-crates-io policy applied (used in crate
+/// policy verification).
+fn foreign_packages_strict<'a>(
+    metadata: &'a Metadata,
+    _config: &ConfigFile,
+) -> impl Iterator<Item = &'a Package> + 'a {
+    metadata
+        .packages
+        .iter()
+        .filter(move |package| package.is_crates_io())
+}
+
+async fn check_audit_as_crates_io(
+    cfg: &Config,
+    store: &Store,
+    network: Option<&Network>,
+    cache: &mut Cache,
+) -> Result<(), AuditAsErrors> {
+    let first_party_packages: Vec<_> =
+        first_party_packages_strict(&cfg.metadata, &store.config).collect();
+
+    let mut errors = vec![];
+
+    {
+        let mut unused_audit_as: SortedSet<(PackageName, Option<VetVersion>)> = store
+            .config
+            .policy
+            .iter()
+            .filter(|(_, _, policy)| policy.audit_as_crates_io.is_some())
+            .map(|(name, version, _)| (name.clone(), version.cloned()))
+            .collect();
+
+        for package in &first_party_packages {
+            // Remove both versioned and unversioned entries
+            unused_audit_as.remove(&(package.name.clone(), Some(package.vet_version())));
+            unused_audit_as.remove(&(package.name.clone(), None));
+        }
+        if !unused_audit_as.is_empty() {
+            errors.push(AuditAsError::UnusedAuditAs(UnusedAuditAsErrors {
+                errors: unused_audit_as
+                    .into_iter()
+                    .map(|(package, version)| PackageError { package, version })
+                    .collect(),
+            }))
+        }
+    }
+
+    // We should only check the audit-as-crates-io entries if we have a network, because we
+    // shouldn't make recommendations based on potentially stale information.
+    if network.is_some() {
+        let progress = progress_bar(
+            "Validating",
+            "audit-as-crates-io specifications",
+            first_party_packages.len() as u64,
+        );
+
+        enum CheckAction {
+            NeedAuditAs,
+            ShouldntBeAuditAs,
+        }
+
+        let actions: Vec<_> = join_all(first_party_packages.into_iter().map(|package| {
+            let progress = &progress;
+            let cache = &cache;
+            async move {
+                let _inc_progress = IncProgressOnDrop(progress, 1);
+
+                let audit_policy = package
+                    .policy_entry(&store.config.policy)
+                    .and_then(|policy| policy.audit_as_crates_io);
+                if audit_policy == Some(false) {
+                    // They've explicitly said this is first-party so we don't care about what's in the
+                    // registry.
+                    return None;
+                }
+
+                // To avoid unnecessary metadata lookup, only do so for packages which exist in the
+                // index. The case which doesn't work with this logic is if someone is using a
+                // package before it has ever been published, and then later it is published (in
+                // which case a third-party change causes a warning to unexpectedly come up).
+                // However, this case is sufficiently unlikely that for now it's worth the initial
+                // lookup to avoid unnecessarily trying to fetch metadata for unpublished crates.
+                //
+                // The caching logic already does this for us as an optimization, but since we may
+                // need to look at the specific versions later, we fetch it anyway.
+                let mut matches_crates_io_package = false;
+                if let Ok(metadata) = cache.get_crate_metadata(network, &package.name).await {
+                    matches_crates_io_package = metadata.consider_as_same(package);
+                }
+
+                if matches_crates_io_package && audit_policy.is_none() {
+                    // We found a package that has similar metadata to one with the same name
+                    // on crates.io: having no policy is an error.
+                    return Some((CheckAction::NeedAuditAs, package));
+                }
+                if !matches_crates_io_package && audit_policy == Some(true) {
+                    return Some((CheckAction::ShouldntBeAuditAs, package));
+                }
+                None
+            }
+        }))
+        .await
+        .into_iter()
+        .flatten()
+        .collect();
+
+        let mut needs_audit_as_entry = vec![];
+        let mut shouldnt_be_audit_as = vec![];
+
+        for (action, package) in actions {
+            match action {
+                CheckAction::NeedAuditAs => {
+                    needs_audit_as_entry.push(PackageError {
+                        package: package.name.clone(),
+                        version: Some(package.vet_version()),
+                    });
+                }
+                CheckAction::ShouldntBeAuditAs => {
+                    shouldnt_be_audit_as.push(PackageError {
+                        package: package.name.clone(),
+                        version: Some(package.vet_version()),
+                    });
+                }
+            }
+        }
+
+        if !needs_audit_as_entry.is_empty() {
+            errors.push(AuditAsError::NeedsAuditAs(NeedsAuditAsErrors {
+                errors: needs_audit_as_entry,
+            }));
+        }
+        if !shouldnt_be_audit_as.is_empty() {
+            errors.push(AuditAsError::ShouldntBeAuditAs(ShouldntBeAuditAsErrors {
+                errors: shouldnt_be_audit_as,
+            }));
+        }
+    }
+
+    if !errors.is_empty() {
+        Err(AuditAsErrors { errors })
+    } else {
+        Ok(())
+    }
+}
+
+/// Check crate policies for correctness.
+///
+/// This verifies two rules:
+/// 1. Policies using `dependency-criteria` which relate to third-party crates must have associated
+///    version(s). If a crate has any `dependency-criteria` specified and exists as a third-party
+///    dependency anywhere in the dependency graph, all versions must be specified.
+/// 2. Any versioned policies must correspond to a crate in the graph.
+fn check_crate_policies(cfg: &Config, store: &Store) -> Result<(), CratePolicyErrors> {
+    // All defined policy package names (to be removed).
+    let mut policy_crates: SortedSet<&PackageName> = store.config.policy.package.keys().collect();
+
+    // All defined policy (name, version) pairs (to be visited and removed).
+    let mut versioned_policy_crates: SortedSet<(PackageName, VetVersion)> = store
+        .config
+        .policy
+        .iter()
+        .filter_map(|(name, version, _)| version.map(|version| (name.clone(), version.clone())))
+        .collect();
+
+    // The set of all third-party packages (for lookup of whether a crate has any third-party
+    // versions in use).
+    let third_party_packages = foreign_packages_strict(&cfg.metadata, &store.config)
+        .map(|p| &p.name)
+        .collect::<SortedSet<_>>();
+
+    // The set of all packages which have a `dependency-criteria` specified in a policy.
+    let dependency_criteria_packages = store
+        .config
+        .policy
+        .iter()
+        .filter_map(|(name, _, entry)| (!entry.dependency_criteria.is_empty()).then_some(name))
+        .collect::<SortedSet<_>>();
+
+    let mut needs_policy_version_errors = Vec::new();
+
+    for package in &cfg.metadata.packages {
+        policy_crates.remove(&package.name);
+
+        let versioned_policy_exists =
+            versioned_policy_crates.remove(&(package.name.clone(), package.vet_version()));
+
+        // If a crate has at least one third-party package and some crate policy specifies a
+        // `dependency-criteria`, a versioned policy for all used versions must exist.
+        if third_party_packages.contains(&package.name)
+            && dependency_criteria_packages.contains(&package.name)
+            && !versioned_policy_exists
+        {
+            needs_policy_version_errors.push(PackageError {
+                package: package.name.clone(),
+                version: Some(package.vet_version()),
+            });
+        }
+    }
+
+    let unused_policy_version_errors: Vec<_> = policy_crates
+        .into_iter()
+        .map(|name| PackageError {
+            package: name.clone(),
+            version: None,
+        })
+        .chain(
+            versioned_policy_crates
+                .into_iter()
+                .map(|(package, version)| PackageError {
+                    package,
+                    version: Some(version),
+                }),
+        )
+        .collect();
+
+    if !needs_policy_version_errors.is_empty() || !unused_policy_version_errors.is_empty() {
+        let mut errors = Vec::new();
+        if !needs_policy_version_errors.is_empty() {
+            errors.push(CratePolicyError::NeedsVersion(NeedsPolicyVersionErrors {
+                errors: needs_policy_version_errors,
+            }));
+        }
+        if !unused_policy_version_errors.is_empty() {
+            errors.push(CratePolicyError::UnusedVersion(UnusedPolicyVersionErrors {
+                errors: unused_policy_version_errors,
+            }));
+        }
+        Err(CratePolicyErrors { errors })
+    } else {
+        Ok(())
+    }
+}
diff --git a/src/network.rs b/src/network.rs
new file mode 100644
index 0000000..48d054d
--- /dev/null
+++ b/src/network.rs
@@ -0,0 +1,329 @@
+//! Networking as a capability
+//!
+//! All code that wants to hit the network should go through this module.
+//!
+//! Currently it produces the output all at once, but in the future it would
+//! ideally provide hooks to give you streaming access to the download so
+//! that you could do a streaming parse and reduce latency on network-bound
+//! tasks.
+
+use std::{
+    ffi::{OsStr, OsString},
+    io::Write,
+    path::{Path, PathBuf},
+    sync::Mutex,
+    time::Duration,
+};
+
+use base64_stream::FromBase64Writer;
+use bytes::Bytes;
+use reqwest::{Client, Url};
+use tokio::io::AsyncWriteExt;
+
+use crate::{
+    errors::{DownloadError, SourceFile},
+    PartialConfig,
+};
+
+/// Wrapper for the pair of a `reqwest::Response` and the `SemaphorePermit` used
+/// to limit concurrent connections, with a test-only variant for mocking.
+enum Response<'a> {
+    Real(reqwest::Response, tokio::sync::SemaphorePermit<'a>),
+    #[cfg(test)]
+    Mock(Option<Bytes>),
+}
+
+impl Response<'_> {
+    fn has_header(&self, name: &str) -> bool {
+        match self {
+            Response::Real(response, _) => response.headers().contains_key(name),
+            #[cfg(test)]
+            Response::Mock(_) => false,
+        }
+    }
+
+    /// Get the next chunk in the response stream, or `None` if at the end of
+    /// the stream.
+    async fn chunk(&mut self) -> Result<Option<Bytes>, DownloadError> {
+        match self {
+            Response::Real(res, _) => {
+                res.chunk()
+                    .await
+                    .map_err(|error| DownloadError::FailedToReadDownload {
+                        url: Box::new(res.url().clone()),
+                        error,
+                    })
+            }
+            #[cfg(test)]
+            Response::Mock(data) => Ok(data.take()),
+        }
+    }
+}
+
+pub struct Network {
+    /// The HTTP client all requests go through
+    client: Client,
+    /// Semaphore preventing exceeding the maximum number of connections.
+    connection_semaphore: tokio::sync::Semaphore,
+    /// Cache of source files downloaded by Url
+    source_file_cache: Mutex<std::collections::HashMap<Url, SourceFile>>,
+    /// Test-only override for download requests.
+    #[cfg(test)]
+    mock_network: Option<std::collections::HashMap<Url, Bytes>>,
+}
+
+const DEFAULT_TIMEOUT_SECS: u64 = 60;
+const MAX_CONCURRENT_CONNECTIONS: usize = 40;
+const USER_AGENT: &str = concat!(
+    env!("CARGO_PKG_NAME"),
+    "/",
+    env!("CARGO_PKG_VERSION"),
+    " (",
+    env!("CARGO_PKG_HOMEPAGE"),
+    ")"
+);
+
+/// The network payload encoding.
+///
+/// This is only used in `download` (not `download_and_persist`) because (for now) it's only needed
+/// in downloading imports (not packages, which is what `download_and_persist` is used for) to
+/// workaround known server shortcomings. It could be added to `download_and_persist`, but due to
+/// the use of `tokio::io::File` it gets messy and either this or that would need a refactor.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum PayloadEncoding {
+    Plaintext,
+    Base64,
+}
+
+impl PayloadEncoding {
+    fn for_response(response: &Response) -> Self {
+        // gitiles always encodes content in base64
+        if response.has_header("x-gitiles-object-type") {
+            Self::Base64
+        } else {
+            Self::Plaintext
+        }
+    }
+
+    pub fn to_plaintext<'a, W: Write + 'a>(&self, target: W) -> Box<dyn Write + 'a> {
+        match self {
+            Self::Plaintext => Box::new(target),
+            Self::Base64 => Box::new(FromBase64Writer::new(target)),
+        }
+    }
+}
+
+impl std::fmt::Display for PayloadEncoding {
+    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+        match self {
+            Self::Plaintext => write!(f, "plaintext"),
+            Self::Base64 => write!(f, "base64"),
+        }
+    }
+}
+
+impl Network {
+    /// Acquire access to the network
+    ///
+    /// There should only ever be one Network instance instantiated. Do it early
+    /// and then pass it around by-ref.
+    pub fn acquire(cfg: &PartialConfig) -> Option<Self> {
+        if cfg.cli.frozen {
+            None
+        } else {
+            // TODO: make this configurable on the CLI or something
+            let timeout = Duration::from_secs(DEFAULT_TIMEOUT_SECS);
+            // TODO: make this configurable on the CLI or something
+            let client = Client::builder()
+                .user_agent(USER_AGENT)
+                .timeout(timeout)
+                .build()
+                .expect("Couldn't construct HTTP Client?");
+            Some(Self {
+                client,
+                connection_semaphore: tokio::sync::Semaphore::new(MAX_CONCURRENT_CONNECTIONS),
+                source_file_cache: Default::default(),
+                #[cfg(test)]
+                mock_network: None,
+            })
+        }
+    }
+
+    /// Download a file and persist it to disk
+    pub async fn download_and_persist(
+        &self,
+        url: Url,
+        persist_to: &Path,
+    ) -> Result<(), DownloadError> {
+        let download_tmp_path = PathBuf::from(OsString::from_iter([
+            persist_to.as_os_str(),
+            OsStr::new(".part"),
+        ]));
+        {
+            let mut res = self.fetch_core(url).await?;
+
+            let mut download_tmp =
+                tokio::fs::File::create(&download_tmp_path)
+                    .await
+                    .map_err(|error| DownloadError::FailedToCreateDownload {
+                        target: download_tmp_path.clone(),
+                        error,
+                    })?;
+
+            while let Some(chunk) = res.chunk().await? {
+                download_tmp.write_all(&chunk[..]).await.map_err(|error| {
+                    DownloadError::FailedToWriteDownload {
+                        target: download_tmp_path.clone(),
+                        error,
+                    }
+                })?;
+            }
+        }
+
+        // Rename the downloaded file into the final location.
+        match tokio::fs::rename(&download_tmp_path, &persist_to).await {
+            Ok(()) => {}
+            Err(err) => {
+                let _ = tokio::fs::remove_file(&download_tmp_path).await;
+                return Err(err).map_err(|error| DownloadError::FailedToFinalizeDownload {
+                    target: persist_to.to_owned(),
+                    error,
+                })?;
+            }
+        }
+
+        Ok(())
+    }
+
+    /// Download a file into memory
+    pub async fn download(&self, url: Url) -> Result<Vec<u8>, DownloadError> {
+        let mut res = self.fetch_core(url).await?;
+
+        let encoding = PayloadEncoding::for_response(&res);
+
+        let mut output = vec![];
+        {
+            let mut writer = encoding.to_plaintext(&mut output);
+            while let Some(chunk) = res.chunk().await? {
+                writer
+                    .write_all(&chunk[..])
+                    .map_err(|error| DownloadError::InvalidEncoding { encoding, error })?;
+            }
+            writer
+                .flush()
+                .map_err(|error| DownloadError::InvalidEncoding { encoding, error })?;
+        }
+        Ok(output)
+    }
+
+    /// Download a file into memory as a SourceFile, with in-memory caching
+    pub async fn download_source_file_cached(&self, url: Url) -> Result<SourceFile, DownloadError> {
+        if let Some(source_file) = self.source_file_cache.lock().unwrap().get(&url) {
+            return Ok(source_file.clone());
+        }
+
+        let bytes = self.download(url.clone()).await?;
+        match String::from_utf8(bytes) {
+            Ok(string) => {
+                let source_file = SourceFile::new(url.as_str(), string);
+                self.source_file_cache
+                    .lock()
+                    .unwrap()
+                    .insert(url, source_file.clone());
+                Ok(source_file)
+            }
+            Err(error) => Err(DownloadError::InvalidText {
+                url: Box::new(url),
+                error,
+            }),
+        }
+    }
+
+    /// Internal core implementation of network fetching which is shared between
+    /// `download` and `download_and_persist`.
+    async fn fetch_core(&self, url: Url) -> Result<Response, DownloadError> {
+        #[cfg(test)]
+        if let Some(mock_network) = &self.mock_network {
+            let chunk = mock_network
+                .get(&url)
+                .cloned()
+                // The error is complete nonsense, but this is test-only.
+                .ok_or_else(|| {
+                    tracing::warn!("Attempt to fetch unsupported URL from mock network: {url}");
+                    DownloadError::FailedToWriteDownload {
+                        target: url.to_string().into(),
+                        error: std::io::Error::new(
+                            std::io::ErrorKind::Other,
+                            format!("mock network does not support URL: {url}"),
+                        ),
+                    }
+                })?;
+            return Ok(Response::Mock(Some(chunk)));
+        }
+
+        let permit = self
+            .connection_semaphore
+            .acquire()
+            .await
+            .expect("Semaphore dropped?!");
+
+        let res = self
+            .client
+            .get(url.clone())
+            .send()
+            .await
+            .and_then(|res| res.error_for_status())
+            .map_err(|error| DownloadError::FailedToStartDownload {
+                url: Box::new(url.clone()),
+                error,
+            })?;
+
+        Ok(Response::Real(res, permit))
+    }
+}
+
+#[cfg(test)]
+impl Network {
+    /// Create a new Network which is serving mocked out resources.
+    pub(crate) fn new_mock() -> Self {
+        let mut network = Network {
+            client: Client::new(),
+            connection_semaphore: tokio::sync::Semaphore::new(MAX_CONCURRENT_CONNECTIONS),
+            source_file_cache: Default::default(),
+            #[cfg(test)]
+            mock_network: Some(Default::default()),
+        };
+        // Serve an empty registry by default.
+        network.mock_serve_toml(
+            crate::storage::REGISTRY_URL,
+            &crate::format::RegistryFile::default(),
+        );
+        network
+    }
+
+    /// Add a new resource to be served by a mocked-out network.
+    pub(crate) fn mock_serve(&mut self, url: impl AsRef<str>, data: impl AsRef<[u8]>) {
+        self.mock_network
+            .as_mut()
+            .expect("not a mock network")
+            .insert(
+                url.as_ref().parse().unwrap(),
+                Bytes::copy_from_slice(data.as_ref()),
+            );
+    }
+
+    /// Add a new toml resource to be served by a mocked-out network.
+    pub(crate) fn mock_serve_toml(&mut self, url: impl AsRef<str>, data: &impl serde::Serialize) {
+        self.mock_serve(
+            url,
+            crate::serialization::to_formatted_toml(data, None)
+                .unwrap()
+                .to_string(),
+        );
+    }
+
+    /// Add a new json resource to be served by a mocked-out network.
+    pub(crate) fn mock_serve_json(&mut self, url: impl AsRef<str>, data: &impl serde::Serialize) {
+        self.mock_serve(url, serde_json::to_string(data).unwrap());
+    }
+}
diff --git a/src/out.rs b/src/out.rs
new file mode 100644
index 0000000..d6e3f14
--- /dev/null
+++ b/src/out.rs
@@ -0,0 +1,201 @@
+//! Types for abstracting over communicating with the user, with support for
+//! mocking in tests.
+//!
+//! In general, this type should be preferred over directly writing to stdout or
+//! stderr.
+
+use crate::git_tool::Editor;
+use console::{Style, Term};
+use indicatif::{MultiProgress, ProgressBar, ProgressDrawTarget, ProgressStyle};
+use lazy_static::lazy_static;
+use std::{borrow::Cow, fmt, fs::File, io, mem, time::Duration};
+
+/// Object-safe extension of `std::io::Write` with extra features for
+/// interacting with the terminal. Can be mocked in tests to allow them to test
+/// other features.
+pub trait Out: Send + Sync + 'static {
+    /// Write to the output.
+    fn write(&self, buf: &[u8]) -> io::Result<usize>;
+
+    /// Write to the output
+    fn write_fmt(&self, args: fmt::Arguments<'_>) {
+        struct AsWrite<'a, T: ?Sized>(&'a T);
+        impl<'a, T: ?Sized + Out> io::Write for AsWrite<'a, T> {
+            fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+                Out::write(self.0, buf)
+            }
+
+            fn flush(&mut self) -> io::Result<()> {
+                Ok(())
+            }
+        }
+
+        io::Write::write_fmt(&mut AsWrite(self), args).unwrap();
+    }
+
+    /// Check if this output is a real terminal.
+    fn is_term(&self) -> bool {
+        false
+    }
+
+    /// If the user is interacting through a terminal, clear the screen.
+    /// Should fail silently if the screen cannot be cleared.
+    fn clear_screen(&self) -> io::Result<()> {
+        Ok(())
+    }
+
+    /// Ask the user a question, and read in a line with the user's response. If
+    /// there's no user able to respond, an error will be returned instead.
+    fn read_line_with_prompt(&self, _prompt: &str) -> io::Result<String> {
+        Err(io::ErrorKind::Unsupported.into())
+    }
+
+    /// Get a `Style` object which can be used to style text written to this
+    /// user. Defaults to a disabled style object.
+    fn style(&self) -> Style {
+        Style::new().force_styling(false)
+    }
+
+    /// Create an editor to prompt the user with.
+    /// Exists primarily to allow tests to mock out this feature, and block
+    /// editor usage when using a file output.
+    fn editor<'a>(&'a self, _name: &'a str) -> io::Result<Editor<'a>> {
+        Err(io::ErrorKind::Unsupported.into())
+    }
+}
+
+// "Real" user-facing terminal on stdout
+impl Out for Term {
+    fn write(&self, buf: &[u8]) -> io::Result<usize> {
+        // XXX: Consider suspending the MultiProgress when writing if we ever
+        // want to write to `Out` while a progress bar is rendering.
+        io::Write::write(&mut &*self, buf)
+    }
+
+    fn is_term(&self) -> bool {
+        self.is_term()
+    }
+
+    fn clear_screen(&self) -> io::Result<()> {
+        self.clear_screen()
+    }
+
+    fn read_line_with_prompt(&self, prompt: &str) -> io::Result<String> {
+        self.write_str(prompt)?;
+        self.flush()?;
+        self.read_line()
+    }
+
+    fn style(&self) -> Style {
+        self.style()
+    }
+
+    fn editor(&self, name: &str) -> io::Result<Editor<'_>> {
+        Editor::new(name)
+    }
+}
+
+// File based output with no special features.
+impl Out for File {
+    fn write(&self, buf: &[u8]) -> io::Result<usize> {
+        io::Write::write(&mut &*self, buf)
+    }
+}
+
+impl io::Write for &'_ dyn Out {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        Out::write(*self, buf)
+    }
+
+    fn flush(&mut self) -> io::Result<()> {
+        Ok(())
+    }
+}
+
+lazy_static! {
+    /// The global `MultiProgress` which can be used to write out progress reports.
+    ///
+    /// By default, this is invisible so that it isn't shown during tests, but
+    /// it will be configured to be visible early during main.
+    pub static ref MULTIPROGRESS: MultiProgress =
+        MultiProgress::with_draw_target(ProgressDrawTarget::hidden());
+}
+
+/// Helper for bracketing some region with an indeterminate spinner which shows
+/// no meaningful progress.
+pub fn indeterminate_spinner(
+    prefix: impl Into<Cow<'static, str>>,
+    message: impl Into<Cow<'static, str>>,
+) -> ProgressBar {
+    let progress_bar = MULTIPROGRESS.add(
+        ProgressBar::new_spinner()
+            .with_style(
+                ProgressStyle::with_template("{prefix:>12.cyan.bold} {msg} {spinner}").unwrap(),
+            )
+            .with_prefix(prefix)
+            .with_message(message),
+    );
+    progress_bar.enable_steady_tick(Duration::from_millis(100));
+    progress_bar
+}
+
+/// Create a new progress bar with a cargo-inspired style.
+pub fn progress_bar(
+    prefix: impl Into<Cow<'static, str>>,
+    message: impl Into<Cow<'static, str>>,
+    len: u64,
+) -> ProgressBar {
+    let progress_bar = MULTIPROGRESS.add(
+        ProgressBar::new(len)
+            .with_style(
+                ProgressStyle::with_template("{prefix:>12.cyan.bold} {msg} [{bar:57}] {pos}/{len}")
+                    .unwrap()
+                    .progress_chars("=> "),
+            )
+            .with_prefix(prefix)
+            .with_message(message),
+    );
+    progress_bar.tick();
+    progress_bar
+}
+
+/// Helper guard object to increment progress for the given progress bar by the
+/// given amount when this object is destroyed.
+pub struct IncProgressOnDrop<'a>(pub &'a ProgressBar, pub u64);
+impl Drop for IncProgressOnDrop<'_> {
+    fn drop(&mut self) {
+        self.0.inc(self.1);
+    }
+}
+
+/// A helper type which implements `io::Write`, and will buffer up input, then
+/// suspend the MultiProgress and write it all out when dropped to avoid
+/// tearing.
+///
+/// This is used for tracing logs when no log file is specified.
+pub struct StderrLogWriter {
+    buffer: Vec<u8>,
+}
+
+impl StderrLogWriter {
+    pub fn new() -> Self {
+        StderrLogWriter { buffer: Vec::new() }
+    }
+}
+
+impl io::Write for StderrLogWriter {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        io::Write::write(&mut self.buffer, buf)
+    }
+
+    fn flush(&mut self) -> io::Result<()> {
+        let buffer = mem::take(&mut self.buffer);
+        MULTIPROGRESS.suspend(|| io::stderr().write_all(&buffer))
+    }
+}
+
+impl Drop for StderrLogWriter {
+    fn drop(&mut self) {
+        let _ = io::Write::flush(self);
+    }
+}
diff --git a/src/resolver.rs b/src/resolver.rs
new file mode 100644
index 0000000..51e0107
--- /dev/null
+++ b/src/resolver.rs
@@ -0,0 +1,3087 @@
+//! 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:
+//!
+//! 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 Searching and producing a [`ResolveReport`]
+//! * [`ResolveReport::compute_suggest`] does Suggesting and produces a [`Suggest`]
+//! * various methods on [`ResolveReport`] and [`Suggest`] handle printing
+//! * [`update_store`] handles automatically minimizing and generating exemptions and imports
+//!
+//! # Low-level Design
+//!
+//!
+//! ## Resolve
+//!
+//! * construct the [`DepGraph`] and [`CriteriaMapper`]
+//!     * the DepGraph contains computed facts like whether a node is a third-party or dev-only
+//!       and includes a special topological sorting of the packages that prioritizes the normal
+//!       build over the dev build (it's complicated...)
+//!
+//! * 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
+//!
+//! * 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 futures_util::future::join_all;
+use miette::IntoDiagnostic;
+use serde::{Deserialize, Serialize};
+use std::cell::RefCell;
+use std::cmp::Reverse;
+use std::collections::BinaryHeap;
+use std::sync::Arc;
+use tracing::{trace, trace_span, warn};
+
+use crate::cli::{DumpGraphArgs, GraphFilter, GraphFilterProperty, GraphFilterQuery, OutputFormat};
+use crate::criteria::{CriteriaMapper, CriteriaSet};
+use crate::errors::SuggestError;
+use crate::format::{
+    self, AuditEntry, AuditKind, AuditsFile, CratesCacheUser, CratesPublisher, CriteriaName, Delta,
+    DiffStat, ExemptedDependency, FastMap, FastSet, ImportName, ImportsFile, JsonPackage,
+    JsonReport, JsonReportConclusion, JsonReportFailForVet, JsonReportFailForViolationConflict,
+    JsonReportSuccess, JsonSuggest, JsonSuggestItem, JsonVetFailure, PackageName, PackageStr,
+    Policy, UnpublishedEntry, VetVersion, WildcardEntry,
+};
+use crate::format::{SortedMap, SortedSet};
+use crate::network::Network;
+use crate::out::{progress_bar, IncProgressOnDrop, Out};
+use crate::storage::Cache;
+use crate::string_format::FormatShortList;
+use crate::{Config, PackageExt, Store};
+
+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`][]. Will be `None` for first-party
+    /// crates or crates with violation conflicts.
+    pub results: Vec<Option<ResolveResult>>,
+
+    /// The final conclusion of our analysis.
+    pub conclusion: Conclusion,
+}
+
+#[derive(Debug)]
+pub enum Conclusion {
+    Success(Success),
+    FailForViolationConflict(FailForViolationConflict),
+    FailForVet(FailForVet),
+}
+
+#[derive(Debug, Clone)]
+pub struct Success {
+    /// Third-party packages that were successfully vetted using only 'exemptions'
+    pub vetted_with_exemptions: Vec<PackageIdx>,
+    /// Third-party packages that were successfully vetted using both 'audits' and 'exemptions'
+    pub vetted_partially: Vec<PackageIdx>,
+    /// Third-party packages that were successfully vetted using only 'audits'
+    pub vetted_fully: Vec<PackageIdx>,
+}
+
+#[derive(Debug, Clone)]
+pub struct FailForViolationConflict {
+    pub violations: Vec<(PackageIdx, Vec<ViolationConflict>)>,
+}
+
+#[derive(Debug)]
+pub struct FailForVet {
+    /// These packages are to blame and need to be fixed
+    pub failures: Vec<(PackageIdx, AuditFailure)>,
+    pub suggest: Option<Suggest>,
+}
+
+// FIXME: This format is pretty janky and unstable, so we probably should come
+// up with an actually-useful format for this.
+#[allow(clippy::large_enum_variant)]
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub enum ViolationConflict {
+    UnauditedConflict {
+        violation_source: Option<ImportName>,
+        violation: AuditEntry,
+        exemptions: ExemptedDependency,
+    },
+    AuditConflict {
+        violation_source: Option<ImportName>,
+        violation: AuditEntry,
+        audit_source: Option<ImportName>,
+        audit: AuditEntry,
+    },
+}
+
+#[derive(Debug, Default)]
+pub struct Suggest {
+    pub suggestions: Vec<SuggestItem>,
+    pub suggestions_by_criteria: SortedMap<CriteriaName, Vec<SuggestItem>>,
+    pub total_lines: u64,
+    pub warnings: Vec<String>,
+}
+
+#[derive(Debug, Clone)]
+pub struct TrustHint {
+    trusted_by: Vec<String>,
+    publisher: CratesCacheUser,
+    exact_version: bool,
+}
+
+#[derive(Debug, Clone)]
+pub struct SuggestItem {
+    pub package: PackageIdx,
+    pub suggested_criteria: CriteriaSet,
+    pub suggested_diff: DiffRecommendation,
+    pub notable_parents: Vec<String>,
+    pub publisher_login: Option<String>,
+    pub trust_hint: Option<TrustHint>,
+    pub is_sole_publisher: bool,
+    pub registry_suggestion: Vec<RegistrySuggestion>,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
+pub struct DiffRecommendation {
+    pub from: Option<VetVersion>,
+    pub to: VetVersion,
+    pub diffstat: DiffStat,
+}
+
+#[derive(Debug, Clone)]
+pub struct RegistrySuggestion {
+    pub name: ImportName,
+    pub url: Vec<String>,
+    pub diff: DiffRecommendation,
+}
+
+/// An "interned" cargo PackageId which is used to uniquely identify packages throughout
+/// the code. This is simpler and faster than actually using PackageIds (strings) or name+version.
+/// In the current implementation it can be used to directly index into the `graph` or `results`.
+pub type PackageIdx = usize;
+
+#[derive(Debug, Clone, Serialize)]
+pub struct PackageNode<'a> {
+    #[serde(skip_serializing_if = "pkgid_unstable")]
+    /// The PackageId that cargo uses to uniquely identify this package
+    ///
+    /// Prefer using a [`DepGraph`] and its memoized [`PackageIdx`]'s.
+    pub package_id: &'a PackageId,
+    /// The name of the package
+    pub name: PackageStr<'a>,
+    /// The version of this package
+    pub version: VetVersion,
+    /// All normal deps (shipped in the project or a proc-macro it uses)
+    pub normal_deps: Vec<PackageIdx>,
+    /// All build deps (used for build.rs)
+    pub build_deps: Vec<PackageIdx>,
+    /// All dev deps (used for tests/benches)
+    pub dev_deps: Vec<PackageIdx>,
+    /// Just the normal and build deps (deduplicated)
+    pub normal_and_build_deps: Vec<PackageIdx>,
+    /// All deps combined (deduplicated)
+    pub all_deps: Vec<PackageIdx>,
+    /// All reverse-deps (mostly just used for contextualizing what uses it)
+    pub reverse_deps: SortedSet<PackageIdx>,
+    /// Whether this package is a workspace member (can have dev-deps)
+    pub is_workspace_member: bool,
+    /// Whether this package is third-party (from crates.io)
+    pub is_third_party: bool,
+    /// Whether this package is a root in the "normal" build graph
+    pub is_root: bool,
+    /// Whether this package only shows up in dev (test/bench) builds
+    pub is_dev_only: bool,
+}
+
+/// Don't serialize path package ids, not stable across systems
+fn pkgid_unstable(pkgid: &PackageId) -> bool {
+    pkgid.repr.contains("(path+file:/")
+}
+
+/// The dependency graph in a form we can use more easily.
+#[derive(Debug, Clone)]
+pub struct DepGraph<'a> {
+    pub nodes: Vec<PackageNode<'a>>,
+    pub interner_by_pkgid: SortedMap<&'a PackageId, PackageIdx>,
+    pub topo_index: Vec<PackageIdx>,
+}
+
+/// Results and notes from running vet on a particular package.
+#[derive(Debug, Clone)]
+pub struct ResolveResult {
+    /// Cache of search results for each criteria.
+    pub search_results: Vec<Result<Vec<DeltaEdgeOrigin>, SearchFailure>>,
+}
+
+#[derive(Debug, Clone)]
+pub struct AuditFailure {
+    pub criteria_failures: CriteriaSet,
+}
+
+/// Value indicating a failure to find a path in the audit graph between two nodes.
+#[derive(Debug, Clone)]
+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>>,
+}
+
+type DirectedAuditGraph<'a> = SortedMap<Option<&'a VetVersion>, Vec<DeltaEdge<'a>>>;
+
+/// A graph of the audits for a package.
+///
+/// The nodes of the graph are Versions and the edges are audits.
+/// An AuditGraph is directed, potentially cyclic, and potentially disconnected.
+///
+/// There are two important versions in each AuditGraph:
+///
+/// * The "root" version (None) which exists as a dummy node for full-audits
+/// * The "target" version which is the current version of the package
+///
+/// The edges are constructed as follows:
+///
+/// * Delta Audits desugar directly to edges
+/// * Full Audits and Unaudited desugar to None -> Some(Version)
+///
+/// If there are multiple versions of a package in-tree, we analyze each individually
+/// so there is always one root and one target. All we want to know is if there exists
+/// a path between the two where every edge on that path has a given criteria. We do this
+/// check for every possible criteria in a loop to keep the analysis simple and composable.
+///
+/// When resolving the audits for a package, we create a "forward" graph and a "backward" graph.
+/// These are the same graphs but with the edges reversed. The backward graph is only used if
+/// 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`.
+#[derive(Debug, Clone)]
+pub struct AuditGraph<'a> {
+    forward_audits: DirectedAuditGraph<'a>,
+    backward_audits: DirectedAuditGraph<'a>,
+}
+
+/// The precise origin of an edge in the audit graph.
+#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
+pub enum DeltaEdgeOrigin {
+    /// This edge represents an audit from the local audits.toml.
+    StoredLocalAudit { audit_index: usize },
+    /// This edge represents an audit imported from a peer, potentially stored
+    /// in the local imports.lock.
+    ImportedAudit {
+        import_index: usize,
+        audit_index: usize,
+    },
+    /// This edge represents a reified wildcard audit.
+    WildcardAudit {
+        import_index: Option<usize>,
+        audit_index: usize,
+        publisher_index: usize,
+    },
+    /// This edge represents a trusted publisher.
+    Trusted { publisher_index: usize },
+    /// This edge represents an exemption from the local config.toml.
+    Exemption { exemption_index: usize },
+    /// This edge represents an unpublished entry in imports.lock.
+    Unpublished { unpublished_index: usize },
+    /// This edge represents brand new exemption which didn't previously exist
+    /// in the audit graph. Will only ever be produced from
+    /// SearchMode::RegenerateExemptions.
+    FreshExemption { version: VetVersion },
+}
+
+/// An indication of a required imported entry or exemption. Used to compute the
+/// minimal set of possible imports for imports.lock.
+#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
+pub enum RequiredEntry {
+    Audit {
+        import_index: usize,
+        audit_index: usize,
+    },
+    WildcardAudit {
+        import_index: usize,
+        audit_index: usize,
+    },
+    Publisher {
+        publisher_index: usize,
+    },
+    Exemption {
+        exemption_index: usize,
+    },
+    Unpublished {
+        unpublished_index: usize,
+    },
+    // NOTE: This variant must come last, as code in `update_store` depends on
+    // `FreshExemption` entries sorting after all other entries.
+    FreshExemption {
+        version: VetVersion,
+    },
+}
+
+/// A directed edge in the graph of audits. This may be forward or backwards,
+/// 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)]
+struct DeltaEdge<'a> {
+    /// The version this edge goes to.
+    version: Option<&'a VetVersion>,
+    /// The criteria that this edge is valid for.
+    criteria: CriteriaSet,
+    /// The origin of this edge. See `DeltaEdgeOrigin`'s documentation for more
+    /// details.
+    origin: DeltaEdgeOrigin,
+    /// Whether or not the edge is a "fresh import", and should be
+    /// de-prioritized to avoid unnecessary imports.lock updates.
+    is_fresh_import: bool,
+}
+
+#[derive(Debug, Copy, Clone, Eq, PartialEq)]
+pub enum SearchMode {
+    /// Prefer exemptions over fresh imports when searching.
+    PreferExemptions,
+    /// Prefer fresh imports over exemptions when searching for paths.
+    PreferFreshImports,
+    /// Prefer fresh imports over exemptions, and allow introducing new
+    /// exemptions or expanding their criteria beyond the written criteria
+    /// (unless they are suggest=false).
+    RegenerateExemptions,
+}
+
+impl<'a> DepGraph<'a> {
+    pub fn new(
+        metadata: &'a Metadata,
+        filter_graph: Option<&Vec<GraphFilter>>,
+        policy: Option<&Policy>,
+    ) -> Self {
+        let default_policy = Policy::default();
+        let policy = policy.unwrap_or(&default_policy);
+        let package_list = &*metadata.packages;
+        let resolve_list = &*metadata
+            .resolve
+            .as_ref()
+            .expect("cargo metadata did not yield resolve!")
+            .nodes;
+        let package_index_by_pkgid = package_list
+            .iter()
+            .enumerate()
+            .map(|(idx, pkg)| (&pkg.id, idx))
+            .collect::<SortedMap<_, _>>();
+        let resolve_index_by_pkgid = resolve_list
+            .iter()
+            .enumerate()
+            .map(|(idx, pkg)| (&pkg.id, idx))
+            .collect();
+
+        // Do a first-pass where we populate skeletons of the primary nodes
+        // and setup the interners, which will only ever refer to these nodes
+        let mut interner_by_pkgid = SortedMap::<&PackageId, PackageIdx>::new();
+        let mut nodes = vec![];
+
+        // Stub out the initial state of all the nodes
+        for resolve_node in resolve_list {
+            let package = &package_list[package_index_by_pkgid[&resolve_node.id]];
+            nodes.push(PackageNode {
+                package_id: &resolve_node.id,
+                name: &package.name,
+                version: package.vet_version(),
+                is_third_party: package.is_third_party(policy),
+                // These will get (re)computed later
+                normal_deps: vec![],
+                build_deps: vec![],
+                dev_deps: vec![],
+                normal_and_build_deps: vec![],
+                all_deps: vec![],
+                reverse_deps: SortedSet::new(),
+                is_workspace_member: false,
+                is_root: false,
+                is_dev_only: true,
+            });
+        }
+
+        // Sort the nodes by package_id to make the graph more stable and to make
+        // anything sorted by package_idx to also be approximately sorted by name and version.
+        nodes.sort_by_key(|k| k.package_id);
+
+        // Populate the interners based on the new ordering
+        for (idx, node) in nodes.iter_mut().enumerate() {
+            assert!(interner_by_pkgid.insert(node.package_id, idx).is_none());
+        }
+
+        // Do topological sort: just recursively visit all of a node's children, and only add it
+        // to the list *after* visiting the children. In this way we have trivially already added
+        // all of the dependencies of a node to the list by the time we add itself to the list.
+        let mut topo_index = vec![];
+        {
+            let mut visited = FastMap::new();
+            // All of the roots can be found in the workspace_members.
+            // First we visit all the workspace members while ignoring dev-deps,
+            // this should get us an analysis of the "normal" build graph, which
+            // we should compute roots from. Then we will do a second pass on
+            // the dev-deps. If we don't do it this way, then dev-dep cycles can
+            // confuse us about which nodes are roots or not (potentially resulting
+            // in no roots at all!
+            for pkgid in &metadata.workspace_members {
+                let node_idx = interner_by_pkgid[pkgid];
+                nodes[node_idx].is_workspace_member = true;
+                visit_node(
+                    &mut nodes,
+                    &mut topo_index,
+                    &mut visited,
+                    &interner_by_pkgid,
+                    &resolve_index_by_pkgid,
+                    resolve_list,
+                    node_idx,
+                );
+            }
+
+            // Anything we visited in the first pass isn't dev-only
+            for (&node_idx, ()) in &visited {
+                nodes[node_idx].is_dev_only = false;
+            }
+
+            // Now that we've visited the normal build graph, mark the nodes that are roots
+            for pkgid in &metadata.workspace_members {
+                let node = &mut nodes[interner_by_pkgid[pkgid]];
+                node.is_root = node.reverse_deps.is_empty();
+            }
+
+            // And finally visit workspace-members' dev-deps, safe in the knowledge that
+            // we know what all the roots are now.
+            for pkgid in &metadata.workspace_members {
+                let node_idx = interner_by_pkgid[pkgid];
+                let resolve_node = &resolve_list[resolve_index_by_pkgid[pkgid]];
+                let dev_deps = deps(
+                    resolve_node,
+                    &[DependencyKind::Development],
+                    &interner_by_pkgid,
+                );
+
+                // Now visit all the dev deps
+                for &child in &dev_deps {
+                    visit_node(
+                        &mut nodes,
+                        &mut topo_index,
+                        &mut visited,
+                        &interner_by_pkgid,
+                        &resolve_index_by_pkgid,
+                        resolve_list,
+                        child,
+                    );
+                    // Note that these edges do not change whether something is a "root"
+                    nodes[child].reverse_deps.insert(node_idx);
+                }
+
+                let node = &mut nodes[node_idx];
+                node.dev_deps = dev_deps;
+            }
+            fn visit_node<'a>(
+                nodes: &mut Vec<PackageNode<'a>>,
+                topo_index: &mut Vec<PackageIdx>,
+                visited: &mut FastMap<PackageIdx, ()>,
+                interner_by_pkgid: &SortedMap<&'a PackageId, PackageIdx>,
+                resolve_index_by_pkgid: &SortedMap<&'a PackageId, usize>,
+                resolve_list: &'a [cargo_metadata::Node],
+                normal_idx: PackageIdx,
+            ) {
+                // Don't revisit a node we've already seen
+                let query = visited.entry(normal_idx);
+                if matches!(query, std::collections::hash_map::Entry::Vacant(..)) {
+                    query.or_insert(());
+                    let resolve_node =
+                        &resolve_list[resolve_index_by_pkgid[nodes[normal_idx].package_id]];
+
+                    // Compute the different kinds of dependencies
+                    let all_deps = resolve_node
+                        .dependencies
+                        .iter()
+                        .map(|pkgid| interner_by_pkgid[pkgid])
+                        .collect::<Vec<_>>();
+                    let build_deps =
+                        deps(resolve_node, &[DependencyKind::Build], interner_by_pkgid);
+                    let normal_deps =
+                        deps(resolve_node, &[DependencyKind::Normal], interner_by_pkgid);
+                    let normal_and_build_deps = deps(
+                        resolve_node,
+                        &[DependencyKind::Normal, DependencyKind::Build],
+                        interner_by_pkgid,
+                    );
+
+                    // Now visit all the normal and build deps
+                    for &child in &normal_and_build_deps {
+                        visit_node(
+                            nodes,
+                            topo_index,
+                            visited,
+                            interner_by_pkgid,
+                            resolve_index_by_pkgid,
+                            resolve_list,
+                            child,
+                        );
+                        nodes[child].reverse_deps.insert(normal_idx);
+                    }
+
+                    // Now visit this node itself
+                    topo_index.push(normal_idx);
+
+                    // Now commit all the deps
+                    let cur_node = &mut nodes[normal_idx];
+                    cur_node.build_deps = build_deps;
+                    cur_node.normal_deps = normal_deps;
+                    cur_node.normal_and_build_deps = normal_and_build_deps;
+                    cur_node.all_deps = all_deps;
+
+                    // dev-deps will be handled in a second pass
+                }
+            }
+            fn deps(
+                resolve_node: &Node,
+                kinds: &[DependencyKind],
+                interner_by_pkgid: &SortedMap<&PackageId, PackageIdx>,
+            ) -> Vec<PackageIdx> {
+                // Note that dep_kinds has target cfg info. If we want to handle targets
+                // we should gather those up with filter/fold instead of just 'any'.
+                // TODO: map normal-deps that whose package has a "proc-macro" target to be build-deps
+                resolve_node
+                    .deps
+                    .iter()
+                    .filter(|dep| {
+                        dep.dep_kinds
+                            .iter()
+                            .any(|dep_kind| kinds.contains(&dep_kind.kind))
+                    })
+                    .map(|dep| interner_by_pkgid[&dep.pkg])
+                    .collect()
+            }
+        }
+
+        let result = Self {
+            interner_by_pkgid,
+            nodes,
+            topo_index,
+        };
+
+        // Now apply filters, if any
+        if let Some(filters) = filter_graph {
+            result.filter(filters)
+        } else {
+            result
+        }
+    }
+
+    pub fn filter(self, filters: &[GraphFilter]) -> Self {
+        use GraphFilter::*;
+        use GraphFilterProperty::*;
+        use GraphFilterQuery::*;
+
+        fn matches_query(package: &PackageNode, query: &GraphFilterQuery) -> bool {
+            match query {
+                All(queries) => queries.iter().all(|q| matches_query(package, q)),
+                Any(queries) => queries.iter().any(|q| matches_query(package, q)),
+                Not(query) => !matches_query(package, query),
+                Prop(property) => matches_property(package, property),
+            }
+        }
+        fn matches_property(package: &PackageNode, property: &GraphFilterProperty) -> bool {
+            match property {
+                Name(val) => package.name == val,
+                Version(val) => &package.version == val,
+                IsRoot(val) => &package.is_root == val,
+                IsWorkspaceMember(val) => &package.is_workspace_member == val,
+                IsThirdParty(val) => &package.is_third_party == val,
+                IsDevOnly(val) => &package.is_dev_only == val,
+            }
+        }
+
+        let mut passed_filters = FastSet::new();
+        'nodes: for (idx, package) in self.nodes.iter().enumerate() {
+            for filter in filters {
+                match filter {
+                    Include(query) => {
+                        if !matches_query(package, query) {
+                            continue 'nodes;
+                        }
+                    }
+
+                    Exclude(query) => {
+                        if matches_query(package, query) {
+                            continue 'nodes;
+                        }
+                    }
+                }
+            }
+            // If we pass all the filters, then we get to be included
+            passed_filters.insert(idx);
+        }
+
+        let mut reachable = FastMap::new();
+        for (idx, package) in self.nodes.iter().enumerate() {
+            if package.is_workspace_member {
+                visit(&mut reachable, &self, &passed_filters, idx);
+            }
+            fn visit(
+                visited: &mut FastMap<PackageIdx, ()>,
+                graph: &DepGraph,
+                passed_filters: &FastSet<PackageIdx>,
+                node_idx: PackageIdx,
+            ) {
+                if !passed_filters.contains(&node_idx) {
+                    return;
+                }
+                let query = visited.entry(node_idx);
+                if matches!(query, std::collections::hash_map::Entry::Vacant(..)) {
+                    query.or_insert(());
+                    for &child in &graph.nodes[node_idx].all_deps {
+                        visit(visited, graph, passed_filters, child);
+                    }
+                }
+            }
+        }
+
+        let mut old_to_new = FastMap::new();
+        let mut nodes = Vec::new();
+        let mut interner_by_pkgid = SortedMap::new();
+        let mut topo_index = Vec::new();
+        for (old_idx, package) in self.nodes.iter().enumerate() {
+            if !reachable.contains_key(&old_idx) {
+                continue;
+            }
+            let new_idx = nodes.len();
+            old_to_new.insert(old_idx, new_idx);
+            nodes.push(PackageNode {
+                package_id: package.package_id,
+                name: package.name,
+                version: package.version.clone(),
+                normal_deps: vec![],
+                build_deps: vec![],
+                dev_deps: vec![],
+                normal_and_build_deps: vec![],
+                all_deps: vec![],
+                reverse_deps: SortedSet::new(),
+                is_workspace_member: package.is_workspace_member,
+                is_third_party: package.is_third_party,
+                is_root: package.is_root,
+                is_dev_only: package.is_dev_only,
+            });
+            interner_by_pkgid.insert(package.package_id, new_idx);
+        }
+        for old_idx in &self.topo_index {
+            if let Some(&new_idx) = old_to_new.get(old_idx) {
+                topo_index.push(new_idx);
+            }
+        }
+        for (old_idx, old_package) in self.nodes.iter().enumerate() {
+            if let Some(&new_idx) = old_to_new.get(&old_idx) {
+                let new_package = &mut nodes[new_idx];
+                for old_dep in &old_package.normal_deps {
+                    if let Some(&new_dep) = old_to_new.get(old_dep) {
+                        new_package.normal_deps.push(new_dep);
+                    }
+                }
+                for old_dep in &old_package.build_deps {
+                    if let Some(&new_dep) = old_to_new.get(old_dep) {
+                        new_package.build_deps.push(new_dep);
+                    }
+                }
+                for old_dep in &old_package.dev_deps {
+                    if let Some(&new_dep) = old_to_new.get(old_dep) {
+                        new_package.dev_deps.push(new_dep);
+                    }
+                }
+                for old_dep in &old_package.normal_and_build_deps {
+                    if let Some(&new_dep) = old_to_new.get(old_dep) {
+                        new_package.normal_and_build_deps.push(new_dep);
+                    }
+                }
+                for old_dep in &old_package.all_deps {
+                    if let Some(&new_dep) = old_to_new.get(old_dep) {
+                        new_package.all_deps.push(new_dep);
+                    }
+                }
+                for old_dep in &old_package.reverse_deps {
+                    if let Some(&new_dep) = old_to_new.get(old_dep) {
+                        new_package.reverse_deps.insert(new_dep);
+                    }
+                }
+            }
+        }
+
+        Self {
+            nodes,
+            interner_by_pkgid,
+            topo_index,
+        }
+    }
+
+    pub fn print_mermaid(
+        &self,
+        out: &Arc<dyn Out>,
+        sub_args: &DumpGraphArgs,
+    ) -> Result<(), std::io::Error> {
+        use crate::DumpGraphDepth::*;
+        let depth = sub_args.depth;
+
+        let mut visible_nodes = SortedSet::new();
+        let mut nodes_with_children = SortedSet::new();
+        let mut shown = SortedSet::new();
+
+        for (idx, package) in self.nodes.iter().enumerate() {
+            if (package.is_root && depth >= Roots)
+                || (package.is_workspace_member && depth >= Workspace)
+                || (!package.is_third_party && depth >= FirstParty)
+                || depth >= Full
+            {
+                visible_nodes.insert(idx);
+                nodes_with_children.insert(idx);
+
+                if depth >= FirstPartyAndDirects {
+                    for &dep in &package.all_deps {
+                        visible_nodes.insert(dep);
+                    }
+                }
+            }
+        }
+
+        writeln!(out, "graph LR");
+
+        writeln!(out, "    subgraph roots");
+        for &idx in &visible_nodes {
+            let package = &self.nodes[idx];
+            if package.is_root && shown.insert(idx) {
+                writeln!(
+                    out,
+                    "        node{idx}{{{}:{}}}",
+                    package.name, package.version
+                );
+            }
+        }
+        writeln!(out, "    end");
+
+        writeln!(out, "    subgraph workspace-members");
+        for &idx in &visible_nodes {
+            let package = &self.nodes[idx];
+            if package.is_workspace_member && shown.insert(idx) {
+                writeln!(
+                    out,
+                    "        node{idx}[/{}:{}/]",
+                    package.name, package.version
+                );
+            }
+        }
+        writeln!(out, "    end");
+
+        writeln!(out, "    subgraph first-party");
+        for &idx in &visible_nodes {
+            let package = &self.nodes[idx];
+            if !package.is_third_party && shown.insert(idx) {
+                writeln!(
+                    out,
+                    "        node{idx}[{}:{}]",
+                    package.name, package.version
+                );
+            }
+        }
+        writeln!(out, "    end");
+
+        writeln!(out, "    subgraph third-party");
+        for &idx in &visible_nodes {
+            let package = &self.nodes[idx];
+            if shown.insert(idx) {
+                writeln!(
+                    out,
+                    "        node{idx}({}:{})",
+                    package.name, package.version
+                );
+            }
+        }
+        writeln!(out, "    end");
+
+        for &idx in &nodes_with_children {
+            let package = &self.nodes[idx];
+            for &dep_idx in &package.all_deps {
+                if visible_nodes.contains(&dep_idx) {
+                    writeln!(out, "    node{idx} --> node{dep_idx}");
+                }
+            }
+        }
+
+        Ok(())
+    }
+}
+
+pub fn resolve<'a>(
+    metadata: &'a Metadata,
+    filter_graph: Option<&Vec<GraphFilter>>,
+    store: &Store,
+) -> ResolveReport<'a> {
+    // A large part of our algorithm is unioning and intersecting criteria, so we map all
+    // the criteria into indexed boolean sets (*whispers* an integer with lots of bits).
+    let graph = DepGraph::new(metadata, filter_graph, Some(&store.config.policy));
+    // trace!("built DepGraph: {:#?}", graph);
+    trace!("built DepGraph!");
+
+    let criteria_mapper = CriteriaMapper::new(&store.audits.criteria);
+    trace!("built CriteriaMapper!");
+
+    let requirements = resolve_requirements(&graph, &store.config.policy, &criteria_mapper);
+
+    let (results, conclusion) = resolve_audits(&graph, store, &criteria_mapper, &requirements);
+
+    ResolveReport {
+        graph,
+        criteria_mapper,
+        results,
+        conclusion,
+    }
+}
+
+fn resolve_requirements(
+    graph: &DepGraph<'_>,
+    policy: &Policy,
+    criteria_mapper: &CriteriaMapper,
+) -> Vec<CriteriaSet> {
+    let _resolve_requirements = trace_span!("resolve_requirements").entered();
+
+    let mut requirements = vec![criteria_mapper.no_criteria(); graph.nodes.len()];
+
+    // 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;
+        }
+
+        let policy = policy.get(package.name, &package.version);
+        let dev_criteria = if let Some(c) = policy.and_then(|p| p.dev_criteria.as_ref()) {
+            criteria_mapper.criteria_from_list(c)
+        } else {
+            criteria_mapper.criteria_from_list([format::DEFAULT_POLICY_DEV_CRITERIA])
+        };
+
+        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));
+        }
+    }
+
+    // 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];
+        let policy = policy.get(package.name, &package.version);
+
+        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();
+
+        // 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));
+        }
+    }
+
+    requirements
+}
+
+fn resolve_audits(
+    graph: &DepGraph<'_>,
+    store: &Store,
+    criteria_mapper: &CriteriaMapper,
+    requirements: &[CriteriaSet],
+) -> (Vec<Option<ResolveResult>>, 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 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, None)
+                .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, SearchMode::PreferExemptions)
+                })
+                .collect();
+
+            let mut needed_exemptions = false;
+            let mut directly_exempted = false;
+            let mut criteria_failures = criteria_mapper.no_criteria();
+            for criteria_idx in required_criteria.indices() {
+                match &search_results[criteria_idx] {
+                    Ok(path) => {
+                        needed_exemptions |= path
+                            .iter()
+                            .any(|o| matches!(o, DeltaEdgeOrigin::Exemption { .. }));
+                        // Ignore `Unpublished` entries when deciding if a crate
+                        // is directly exempted.
+                        directly_exempted |= path.iter().all(|o| {
+                            matches!(
+                                o,
+                                DeltaEdgeOrigin::Exemption { .. }
+                                    | DeltaEdgeOrigin::Unpublished { .. }
+                            )
+                        });
+                    }
+                    Err(_) => criteria_failures.set_criteria(criteria_idx),
+                }
+            }
+
+            if !criteria_failures.is_empty() {
+                failures.push((pkgidx, AuditFailure { criteria_failures }));
+            }
+
+            // XXX: Callers using these fields in success should perhaps be
+            // changed to instead walk the results?
+            if !needed_exemptions {
+                vetted_fully.push(pkgidx);
+            } else if directly_exempted {
+                vetted_with_exemptions.push(pkgidx);
+            } else {
+                vetted_partially.push(pkgidx);
+            }
+
+            Some(ResolveResult { search_results })
+        })
+        .collect();
+
+    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,
+        })
+    };
+
+    (results, conclusion)
+}
+
+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<'_>,
+        extra_audits_file: Option<&'a AuditsFile>,
+    ) -> 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<Option<ImportName>> = store
+            .imported_audits()
+            .keys()
+            .map(|import_name| Some(import_name.clone()))
+            .collect();
+
+        // Iterator over every audits file, including imported audits.
+        let all_audits_files = store
+            .imported_audits()
+            .values()
+            .enumerate()
+            .map(|(import_index, audits_file)| {
+                (
+                    Some(import_index),
+                    &foreign_namespaces[import_index],
+                    audits_file,
+                )
+            })
+            .chain([(None, &None, &store.audits)])
+            .chain(
+                // Consider extra audits as local for now - we don't care about
+                // how the audits from it are prioritized.
+                extra_audits_file
+                    .iter()
+                    .map(|&audits_file| (None, &None, audits_file)),
+            );
+
+        // Iterator over every normal audit.
+        let all_audits =
+            all_audits_files
+                .clone()
+                .flat_map(|(import_index, namespace, audits_file)| {
+                    audits_file
+                        .audits
+                        .get(package)
+                        .map(|v| &v[..])
+                        .unwrap_or(&[])
+                        .iter()
+                        .enumerate()
+                        .map(move |(audit_index, audit)| {
+                            (
+                                namespace,
+                                match import_index {
+                                    Some(import_index) => DeltaEdgeOrigin::ImportedAudit {
+                                        import_index,
+                                        audit_index,
+                                    },
+                                    None => DeltaEdgeOrigin::StoredLocalAudit { audit_index },
+                                },
+                                audit,
+                            )
+                        })
+                });
+
+        // Iterator over every wildcard audit.
+        let all_wildcard_audits =
+            all_audits_files
+                .clone()
+                .flat_map(|(import_index, namespace, audits_file)| {
+                    audits_file
+                        .wildcard_audits
+                        .get(package)
+                        .map(|v| &v[..])
+                        .unwrap_or(&[])
+                        .iter()
+                        .enumerate()
+                        .map(move |(audit_index, audit)| {
+                            (namespace, import_index, audit_index, audit)
+                        })
+                });
+
+        // Iterator over every trusted entry.
+        let trusteds = store
+            .audits
+            .trusted
+            .get(package)
+            .map(|v| &v[..])
+            .unwrap_or(&[]);
+
+        let publishers = store
+            .publishers()
+            .get(package)
+            .map(|v| &v[..])
+            .unwrap_or(&[]);
+
+        let unpublished = store
+            .unpublished()
+            .get(package)
+            .map(|v| &v[..])
+            .unwrap_or(&[]);
+
+        let exemptions = store.config.exemptions.get(package);
+
+        let mut forward_audits = DirectedAuditGraph::new();
+        let mut backward_audits = DirectedAuditGraph::new();
+        let mut violation_nodes = Vec::new();
+
+        // Collect up all the deltas, and their criteria
+        for (namespace, origin, 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 criteria = criteria_mapper.criteria_from_list(&entry.criteria);
+
+            forward_audits.entry(from_ver).or_default().push(DeltaEdge {
+                version: Some(to_ver),
+                criteria: criteria.clone(),
+                origin: origin.clone(),
+                is_fresh_import: entry.is_fresh_import,
+            });
+            backward_audits
+                .entry(Some(to_ver))
+                .or_default()
+                .push(DeltaEdge {
+                    version: from_ver,
+                    criteria,
+                    origin,
+                    is_fresh_import: entry.is_fresh_import,
+                });
+        }
+
+        // For each published version of the crate we're aware of, check if any
+        // wildcard audits apply and add full-audits to those versions if they
+        // do.
+        for (publisher_index, publisher) in publishers.iter().enumerate() {
+            for (_, import_index, audit_index, entry) in all_wildcard_audits.clone() {
+                if entry.user_id == publisher.user_id
+                    && *entry.start <= publisher.when
+                    && publisher.when < *entry.end
+                {
+                    let from_ver = None;
+                    let to_ver = Some(&publisher.version);
+                    let criteria = criteria_mapper.criteria_from_list(&entry.criteria);
+                    let origin = DeltaEdgeOrigin::WildcardAudit {
+                        import_index,
+                        audit_index,
+                        publisher_index,
+                    };
+                    let is_fresh_import = entry.is_fresh_import || publisher.is_fresh_import;
+
+                    forward_audits.entry(from_ver).or_default().push(DeltaEdge {
+                        version: to_ver,
+                        criteria: criteria.clone(),
+                        origin: origin.clone(),
+                        is_fresh_import,
+                    });
+                    backward_audits.entry(to_ver).or_default().push(DeltaEdge {
+                        version: from_ver,
+                        criteria,
+                        origin,
+                        is_fresh_import,
+                    });
+                }
+            }
+
+            for entry in trusteds {
+                if entry.user_id == publisher.user_id
+                    && *entry.start <= publisher.when
+                    && publisher.when < *entry.end
+                {
+                    let from_ver = None;
+                    let to_ver = Some(&publisher.version);
+                    let criteria = criteria_mapper.criteria_from_list(&entry.criteria);
+                    let origin = DeltaEdgeOrigin::Trusted { publisher_index };
+
+                    forward_audits.entry(from_ver).or_default().push(DeltaEdge {
+                        version: to_ver,
+                        criteria: criteria.clone(),
+                        origin: origin.clone(),
+                        is_fresh_import: publisher.is_fresh_import,
+                    });
+                    backward_audits.entry(to_ver).or_default().push(DeltaEdge {
+                        version: from_ver,
+                        criteria,
+                        origin,
+                        is_fresh_import: publisher.is_fresh_import,
+                    });
+                }
+            }
+        }
+
+        // For each unpublished entry for the crate we're aware of, generate a delta audit for that edge.
+        for (unpublished_index, unpublished) in unpublished.iter().enumerate() {
+            let from_ver = Some(&unpublished.audited_as);
+            let to_ver = Some(&unpublished.version);
+            let criteria = criteria_mapper.all_criteria();
+            let origin = DeltaEdgeOrigin::Unpublished { unpublished_index };
+            let is_fresh_import = unpublished.is_fresh_import;
+
+            forward_audits.entry(from_ver).or_default().push(DeltaEdge {
+                version: to_ver,
+                criteria: criteria.clone(),
+                origin: origin.clone(),
+                is_fresh_import,
+            });
+            backward_audits.entry(to_ver).or_default().push(DeltaEdge {
+                version: from_ver,
+                criteria,
+                origin,
+                is_fresh_import,
+            });
+        }
+
+        // Exempted entries are equivalent to full-audits
+        if let Some(alloweds) = exemptions {
+            for (exemption_index, allowed) in alloweds.iter().enumerate() {
+                let from_ver = None;
+                let to_ver = Some(&allowed.version);
+                let criteria = criteria_mapper.criteria_from_list(&allowed.criteria);
+                let origin = DeltaEdgeOrigin::Exemption { exemption_index };
+
+                // 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: origin.clone(),
+                    is_fresh_import: false,
+                });
+                backward_audits.entry(to_ver).or_default().push(DeltaEdge {
+                    version: from_ver,
+                    criteria,
+                    origin,
+                    is_fresh_import: false,
+                });
+            }
+        }
+
+        // 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_list([&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, _origin, audit) in all_audits.clone() {
+                let audit_criteria = criteria_mapper.criteria_from_list(&audit.criteria);
+                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.push(ViolationConflict::AuditConflict {
+                                violation_source: violation_source.clone(),
+                                violation: (*violation_entry).clone(),
+                                audit_source: namespace.clone(),
+                                audit: audit.clone(),
+                            });
+                        }
+                    }
+                    AuditKind::Violation { .. } => {
+                        // don't care
+                    }
+                }
+            }
+        }
+
+        // If we enountered any violations, report them.
+        if !violations.is_empty() {
+            return Err(violations);
+        }
+
+        Ok(AuditGraph {
+            forward_audits,
+            backward_audits,
+        })
+    }
+
+    /// Search for a path in this AuditGraph which indicates that the given
+    /// version of the crate satisfies the given criteria. Returns the path used
+    /// 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,
+        mode: SearchMode,
+    ) -> Result<Vec<DeltaEdgeOrigin>, SearchFailure> {
+        // First, search backwards, starting from the target, as that's more
+        // likely to have a limited graph to traverse.
+        // This also interacts well with the search ordering from
+        // search_for_path, which prefers edges closer to `None` when
+        // traversing.
+        search_for_path(
+            &self.backward_audits,
+            criteria_idx,
+            Some(version),
+            None,
+            mode,
+        )
+        .map_err(|reachable_from_target| {
+            assert!(
+                mode != SearchMode::RegenerateExemptions,
+                "RegenerateExemptions search mode cannot fail"
+            );
+
+            // The search failed, perform the search in the other direction
+            // in order to also get the set of nodes reachable from the
+            // root. We can `unwrap_err()` here, as we'll definitely fail.
+            let reachable_from_root = search_for_path(
+                &self.forward_audits,
+                criteria_idx,
+                None,
+                Some(version),
+                mode,
+            )
+            .unwrap_err();
+            SearchFailure {
+                reachable_from_root,
+                reachable_from_target,
+            }
+        })
+    }
+}
+
+/// 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(
+    audit_graph: &DirectedAuditGraph<'_>,
+    criteria_idx: usize,
+    from_version: Option<&VetVersion>,
+    to_version: Option<&VetVersion>,
+    mode: SearchMode,
+) -> Result<Vec<DeltaEdgeOrigin>, SortedSet<Option<VetVersion>>> {
+    assert!(
+        mode != SearchMode::RegenerateExemptions || to_version.is_none(),
+        "RegenerateExemptions requires searching towards root"
+    );
+
+    // 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,
+    // significantly simplifying the start and end conditions.
+    //
+    // Some edges have caveats which we want to avoid requiring, so we defer
+    // edges with caveats to be checked later, after all edges without caveats
+    // have been visited. This is done by storing work to do in a BinaryHeap,
+    // sorted by the caveats which apply to each node. This means that if we
+    // find a patch without using a node with caveats, it's unambiguous proof we
+    // don't need edges with caveats.
+    #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
+    enum CaveatLevel {
+        None,
+        PreferredExemption,
+        PreferredUnpublished,
+        FreshImport,
+        Exemption,
+        Unpublished,
+        FreshExemption,
+    }
+
+    #[derive(Debug)]
+    struct Node<'a> {
+        version: Option<&'a VetVersion>,
+        origin_version: Option<&'a VetVersion>,
+        path: Vec<DeltaEdgeOrigin>,
+        caveat_level: CaveatLevel,
+    }
+
+    impl Node<'_> {
+        fn key(&self) -> impl Ord + '_ {
+            // Nodes are compared by caveat level. A lower caveat level makes
+            // the node sort higher, as it will be stored in a max heap.
+            //
+            // Once we've sorted by all caveats, we sort by the version
+            // (preferring lower versions), exemption origin version (preferring
+            // smaller exemptions), the length of the path (preferring short
+            // paths), and then the most recently added DeltaEdgeOrigin
+            // (preferring more-local audits).
+            //
+            // NOTE: This ordering logic priorities assume `to_version == None`,
+            // as we will only be searched in the other direction if the search
+            // is guaranteed to fail, in which case ordering doesn't matter (as
+            // we're going to visit every node).
+            Reverse((
+                self.caveat_level,
+                self.version,
+                self.exemption_origin_version(),
+                self.path.len(),
+                self.path.last(),
+            ))
+        }
+
+        // To make better decisions when selecting exemptions, exemption edges
+        // with lower origin versions are preferred over those with higher
+        // origin versions. This is checked before path length, as a longer path
+        // which uses a smaller exemption is generally preferred to a short one
+        // which uses a full-exemption. This is ignored for other edge types.
+        fn exemption_origin_version(&self) -> Option<&VetVersion> {
+            if matches!(
+                self.caveat_level,
+                CaveatLevel::PreferredExemption
+                    | CaveatLevel::Exemption
+                    | CaveatLevel::FreshExemption
+            ) {
+                self.origin_version
+            } else {
+                None
+            }
+        }
+    }
+    impl<'a> PartialEq for Node<'a> {
+        fn eq(&self, other: &Self) -> bool {
+            self.key() == other.key()
+        }
+    }
+    impl<'a> Eq for Node<'a> {}
+    impl<'a> PartialOrd for Node<'a> {
+        fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+            Some(self.cmp(other))
+        }
+    }
+    impl<'a> Ord for Node<'a> {
+        fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+            self.key().cmp(&other.key())
+        }
+    }
+
+    let mut queue = BinaryHeap::new();
+    queue.push(Node {
+        version: from_version,
+        origin_version: from_version,
+        path: Vec::new(),
+        caveat_level: CaveatLevel::None,
+    });
+
+    let mut visited = SortedSet::new();
+    while let Some(Node {
+        version,
+        origin_version: _,
+        path,
+        caveat_level,
+    }) = 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) {
+            continue;
+        }
+
+        // We found a path! Return a search result reflecting what we
+        // discovered.
+        if version == to_version {
+            return Ok(path);
+        }
+
+        // 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 {
+            // We'll allow any criteria if we're regenerating exemption edges.
+            let allow_any_criteria = mode == SearchMode::RegenerateExemptions
+                && matches!(edge.origin, DeltaEdgeOrigin::Exemption { .. });
+            if !allow_any_criteria && !edge.criteria.has_criteria(criteria_idx) {
+                // This edge never would have been useful to us.
+                continue;
+            }
+            if visited.contains(&edge.version) {
+                // We've been to the target of this edge already.
+                continue;
+            }
+
+            // Compute the level of caveats which are being added by the current edge
+            let edge_caveat_level = match &edge.origin {
+                DeltaEdgeOrigin::Exemption { .. } if mode == SearchMode::PreferExemptions => {
+                    CaveatLevel::PreferredExemption
+                }
+                DeltaEdgeOrigin::Exemption { .. } => CaveatLevel::Exemption,
+                DeltaEdgeOrigin::FreshExemption { .. } => unreachable!(),
+                DeltaEdgeOrigin::Unpublished { .. } => match mode {
+                    // When preferring exemptions, prefer existing unpublished
+                    // entries to avoid imports.lock churn.
+                    SearchMode::PreferExemptions if !edge.is_fresh_import => {
+                        CaveatLevel::PreferredUnpublished
+                    }
+                    SearchMode::PreferExemptions => CaveatLevel::Unpublished,
+                    // Otherwise, prefer fresh to avoid outdated versions.
+                    _ if edge.is_fresh_import => CaveatLevel::PreferredUnpublished,
+                    _ => CaveatLevel::Unpublished,
+                },
+                _ if edge.is_fresh_import => CaveatLevel::FreshImport,
+                _ => CaveatLevel::None,
+            };
+
+            queue.push(Node {
+                version: edge.version,
+                origin_version: version,
+                path: path.iter().cloned().chain([edge.origin.clone()]).collect(),
+                caveat_level: caveat_level.max(edge_caveat_level),
+            });
+        }
+
+        // If we're regenerating exemptions, add a fresh exemption edge which
+        // directly leads to the root version.
+        if mode == SearchMode::RegenerateExemptions {
+            queue.push(Node {
+                version: None,
+                origin_version: version,
+                path: path
+                    .iter()
+                    .cloned()
+                    .chain([DeltaEdgeOrigin::FreshExemption {
+                        version: version
+                            .expect("RegenerateExemptions requires searching towards None")
+                            .clone(),
+                    }])
+                    .collect(),
+                caveat_level: caveat_level.max(CaveatLevel::FreshExemption),
+            })
+        }
+    }
+
+    // Complete failure, we need more audits for this package, so all that
+    // matters is what nodes were reachable.
+    Err(visited.into_iter().map(|v| v.cloned()).collect())
+}
+
+impl<'a> ResolveReport<'a> {
+    pub fn has_errors(&self) -> bool {
+        // Just check the conclusion
+        !matches!(self.conclusion, Conclusion::Success(_))
+    }
+
+    pub fn _has_warnings(&self) -> bool {
+        false
+    }
+
+    pub fn compute_suggest(
+        &self,
+        cfg: &Config,
+        store: &Store,
+        network: Option<&Network>,
+    ) -> Result<Option<Suggest>, SuggestError> {
+        let _suggest_span = trace_span!("suggest").entered();
+        let fail = if let Conclusion::FailForVet(fail) = &self.conclusion {
+            fail
+        } else {
+            // Nothing to suggest unless we failed for vet
+            return Ok(None);
+        };
+
+        let cache = Cache::acquire(cfg)?;
+
+        let warnings = RefCell::new(Vec::new());
+
+        let mut store = store.clone_for_suggest(false);
+        let registry = if let (false, OutputFormat::Human, Some(network)) = (
+            cfg.cli.no_registry_suggestions,
+            cfg.cli.output_format,
+            network,
+        ) {
+            tokio::runtime::Handle::current()
+                .block_on(store.fetch_registry_audits(cfg, network, &cache))
+                .map_err(|error| warnings.borrow_mut().push(error.to_string()))
+                .ok()
+        } else {
+            None
+        };
+
+        const THIS_PROJECT: &str = "this project";
+
+        let mut trusted_publishers: FastMap<u64, SortedSet<ImportName>> = FastMap::new();
+        for trusted_entry in store.audits.trusted.values().flatten() {
+            trusted_publishers
+                .entry(trusted_entry.user_id)
+                .or_default()
+                .insert(THIS_PROJECT.to_owned());
+        }
+        for (import_name, audits_file) in store.imported_audits() {
+            for trusted_entry in audits_file.trusted.values().flatten() {
+                trusted_publishers
+                    .entry(trusted_entry.user_id)
+                    .or_default()
+                    .insert(import_name.clone());
+            }
+        }
+
+        let suggest_progress =
+            progress_bar("Suggesting", "relevant audits", fail.failures.len() as u64);
+
+        let mut suggestions = tokio::runtime::Handle::current()
+            .block_on(join_all(fail.failures.iter().map(
+                |(failure_idx, audit_failure)| async {
+                    let _guard = IncProgressOnDrop(&suggest_progress, 1);
+
+                    let failure_idx = *failure_idx;
+                    let package = &self.graph.nodes[failure_idx];
+                    let result = self.results[failure_idx]
+                        .as_ref()
+                        .expect("failed package without ResolveResults?");
+
+                    // Precompute some "notable" parents
+                    let notable_parents: Vec<_> = self.graph.nodes[failure_idx]
+                        .reverse_deps
+                        .iter()
+                        .map(|&parent| self.graph.nodes[parent].name.to_string())
+                        .collect();
+
+                    let Some((suggested_diff, extra_suggested_diff)) = suggest_delta(
+                        &cfg.metadata,
+                        network,
+                        &cache,
+                        package.name,
+                        &package.version,
+                        audit_failure
+                            .criteria_failures
+                            .indices()
+                            .map(|criteria_idx| {
+                                result.search_results[criteria_idx].as_ref().unwrap_err()
+                            }),
+                        &warnings,
+                    )
+                    .await else {
+                        return vec![];
+                    };
+
+                    // Attempt to look up the publisher of the target version
+                    // for the suggested diff, and also record whether the given
+                    // package has a sole publisher.
+                    let mut is_sole_publisher = false;
+                    let publisher_id =
+                        if let (Some(network), None) = (&network, &suggested_diff.to.git_rev) {
+                            let versions = cache
+                                .get_publishers(
+                                    Some(network),
+                                    package.name,
+                                    [&suggested_diff.to.semver].into_iter().collect(),
+                                )
+                                .await
+                                .unwrap_or_default();
+                            let publisher_count = versions
+                                .iter()
+                                .flat_map(|(_, details)| &details.published_by)
+                                .collect::<FastSet<_>>()
+                                .len();
+                            is_sole_publisher = publisher_count == 1;
+                            versions
+                                .into_iter()
+                                .find(|(v, _)| v == &suggested_diff.to.semver)
+                                .and_then(|(_, d)| d.published_by)
+                        } else {
+                            None
+                        };
+
+                    // Compute the trust hint, which is the information used to generate "consider
+                    // cargo trust FOO" messages. There can be multiple potential hints, but we
+                    // only provide the most relevant one. If the publisher of the in-use version
+                    // of the crate is potentially trustworthy, we suggest that. If not (and we
+                    // don't already have at least one trusted entry for this crate), we iterate
+                    // over the crate releases in reverse order to see if another version was
+                    // published by a potentially-trustworth author. We pick the first one of
+                    // those we find, if any.
+                    let trust_hint = {
+                        let mut exact_version = false;
+                        let id_for_hint = if publisher_id
+                            .map_or(false, |i| trusted_publishers.contains_key(&i))
+                        {
+                            exact_version = true;
+                            publisher_id
+                        } else if store.audits.trusted.get(package.name).is_none() {
+                            cache
+                                .get_cached_publishers(package.name)
+                                .iter()
+                                .rev()
+                                .filter_map(|(_, details)| details.published_by)
+                                .find(|i| trusted_publishers.contains_key(i))
+                        } else {
+                            None
+                        };
+
+                        id_for_hint.map(|id| {
+                            let mut trusted_by: Vec<String> = trusted_publishers
+                                .get(&id)
+                                .unwrap()
+                                .iter()
+                                .cloned()
+                                .collect();
+                            // If we're already trusted by this project, don't
+                            // bother listing anyone else.
+                            if trusted_by.iter().any(|s| s == THIS_PROJECT) {
+                                trusted_by.retain(|s| s == THIS_PROJECT);
+                            }
+                            let publisher = cache.get_crates_user_info(id).unwrap();
+                            TrustHint {
+                                trusted_by,
+                                publisher,
+                                exact_version,
+                            }
+                        })
+                    };
+
+                    let publisher_login = publisher_id
+                        .and_then(|user_id| cache.get_crates_user_info(user_id))
+                        .map(|pi| pi.login);
+
+                    let mut registry_suggestion: Vec<_> = join_all(registry.iter().flatten().map(
+                        |(name, entry, audits)| async {
+                            // Don't search for git deltas in the registry.
+                            if suggested_diff.to.git_rev.is_some() {
+                                return None;
+                            }
+
+                            let audit_graph = AuditGraph::build(
+                                &store,
+                                &self.criteria_mapper,
+                                package.name,
+                                Some(audits),
+                            )
+                            .ok()?;
+
+                            // If we have an extra diff, only try to search for
+                            // a path to the "from" version in that diff, to
+                            // make the results more comparable.
+                            let target_version = extra_suggested_diff
+                                .as_ref()
+                                .and_then(|d| d.from.as_ref())
+                                .unwrap_or(&package.version);
+
+                            let failures: Vec<_> = audit_failure
+                                .criteria_failures
+                                .indices()
+                                .filter_map(|criteria_idx| {
+                                    audit_graph
+                                        .search(
+                                            criteria_idx,
+                                            target_version,
+                                            SearchMode::PreferExemptions,
+                                        )
+                                        .err()
+                                })
+                                .collect();
+
+                            let (registry_suggested_diff, _) = suggest_delta(
+                                &cfg.metadata,
+                                network,
+                                &cache,
+                                package.name,
+                                target_version,
+                                failures.iter(),
+                                &warnings,
+                            )
+                            .await?;
+
+                            if registry_suggested_diff.diffstat.count()
+                                < suggested_diff.diffstat.count()
+                            {
+                                Some(RegistrySuggestion {
+                                    name: name.clone(),
+                                    url: entry.url.clone(),
+                                    diff: registry_suggested_diff,
+                                })
+                            } else {
+                                None
+                            }
+                        },
+                    ))
+                    .await
+                    .into_iter()
+                    .flatten()
+                    .collect();
+                    registry_suggestion.sort_by_key(|suggestion| suggestion.diff.diffstat.count());
+
+                    extra_suggested_diff
+                        .into_iter()
+                        .map(|suggested_diff| SuggestItem {
+                            package: failure_idx,
+                            suggested_diff,
+                            suggested_criteria: audit_failure.criteria_failures.clone(),
+                            notable_parents: notable_parents.clone(),
+                            publisher_login: None,
+                            trust_hint: None,
+                            is_sole_publisher: false,
+                            registry_suggestion: vec![],
+                        })
+                        .chain([SuggestItem {
+                            package: failure_idx,
+                            suggested_diff,
+                            suggested_criteria: audit_failure.criteria_failures.clone(),
+                            notable_parents: notable_parents.clone(),
+                            publisher_login,
+                            trust_hint,
+                            is_sole_publisher,
+                            registry_suggestion,
+                        }])
+                        .collect()
+                },
+            )))
+            .into_iter()
+            .flatten()
+            .collect::<Vec<_>>();
+
+        // First sort by diff size (ascending), then package name, then version
+        // being certified, to have stable output ordering.
+        suggestions.sort_by_key(|item| {
+            (
+                item.suggested_diff.diffstat.count(),
+                self.graph.nodes[item.package].name,
+                item.suggested_diff.to.clone(),
+            )
+        });
+
+        // If we have duplicate suggestions in the output, e.g. due to multiple
+        // versions of the same crate requiring the same new audit, deduplicate
+        // them in the output to avoid clutter.
+        suggestions.dedup_by(|a, b| {
+            if self.graph.nodes[a.package].name == self.graph.nodes[b.package].name
+                && a.suggested_diff == b.suggested_diff
+            {
+                // Per the `dedup_by` documentation, if true is returned, `a`
+                // will be removed. Preserve its notable parents.
+                b.notable_parents.extend_from_slice(&a.notable_parents);
+                true
+            } else {
+                false
+            }
+        });
+
+        // Sort and remove any duplicate entries from `notable_parents`.
+        for s in &mut suggestions {
+            s.notable_parents.sort();
+            s.notable_parents.dedup();
+        }
+
+        let total_lines = suggestions
+            .iter()
+            .map(|s| s.suggested_diff.diffstat.count())
+            .sum();
+
+        let mut suggestions_by_criteria = SortedMap::<CriteriaName, Vec<SuggestItem>>::new();
+        for s in suggestions.clone().into_iter() {
+            let criteria_names = self
+                .criteria_mapper
+                .criteria_names(&s.suggested_criteria)
+                .collect::<Vec<_>>()
+                .join(", ");
+
+            suggestions_by_criteria
+                .entry(criteria_names)
+                .or_default()
+                .push(s);
+        }
+
+        Ok(Some(Suggest {
+            suggestions,
+            suggestions_by_criteria,
+            total_lines,
+            warnings: warnings.into_inner(),
+        }))
+    }
+
+    /// Given a package name and a delta to be certified, determine the set of
+    /// additional criteria for that delta/version pair which would have a
+    /// healing impact on the audit graph.
+    ///
+    /// This is more reliable than running a suggest and looking for a matching
+    /// output, as it will also select criteria for non-suggested audits.
+    pub fn compute_suggested_criteria(
+        &self,
+        package_name: PackageStr<'_>,
+        from: Option<&VetVersion>,
+        to: &VetVersion,
+    ) -> Vec<CriteriaName> {
+        let fail = if let Conclusion::FailForVet(fail) = &self.conclusion {
+            fail
+        } else {
+            return Vec::new();
+        };
+
+        let mut criteria = self.criteria_mapper.no_criteria();
+
+        // Make owned versions of the `Version` types, such that we can look
+        // them up in search results more easily.
+        let from = from.cloned();
+        let to = Some(to.clone());
+
+        // 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];
+            if package.name != package_name {
+                continue;
+            }
+
+            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 Err(SearchFailure {
+                    reachable_from_root,
+                    reachable_from_target,
+                }) = search_result
+                {
+                    if reachable_from_target.contains(&to) && reachable_from_root.contains(&from) {
+                        criteria.set_criteria(criteria_idx);
+                    }
+                }
+            }
+        }
+
+        self.criteria_mapper
+            .criteria_names(&criteria)
+            .map(str::to_owned)
+            .collect()
+    }
+
+    /// Print a full human-readable report
+    pub fn print_human(
+        &self,
+        out: &Arc<dyn Out>,
+        cfg: &Config,
+        suggest: Option<&Suggest>,
+    ) -> Result<(), std::io::Error> {
+        match &self.conclusion {
+            Conclusion::Success(res) => res.print_human(out, self, cfg),
+            Conclusion::FailForViolationConflict(res) => res.print_human(out, self, cfg),
+            Conclusion::FailForVet(res) => res.print_human(out, self, cfg, suggest),
+        }
+    }
+
+    /// Print only the suggest portion of a human-readable report
+    pub fn print_suggest_human(
+        &self,
+        out: &Arc<dyn Out>,
+        _cfg: &Config,
+        suggest: Option<&Suggest>,
+    ) -> Result<(), std::io::Error> {
+        if let Some(suggest) = suggest {
+            suggest.print_human(out, self)?;
+        } else {
+            // This API is only used for vet-suggest
+            writeln!(out, "Nothing to suggest, you're fully audited!");
+        }
+        Ok(())
+    }
+
+    /// Print a full json report
+    pub fn print_json(
+        &self,
+        out: &Arc<dyn Out>,
+        suggest: Option<&Suggest>,
+    ) -> Result<(), miette::Report> {
+        let result = JsonReport {
+            conclusion: match &self.conclusion {
+                Conclusion::Success(success) => {
+                    let json_package = |pkgidx: &PackageIdx| {
+                        let package = &self.graph.nodes[*pkgidx];
+                        JsonPackage {
+                            name: package.name.to_owned(),
+                            version: package.version.clone(),
+                        }
+                    };
+                    JsonReportConclusion::Success(JsonReportSuccess {
+                        vetted_fully: success.vetted_fully.iter().map(json_package).collect(),
+                        vetted_partially: success
+                            .vetted_partially
+                            .iter()
+                            .map(json_package)
+                            .collect(),
+                        vetted_with_exemptions: success
+                            .vetted_with_exemptions
+                            .iter()
+                            .map(json_package)
+                            .collect(),
+                    })
+                }
+                Conclusion::FailForViolationConflict(fail) => {
+                    JsonReportConclusion::FailForViolationConflict(
+                        JsonReportFailForViolationConflict {
+                            violations: fail
+                                .violations
+                                .iter()
+                                .map(|(pkgidx, violations)| {
+                                    let package = &self.graph.nodes[*pkgidx];
+                                    let key = format!("{}:{}", package.name, package.version);
+                                    (key, violations.clone())
+                                })
+                                .collect(),
+                        },
+                    )
+                }
+                Conclusion::FailForVet(fail) => {
+                    // FIXME: How to report confidence for suggested criteria?
+                    let json_suggest_item = |item: &SuggestItem| {
+                        let package = &self.graph.nodes[item.package];
+                        JsonSuggestItem {
+                            name: package.name.to_owned(),
+                            notable_parents: FormatShortList::string(item.notable_parents.clone()),
+                            suggested_criteria: self
+                                .criteria_mapper
+                                .criteria_names(&item.suggested_criteria)
+                                .map(|s| s.to_owned())
+                                .collect(),
+                            suggested_diff: item.suggested_diff.clone(),
+                        }
+                    };
+                    JsonReportConclusion::FailForVet(JsonReportFailForVet {
+                        failures: fail
+                            .failures
+                            .iter()
+                            .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
+                                        .criteria_names(&audit_fail.criteria_failures)
+                                        .map(|s| s.to_owned())
+                                        .collect(),
+                                }
+                            })
+                            .collect(),
+                        suggest: suggest.as_ref().map(|suggest| JsonSuggest {
+                            suggestions: suggest
+                                .suggestions
+                                .iter()
+                                .map(json_suggest_item)
+                                .collect(),
+                            suggest_by_criteria: suggest
+                                .suggestions_by_criteria
+                                .iter()
+                                .map(|(criteria, items)| {
+                                    (
+                                        criteria.to_owned(),
+                                        items.iter().map(json_suggest_item).collect::<Vec<_>>(),
+                                    )
+                                })
+                                .collect(),
+                            total_lines: suggest.total_lines,
+                        }),
+                    })
+                }
+            },
+        };
+
+        serde_json::to_writer_pretty(&**out, &result).into_diagnostic()?;
+
+        Ok(())
+    }
+}
+
+impl Success {
+    pub fn print_human(
+        &self,
+        out: &Arc<dyn Out>,
+        _report: &ResolveReport<'_>,
+        _cfg: &Config,
+    ) -> Result<(), std::io::Error> {
+        let fully_audited_count = self.vetted_fully.len();
+        let partially_audited_count: usize = self.vetted_partially.len();
+        let exemptions_count = self.vetted_with_exemptions.len();
+
+        // Figure out how many entries we're going to print
+        let mut count_count = (fully_audited_count != 0) as usize
+            + (partially_audited_count != 0) as usize
+            + (exemptions_count != 0) as usize;
+
+        // Print out a summary of how we succeeded
+        if count_count == 0 {
+            writeln!(
+                out,
+                "Vetting Succeeded (because you have no third-party dependencies)"
+            );
+        } else {
+            write!(out, "Vetting Succeeded (");
+
+            if fully_audited_count != 0 {
+                write!(out, "{fully_audited_count} fully audited");
+                count_count -= 1;
+                if count_count > 0 {
+                    write!(out, ", ");
+                }
+            }
+            if partially_audited_count != 0 {
+                write!(out, "{partially_audited_count} partially audited");
+                count_count -= 1;
+                if count_count > 0 {
+                    write!(out, ", ");
+                }
+            }
+            if exemptions_count != 0 {
+                write!(out, "{exemptions_count} exempted");
+                count_count -= 1;
+                if count_count > 0 {
+                    write!(out, ", ");
+                }
+            }
+
+            writeln!(out, ")");
+        }
+        Ok(())
+    }
+}
+
+impl Suggest {
+    pub fn print_human(
+        &self,
+        out: &Arc<dyn Out>,
+        report: &ResolveReport<'_>,
+    ) -> Result<(), std::io::Error> {
+        for (criteria, suggestions) in &self.suggestions_by_criteria {
+            writeln!(out, "recommended audits for {criteria}:");
+
+            let mut strings = suggestions
+                .iter()
+                .map(|item| {
+                    let package = &report.graph.nodes[item.package];
+                    let cmd = match &item.suggested_diff.from {
+                        Some(from) => format!(
+                            "cargo vet diff {} {} {}",
+                            package.name, from, item.suggested_diff.to
+                        ),
+                        None => format!(
+                            "cargo vet inspect {} {}",
+                            package.name, item.suggested_diff.to
+                        ),
+                    };
+                    let publisher = item
+                        .publisher_login
+                        .clone()
+                        .unwrap_or_else(|| "UNKNOWN".into());
+                    let parents = FormatShortList::string(item.notable_parents.clone());
+                    let diffstat = match &item.suggested_diff.from {
+                        Some(_) => format!("{}", item.suggested_diff.diffstat),
+                        None => format!("{} lines", item.suggested_diff.diffstat.count()),
+                    };
+                    (cmd, publisher, parents, diffstat, item)
+                })
+                .collect::<Vec<_>>();
+
+            let (h0, h1, h2, h3) = ("Command", "Publisher", "Used By", "Audit Size");
+            let mut max0 = console::measure_text_width(h0);
+            let mut max1 = console::measure_text_width(h1);
+            let mut max2 = console::measure_text_width(h2);
+            for (s0, s1, s2, ..) in &mut strings {
+                // If the command is too long (happens occasionally, particularly with @git
+                // version specifiers), wrap subsequent columns to the next line.
+                const MAX_COMMAND_CHARS: usize = 52;
+                let command_width = console::measure_text_width(s0);
+                if command_width > MAX_COMMAND_CHARS {
+                    s0.push('\n');
+                    s0.push_str(&" ".repeat(MAX_COMMAND_CHARS + 4));
+                }
+                max0 = max0.max(command_width.min(MAX_COMMAND_CHARS));
+                max1 = max1.max(console::measure_text_width(s1));
+                max2 = max2.max(console::measure_text_width(s2));
+            }
+
+            writeln!(
+                out,
+                "{}",
+                out.style()
+                    .bold()
+                    .dim()
+                    .apply_to(format_args!("    {h0:max0$}  {h1:max1$}  {h2:max2$}  {h3}"))
+            );
+            for (s0, s1, s2, s3, item) in strings {
+                let package = &report.graph.nodes[item.package];
+
+                write!(
+                    out,
+                    "{}",
+                    out.style()
+                        .cyan()
+                        .bold()
+                        .apply_to(format_args!("    {s0:max0$}"))
+                );
+                writeln!(out, "  {s1:max1$}  {s2:max2$}  {s3}");
+
+                let dim = out.style().dim();
+                for suggestion in &item.registry_suggestion {
+                    writeln!(
+                        out,
+                        "      {} {} {}",
+                        dim.clone().apply_to("NOTE:"),
+                        dim.clone()
+                            .cyan()
+                            .bold()
+                            .apply_to(format_args!("cargo vet import {}", suggestion.name)),
+                        dim.clone()
+                            .apply_to(match suggestion.diff.diffstat.count() {
+                                0 => "would eliminate this".to_owned(),
+                                n => format!("would reduce this to a {n}-line diff"),
+                            }),
+                    );
+                }
+                if let Some(hint) = &item.trust_hint {
+                    let trust = if hint.trusted_by.len() == 1 {
+                        "trusts"
+                    } else {
+                        "trust"
+                    };
+                    let caveat = if !hint.exact_version {
+                        ", who published another version of this crate"
+                    } else {
+                        ""
+                    };
+                    let publisher = hint.publisher.clone();
+                    let trusted_by = FormatShortList::new(hint.trusted_by.clone());
+                    writeln!(
+                        out,
+                        "      {} {}",
+                        dim.clone().apply_to(format_args!(
+                            "NOTE: {trusted_by} {trust} {publisher}{caveat} - consider",
+                        )),
+                        if item.is_sole_publisher {
+                            let this_cmd = format!("cargo vet trust {}", package.name);
+                            let all_cmd = format!("cargo vet trust --all {}", publisher.login);
+                            format!(
+                                "{} {} {}",
+                                dim.clone().cyan().apply_to(this_cmd),
+                                dim.clone().apply_to("or"),
+                                dim.clone().cyan().apply_to(all_cmd),
+                            )
+                        } else {
+                            let cmd =
+                                format!("cargo vet trust {} {}", package.name, publisher.login);
+                            dim.clone().cyan().apply_to(cmd).to_string()
+                        }
+                    );
+                }
+            }
+
+            writeln!(out);
+        }
+
+        writeln!(out, "estimated audit backlog: {} lines", self.total_lines);
+
+        if !self.warnings.is_empty() {
+            writeln!(out);
+            for warning in &self.warnings {
+                writeln!(
+                    out,
+                    "{}: {warning}",
+                    out.style().yellow().apply_to("WARNING"),
+                );
+            }
+        }
+
+        writeln!(out);
+        writeln!(out, "Use |cargo vet certify| to record the audits.");
+
+        Ok(())
+    }
+}
+
+impl FailForVet {
+    fn print_human(
+        &self,
+        out: &Arc<dyn Out>,
+        report: &ResolveReport<'_>,
+        _cfg: &Config,
+        suggest: Option<&Suggest>,
+    ) -> Result<(), std::io::Error> {
+        writeln!(out, "Vetting Failed!");
+        writeln!(out);
+        writeln!(out, "{} unvetted dependencies:", self.failures.len());
+        let mut failures = self
+            .failures
+            .iter()
+            .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);
+        for (failed_package, failed_audit) in failures {
+            let criteria = report
+                .criteria_mapper
+                .criteria_names(&failed_audit.criteria_failures)
+                .collect::<Vec<_>>();
+
+            let label = format!("  {}:{}", failed_package.name, failed_package.version);
+            writeln!(out, "{label} missing {criteria:?}");
+        }
+
+        // Suggest output generally requires hitting the network.
+        if let Some(suggest) = suggest {
+            writeln!(out);
+            suggest.print_human(out, report)?;
+        }
+
+        Ok(())
+    }
+}
+
+impl FailForViolationConflict {
+    fn print_human(
+        &self,
+        out: &Arc<dyn Out>,
+        report: &ResolveReport<'_>,
+        _cfg: &Config,
+    ) -> Result<(), std::io::Error> {
+        writeln!(out, "Violations Found!");
+
+        for (pkgidx, violations) in &self.violations {
+            let package = &report.graph.nodes[*pkgidx];
+            writeln!(out, "  {}:{}", package.name, package.version);
+            for violation in violations {
+                match violation {
+                    ViolationConflict::UnauditedConflict {
+                        violation_source,
+                        violation,
+                        exemptions,
+                    } => {
+                        write!(out, "    the ");
+                        print_exemption(out, exemptions)?;
+                        write!(out, "    conflicts with ");
+                        print_entry(out, violation_source, violation)?;
+                    }
+                    ViolationConflict::AuditConflict {
+                        violation_source,
+                        violation,
+                        audit_source,
+                        audit,
+                    } => {
+                        write!(out, "    the ");
+                        print_entry(out, audit_source, audit)?;
+                        write!(out, "    conflicts with ");
+                        print_entry(out, violation_source, violation)?;
+                    }
+                }
+                writeln!(out);
+            }
+        }
+
+        fn print_exemption(
+            out: &Arc<dyn Out>,
+            entry: &ExemptedDependency,
+        ) -> Result<(), std::io::Error> {
+            writeln!(out, "exemption {}", entry.version);
+            writeln!(out, "      criteria: {:?}", entry.criteria);
+            if let Some(notes) = &entry.notes {
+                writeln!(out, "      notes: {notes}");
+            }
+            Ok(())
+        }
+
+        fn print_entry(
+            out: &Arc<dyn Out>,
+            source: &Option<ImportName>,
+            entry: &AuditEntry,
+        ) -> Result<(), std::io::Error> {
+            match source {
+                None => write!(out, "own "),
+                Some(name) => write!(out, "foreign ({name}) "),
+            }
+            match &entry.kind {
+                AuditKind::Full { version, .. } => {
+                    writeln!(out, "audit {version}");
+                }
+                AuditKind::Delta { from, to, .. } => {
+                    writeln!(out, "audit {from} -> {to}");
+                }
+                AuditKind::Violation { violation } => {
+                    writeln!(out, "violation against {violation}");
+                }
+            }
+            writeln!(out, "      criteria: {:?}", entry.criteria);
+            for (idx, who) in entry.who.iter().enumerate() {
+                if idx == 0 {
+                    write!(out, "      who: {who}");
+                } else {
+                    write!(out, ", {who}");
+                }
+            }
+            if !entry.who.is_empty() {
+                writeln!(out);
+            }
+            if let Some(notes) = &entry.notes {
+                writeln!(out, "      notes: {notes}");
+            }
+            Ok(())
+        }
+
+        Ok(())
+    }
+}
+
+async fn suggest_delta(
+    metadata: &cargo_metadata::Metadata,
+    network: Option<&Network>,
+    cache: &Cache,
+    package_name: PackageStr<'_>,
+    package_version: &VetVersion,
+    failures: impl Iterator<Item = &SearchFailure>,
+    warnings: &RefCell<Vec<String>>,
+) -> Option<(DiffRecommendation, Option<DiffRecommendation>)> {
+    // Fetch the set of known versions from crates.io so we know which versions
+    // we'll have sources for.
+    let known_versions = cache.get_versions(network, package_name).await.ok();
+
+    // Collect up the details of how we failed
+    struct Reachable<'a> {
+        from_root: SortedSet<&'a Option<VetVersion>>,
+        from_target: SortedSet<&'a Option<VetVersion>>,
+    }
+    let mut reachable = None::<Reachable<'_>>;
+    for SearchFailure {
+        reachable_from_root,
+        reachable_from_target,
+    } in failures
+    {
+        if let Some(Reachable {
+            from_root,
+            from_target,
+        }) = reachable.as_mut()
+        {
+            // This does the right thing in the common cases, by restricting
+            // ourselves to the reachable nodes that are common to all failures,
+            // so that we can suggest just one change that will fix everything.
+            from_root.retain(|ver| reachable_from_root.contains(ver));
+            from_target.retain(|ver| reachable_from_target.contains(ver));
+        } else {
+            let version_has_sources = |ver: &&Option<VetVersion>| -> bool {
+                // We always have sources for an empty crate.
+                let Some(ver) = ver else { return true; };
+                // We only have git sources for the package itself.
+                if ver.git_rev.is_some() {
+                    return ver == package_version;
+                }
+                // We have sources if the version has been published to crates.io.
+                //
+                // For testing fallbacks, assume we always have sources if the
+                // index is unavailable.
+                known_versions
+                    .as_ref()
+                    .map_or(true, |versions| versions.contains(&ver.semver))
+            };
+            reachable = Some(Reachable {
+                from_root: reachable_from_root
+                    .iter()
+                    .filter(version_has_sources)
+                    .collect(),
+                from_target: reachable_from_target
+                    .iter()
+                    .filter(version_has_sources)
+                    .collect(),
+            });
+        }
+    }
+
+    let Some(Reachable { from_root, from_target }) = &mut reachable else {
+        // Nothing failed, return a dummy suggestion for an empty diff.
+        return Some((
+            DiffRecommendation {
+                from: Some(package_version.clone()),
+                to: package_version.clone(),
+                diffstat: DiffStat { insertions: 0, deletions: 0, files_changed: 0 },
+            },
+            None,
+        ));
+    };
+
+    // If we have a git revision, we want to ensure the nearest published
+    // version has been audited before we suggest an audit for the git revision
+    // itself.
+    let published_version;
+    let mut extra_delta = None;
+    if package_version.git_rev.is_some() {
+        // Find the largest published revision with an equal or lower semver
+        // than the git revision. This will be the published version we
+        // encourage auditing first.
+        let closest_below = if let Some(known_versions) = &known_versions {
+            known_versions
+                .iter()
+                .filter(|&v| v <= &package_version.semver)
+                .max()
+        } else {
+            // For testing fallbacks, assume the bare version has been published
+            // to crates.io.
+            Some(&package_version.semver)
+        };
+        published_version = closest_below.map(|semver| VetVersion {
+            semver: semver.clone(),
+            git_rev: None,
+        });
+        // If the closest published version is not already audited, replace the
+        // target version with `published_version` in the reachable from target
+        // set, ensuring that a delta to that version is suggested rather than a
+        // full audit for the git revision.
+        if !from_root.contains(&published_version) {
+            from_target.remove(&Some(package_version.clone()));
+            if from_target.insert(&published_version) {
+                extra_delta = Some(Delta {
+                    from: published_version.clone(),
+                    to: package_version.clone(),
+                });
+            }
+        }
+    }
+
+    // Now suggest solutions of those failures
+    let mut candidates = Vec::new();
+    for &dest in &*from_target {
+        let closest_above = from_root.range::<&Option<VetVersion>, _>(dest..).next();
+        let closest_below = from_root
+            .range::<&Option<VetVersion>, _>(..dest)
+            .next_back();
+
+        for &closest in closest_below.into_iter().chain(closest_above) {
+            candidates.push(Delta {
+                from: closest.clone(),
+                to: dest.clone().unwrap(),
+            });
+        }
+    }
+
+    let do_fetch_and_diffstat = |delta| async move {
+        match cache
+            .fetch_and_diffstat_package(metadata, network, package_name, &delta)
+            .await
+        {
+            Ok(diffstat) => Some(DiffRecommendation {
+                diffstat,
+                from: delta.from.clone(),
+                to: delta.to.clone(),
+            }),
+            Err(err) => {
+                // We don't want to actually error out completely here,
+                // as other packages might still successfully diff!
+                warnings
+                    .borrow_mut()
+                    .push(format!("error diffing {}:{}: {}", package_name, delta, err));
+                None
+            }
+        }
+    };
+
+    let diffstats = join_all(candidates.into_iter().map(do_fetch_and_diffstat)).await;
+
+    // If we need an "extra delta" due to a git revision, also get the diffstat
+    // for that entry.
+    let extra_diffstat = if let Some(delta) = extra_delta {
+        do_fetch_and_diffstat(delta).await
+    } else {
+        None
+    };
+
+    let recommendation = diffstats
+        .into_iter()
+        .flatten()
+        .min_by_key(|diff| diff.diffstat.count())?;
+
+    Some((recommendation, extra_diffstat))
+}
+
+/// Resolve which entries in the store and imports.lock are required to
+/// successfully audit this package. May return `None` if the package cannot
+/// successfully vet given the restrictions placed upon it.
+///
+/// NOTE: A package which does not exist in the dependency will return the empty
+/// set, as it does not have any audit requirements.
+///
+/// [`SearchMode`] controls how edges are selected when searching for paths.
+#[tracing::instrument(skip(graph, criteria_mapper, requirements, store))]
+fn resolve_package_required_entries(
+    graph: &DepGraph<'_>,
+    criteria_mapper: &CriteriaMapper,
+    requirements: &[CriteriaSet],
+    store: &Store,
+    package_name: PackageStr<'_>,
+    search_mode: SearchMode,
+) -> Option<SortedMap<RequiredEntry, CriteriaSet>> {
+    assert_eq!(graph.nodes.len(), requirements.len());
+
+    // Collect the list of third-party packages with the given name, along with
+    // their requirements.
+    let packages: Vec<_> = graph
+        .nodes
+        .iter()
+        .zip(requirements)
+        .filter(|(package, _)| package.name == package_name && package.is_third_party)
+        .collect();
+
+    // If there are no third-party packages with the name, we definitely don't
+    // need any entries.
+    if packages.is_empty() {
+        return Some(SortedMap::new());
+    }
+
+    let Ok(audit_graph) = AuditGraph::build(store, criteria_mapper, package_name, None) else {
+        // There were violations when building the audit graph, return `None` to
+        // indicate that this package is failing.
+        return None;
+    };
+
+    let mut required_entries = SortedMap::new();
+    for &(package, reqs) in &packages {
+        // Do the minimal set of searches to validate that the required criteria
+        // are matched.
+        for criteria_idx in criteria_mapper.minimal_indices(reqs) {
+            let Ok(path) = audit_graph.search(criteria_idx, &package.version, search_mode) else {
+                // This package failed to vet, return `None`.
+                return None;
+            };
+
+            let mut add_entry = |entry: RequiredEntry| {
+                required_entries
+                    .entry(entry)
+                    .or_insert_with(|| criteria_mapper.no_criteria())
+                    .set_criteria(criteria_idx);
+            };
+
+            for origin in path {
+                match origin {
+                    DeltaEdgeOrigin::Exemption { exemption_index } => {
+                        add_entry(RequiredEntry::Exemption { exemption_index });
+                    }
+                    DeltaEdgeOrigin::FreshExemption { version } => {
+                        add_entry(RequiredEntry::FreshExemption { version });
+                    }
+                    DeltaEdgeOrigin::ImportedAudit {
+                        import_index,
+                        audit_index,
+                    } => {
+                        add_entry(RequiredEntry::Audit {
+                            import_index,
+                            audit_index,
+                        });
+                    }
+                    DeltaEdgeOrigin::WildcardAudit {
+                        import_index,
+                        audit_index,
+                        publisher_index,
+                    } => {
+                        if let Some(import_index) = import_index {
+                            add_entry(RequiredEntry::WildcardAudit {
+                                import_index,
+                                audit_index,
+                            })
+                        }
+                        add_entry(RequiredEntry::Publisher { publisher_index })
+                    }
+                    DeltaEdgeOrigin::Trusted { publisher_index } => {
+                        add_entry(RequiredEntry::Publisher { publisher_index })
+                    }
+                    DeltaEdgeOrigin::Unpublished { unpublished_index } => {
+                        add_entry(RequiredEntry::Unpublished { unpublished_index })
+                    }
+                    DeltaEdgeOrigin::StoredLocalAudit { .. } => {}
+                }
+            }
+        }
+        continue;
+    }
+
+    Some(required_entries)
+}
+
+/// Per-package options to control store pruning.
+#[derive(Copy, Clone)]
+pub struct UpdateMode {
+    pub search_mode: SearchMode,
+    pub prune_exemptions: bool,
+    pub prune_imports: bool,
+}
+
+/// Refresh the state of the store, importing required audits, and optionally
+/// pruning unnecessary exemptions and/or imports.
+pub fn update_store(
+    cfg: &Config,
+    store: &mut Store,
+    mode: impl FnMut(PackageStr<'_>) -> UpdateMode,
+) {
+    let (new_imports, new_exemptions) = get_store_updates(cfg, store, mode);
+
+    // Update the store to reflect the new imports and exemptions files.
+    store.imports = new_imports;
+    store.config.exemptions = new_exemptions;
+}
+
+/// The non-mutating core of `update_store` for use in non-mutating situations.
+pub(crate) fn get_store_updates(
+    cfg: &Config,
+    store: &Store,
+    mut mode: impl FnMut(PackageStr<'_>) -> UpdateMode,
+) -> (ImportsFile, SortedMap<PackageName, Vec<ExemptedDependency>>) {
+    // Compute the set of required entries from the store for all packages in
+    // the dependency graph.
+    let graph = DepGraph::new(
+        &cfg.metadata,
+        cfg.cli.filter_graph.as_ref(),
+        Some(&store.config.policy),
+    );
+    let criteria_mapper = CriteriaMapper::new(&store.audits.criteria);
+    let requirements = resolve_requirements(&graph, &store.config.policy, &criteria_mapper);
+
+    let mut required_entries = SortedMap::new();
+    for package in &graph.nodes {
+        required_entries.entry(package.name).or_insert_with(|| {
+            resolve_package_required_entries(
+                &graph,
+                &criteria_mapper,
+                &requirements,
+                store,
+                package.name,
+                mode(package.name).search_mode,
+            )
+        });
+    }
+
+    // Dummy value to use if a package isn't found in `required_entries` - no
+    // edges will be required.
+    let no_required_entries = Some(SortedMap::new());
+
+    let mut new_imports = ImportsFile {
+        unpublished: SortedMap::new(),
+        publisher: SortedMap::new(),
+        audits: SortedMap::new(),
+    };
+
+    // Determine which live imports to keep in the imports.lock file.
+    for (import_index, (import_name, live_audits_file)) in
+        store.imported_audits().iter().enumerate()
+    {
+        let new_audits_file = AuditsFile {
+            criteria: live_audits_file.criteria.clone(),
+
+            wildcard_audits: live_audits_file
+                .wildcard_audits
+                .iter()
+                .map(|(pkgname, wildcard_audits)| {
+                    let prune_imports = mode(&pkgname[..]).prune_imports;
+                    let required_entries = required_entries
+                        .get(&pkgname[..])
+                        .unwrap_or(&no_required_entries);
+                    (
+                        pkgname,
+                        wildcard_audits
+                            .iter()
+                            .enumerate()
+                            .filter(|&(audit_index, entry)| {
+                                // Keep existing if we're not pruning imports.
+                                if !prune_imports && !entry.is_fresh_import {
+                                    return true;
+                                }
+
+                                if let Some(required_entries) = required_entries {
+                                    required_entries.contains_key(&RequiredEntry::WildcardAudit {
+                                        import_index,
+                                        audit_index,
+                                    })
+                                } else {
+                                    !entry.is_fresh_import
+                                }
+                            })
+                            .map(|(_, entry)| WildcardEntry {
+                                is_fresh_import: false,
+                                ..entry.clone()
+                            })
+                            .collect::<Vec<_>>(),
+                    )
+                })
+                .filter(|(_, l)| !l.is_empty())
+                .map(|(n, mut l)| {
+                    l.sort();
+                    (n.clone(), l)
+                })
+                .collect(),
+
+            audits: live_audits_file
+                .audits
+                .iter()
+                .map(|(pkgname, audits)| {
+                    let prune_imports = mode(&pkgname[..]).prune_imports;
+                    let (uses_package, required_entries) = match required_entries.get(&pkgname[..])
+                    {
+                        Some(e) => (true, e),
+                        None => (false, &no_required_entries),
+                    };
+                    (
+                        pkgname,
+                        audits
+                            .iter()
+                            .enumerate()
+                            .filter(|&(audit_index, entry)| {
+                                // Keep existing if we're not pruning imports.
+                                if !prune_imports && !entry.is_fresh_import {
+                                    return true;
+                                }
+
+                                // Keep violations if the package is used in the graph.
+                                if matches!(entry.kind, AuditKind::Violation { .. }) {
+                                    return uses_package;
+                                }
+
+                                if let Some(required_entries) = required_entries {
+                                    required_entries.contains_key(&RequiredEntry::Audit {
+                                        import_index,
+                                        audit_index,
+                                    })
+                                } else {
+                                    !entry.is_fresh_import
+                                }
+                            })
+                            .map(|(_, entry)| AuditEntry {
+                                is_fresh_import: false,
+                                ..entry.clone()
+                            })
+                            .collect::<Vec<_>>(),
+                    )
+                })
+                .filter(|(_, l)| !l.is_empty())
+                .map(|(n, mut l)| {
+                    l.sort();
+                    (n.clone(), l)
+                })
+                .collect(),
+
+            // We never import trusted entries in imports.lock.
+            trusted: SortedMap::new(),
+        };
+        new_imports
+            .audits
+            .insert(import_name.clone(), new_audits_file);
+    }
+
+    // Determine which live publisher information to keep in the imports.lock file.
+    for (pkgname, publishers) in store.publishers() {
+        let prune_imports = mode(&pkgname[..]).prune_imports;
+        let required_entries = required_entries
+            .get(&pkgname[..])
+            .unwrap_or(&no_required_entries);
+        let mut publishers: Vec<_> = publishers
+            .iter()
+            .enumerate()
+            .filter(|&(publisher_index, entry)| {
+                // Keep existing if we're not pruning imports.
+                if !prune_imports && !entry.is_fresh_import {
+                    return true;
+                }
+
+                if let Some(required_entries) = required_entries {
+                    required_entries.contains_key(&RequiredEntry::Publisher { publisher_index })
+                } else {
+                    !entry.is_fresh_import
+                }
+            })
+            .map(|(_, entry)| CratesPublisher {
+                is_fresh_import: false,
+                ..entry.clone()
+            })
+            .collect();
+        publishers.sort();
+        if !publishers.is_empty() {
+            new_imports.publisher.insert(pkgname.clone(), publishers);
+        }
+    }
+
+    // Determine which live publisher information to keep in the imports.lock file.
+    for (pkgname, unpublished) in store.unpublished() {
+        // Although `unpublished` entries are stored in imports.lock, they're
+        // more like automatically-managed delta-exemptions than imports, so
+        // we'll prune them when pruning exemptions.
+        let prune_exemptions = mode(&pkgname[..]).prune_exemptions;
+        let required_entries = required_entries
+            .get(&pkgname[..])
+            .unwrap_or(&no_required_entries);
+        let mut unpublished: Vec<_> = unpublished
+            .iter()
+            .enumerate()
+            .filter(|&(unpublished_index, entry)| {
+                // Keep existing if we're not pruning exemptions.
+                if !prune_exemptions && !entry.is_fresh_import {
+                    return true;
+                }
+
+                if let Some(required_entries) = required_entries {
+                    required_entries.contains_key(&RequiredEntry::Unpublished { unpublished_index })
+                } else {
+                    !entry.is_fresh_import
+                }
+            })
+            .map(|(_, entry)| UnpublishedEntry {
+                is_fresh_import: false,
+                ..entry.clone()
+            })
+            .collect();
+        unpublished.sort();
+        // Clean up any duplicate Unpublished entries now that `is_fresh_import`
+        // has been cleared.  This ensures that even if we end up using
+        // `PreferFreshImports` when not pruning exemptions, we won't end up
+        // with duplicate unpublished entries.
+        unpublished.dedup();
+        if !unpublished.is_empty() {
+            new_imports.unpublished.insert(pkgname.clone(), unpublished);
+        }
+    }
+
+    let mut all_new_exemptions = SortedMap::new();
+
+    // Enumerate existing exemptions to check for criteria changes.
+    for (pkgname, exemptions) in &store.config.exemptions {
+        let prune_exemptions = mode(pkgname).prune_exemptions;
+        let required_entries = required_entries
+            .get(&pkgname[..])
+            .unwrap_or(&no_required_entries);
+
+        let mut new_exemptions = Vec::with_capacity(exemptions.len());
+        for (exemption_index, entry) in exemptions.iter().enumerate() {
+            let original_criteria = criteria_mapper.criteria_from_list(&entry.criteria);
+
+            // Determine the set of useful criteria from required_entries,
+            // falling back to `original_criteria` if it failed to audit.
+            let mut useful_criteria = if let Some(required_entries) = required_entries {
+                required_entries
+                    .get(&RequiredEntry::Exemption { exemption_index })
+                    .cloned()
+                    .unwrap_or_else(|| criteria_mapper.no_criteria())
+            } else {
+                original_criteria.clone()
+            };
+
+            // If we're not pruning exemptions, maintain all existing criteria.
+            if !prune_exemptions {
+                useful_criteria.unioned_with(&original_criteria);
+            }
+            if useful_criteria.is_empty() {
+                continue; // Skip this exemption
+            }
+
+            // If we're expanding the criteria set and are `suggest = false`, we
+            // can't update the existing entry, so try adding a new one, and
+            // reset the existing node to the original criteria.
+            // XXX: The behaviour around suggest here is a bit jank, we might
+            // want to change it.
+            if !entry.suggest && !original_criteria.contains(&useful_criteria) {
+                let mut extra_criteria = useful_criteria.clone();
+                extra_criteria.clear_criteria(&original_criteria);
+                new_exemptions.push(ExemptedDependency {
+                    version: entry.version.clone(),
+                    criteria: criteria_mapper
+                        .criteria_names(&extra_criteria)
+                        .map(|n| n.to_owned().into())
+                        .collect(),
+                    suggest: true,
+                    notes: None,
+                });
+                useful_criteria = original_criteria;
+            }
+
+            // Add the exemption with the determined minimal useful criteria.
+            new_exemptions.push(ExemptedDependency {
+                version: entry.version.clone(),
+                criteria: criteria_mapper
+                    .criteria_names(&useful_criteria)
+                    .map(|n| n.to_owned().into())
+                    .collect(),
+                suggest: entry.suggest,
+                notes: entry.notes.clone(),
+            });
+        }
+        if !new_exemptions.is_empty() {
+            all_new_exemptions.insert(pkgname.clone(), new_exemptions);
+        }
+    }
+
+    // Check if we have any FreshExemption entries which should be converted
+    // into new exemptions.
+    for (&pkgname, required_entries) in &required_entries {
+        let Some(required_entries) = required_entries else { continue };
+
+        for (entry, criteria) in required_entries.iter().rev() {
+            let RequiredEntry::FreshExemption { version } = entry else {
+                // FreshExemption entries always sort last in the BTreeMap, so
+                // we can rely on there being no more FreshExemption entries
+                // after we've seen one.
+                break;
+            };
+
+            all_new_exemptions
+                .entry(pkgname.to_owned())
+                .or_default()
+                .push(ExemptedDependency {
+                    version: version.clone(),
+                    criteria: criteria_mapper
+                        .criteria_names(criteria)
+                        .map(|n| n.to_owned().into())
+                        .collect(),
+                    suggest: true,
+                    notes: None,
+                });
+        }
+    }
+
+    for exemptions in all_new_exemptions.values_mut() {
+        exemptions.sort();
+    }
+
+    (new_imports, all_new_exemptions)
+}
diff --git a/src/serialization.rs b/src/serialization.rs
new file mode 100644
index 0000000..5562e66
--- /dev/null
+++ b/src/serialization.rs
@@ -0,0 +1,811 @@
+//! Serialization helpers
+
+use crate::format::{CratesCacheUser, CratesUserId, CriteriaMap, FastMap, SortedMap};
+use core::fmt;
+use serde::{
+    de::{self, value, SeqAccess, Visitor},
+    Deserialize, Deserializer, Serialize, Serializer,
+};
+use spanned::Spanned;
+
+/// Serde handler to allow specifying any of [], "foo", ["foo"], or ["foo", "bar"],
+/// with the strings getting proper toml spans from the original source. Specifically,
+/// we deserialize `Vec<Spanned<String>>`.
+///
+/// This needs to solve three problems:
+///
+/// 1. Getting spans from toml deserialization, in general
+/// 2. Being able to take two very different inputs and squish them into one form
+/// 3. Wiring the span machinery into that process
+///
+/// Problem 1 is solved by toml-rs having a [Spanned] type for this exact thing.
+/// It's genuinely magic in that the deserializer just checks if it's deserializing
+/// a struct with its exact magic field names and then emits two extra values for the span.
+/// We fork the actual Spanned type here to make it more ergonomic/transparent
+///
+/// Problem 2 is solved by [string_or_vec_inner], which is based on an example in serde's
+/// own docs. We create a custom visitor that can be visited with either a sequence or
+/// a string. In the sequence case we just do a normal deserialization of our target.
+/// In the string case we create a new Vec to wrap our string.
+///
+/// Problem 3 is where things get messy. Our solution to Problem 2 naturally gets spanned
+/// for the sequence case, but in the string case we're "too late" to request spanning,
+/// as we no longer have a deserializer, just a `str`. All we can do is emit a dummy Spanned.
+///
+/// Our solution to this is to recognize that in this case we have an array of one element,
+/// and so a span over the entire array is just as good as a span over the element.
+/// So we deserialize a `Spanned<Vec<Spanned<String>>>` and if the Vec has `len == 1`,
+/// we edit the dummy inner span to equal the outer span. We then just discard the outer span
+/// (or we could keep it if we decide that's useful later).
+///
+/// To make this a bit easier to do, we wrap Problem 2's solution in a `StringOrVec` struct,
+/// deserialize `Spanned<StringOrVec>`, and then transform that into `Vec<Spanned<String>>`
+pub mod string_or_vec {
+    use super::*;
+
+    /// A type using string_or_vec_inner to make it easy to wrap in Spanned.
+    #[derive(Serialize, Deserialize)]
+    pub struct StringOrVec(
+        #[serde(default)]
+        #[serde(with = "string_or_vec_inner")]
+        pub Vec<Spanned<String>>,
+    );
+
+    pub fn serialize<S, T>(v: &Vec<T>, s: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+        T: AsRef<str> + Serialize,
+    {
+        if v.len() == 1 {
+            s.serialize_str(v[0].as_ref())
+        } else {
+            v.serialize(s)
+        }
+    }
+
+    pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
+    where
+        D: Deserializer<'de>,
+        T: StringOrVecLike,
+    {
+        // Get a Spanned<StringOrVec> and then use it to fixup a dummy span for len == 1
+        let spanned_vec = Spanned::<StringOrVec>::deserialize(deserializer)?;
+        let start = Spanned::start(&spanned_vec);
+        let end = Spanned::end(&spanned_vec);
+        let mut vec = Spanned::into_inner(spanned_vec).0;
+        if vec.len() == 1 {
+            Spanned::update_span(&mut vec[0], start, end);
+        }
+        Ok(StringOrVecLike::convert(vec))
+    }
+
+    // Helper trait to allow non-spanned deserialization of string_or_vec.
+    pub trait StringOrVecLike {
+        fn convert(from: Vec<Spanned<String>>) -> Self;
+    }
+    impl StringOrVecLike for Vec<Spanned<String>> {
+        fn convert(from: Vec<Spanned<String>>) -> Self {
+            from
+        }
+    }
+    impl StringOrVecLike for Vec<String> {
+        fn convert(from: Vec<Spanned<String>>) -> Self {
+            from.into_iter().map(Spanned::into_inner).collect()
+        }
+    }
+}
+
+/// See [string_or_vec]
+pub mod string_or_vec_inner {
+    use super::*;
+
+    pub fn serialize<S>(v: &Vec<Spanned<String>>, s: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        if v.len() == 1 {
+            s.serialize_str(&v[0])
+        } else {
+            v.serialize(s)
+        }
+    }
+
+    pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<Spanned<String>>, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        struct StringOrVec;
+
+        impl<'de> Visitor<'de> for StringOrVec {
+            type Value = Vec<Spanned<String>>;
+
+            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+                formatter.write_str("string or list of strings")
+            }
+
+            fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
+            where
+                E: de::Error,
+            {
+                Ok(vec![Spanned::from(s.to_owned())])
+            }
+
+            fn visit_seq<S>(self, seq: S) -> Result<Self::Value, S::Error>
+            where
+                S: SeqAccess<'de>,
+            {
+                Deserialize::deserialize(value::SeqAccessDeserializer::new(seq))
+            }
+        }
+
+        deserializer.deserialize_any(StringOrVec)
+    }
+}
+
+/// Similar to the above, but distinguishes an empty list from an absent one.
+///
+/// Fields using these handlers must be annotated with #[serde(default)]
+pub mod string_or_vec_or_none {
+    use super::*;
+
+    pub fn serialize<S>(maybe_v: &Option<Vec<Spanned<String>>>, s: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        if let Some(v) = maybe_v {
+            string_or_vec::serialize(v, s)
+        } else {
+            s.serialize_none()
+        }
+    }
+
+    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Vec<Spanned<String>>>, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        // If the value isn't present in the stream, this deserializer won't be
+        // invoked at all and the #[serde(default)] will result in `None`.
+        string_or_vec::deserialize(deserializer).map(Some)
+    }
+}
+
+/// Allows the `Vec<String>` map value in dependency-criteria or criteria-map to
+/// support `string_or_vec` semantics.
+pub mod criteria_map {
+    use crate::format::CriteriaName;
+
+    use super::*;
+    #[derive(Serialize, Deserialize)]
+    struct Wrapper(#[serde(with = "string_or_vec")] Vec<Spanned<CriteriaName>>);
+
+    pub fn serialize<S>(c: &CriteriaMap, s: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        let m: SortedMap<Spanned<String>, Wrapper> = c
+            .iter()
+            .map(|(k, v)| (k.clone(), Wrapper(v.clone())))
+            .collect();
+        m.serialize(s)
+    }
+
+    pub fn deserialize<'de, D>(deserializer: D) -> Result<CriteriaMap, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        let m = SortedMap::<Spanned<String>, Wrapper>::deserialize(deserializer)?;
+        Ok(m.into_iter().map(|(k, v)| (k, v.0)).collect())
+    }
+}
+
+pub mod policy {
+    use super::*;
+    use crate::format::{PackageName, PackagePolicyEntry, Policy, PolicyEntry, VetVersion};
+
+    const VERSION_SEPARATOR: &str = ":";
+
+    #[derive(serde::Serialize, serde::Deserialize)]
+    #[serde(transparent)]
+    pub struct AllPolicies(SortedMap<String, PolicyEntry>);
+
+    #[derive(Debug, thiserror::Error)]
+    pub enum FromAllPoliciesError {
+        #[error("cannot mix versioned and unversioned policies for {name}")]
+        MixedVersioning { name: PackageName },
+        #[error("more than one policy provided for {name}:{version}")]
+        Duplicate {
+            name: PackageName,
+            version: VetVersion,
+        },
+        #[error(transparent)]
+        VersionParse(#[from] crate::errors::VersionParseError),
+    }
+
+    impl std::convert::TryFrom<AllPolicies> for Policy {
+        type Error = FromAllPoliciesError;
+
+        fn try_from(value: AllPolicies) -> Result<Self, Self::Error> {
+            let mut policy = Policy::default();
+            for (name, entry) in value.0 {
+                match name.split_once(VERSION_SEPARATOR) {
+                    Some((crate_name, crate_version)) => {
+                        match policy
+                            .package
+                            .entry(crate_name.to_owned())
+                            .or_insert_with(|| PackagePolicyEntry::Versioned {
+                                version: Default::default(),
+                            }) {
+                            PackagePolicyEntry::Versioned { version } => {
+                                version.insert(crate_version.parse()?, entry);
+                            }
+                            PackagePolicyEntry::Unversioned(_) => {
+                                return Err(FromAllPoliciesError::MixedVersioning {
+                                    name: crate_name.to_owned(),
+                                });
+                            }
+                        }
+                    }
+                    None => {
+                        if policy
+                            .package
+                            .insert(name.clone(), PackagePolicyEntry::Unversioned(entry))
+                            .is_some()
+                        {
+                            // The entry _must_ have been `PackagePolicyEntry::Versioned`, because
+                            // if it were unversioned there would be no way for more than one entry
+                            // to exist in the AllPolicies map.
+                            return Err(FromAllPoliciesError::MixedVersioning { name });
+                        }
+                    }
+                }
+            }
+            Ok(policy)
+        }
+    }
+
+    impl From<Policy> for AllPolicies {
+        fn from(policy: Policy) -> Self {
+            let mut ret = SortedMap::default();
+
+            for (name, v) in policy.package {
+                match v {
+                    PackagePolicyEntry::Versioned { version } => {
+                        for (version, entry) in version {
+                            ret.insert(format!("{name}{VERSION_SEPARATOR}{version}"), entry);
+                        }
+                    }
+                    PackagePolicyEntry::Unversioned(entry) => {
+                        ret.insert(name, entry);
+                    }
+                }
+            }
+
+            AllPolicies(ret)
+        }
+    }
+}
+
+pub mod audit {
+    use super::*;
+
+    use crate::format::{AuditEntry, AuditKind, CriteriaName, Delta, VersionReq, VetVersion};
+
+    #[derive(Serialize, Deserialize)]
+    #[serde(deny_unknown_fields)]
+    pub struct AuditEntryAll {
+        #[serde(default)]
+        #[serde(skip_serializing_if = "Vec::is_empty")]
+        #[serde(with = "string_or_vec")]
+        who: Vec<Spanned<String>>,
+        #[serde(default)]
+        #[serde(with = "string_or_vec")]
+        criteria: Vec<Spanned<CriteriaName>>,
+        version: Option<VetVersion>,
+        delta: Option<Delta>,
+        violation: Option<VersionReq>,
+        notes: Option<String>,
+        #[serde(rename = "aggregated-from")]
+        #[serde(skip_serializing_if = "Vec::is_empty")]
+        #[serde(with = "string_or_vec")]
+        #[serde(default)]
+        pub aggregated_from: Vec<Spanned<String>>,
+    }
+
+    impl TryFrom<AuditEntryAll> for AuditEntry {
+        type Error = String;
+        fn try_from(val: AuditEntryAll) -> Result<AuditEntry, Self::Error> {
+            let kind = match (val.version, val.delta, val.violation) {
+                (Some(version), None, None) => Ok(AuditKind::Full { version }),
+                (None, Some(delta), None) => {
+                    if let Some(from) = delta.from {
+                        Ok(AuditKind::Delta { from, to: delta.to })
+                    } else {
+                        Err("'delta' must be a delta of the form 'VERSION -> VERSION'".to_string())
+                    }
+                }
+                (None, None, Some(violation)) => Ok(AuditKind::Violation { violation }),
+                _ => Err(
+                    "audit entires must have exactly one of 'version', 'delta', and 'violation'"
+                        .to_string(),
+                ),
+            };
+            Ok(AuditEntry {
+                who: val.who,
+                notes: val.notes,
+                criteria: val.criteria,
+                kind: kind?,
+                aggregated_from: val.aggregated_from,
+                // By default, always read entries as non-fresh. The import code
+                // will set this flag to true for imported entries.
+                is_fresh_import: false,
+            })
+        }
+    }
+
+    impl From<AuditEntry> for AuditEntryAll {
+        fn from(val: AuditEntry) -> AuditEntryAll {
+            let (version, delta, violation) = match val.kind {
+                AuditKind::Full { version } => (Some(version), None, None),
+                AuditKind::Delta { from, to } => (
+                    None,
+                    Some(Delta {
+                        from: Some(from),
+                        to,
+                    }),
+                    None,
+                ),
+                AuditKind::Violation { violation } => (None, None, Some(violation)),
+            };
+            AuditEntryAll {
+                who: val.who,
+                notes: val.notes,
+                criteria: val.criteria,
+                version,
+                delta,
+                violation,
+                aggregated_from: val.aggregated_from,
+            }
+        }
+    }
+}
+
+/// Inline arrays which have a representation longer than this will be rendered
+/// over multiple lines.
+const ARRAY_WRAP_THRESHOLD: usize = 80;
+
+/// Tables which would be rendered inline (see `INLINE_TABLE_KEYS`) and have an
+/// inline representation larger than this threshold will be rendered by
+/// TomlFormatter as non-inline tables.
+const INLINE_TABLE_THRESHOLD: usize = 120;
+
+/// Names for tables which should be rendered inline by TomlFormatter unless
+/// their inline representation exceeds `INLINE_TABLE_THRESHOLD`.
+const INLINE_TABLE_KEYS: &[&str] = &["dependency-criteria"];
+
+fn inline_length(key: &str, value: &toml_edit::Item) -> usize {
+    // Length of the string " = " which will appear between the key and value.
+    const DECORATION_LENGTH: usize = 3;
+
+    // The `Display` implementation for toml_edit values will produce the final
+    // serialized representation of the value in the TOML document, which we can
+    // use to determine the line length in utf-8 characters.
+    let value_repr = value.to_string();
+    key.chars().count() + value_repr.chars().count() + DECORATION_LENGTH
+}
+
+/// Tables with these names will be rendered by TomlFormatter as inline tables,
+/// rather than dotted tables unless their inline representation exceeds
+/// `INLINE_TABLE_THRESHOLD` UTF-8 characters in length.
+fn table_should_be_inline(key: &str, value: &toml_edit::Item) -> bool {
+    if !INLINE_TABLE_KEYS.contains(&key) {
+        return false;
+    }
+    inline_length(key, value) <= INLINE_TABLE_THRESHOLD
+}
+
+/// Serialize the given data structure as a formatted `toml_edit::Document`.
+///
+/// The returned document can be converted to a string or otherwise written out
+/// using it's `Display` implementation.
+///
+/// Can fail if `T`'s implementation of `Serialize` fails.
+pub fn to_formatted_toml<T>(
+    val: T,
+    user_info: Option<&FastMap<CratesUserId, CratesCacheUser>>,
+) -> Result<toml_edit::Document, toml_edit::ser::Error>
+where
+    T: Serialize,
+{
+    use toml_edit::visit_mut::VisitMut;
+
+    struct TomlFormatter<'a> {
+        user_info: Option<&'a FastMap<CratesUserId, CratesCacheUser>>,
+    }
+    impl TomlFormatter<'_> {
+        fn add_user_login_comments(&self, v: &mut toml_edit::Item) {
+            let Some(user_info) = &self.user_info else { return };
+            let Some(v) = v.as_value_mut() else { return };
+            let Some(user_id) = v.as_integer() else { return };
+            let Some(info) = user_info.get(&(user_id as u64)) else { return };
+            v.decor_mut().set_suffix(format!(" # {}", info));
+        }
+    }
+    impl VisitMut for TomlFormatter<'_> {
+        fn visit_table_mut(&mut self, node: &mut toml_edit::Table) {
+            // Hide unnecessary implicit table headers for tables containing
+            // only other tables. We don't do this for empty tables as otherwise
+            // they could be hidden.
+            if !node.is_empty() {
+                node.set_implicit(true);
+            }
+            let is_publisher_entry = node.get("user-login").is_some();
+            for (k, v) in node.iter_mut() {
+                if !table_should_be_inline(&k, v) {
+                    // Try to convert the value into either a table or an array of
+                    // tables if it is currently an inline table or inline array of
+                    // tables.
+                    *v = std::mem::take(v)
+                        .into_table()
+                        .map(toml_edit::Item::Table)
+                        .unwrap_or_else(|i| i)
+                        .into_array_of_tables()
+                        .map(toml_edit::Item::ArrayOfTables)
+                        .unwrap_or_else(|i| i);
+                }
+
+                // If we didn't convert the array into an array of tables above,
+                // check if it would be too long and wrap it onto multiple lines
+                // if it would.
+                if v.is_array() && inline_length(&k, v) > ARRAY_WRAP_THRESHOLD {
+                    let array = v.as_array_mut().unwrap();
+                    for item in array.iter_mut() {
+                        item.decor_mut().set_prefix("\n    ");
+                    }
+                    array.set_trailing("\n");
+                    array.set_trailing_comma(true);
+                }
+
+                if k == "user-id" && !is_publisher_entry {
+                    self.add_user_login_comments(v);
+                }
+
+                self.visit_item_mut(v);
+            }
+        }
+    }
+
+    let mut toml_document = toml_edit::ser::to_document(&val)?;
+    TomlFormatter { user_info }.visit_document_mut(&mut toml_document);
+    Ok(toml_document)
+}
+
+/// Deserialize the given data structure from a toml::Value, without falling
+/// over due to Spanned failing to parse.
+pub fn parse_from_value<T>(value: toml::Value) -> Result<T, toml::de::Error>
+where
+    T: for<'a> Deserialize<'a>,
+{
+    spanned::DISABLE_SPANNED_DESERIALIZATION.with(|disabled| {
+        let prev = disabled.replace(true);
+        let rv = T::deserialize(value);
+        disabled.set(prev);
+        rv
+    })
+}
+
+pub mod spanned {
+    use std::{
+        borrow::Borrow,
+        cell::Cell,
+        cmp::Ordering,
+        fmt::{self, Display},
+        hash::{Hash, Hasher},
+        ops::{Deref, DerefMut},
+    };
+
+    use miette::SourceSpan;
+    use serde::{de, ser};
+
+    thread_local! {
+        /// Hack to work around `toml::Spanned` failing to be deserialized when
+        /// used with the `toml::Value` deserializer.
+        pub(super) static DISABLE_SPANNED_DESERIALIZATION: Cell<bool> = Cell::new(false);
+    }
+
+    /// A spanned value, indicating the range at which it is defined in the source.
+    #[derive(Clone, Default)]
+    pub struct Spanned<T> {
+        start: usize,
+        end: usize,
+        value: T,
+    }
+
+    impl<T> Spanned<T> {
+        /// Create a Spanned with a specific SourceSpan.
+        pub fn with_source_span(value: T, source: SourceSpan) -> Self {
+            Spanned {
+                start: source.offset(),
+                end: source.offset() + source.len(),
+                value,
+            }
+        }
+
+        /// Access the start of the span of the contained value.
+        pub fn start(this: &Self) -> usize {
+            this.start
+        }
+
+        /// Access the end of the span of the contained value.
+        pub fn end(this: &Self) -> usize {
+            this.end
+        }
+
+        /// Update the span
+        pub fn update_span(this: &mut Self, start: usize, end: usize) {
+            this.start = start;
+            this.end = end;
+        }
+
+        /// Alter a span to a length anchored from the end.
+        pub fn from_end(mut this: Self, length: usize) -> Self {
+            this.start = this.end - length;
+            this
+        }
+
+        /// Get the span of the contained value.
+        pub fn span(this: &Self) -> SourceSpan {
+            (Self::start(this)..Self::end(this)).into()
+        }
+
+        /// Consumes the spanned value and returns the contained value.
+        pub fn into_inner(this: Self) -> T {
+            this.value
+        }
+    }
+
+    impl<T> IntoIterator for Spanned<T>
+    where
+        T: IntoIterator,
+    {
+        type IntoIter = T::IntoIter;
+        type Item = T::Item;
+        fn into_iter(self) -> Self::IntoIter {
+            self.value.into_iter()
+        }
+    }
+
+    impl<'a, T> IntoIterator for &'a Spanned<T>
+    where
+        &'a T: IntoIterator,
+    {
+        type IntoIter = <&'a T as IntoIterator>::IntoIter;
+        type Item = <&'a T as IntoIterator>::Item;
+        fn into_iter(self) -> Self::IntoIter {
+            self.value.into_iter()
+        }
+    }
+
+    impl<'a, T> IntoIterator for &'a mut Spanned<T>
+    where
+        &'a mut T: IntoIterator,
+    {
+        type IntoIter = <&'a mut T as IntoIterator>::IntoIter;
+        type Item = <&'a mut T as IntoIterator>::Item;
+        fn into_iter(self) -> Self::IntoIter {
+            self.value.into_iter()
+        }
+    }
+
+    impl<T> fmt::Debug for Spanned<T>
+    where
+        T: fmt::Debug,
+    {
+        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+            self.value.fmt(f)
+        }
+    }
+
+    impl<T> Display for Spanned<T>
+    where
+        T: Display,
+    {
+        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+            self.value.fmt(f)
+        }
+    }
+
+    impl<T> Deref for Spanned<T> {
+        type Target = T;
+        fn deref(&self) -> &Self::Target {
+            &self.value
+        }
+    }
+
+    impl<T> DerefMut for Spanned<T> {
+        fn deref_mut(&mut self) -> &mut Self::Target {
+            &mut self.value
+        }
+    }
+
+    impl Borrow<str> for Spanned<String> {
+        fn borrow(&self) -> &str {
+            self
+        }
+    }
+
+    impl<T> Borrow<T> for Spanned<T> {
+        fn borrow(&self) -> &T {
+            self
+        }
+    }
+
+    impl<T, U: ?Sized> AsRef<U> for Spanned<T>
+    where
+        T: AsRef<U>,
+    {
+        fn as_ref(&self) -> &U {
+            self.value.as_ref()
+        }
+    }
+
+    impl<T: PartialEq> PartialEq for Spanned<T> {
+        fn eq(&self, other: &Self) -> bool {
+            self.value.eq(&other.value)
+        }
+    }
+
+    impl<T: PartialEq<T>> PartialEq<T> for Spanned<T> {
+        fn eq(&self, other: &T) -> bool {
+            self.value.eq(other)
+        }
+    }
+
+    impl<T: Eq> Eq for Spanned<T> {}
+
+    impl<T: Hash> Hash for Spanned<T> {
+        fn hash<H: Hasher>(&self, state: &mut H) {
+            self.value.hash(state);
+        }
+    }
+
+    impl<T: PartialOrd> PartialOrd for Spanned<T> {
+        fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+            self.value.partial_cmp(&other.value)
+        }
+    }
+
+    impl<T: PartialOrd<T>> PartialOrd<T> for Spanned<T> {
+        fn partial_cmp(&self, other: &T) -> Option<Ordering> {
+            self.value.partial_cmp(other)
+        }
+    }
+
+    impl<T: Ord> Ord for Spanned<T> {
+        fn cmp(&self, other: &Self) -> Ordering {
+            self.value.cmp(&other.value)
+        }
+    }
+
+    impl<T> From<T> for Spanned<T> {
+        fn from(value: T) -> Self {
+            Self {
+                start: 0,
+                end: 0,
+                value,
+            }
+        }
+    }
+
+    impl<T> From<toml::Spanned<T>> for Spanned<T> {
+        fn from(value: toml::Spanned<T>) -> Self {
+            Self {
+                start: value.start(),
+                end: value.end(),
+                value: value.into_inner(),
+            }
+        }
+    }
+
+    impl<'de, T: de::Deserialize<'de>> de::Deserialize<'de> for Spanned<T> {
+        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+        where
+            D: de::Deserializer<'de>,
+        {
+            Ok(if DISABLE_SPANNED_DESERIALIZATION.with(|d| d.get()) {
+                T::deserialize(deserializer)?.into()
+            } else {
+                toml::Spanned::<T>::deserialize(deserializer)?.into()
+            })
+        }
+    }
+
+    impl<T: ser::Serialize> ser::Serialize for Spanned<T> {
+        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+        where
+            S: ser::Serializer,
+        {
+            self.value.serialize(serializer)
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use crate::format::*;
+
+    #[test]
+    fn toml_formatter_wrapping() {
+        let mut dc_long = SortedMap::new();
+        dc_long.insert(
+            "example-crate-1".to_owned().into(),
+            vec![
+                "criteria-one-very-long".to_owned().into(),
+                "criteria-two-very-long".to_owned().into(),
+            ],
+        );
+        dc_long.insert(
+            "example-crate-2".to_owned().into(),
+            vec![
+                // This array would wrap over multiple lines if byte length was
+                // used rather than utf-8 character length.
+                "criteria-one-✨✨✨✨✨✨✨✨✨✨".to_owned().into(),
+                "criteria-two-✨✨✨✨✨✨✨✨✨✨".to_owned().into(),
+            ],
+        );
+        dc_long.insert(
+            "example-crate-3".to_owned().into(),
+            vec![
+                "criteria-one-very-long".to_owned().into(),
+                "criteria-two-very-long".to_owned().into(),
+                "criteria-three-extremely-long-this-array-should-wrap"
+                    .to_owned()
+                    .into(),
+            ],
+        );
+
+        let mut dc_short = SortedMap::new();
+        dc_short.insert(
+            "example-crate-1".to_owned().into(),
+            vec!["criteria-one".to_owned().into()],
+        );
+
+        let mut policy = Policy::default();
+        policy.insert(
+            "long-criteria".to_owned(),
+            PackagePolicyEntry::Unversioned(PolicyEntry {
+                audit_as_crates_io: None,
+                criteria: Some(vec!["long-criteria".to_owned().into()]),
+                dev_criteria: None,
+                dependency_criteria: dc_long,
+                notes: Some("notes go here!".to_owned()),
+            }),
+        );
+        policy.insert(
+            "short-criteria".to_owned(),
+            PackagePolicyEntry::Unversioned(PolicyEntry {
+                audit_as_crates_io: None,
+                criteria: Some(vec!["short-criteria".to_owned().into()]),
+                dev_criteria: None,
+                dependency_criteria: dc_short,
+                notes: Some("notes go here!".to_owned()),
+            }),
+        );
+
+        let formatted = super::to_formatted_toml(
+            ConfigFile {
+                cargo_vet: CargoVetConfig {
+                    version: StoreVersion { major: 1, minor: 0 },
+                },
+                default_criteria: get_default_criteria(),
+                imports: SortedMap::new(),
+                policy,
+                exemptions: SortedMap::new(),
+            },
+            None,
+        )
+        .unwrap()
+        .to_string();
+
+        insta::assert_snapshot!("formatted_toml_long_inline", formatted);
+    }
+}
diff --git a/src/snapshots/cargo_vet__serialization__test__formatted_toml_long_inline.snap b/src/snapshots/cargo_vet__serialization__test__formatted_toml_long_inline.snap
new file mode 100644
index 0000000..0310374
--- /dev/null
+++ b/src/snapshots/cargo_vet__serialization__test__formatted_toml_long_inline.snap
@@ -0,0 +1,26 @@
+---
+source: src/serialization.rs
+expression: formatted
+---
+
+[cargo-vet]
+version = "1.0"
+
+[policy.long-criteria]
+criteria = "long-criteria"
+notes = "notes go here!"
+
+[policy.long-criteria.dependency-criteria]
+example-crate-1 = ["criteria-one-very-long", "criteria-two-very-long"]
+example-crate-2 = ["criteria-one-✨✨✨✨✨✨✨✨✨✨", "criteria-two-✨✨✨✨✨✨✨✨✨✨"]
+example-crate-3 = [
+    "criteria-one-very-long",
+    "criteria-two-very-long",
+    "criteria-three-extremely-long-this-array-should-wrap",
+]
+
+[policy.short-criteria]
+criteria = "short-criteria"
+dependency-criteria = { example-crate-1 = "criteria-one" }
+notes = "notes go here!"
+
diff --git a/src/storage.rs b/src/storage.rs
new file mode 100644
index 0000000..50ec3c7
--- /dev/null
+++ b/src/storage.rs
@@ -0,0 +1,3039 @@
+use std::{
+    ffi::{OsStr, OsString},
+    fs::{self, File, OpenOptions},
+    io::{self, BufReader, Read, Seek, Write},
+    mem,
+    path::{Path, PathBuf},
+    sync::{Arc, Mutex},
+    time::{Duration, SystemTime},
+};
+
+use cargo_metadata::semver;
+use flate2::read::GzDecoder;
+use futures_util::future::{join_all, try_join_all};
+use miette::SourceOffset;
+use reqwest::Url;
+use serde::{Deserialize, Serialize};
+use similar::{udiff::unified_diff, Algorithm};
+use tar::Archive;
+use tracing::{error, info, log::warn, trace};
+
+use crate::{
+    criteria::CriteriaMapper,
+    errors::{
+        AggregateError, BadFormatError, BadWildcardEndDateError, CacheAcquireError,
+        CacheCommitError, CertifyError, CommandError, CrateInfoError, CriteriaChangeError,
+        CriteriaChangeErrors, DiffError, DownloadError, FetchAndDiffError,
+        FetchAuditAggregateError, FetchAuditError, FetchError, FetchRegistryError, FlockError,
+        InvalidCriteriaError, JsonParseError, LoadJsonError, LoadTomlError, SourceFile,
+        StoreAcquireError, StoreCommitError, StoreCreateError, StoreJsonError, StoreTomlError,
+        StoreValidateError, StoreValidateErrors, TomlParseError, UnpackCheckoutError, UnpackError,
+    },
+    flock::{FileLock, Filesystem},
+    format::{
+        self, AuditEntry, AuditKind, AuditedDependencies, AuditsFile, CommandHistory, ConfigFile,
+        CratesAPICrate, CratesAPICrateMetadata, CratesCache, CratesCacheEntry, CratesCacheUser,
+        CratesCacheVersionDetails, CratesPublisher, CratesUserId, CriteriaEntry, CriteriaMap,
+        CriteriaName, CriteriaStr, Delta, DiffCache, DiffStat, FastMap, FastSet, FetchCommand,
+        ForeignAuditsFile, ImportName, ImportsFile, MetaConfig, PackageName, PackageStr,
+        RegistryEntry, RegistryFile, SortedMap, StoreVersion, TrustEntry, TrustedPackages,
+        UnpublishedEntry, VetVersion, WildcardAudits, WildcardEntry, SAFE_TO_DEPLOY, SAFE_TO_RUN,
+    },
+    network::Network,
+    out::{progress_bar, IncProgressOnDrop},
+    serialization::{parse_from_value, spanned::Spanned, to_formatted_toml},
+    Config, PackageExt, PartialConfig, CARGO_ENV,
+};
+
+// tmp cache for various shenanigans
+const CACHE_DIFF_CACHE: &str = "diff-cache.toml";
+const CACHE_COMMAND_HISTORY: &str = "command-history.json";
+const CACHE_CRATES_IO_CACHE: &str = "crates-io-cache.json";
+const CACHE_EMPTY_PACKAGE: &str = "empty";
+const CACHE_REGISTRY_SRC: &str = "src";
+const CACHE_REGISTRY_CACHE: &str = "cache";
+const CACHE_VET_LOCK: &str = ".vet-lock";
+
+// Files which are allowed to appear in the root of the cache directory, and
+// will not be GC'd
+const CACHE_ALLOWED_FILES: &[&str] = &[
+    CACHE_DIFF_CACHE,
+    CACHE_COMMAND_HISTORY,
+    CACHE_CRATES_IO_CACHE,
+    CACHE_EMPTY_PACKAGE,
+    CACHE_REGISTRY_SRC,
+    CACHE_REGISTRY_CACHE,
+    CACHE_VET_LOCK,
+];
+
+// Various cargo values
+const CARGO_REGISTRY: &str = "registry";
+const CARGO_REGISTRY_SRC: &str = "src";
+const CARGO_REGISTRY_CRATES_IO_GIT: &str = "github.com-1ecc6299db9ec823";
+const CARGO_REGISTRY_CRATES_IO_HTTP: &str = "index.crates.io-6f17d22bba15001f";
+const CARGO_TOML_FILE: &str = "Cargo.toml";
+const CARGO_OK_FILE: &str = ".cargo-ok";
+const CARGO_OK_BODY: &str = "ok";
+
+pub const DEFAULT_STORE: &str = "supply-chain";
+
+const AUDITS_TOML: &str = "audits.toml";
+const CONFIG_TOML: &str = "config.toml";
+const IMPORTS_LOCK: &str = "imports.lock";
+
+// Files which are skipped when counting changes for diffs.
+const DIFF_SKIP_PATHS: &[&str] = &["Cargo.lock", ".cargo_vcs_info.json", ".cargo-ok"];
+
+// FIXME: This is a completely arbitrary number, and may be too high or too low.
+const MAX_CONCURRENT_DIFFS: usize = 40;
+
+// Check for new crate metadata every 60 days.
+const METADATA_CACHE_EXPIRY_DAYS: i64 = 60;
+// Re-check the set of versions that exist for a specific crate every day.
+const VERSIONS_CACHE_EXPIRY_DAYS: i64 = 1;
+// Check whether a crate which was previously found to not exist now exists every 60 days.
+const NONEXISTENT_CRATE_EXPIRY_DAYS: i64 = 60;
+
+// Url of the registry.
+pub const REGISTRY_URL: &str =
+    "https://raw.githubusercontent.com/mozilla/cargo-vet/main/registry.toml";
+
+struct StoreLock {
+    config: FileLock,
+}
+
+impl StoreLock {
+    fn new(store: &Filesystem) -> Result<Self, FlockError> {
+        Ok(StoreLock {
+            config: store.open_rw(CONFIG_TOML, "vet store")?,
+        })
+    }
+    fn read_config(&self) -> io::Result<impl Read + '_> {
+        let mut file = self.config.file();
+        file.rewind()?;
+        Ok(file)
+    }
+    fn write_config(&self) -> io::Result<impl Write + '_> {
+        let mut file = self.config.file();
+        file.rewind()?;
+        file.set_len(0)?;
+        Ok(file)
+    }
+    fn read_audits(&self) -> io::Result<impl Read> {
+        File::open(self.config.parent().join(AUDITS_TOML))
+    }
+    fn write_audits(&self) -> io::Result<impl Write> {
+        File::create(self.config.parent().join(AUDITS_TOML))
+    }
+    fn read_imports(&self) -> io::Result<impl Read> {
+        File::open(self.config.parent().join(IMPORTS_LOCK))
+    }
+    fn write_imports(&self) -> io::Result<impl Write> {
+        File::create(self.config.parent().join(IMPORTS_LOCK))
+    }
+}
+
+/// The store (typically `supply-chain/`)
+///
+/// All access to this directory should be managed by this type to avoid races.
+/// By default, modifications to this type will not be written back to the store
+/// because we don't generally want to write back any results unless everything
+/// goes perfectly.
+///
+/// To write back this value, use [`Store::commit`][].
+pub struct Store {
+    // Exclusive file lock held for the config file
+    lock: Option<StoreLock>,
+
+    // Contents of the store, eagerly loaded and already validated.
+    pub config: ConfigFile,
+    pub imports: ImportsFile,
+    pub audits: AuditsFile,
+
+    // The complete live set of imports fetched from the network. Will be
+    // initialized to `None` if `--locked` was passed.
+    pub live_imports: Option<ImportsFile>,
+
+    pub config_src: SourceFile,
+    pub imports_src: SourceFile,
+    pub audits_src: SourceFile,
+}
+
+impl Store {
+    /// Create a new store (files will be completely empty, must be committed for files to be created)
+    pub fn create(cfg: &Config) -> Result<Self, StoreCreateError> {
+        let root = cfg.metacfg.store_path();
+        root.create_dir().map_err(StoreCreateError::CouldntCreate)?;
+
+        let lock = StoreLock::new(&root)?;
+
+        Ok(Self {
+            lock: Some(lock),
+            config: ConfigFile {
+                cargo_vet: Default::default(),
+                default_criteria: format::get_default_criteria(),
+                imports: SortedMap::new(),
+                policy: Default::default(),
+                exemptions: SortedMap::new(),
+            },
+            imports: ImportsFile {
+                unpublished: SortedMap::new(),
+                publisher: SortedMap::new(),
+                audits: SortedMap::new(),
+            },
+            audits: AuditsFile {
+                criteria: SortedMap::new(),
+                wildcard_audits: SortedMap::new(),
+                audits: SortedMap::new(),
+                trusted: SortedMap::new(),
+            },
+            live_imports: None,
+            config_src: SourceFile::new_empty(CONFIG_TOML),
+            audits_src: SourceFile::new_empty(AUDITS_TOML),
+            imports_src: SourceFile::new_empty(IMPORTS_LOCK),
+        })
+    }
+
+    pub fn is_init(metacfg: &MetaConfig) -> bool {
+        // Probably want to do more here later...
+        metacfg.store_path().as_path_unlocked().exists()
+    }
+
+    pub fn acquire_offline(cfg: &Config) -> Result<Self, StoreAcquireError> {
+        let root = cfg.metacfg.store_path();
+
+        // Before we do anything else, acquire an exclusive lock on the
+        // config.toml file in the store.
+        // XXX: Consider acquiring a non-exclusive lock in cases where an
+        // exclusive one isn't needed.
+        let lock = StoreLock::new(&root)?;
+
+        let (config_src, mut config): (_, ConfigFile) =
+            load_toml(CONFIG_TOML, lock.read_config()?)?;
+
+        // Compare the version from the store with the current StoreVersion.
+        // It's always an error to downgrade cargo-vet versions, but only an
+        // error to upgrade versions when --locked.
+        let current_version = StoreVersion::current();
+        if config.cargo_vet.version < current_version && cfg.cli.locked {
+            return Err(StoreAcquireError::OutdatedStore(config.cargo_vet.version));
+        } else if config.cargo_vet.version > current_version {
+            return Err(StoreAcquireError::NewerStore(config.cargo_vet.version));
+        }
+        config.cargo_vet.version = current_version;
+
+        let (audits_src, audits): (_, AuditsFile) = load_toml(AUDITS_TOML, lock.read_audits()?)?;
+        let (imports_src, imports): (_, ImportsFile) =
+            load_toml(IMPORTS_LOCK, lock.read_imports()?)?;
+
+        let store = Self {
+            lock: Some(lock),
+            config,
+            audits,
+            imports,
+            live_imports: None,
+            config_src,
+            audits_src,
+            imports_src,
+        };
+
+        // Check that the store isn't corrupt
+        store.validate(cfg.today(), cfg.cli.locked)?;
+
+        Ok(store)
+    }
+
+    /// Acquire an existing store
+    ///
+    /// If `network` is passed and `!cfg.cli.locked`, this will fetch remote
+    /// imports to use for comparison purposes.
+    pub fn acquire(
+        cfg: &Config,
+        network: Option<&Network>,
+        allow_criteria_changes: bool,
+    ) -> Result<Self, StoreAcquireError> {
+        let mut this = Self::acquire_offline(cfg)?;
+        if let Some(network) = network {
+            let cache = Cache::acquire(cfg).map_err(Box::new)?;
+            tokio::runtime::Handle::current().block_on(this.go_online(
+                cfg,
+                network,
+                &cache,
+                allow_criteria_changes,
+            ))?;
+
+            this.validate(cfg.today(), cfg.cli.locked)?;
+        }
+        Ok(this)
+    }
+
+    pub async fn go_online(
+        &mut self,
+        cfg: &Config,
+        network: &Network,
+        cache: &Cache,
+        allow_criteria_changes: bool,
+    ) -> Result<(), StoreAcquireError> {
+        if cfg.cli.locked {
+            return Ok(());
+        }
+
+        // If this command isn't locked, and the network is available, fetch the
+        // live state of imported audits.
+        let local_criteria_mapper = CriteriaMapper::new(&self.audits.criteria);
+        let fetched_audits =
+            fetch_imported_audits(network, &local_criteria_mapper, &self.config).await?;
+        let mut live_imports =
+            process_imported_audits(fetched_audits, &self.imports, allow_criteria_changes)?;
+        import_unpublished_entries(
+            &cfg.metadata,
+            network,
+            cache,
+            &self.config,
+            &self.imports,
+            &mut live_imports,
+        )
+        .await
+        .map_err(Box::new)?;
+        import_publisher_versions(
+            &cfg.metadata,
+            network,
+            cache,
+            &wildcard_audits_packages(&self.audits, &live_imports),
+            false,
+            &self.config,
+            &self.audits,
+            &self.imports,
+            &mut live_imports,
+        )
+        .await
+        .map_err(Box::new)?;
+        self.live_imports = Some(live_imports);
+        Ok(())
+    }
+
+    /// Create a mock store
+    #[cfg(test)]
+    pub fn mock(config: ConfigFile, audits: AuditsFile, imports: ImportsFile) -> Self {
+        Self {
+            lock: None,
+            config,
+            imports,
+            audits,
+            live_imports: None,
+            config_src: SourceFile::new_empty(CONFIG_TOML),
+            audits_src: SourceFile::new_empty(AUDITS_TOML),
+            imports_src: SourceFile::new_empty(IMPORTS_LOCK),
+        }
+    }
+
+    /// Create a mock store, also mocking out the unlocked import fetching
+    /// process by providing a mocked `Network` instance.
+    ///
+    /// NOTE: When validating the store, `mock_online` will use a "today" date
+    /// of 2023-01-01.
+    #[cfg(test)]
+    pub fn mock_online(
+        cfg: &Config,
+        config: ConfigFile,
+        audits: AuditsFile,
+        imports: ImportsFile,
+        network: &Network,
+        allow_criteria_changes: bool,
+    ) -> Result<Self, StoreAcquireError> {
+        let local_criteria_mapper = CriteriaMapper::new(&audits.criteria);
+        let fetched_audits = tokio::runtime::Handle::current().block_on(fetch_imported_audits(
+            network,
+            &local_criteria_mapper,
+            &config,
+        ))?;
+        let mut live_imports =
+            process_imported_audits(fetched_audits, &imports, allow_criteria_changes)?;
+        let cache = Cache::acquire(cfg).map_err(Box::new)?;
+        tokio::runtime::Handle::current()
+            .block_on(import_unpublished_entries(
+                &cfg.metadata,
+                network,
+                &cache,
+                &config,
+                &imports,
+                &mut live_imports,
+            ))
+            .map_err(Box::new)?;
+        tokio::runtime::Handle::current()
+            .block_on(import_publisher_versions(
+                &cfg.metadata,
+                network,
+                &cache,
+                &wildcard_audits_packages(&audits, &live_imports),
+                false,
+                &config,
+                &audits,
+                &imports,
+                &mut live_imports,
+            ))
+            .map_err(Box::new)?;
+
+        let store = Self {
+            lock: None,
+            config,
+            imports,
+            audits,
+            live_imports: Some(live_imports),
+            config_src: SourceFile::new_empty(CONFIG_TOML),
+            audits_src: SourceFile::new_empty(AUDITS_TOML),
+            imports_src: SourceFile::new_empty(IMPORTS_LOCK),
+        };
+
+        let today = chrono::NaiveDate::from_ymd_opt(2023, 1, 1).unwrap();
+
+        store.validate(today, false)?;
+
+        Ok(store)
+    }
+
+    #[cfg(test)]
+    pub fn mock_acquire(
+        config: &str,
+        audits: &str,
+        imports: &str,
+        today: chrono::NaiveDate,
+        check_file_formatting: bool,
+    ) -> Result<Self, StoreAcquireError> {
+        let (config_src, config): (_, ConfigFile) = load_toml(CONFIG_TOML, config.as_bytes())?;
+        let (audits_src, audits): (_, AuditsFile) = load_toml(AUDITS_TOML, audits.as_bytes())?;
+        let (imports_src, imports): (_, ImportsFile) = load_toml(IMPORTS_LOCK, imports.as_bytes())?;
+
+        let store = Self {
+            lock: None,
+            config,
+            imports,
+            audits,
+            live_imports: None,
+            config_src,
+            audits_src,
+            imports_src,
+        };
+
+        store.validate(today, check_file_formatting)?;
+
+        Ok(store)
+    }
+
+    /// Create a clone of the store for use to resolve `suggest`.
+    ///
+    /// If `clear_exemptions` is passed, this cloned store will not contain
+    /// `exemptions` entries from the config, unless they're marked as `suggest
+    /// = false`, such that the resolver will identify these missing audits when
+    /// generating a report.
+    ///
+    /// Unlike the primary store created with `Store::acquire` or
+    /// `Store::create`, this store will not hold the store lock, and cannot be
+    /// committed to disk by calling `commit()`.
+    pub fn clone_for_suggest(&self, clear_exemptions: bool) -> Self {
+        let mut clone = Self {
+            lock: None,
+            config: self.config.clone(),
+            imports: self.imports.clone(),
+            audits: self.audits.clone(),
+            live_imports: self.live_imports.clone(),
+            config_src: self.config_src.clone(),
+            audits_src: self.audits_src.clone(),
+            imports_src: self.imports_src.clone(),
+        };
+        if clear_exemptions {
+            // Delete all exemptions entries except those that are suggest=false
+            for versions in clone.config.exemptions.values_mut() {
+                versions.retain(|e| !e.suggest);
+            }
+
+            // If we have a live_imports, clear all stale unpublished entries so
+            // we suggest audits to replace them.
+            if let Some(live_imports) = &mut clone.live_imports {
+                for unpublished in live_imports.unpublished.values_mut() {
+                    unpublished.retain(|e| e.is_fresh_import);
+                }
+            }
+        }
+        clone
+    }
+
+    /// Returns the set of audits which should be operated upon.
+    ///
+    /// If the store was acquired unlocked, this will include audits which are
+    /// not stored in imports.lock, otherwise it will only contain imports
+    /// stored locally.
+    pub fn imported_audits(&self) -> &SortedMap<ImportName, AuditsFile> {
+        match &self.live_imports {
+            Some(live_imports) => &live_imports.audits,
+            None => &self.imports.audits,
+        }
+    }
+
+    /// Returns the set of publisher information which should be operated upon.
+    ///
+    /// If the store was acquired unlocked, whis may include publisher
+    /// information which is not stored in imports.lock, otherwise it will only
+    /// contain imports stored locally.
+    pub fn publishers(&self) -> &SortedMap<PackageName, Vec<CratesPublisher>> {
+        match &self.live_imports {
+            Some(live_imports) => &live_imports.publisher,
+            None => &self.imports.publisher,
+        }
+    }
+
+    /// Returns the set of unpublished information which should be operated upon.
+    ///
+    /// If the store was acquired unlocked, whis may include unpublished
+    /// information which is not stored in imports.lock, otherwise it will only
+    /// contain imports stored locally.
+    pub fn unpublished(&self) -> &SortedMap<PackageName, Vec<UnpublishedEntry>> {
+        match &self.live_imports {
+            Some(live_imports) => &live_imports.unpublished,
+            None => &self.imports.unpublished,
+        }
+    }
+
+    /// Commit the store's contents back to disk
+    pub fn commit(self) -> Result<(), StoreCommitError> {
+        // TODO: make this truly transactional?
+        // (With a dir rename? Does that work with the lock? Fine because it's already closed?)
+        if let Some(lock) = self.lock {
+            let mut audits = lock.write_audits()?;
+            let mut config = lock.write_config()?;
+            let mut imports = lock.write_imports()?;
+            let user_info = user_info_map(&self.imports);
+            audits.write_all(store_audits(self.audits, &user_info)?.as_bytes())?;
+            config.write_all(store_config(self.config)?.as_bytes())?;
+            imports.write_all(store_imports(self.imports, &user_info)?.as_bytes())?;
+        }
+        Ok(())
+    }
+
+    /// Mock `commit`. Returns the serialized value for each file in the store.
+    /// Doesn't take `self` by value so that it can continue to be used.
+    #[cfg(test)]
+    pub fn mock_commit(&self) -> SortedMap<String, String> {
+        let user_info = user_info_map(&self.imports);
+        [
+            (
+                AUDITS_TOML.to_owned(),
+                store_audits(self.audits.clone(), &user_info).unwrap(),
+            ),
+            (
+                CONFIG_TOML.to_owned(),
+                store_config(self.config.clone()).unwrap(),
+            ),
+            (
+                IMPORTS_LOCK.to_owned(),
+                store_imports(self.imports.clone(), &user_info).unwrap(),
+            ),
+        ]
+        .into_iter()
+        .collect()
+    }
+
+    /// Validate the store's integrity
+    #[allow(clippy::for_kv_map)]
+    pub fn validate(
+        &self,
+        today: chrono::NaiveDate,
+        check_file_formatting: bool,
+    ) -> Result<(), StoreValidateErrors> {
+        // ERRORS: ideally these are all gathered diagnostics, want to report as many errors
+        // at once as possible!
+
+        let max_end_date = today + chrono::Months::new(12);
+
+        // TODO(#66): implement validation
+        //
+        // * check that policy entries are only first-party?
+        //   * (we currently allow policy.criteria on third-parties for audit-as-crates-io)
+        // * check that exemptions entries are for things that exist?
+        // * check that lockfile and imports aren't desync'd (catch new/removed import urls)
+        //
+        // * check that each CriteriaEntry has 'description' or 'description_url'
+        // * check that no one is trying to shadow builtin criteria (safe-to-run, safe-to-deploy)
+        // * check that all 'audits' entries are well-formed
+        // * check that all package names are valid (with crates.io...?)
+        // * check that all reviews have a 'who' (currently an Option to stub it out)
+        // * catch no-op deltas?
+        // * nested check imports, complicated because of namespaces
+
+        fn check_criteria(
+            source_code: &SourceFile,
+            valid: &Arc<Vec<CriteriaName>>,
+            errors: &mut Vec<StoreValidateError>,
+            criteria: &[Spanned<CriteriaName>],
+        ) {
+            for criteria in criteria {
+                if !valid.contains(criteria) {
+                    errors.push(StoreValidateError::InvalidCriteria(InvalidCriteriaError {
+                        source_code: source_code.clone(),
+                        span: Spanned::span(criteria),
+                        invalid: criteria.to_string(),
+                        valid_names: valid.clone(),
+                    }))
+                }
+            }
+        }
+
+        let mut errors = Vec::new();
+
+        // Fixme: this should probably be a Map...? Sorted? Stable?
+        let valid_criteria = Arc::new(
+            self.audits
+                .criteria
+                .keys()
+                .map(|c| &**c)
+                .chain([SAFE_TO_RUN, SAFE_TO_DEPLOY])
+                .map(|name| name.to_string())
+                .collect::<Vec<_>>(),
+        );
+        let no_criteria = vec![];
+
+        for (_package, entries) in &self.config.exemptions {
+            for entry in entries {
+                check_criteria(
+                    &self.config_src,
+                    &valid_criteria,
+                    &mut errors,
+                    &entry.criteria,
+                );
+            }
+        }
+        for (_name, _version, policy) in &self.config.policy {
+            check_criteria(
+                &self.config_src,
+                &valid_criteria,
+                &mut errors,
+                policy.criteria.as_ref().unwrap_or(&no_criteria),
+            );
+            check_criteria(
+                &self.config_src,
+                &valid_criteria,
+                &mut errors,
+                policy.dev_criteria.as_ref().unwrap_or(&no_criteria),
+            );
+            for (_dep_package, dep_criteria) in &policy.dependency_criteria {
+                check_criteria(&self.config_src, &valid_criteria, &mut errors, dep_criteria);
+            }
+        }
+        for (_new_criteria, entry) in &self.audits.criteria {
+            // TODO: check that new_criteria isn't shadowing a builtin criteria
+            check_criteria(
+                &self.audits_src,
+                &valid_criteria,
+                &mut errors,
+                &entry.implies,
+            );
+        }
+        for (_package, entries) in &self.audits.audits {
+            for entry in entries {
+                // TODO: check that new_criteria isn't shadowing a builtin criteria
+                check_criteria(
+                    &self.audits_src,
+                    &valid_criteria,
+                    &mut errors,
+                    &entry.criteria,
+                );
+            }
+        }
+        for (_package, entries) in &self.audits.wildcard_audits {
+            for entry in entries {
+                check_criteria(
+                    &self.audits_src,
+                    &valid_criteria,
+                    &mut errors,
+                    &entry.criteria,
+                );
+
+                if entry.end > max_end_date {
+                    errors.push(StoreValidateError::BadWildcardEndDate(
+                        BadWildcardEndDateError {
+                            source_code: self.audits_src.clone(),
+                            span: Spanned::span(&entry.end),
+                            date: *entry.end,
+                            max: max_end_date,
+                        },
+                    ))
+                }
+            }
+        }
+
+        // If requested, verify that files in the store are correctly formatted
+        // and have no unrecognized fields. We don't want to be reformatting
+        // them or dropping unused fields while in CI, as those changes will be
+        // ignored.
+        if check_file_formatting {
+            let user_info = user_info_map(&self.imports);
+            for (name, old, new) in [
+                (
+                    CONFIG_TOML,
+                    self.config_src.source(),
+                    store_config(self.config.clone())
+                        .unwrap_or_else(|_| self.config_src.source().to_owned()),
+                ),
+                (
+                    AUDITS_TOML,
+                    self.audits_src.source(),
+                    store_audits(self.audits.clone(), &user_info)
+                        .unwrap_or_else(|_| self.audits_src.source().to_owned()),
+                ),
+                (
+                    IMPORTS_LOCK,
+                    self.imports_src.source(),
+                    store_imports(self.imports.clone(), &user_info)
+                        .unwrap_or_else(|_| self.imports_src.source().to_owned()),
+                ),
+            ] {
+                if old.trim_end() != new.trim_end() {
+                    errors.push(StoreValidateError::BadFormat(BadFormatError {
+                        unified_diff: unified_diff(
+                            Algorithm::Myers,
+                            old,
+                            &new,
+                            3,
+                            Some((&format!("old/{name}"), &format!("new/{name}"))),
+                        ),
+                    }));
+                }
+            }
+        }
+
+        // If we're locked, and therefore not fetching new live imports,
+        // validate that our imports.lock is in sync with config.toml.
+        if check_file_formatting && self.imports_lock_outdated() {
+            errors.push(StoreValidateError::ImportsLockOutdated);
+        };
+
+        if !errors.is_empty() {
+            return Err(StoreValidateErrors { errors });
+        }
+
+        Ok(())
+    }
+
+    fn imports_lock_outdated(&self) -> bool {
+        // If we have live imports, we're going to be updating imports.lock, so
+        // it's OK if it's out-of-date with regard to the config.
+        if self.live_imports.is_some() {
+            return false;
+        }
+
+        // We must have the exact same set of imports, otherwise an import has
+        // been added or removed and we're out of date.
+        if self.config.imports.keys().ne(self.imports.audits.keys()) {
+            return true;
+        }
+
+        for (import_name, config) in &self.config.imports {
+            let audits_file = self.imports.audits.get(import_name).unwrap();
+            // If we have any excluded crates in the imports.lock, it is out of
+            // date and needs to be regenerated.
+            for crate_name in &config.exclude {
+                if audits_file.audits.contains_key(crate_name) {
+                    return true;
+                }
+            }
+        }
+
+        false
+    }
+
+    /// Called to ensure that there is publisher information in the store's live
+    /// imports for the given crate. This is used when adding new wildcard
+    /// audits from `certify`.
+    pub fn ensure_publisher_versions(
+        &mut self,
+        cfg: &Config,
+        network: Option<&Network>,
+        package: PackageStr<'_>,
+    ) -> Result<&[CratesPublisher], CertifyError> {
+        if let (Some(network), Some(live_imports)) = (network, self.live_imports.as_mut()) {
+            let cache = Cache::acquire(cfg)?;
+            tokio::runtime::Handle::current().block_on(import_publisher_versions(
+                &cfg.metadata,
+                network,
+                &cache,
+                &[package.to_owned()].into_iter().collect(),
+                true,
+                &self.config,
+                &self.audits,
+                &self.imports,
+                live_imports,
+            ))?;
+
+            Ok(live_imports
+                .publisher
+                .get(package)
+                .map(|v| &v[..])
+                .unwrap_or(&[]))
+        } else {
+            Ok(&[])
+        }
+    }
+
+    /// Called when suggesting in order to fetch all audits from potential peers
+    /// in the registry, in case a registry import could solve an encountered
+    /// problem.
+    pub async fn fetch_registry_audits(
+        &mut self,
+        cfg: &Config,
+        network: &Network,
+        cache: &Cache,
+    ) -> Result<Vec<(ImportName, RegistryEntry, AuditsFile)>, FetchRegistryError> {
+        let registry_file = fetch_registry(network).await?;
+
+        let registry_entries = {
+            let progress_bar = progress_bar(
+                "Fetching",
+                "registry audits",
+                registry_file.registry.len() as u64,
+            );
+            let local_criteria_mapper = CriteriaMapper::new(&self.audits.criteria);
+            join_all(
+                registry_file
+                    .registry
+                    .iter()
+                    .map(|(name, entry)| (name.clone(), entry.clone()))
+                    .map(|(name, entry)| async {
+                        let _guard = IncProgressOnDrop(&progress_bar, 1);
+                        let existing_entry = self.config.imports.get(&name);
+                        fetch_imported_audit(
+                            network,
+                            &local_criteria_mapper,
+                            &name,
+                            &entry.url,
+                            existing_entry.map(|e| &e.exclude[..]).unwrap_or(&[]),
+                            existing_entry
+                                .map(|e| &e.criteria_map)
+                                .unwrap_or(&SortedMap::new()),
+                        )
+                        .await
+                        .map_err(|error| {
+                            error!("Error fetching registry audits for '{name}': {error:?}")
+                        })
+                        .map(|audit_file| (name, entry, audit_file))
+                        .ok()
+                    }),
+            )
+            .await
+            .into_iter()
+            .flatten()
+            .collect::<Vec<_>>()
+        };
+
+        // Re-run import_publisher_versions to ensure that we have all publisher
+        // information for any potential wildcard audit imports.
+        // Note: This is the only reason we need mutable access to the store.
+        // XXX: Consider limiting further to only packages which are currently
+        // failing to vet?
+        // XXX: Consider making this fetch async?
+        let wildcard_packages = registry_entries
+            .iter()
+            .flat_map(|(_, _, audits_file)| audits_file.wildcard_audits.keys())
+            .cloned()
+            .collect::<FastSet<_>>();
+        import_publisher_versions(
+            &cfg.metadata,
+            network,
+            cache,
+            &wildcard_packages,
+            false,
+            &self.config,
+            &self.audits,
+            &self.imports,
+            self.live_imports.as_mut().unwrap(),
+        )
+        .await?;
+
+        Ok(registry_entries)
+    }
+}
+
+/// Process imported audits from the network, generating a `LiveImports`
+/// description of the live state of imported audits.
+fn process_imported_audits(
+    fetched_audits: Vec<(ImportName, AuditsFile)>,
+    imports_lock: &ImportsFile,
+    allow_criteria_changes: bool,
+) -> Result<ImportsFile, CriteriaChangeErrors> {
+    let mut new_imports = ImportsFile {
+        unpublished: SortedMap::new(),
+        publisher: SortedMap::new(),
+        audits: SortedMap::new(),
+    };
+    let mut changed_criteria = Vec::new();
+
+    for (import_name, mut audits_file) in fetched_audits {
+        if let Some(existing_audits_file) = imports_lock.audits.get(&import_name) {
+            update_import_freshness(
+                &mut audits_file,
+                existing_audits_file,
+                |criteria_name, old_desc, new_desc| {
+                    if !allow_criteria_changes {
+                        // Compare the new criteria descriptions with existing criteria
+                        // descriptions. If the description already exists, record a
+                        // CriteriaChangeError.
+                        changed_criteria.push(CriteriaChangeError {
+                            import_name: import_name.clone(),
+                            criteria_name: criteria_name.to_owned(),
+                            unified_diff: unified_diff(
+                                Algorithm::Myers,
+                                old_desc,
+                                new_desc,
+                                5,
+                                None,
+                            ),
+                        });
+                    }
+                },
+            );
+        }
+
+        // Now add the new import
+        new_imports.audits.insert(import_name, audits_file);
+    }
+
+    if !changed_criteria.is_empty() {
+        return Err(CriteriaChangeErrors {
+            errors: changed_criteria,
+        });
+    }
+
+    // FIXME: Consider doing some additional validation on these audits
+    // before returning?
+
+    Ok(new_imports)
+}
+
+fn update_import_freshness(
+    audits_file: &mut AuditsFile,
+    existing_audits_file: &AuditsFile,
+    mut on_changed_criteria_description: impl FnMut(CriteriaStr<'_>, &str, &str),
+) {
+    // Compare the new criteria descriptions with existing criteria
+    // descriptions. If the description already exists, notify our caller.
+    for (criteria_name, old_entry) in &existing_audits_file.criteria {
+        if let Some(new_entry) = audits_file.criteria.get(criteria_name) {
+            let old_desc = old_entry.description.as_ref().unwrap();
+            let new_desc = new_entry.description.as_ref().unwrap();
+            if old_desc != new_desc {
+                on_changed_criteria_description(criteria_name, old_desc, new_desc);
+            }
+        }
+    }
+
+    // Compare the new audits with existing audits. If an audit already
+    // existed in the existing audits file, mark it as non-fresh.
+    for (package, existing_audits) in &existing_audits_file.audits {
+        let new_audits = audits_file
+            .audits
+            .get_mut(package)
+            .map(|v| &mut v[..])
+            .unwrap_or(&mut []);
+        for existing_audit in existing_audits {
+            for new_audit in &mut *new_audits {
+                if new_audit.is_fresh_import && new_audit.same_audit_as(existing_audit) {
+                    new_audit.is_fresh_import = false;
+                    break;
+                }
+            }
+        }
+    }
+    for (package, existing_audits) in &existing_audits_file.wildcard_audits {
+        let new_audits = audits_file
+            .wildcard_audits
+            .get_mut(package)
+            .map(|v| &mut v[..])
+            .unwrap_or(&mut []);
+        for existing_audit in existing_audits {
+            for new_audit in &mut *new_audits {
+                if new_audit.is_fresh_import && new_audit.same_audit_as(existing_audit) {
+                    new_audit.is_fresh_import = false;
+                    break;
+                }
+            }
+        }
+    }
+}
+
+/// Fetch all declared imports from the network, mapping criteria to the local
+/// namespace, and filling in any criteria descriptions.
+async fn fetch_imported_audits(
+    network: &Network,
+    local_criteria_mapper: &CriteriaMapper,
+    config: &ConfigFile,
+) -> Result<Vec<(ImportName, AuditsFile)>, Box<FetchAuditError>> {
+    let progress_bar = progress_bar("Fetching", "imported audits", config.imports.len() as u64);
+    try_join_all(config.imports.iter().map(|(name, import)| async {
+        let _guard = IncProgressOnDrop(&progress_bar, 1);
+        let audit_file = fetch_imported_audit(
+            network,
+            local_criteria_mapper,
+            name,
+            &import.url,
+            &import.exclude,
+            &import.criteria_map,
+        )
+        .await
+        .map_err(Box::new)?;
+        Ok::<_, Box<FetchAuditError>>((name.clone(), audit_file))
+    }))
+    .await
+}
+
+async fn fetch_imported_audit(
+    network: &Network,
+    local_criteria_mapper: &CriteriaMapper,
+    name: &str,
+    urls: &[String],
+    exclude: &[PackageName],
+    criteria_map: &CriteriaMap,
+) -> Result<AuditsFile, FetchAuditError> {
+    // Fetch all imported URLs, and then aggregate them.
+    let sources = try_join_all(urls.iter().map(|url| async {
+        fetch_single_imported_audit(
+            network,
+            local_criteria_mapper,
+            name,
+            url,
+            exclude,
+            criteria_map,
+        )
+        .await
+        .map(|audits_file| (url.clone(), audits_file))
+    }))
+    .await?;
+
+    // If we only have a single source, don't aggregate so that we don't add
+    // unnecessary `aggregated-from` members.
+    if sources.len() == 1 {
+        Ok(sources.into_iter().next().unwrap().1)
+    } else {
+        crate::do_aggregate_audits(sources).map_err(|error| FetchAuditError::Aggregate {
+            import_name: name.to_owned(),
+            errors: error
+                .errors
+                .into_iter()
+                .map(|err| match err {
+                    AggregateError::CriteriaDescriptionMismatch(mismatch) => {
+                        FetchAuditAggregateError {
+                            mapped_to: criteria_map
+                                .get(&mismatch.criteria_name)
+                                .cloned()
+                                .unwrap_or_default(),
+                            criteria_name: mismatch.criteria_name,
+                            first: mismatch.first,
+                            second: mismatch.second,
+                        }
+                    }
+                    AggregateError::ImpliesMismatch(_) => {
+                        unreachable!("implies is stripped by fetch_single_imported_audit")
+                    }
+                })
+                .collect(),
+        })
+    }
+}
+
+/// Fetch a single AuditsFile from the network, filling in any criteria
+/// descriptions.
+async fn fetch_single_imported_audit(
+    network: &Network,
+    local_criteria_mapper: &CriteriaMapper,
+    name: &str,
+    url: &str,
+    exclude: &[PackageName],
+    criteria_map: &CriteriaMap,
+) -> Result<AuditsFile, FetchAuditError> {
+    let parsed_url = Url::parse(url).map_err(|error| FetchAuditError::InvalidUrl {
+        import_url: url.to_owned(),
+        import_name: name.to_owned(),
+        error,
+    })?;
+    let audit_source = network.download_source_file_cached(parsed_url).await?;
+
+    // Attempt to parse each criteria and audit independently, to allow
+    // recovering from parsing or validation errors on a per-entry basis when
+    // importing audits. This reduces the risk of an upstream vendor adopting a
+    // new cargo-vet feature breaking projects still using an older version of
+    // cargo-vet.
+    let foreign_audit_file: ForeignAuditsFile = toml::de::from_str(audit_source.source())
+        .map_err(|error| {
+            let (line, col) = error.line_col().unwrap_or((0, 0));
+            TomlParseError {
+                span: SourceOffset::from_location(audit_source.source(), line + 1, col + 1),
+                source_code: audit_source,
+                error,
+            }
+        })
+        .map_err(LoadTomlError::from)?;
+    let ForeignAuditFileToLocalResult {
+        mut audit_file,
+        ignored_criteria,
+        ignored_audits,
+    } = foreign_audit_file_to_local(foreign_audit_file);
+    if !ignored_criteria.is_empty() {
+        warn!(
+            "Ignored {} invalid criteria entries when importing from '{}'\n\
+            These criteria may have been made with a more recent version of cargo-vet",
+            ignored_criteria.len(),
+            name
+        );
+        info!(
+            "The following criteria were ignored when importing from '{}': {:?}",
+            name, ignored_criteria
+        );
+    }
+    if !ignored_audits.is_empty() {
+        warn!(
+            "Ignored {} invalid audits when importing from '{}'\n\
+            These audits may have been made with a more recent version of cargo-vet",
+            ignored_audits.len(),
+            name
+        );
+        info!(
+            "Audits for the following packages were ignored when importing from '{}': {:?}",
+            name, ignored_audits
+        );
+    }
+
+    // Remove any excluded audits from the live copy. We'll effectively
+    // pretend they don't exist upstream.
+    for excluded in exclude {
+        audit_file.audits.remove(excluded);
+    }
+
+    // Construct a mapping from the foreign criteria namespace into the
+    // local criteria namespace based on the criteria map from the config.
+    let foreign_criteria_mapper = CriteriaMapper::new(&audit_file.criteria);
+    let foreign_to_local_mapping: Vec<_> = foreign_criteria_mapper
+        .all_criteria_names()
+        .map(|foreign_name| {
+            // NOTE: We try the map before we check for built-in criteria to
+            // allow overriding the default behaviour.
+            if let Some(mapped) = criteria_map.get(foreign_name) {
+                local_criteria_mapper.criteria_from_list(mapped)
+            } else if foreign_name == SAFE_TO_DEPLOY {
+                local_criteria_mapper.criteria_from_list([SAFE_TO_DEPLOY])
+            } else if foreign_name == SAFE_TO_RUN {
+                local_criteria_mapper.criteria_from_list([SAFE_TO_RUN])
+            } else {
+                local_criteria_mapper.no_criteria()
+            }
+        })
+        .collect();
+
+    // Helper to re-write foreign criteria into the local criteria
+    // namespace.
+    let make_criteria_local = |criteria: &mut Vec<Spanned<CriteriaName>>| {
+        let foreign_set = foreign_criteria_mapper.criteria_from_list(&*criteria);
+        let mut local_set = local_criteria_mapper.no_criteria();
+        for foreign_criteria_idx in foreign_set.indices() {
+            local_set.unioned_with(&foreign_to_local_mapping[foreign_criteria_idx]);
+        }
+        *criteria = local_criteria_mapper
+            .criteria_names(&local_set)
+            .map(|name| name.to_owned().into())
+            .collect();
+    };
+
+    // By default all audits read from the network are fresh.
+    //
+    // Note: This may leave behind useless audits which imply no criteria,
+    // but that's OK - we'll never choose to import them. In the future we
+    // might want to trim them.
+    for audit_entry in audit_file.audits.values_mut().flat_map(|v| v.iter_mut()) {
+        audit_entry.is_fresh_import = true;
+        make_criteria_local(&mut audit_entry.criteria);
+    }
+    for audit_entry in audit_file
+        .wildcard_audits
+        .values_mut()
+        .flat_map(|v| v.iter_mut())
+    {
+        audit_entry.is_fresh_import = true;
+        make_criteria_local(&mut audit_entry.criteria);
+    }
+    for trust_entry in audit_file.trusted.values_mut().flat_map(|v| v.iter_mut()) {
+        make_criteria_local(&mut trust_entry.criteria);
+    }
+
+    // Now that we're done with foreign criteria, trim the set to only
+    // contain mapped criteria, as we don't care about other criteria, so
+    // shouldn't bother importing them.
+    audit_file
+        .criteria
+        .retain(|name, _| criteria_map.contains_key(name));
+
+    // Eagerly fetch all descriptions for criteria in the imported audits file,
+    // and store them inline. We'll error out if any of these descriptions are
+    // unavailable.
+    try_join_all(
+        audit_file
+            .criteria
+            .iter_mut()
+            .map(|(criteria_name, criteria_entry)| async {
+                if criteria_entry.description.is_some() {
+                    return Ok(());
+                }
+
+                let url_string = criteria_entry.description_url.as_ref().ok_or_else(|| {
+                    FetchAuditError::MissingCriteriaDescription {
+                        import_name: name.to_owned(),
+                        criteria_name: criteria_name.clone(),
+                    }
+                })?;
+                let url = Url::parse(url_string).map_err(|error| {
+                    FetchAuditError::InvalidCriteriaDescriptionUrl {
+                        import_name: name.to_owned(),
+                        criteria_name: criteria_name.clone(),
+                        url: url_string.clone(),
+                        error,
+                    }
+                })?;
+                let bytes = network.download(url.clone()).await?;
+                let description =
+                    String::from_utf8(bytes).map_err(|error| DownloadError::InvalidText {
+                        url: Box::new(url.clone()),
+                        error,
+                    })?;
+
+                criteria_entry.description = Some(description);
+                Ok::<(), FetchAuditError>(())
+            }),
+    )
+    .await?;
+
+    // Clear out the description URL and implies, as those will never be used
+    // locally.
+    for criteria_entry in audit_file.criteria.values_mut() {
+        criteria_entry.description_url = None;
+        criteria_entry.implies = Vec::new();
+    }
+
+    Ok(audit_file)
+}
+
+pub(crate) struct ForeignAuditFileToLocalResult {
+    pub audit_file: AuditsFile,
+    pub ignored_criteria: Vec<CriteriaName>,
+    pub ignored_audits: Vec<PackageName>,
+}
+
+fn is_known_criteria(valid_criteria: &[CriteriaName], criteria_name: &CriteriaName) -> bool {
+    criteria_name == format::SAFE_TO_RUN
+        || criteria_name == format::SAFE_TO_DEPLOY
+        || valid_criteria.contains(criteria_name)
+}
+
+/// Convert a foreign audits file into a local audits file, ignoring any entries
+/// which could not be interpreted, due to issues such as being created with a
+/// newer version of cargo-vet.
+pub(crate) fn foreign_audit_file_to_local(
+    foreign_audit_file: ForeignAuditsFile,
+) -> ForeignAuditFileToLocalResult {
+    let mut ignored_criteria = Vec::new();
+    let mut criteria: SortedMap<CriteriaName, CriteriaEntry> = foreign_audit_file
+        .criteria
+        .into_iter()
+        .filter_map(|(criteria, value)| match parse_imported_criteria(value) {
+            Some(entry) => Some((criteria, entry)),
+            None => {
+                ignored_criteria.push(criteria);
+                None
+            }
+        })
+        .collect();
+    let valid_criteria: Vec<CriteriaName> = criteria.keys().cloned().collect();
+
+    // Remove any unknown criteria from implies sets, to ensure we don't run
+    // into errors later on in the resolver.
+    for entry in criteria.values_mut() {
+        entry
+            .implies
+            .retain(|criteria_name| is_known_criteria(&valid_criteria, criteria_name));
+    }
+
+    let mut ignored_audits = Vec::new();
+    let audits: AuditedDependencies = foreign_audit_file
+        .audits
+        .into_iter()
+        .map(|(package, audits)| {
+            let parsed: Vec<_> = audits
+                .into_iter()
+                .filter_map(|value| match parse_imported_audit(&valid_criteria, value) {
+                    Some(audit) => Some(audit),
+                    None => {
+                        ignored_audits.push(package.clone());
+                        None
+                    }
+                })
+                .collect();
+            (package, parsed)
+        })
+        .filter(|(_, audits)| !audits.is_empty())
+        .collect();
+
+    let wildcard_audits: WildcardAudits = foreign_audit_file
+        .wildcard_audits
+        .into_iter()
+        .map(|(package, audits)| {
+            let parsed: Vec<_> = audits
+                .into_iter()
+                .filter_map(
+                    |value| match parse_imported_wildcard_audit(&valid_criteria, value) {
+                        Some(audit) => Some(audit),
+                        None => {
+                            ignored_audits.push(package.clone());
+                            None
+                        }
+                    },
+                )
+                .collect();
+            (package, parsed)
+        })
+        .filter(|(_, audits)| !audits.is_empty())
+        .collect();
+
+    let trusted: TrustedPackages = foreign_audit_file
+        .trusted
+        .into_iter()
+        .map(|(package, trusted)| {
+            let parsed: Vec<_> = trusted
+                .into_iter()
+                .filter_map(|value| parse_imported_trust_entry(&valid_criteria, value))
+                .collect();
+            (package, parsed)
+        })
+        .filter(|(_, trusted)| !trusted.is_empty())
+        .collect();
+
+    ForeignAuditFileToLocalResult {
+        audit_file: AuditsFile {
+            criteria,
+            wildcard_audits,
+            audits,
+            trusted,
+        },
+        ignored_criteria,
+        ignored_audits,
+    }
+}
+
+/// Parse an unparsed criteria entry, validating and returning it.
+fn parse_imported_criteria(value: toml::Value) -> Option<CriteriaEntry> {
+    parse_from_value(value)
+        .map_err(|err| info!("imported criteria parsing failed due to {err}"))
+        .ok()
+}
+
+/// Parse an unparsed audit entry, validating and returning it.
+fn parse_imported_audit(valid_criteria: &[CriteriaName], value: toml::Value) -> Option<AuditEntry> {
+    let mut audit: AuditEntry = parse_from_value(value)
+        .map_err(|err| info!("imported audit parsing failed due to {err}"))
+        .ok()?;
+
+    // Remove any unrecognized criteria to avoid later errors caused by being
+    // unable to find criteria, and ignore the entry if it names no known
+    // criteria.
+    audit
+        .criteria
+        .retain(|criteria_name| is_known_criteria(valid_criteria, criteria_name));
+    if audit.criteria.is_empty() {
+        info!("imported audit parsing failed due to no known criteria");
+        return None;
+    }
+
+    Some(audit)
+}
+
+/// Parse an unparsed wildcard audit entry, validating and returning it.
+fn parse_imported_wildcard_audit(
+    valid_criteria: &[CriteriaName],
+    value: toml::Value,
+) -> Option<WildcardEntry> {
+    let mut audit: WildcardEntry = parse_from_value(value)
+        .map_err(|err| info!("imported wildcard audit parsing failed due to {err}"))
+        .ok()?;
+
+    audit
+        .criteria
+        .retain(|criteria_name| is_known_criteria(valid_criteria, criteria_name));
+    if audit.criteria.is_empty() {
+        info!("imported wildcard audit parsing failed due to no known criteria");
+        return None;
+    }
+
+    Some(audit)
+}
+
+/// Parse an unparsed wildcard audit entry, validating and returning it.
+fn parse_imported_trust_entry(
+    valid_criteria: &[CriteriaName],
+    value: toml::Value,
+) -> Option<TrustEntry> {
+    let mut audit: TrustEntry = parse_from_value(value)
+        .map_err(|err| info!("imported trust entry audit parsing failed due to {err}"))
+        .ok()?;
+
+    audit
+        .criteria
+        .retain(|criteria_name| is_known_criteria(valid_criteria, criteria_name));
+    if audit.criteria.is_empty() {
+        info!("imported trust entry parsing failed due to no known criteria");
+        return None;
+    }
+
+    Some(audit)
+}
+
+async fn import_unpublished_entries(
+    metadata: &cargo_metadata::Metadata,
+    network: &Network,
+    cache: &Cache,
+    config_file: &ConfigFile,
+    imports_lock: &ImportsFile,
+    live_imports: &mut ImportsFile,
+) -> Result<(), CrateInfoError> {
+    // We always persist any unpublished entries from the imports.lock into
+    // live-imports, even if the version has since been published, as it may be
+    // necessary for `cargo vet` to pass.
+    live_imports.unpublished = imports_lock.unpublished.clone();
+
+    // Find all packages which are forced to be audit-as-crates-io, and check if
+    // they are actually published. We also skip git versions, as those can
+    // always be audit-as-crates-io.
+    let audit_as_packages = crate::first_party_packages_strict(metadata, config_file)
+        .filter(|package| package.is_third_party(&config_file.policy))
+        .filter(|package| package.git_rev().is_none());
+    for package in audit_as_packages {
+        // If we have no versions for the crate, it cannot be
+        // audit-as-crates-io, so treat it as an error.
+        // FIXME: better errors here?
+        let versions = cache.get_versions(Some(network), &package.name).await?;
+
+        // Pick which verison of the crate we'd audit as. We prefer the exact
+        // version of the crate, followed by the largest version below, and then
+        // finally the smallest version above.
+        let max_below = versions.iter().filter(|&v| v <= &package.version).max();
+        let audited_as = max_below
+            .or_else(|| versions.iter().filter(|&v| v > &package.version).min())
+            .expect("There must be at least one version");
+
+        // The exact version is published, no unpublished entries are required.
+        if audited_as == &package.version {
+            continue;
+        }
+
+        let unpublished = live_imports
+            .unpublished
+            .entry(package.name.clone())
+            .or_default();
+
+        // Mark each existing entry for this version as `still_unpublished`, as
+        // we now know this version is still unpublished.
+        for entry in &mut *unpublished {
+            if entry.version.equals_semver(&package.version) {
+                entry.still_unpublished = true;
+            }
+        }
+
+        // Push an entry for this audited_as marked as `is_fresh_import`.
+        //
+        // NOTE: We intentionally add a new entry even if there is an
+        // "identical" one already. This allows edge prioritization logic to
+        // prefer stale entries when not pruning, and fresh ones while pruning,
+        // to keep the unaudited delta as small as possible without unnecessary
+        // imports.lock churn. Only one of the two entries should ever appear in
+        // imports.lock.
+        unpublished.push(UnpublishedEntry {
+            version: package.vet_version(),
+            audited_as: VetVersion {
+                semver: audited_as.clone(),
+                git_rev: None,
+            },
+            still_unpublished: true,
+            is_fresh_import: true,
+        });
+    }
+    Ok(())
+}
+
+fn wildcard_audits_packages(
+    audits_file: &AuditsFile,
+    imports_file: &ImportsFile,
+) -> FastSet<PackageName> {
+    // Determine which versions are relevant for the purposes of wildcard audit
+    // checks. We'll only care about crates which have associated wildcard
+    // audits or existing cached publisher info.
+    audits_file
+        .wildcard_audits
+        .keys()
+        .chain(
+            imports_file
+                .audits
+                .values()
+                .flat_map(|audits_file| audits_file.wildcard_audits.keys()),
+        )
+        .chain(imports_file.publisher.keys())
+        .chain(audits_file.trusted.keys())
+        .cloned()
+        .collect()
+}
+
+#[allow(clippy::too_many_arguments)]
+async fn import_publisher_versions(
+    metadata: &cargo_metadata::Metadata,
+    network: &Network,
+    cache: &Cache,
+    relevant_packages: &FastSet<PackageName>,
+    force: bool,
+    config_file: &ConfigFile,
+    audits_file: &AuditsFile,
+    imports_lock: &ImportsFile,
+    live_imports: &mut ImportsFile,
+) -> Result<(), CrateInfoError> {
+    // We also only care about versions for third-party packages which are
+    // actually used in-tree.
+    let mut relevant_versions: FastMap<PackageStr<'_>, FastSet<&semver::Version>> = FastMap::new();
+    for pkg in &metadata.packages {
+        if relevant_packages.contains(&pkg.name)
+            && (force || pkg.is_third_party(&config_file.policy))
+        {
+            relevant_versions
+                .entry(&pkg.name)
+                .or_default()
+                .insert(&pkg.version);
+        }
+    }
+
+    // If we're forcing fetching for this package, ensure an entry exists even
+    // if it isn't in the dependency graph.
+    if force {
+        for package in relevant_packages {
+            relevant_versions.entry(&package[..]).or_default();
+        }
+    }
+
+    // We may care about other versions for these packages as well if they
+    // appear on the `from` side of a delta audit, or if they are named by an
+    // exemption.
+    for (&pkg_name, versions) in relevant_versions.iter_mut() {
+        // If there is a delta audit originating from a specific version, that
+        // version may be relevant, so record it.
+        for audit in [audits_file]
+            .into_iter()
+            .chain(live_imports.audits.values())
+            .flat_map(|audits_file| audits_file.audits.get(pkg_name))
+            .flatten()
+        {
+            if let AuditKind::Delta { from, .. } = &audit.kind {
+                versions.insert(&from.semver);
+            }
+        }
+
+        // If there is an exemption naming a specific version, it is also
+        // potentially relevant.
+        if let Some(exemptions) = config_file.exemptions.get(pkg_name) {
+            for exemption in exemptions {
+                versions.insert(&exemption.version.semver);
+            }
+        }
+
+        // If we have previously cached publisher information, it's relevant.
+        if let Some(publishers) = imports_lock.publisher.get(pkg_name) {
+            for publisher in publishers {
+                versions.insert(&publisher.version.semver);
+            }
+        }
+    }
+
+    let relevant_publishers = {
+        let progress = progress_bar(
+            "Fetching",
+            "crate publishers",
+            relevant_versions.len() as u64,
+        );
+        try_join_all(relevant_versions.into_iter().map(|(pkg_name, versions)| {
+            let progress = &progress;
+            async move {
+                let _inc_progress = IncProgressOnDrop(progress, 1);
+                // Access the set of publishers. We provide the set of relevant versions
+                // to help decide whether or not to fetch new publisher information from
+                // crates.io, to reduce API activity.
+                cache
+                    .get_publishers(Some(network), pkg_name, versions)
+                    .await
+                    .map(|publishers| (pkg_name, publishers))
+            }
+        }))
+        .await?
+    };
+
+    // NOTE: We make sure to process all imports before we look up user
+    // information in the cache, to ensure we're fetching consistent user
+    // information.
+    for (pkg_name, publishers) in relevant_publishers {
+        // Fill in the live imports table with the relevant information.
+        let nonfresh_versions: FastSet<_> = imports_lock
+            .publisher
+            .get(pkg_name)
+            .into_iter()
+            .flatten()
+            .map(|publisher| &publisher.version.semver)
+            .collect();
+
+        live_imports.publisher.insert(
+            pkg_name.to_owned(),
+            publishers
+                .into_iter()
+                .filter_map(|(version, details)| {
+                    let user_id = details.published_by?;
+                    let user_info = cache.get_crates_user_info(user_id)?;
+                    let is_fresh_import = !nonfresh_versions.contains(&version);
+                    Some(CratesPublisher {
+                        version: VetVersion {
+                            semver: version,
+                            git_rev: None,
+                        },
+                        user_id,
+                        user_login: user_info.login,
+                        user_name: user_info.name,
+                        when: details.created_at.date_naive(),
+                        is_fresh_import,
+                    })
+                })
+                .collect(),
+        );
+    }
+
+    Ok(())
+}
+
+pub async fn fetch_registry(network: &Network) -> Result<RegistryFile, FetchRegistryError> {
+    let registry_url = Url::parse(REGISTRY_URL).unwrap();
+    let registry_source = network.download_source_file_cached(registry_url).await?;
+    let registry_file: RegistryFile = toml::de::from_str(registry_source.source())
+        .map_err(|error| {
+            let (line, col) = error.line_col().unwrap_or((0, 0));
+            TomlParseError {
+                span: SourceOffset::from_location(registry_source.source(), line + 1, col + 1),
+                source_code: registry_source,
+                error,
+            }
+        })
+        .map_err(LoadTomlError::from)?;
+    Ok(registry_file)
+}
+
+pub fn user_info_map(imports: &ImportsFile) -> FastMap<CratesUserId, CratesCacheUser> {
+    let mut user_info = FastMap::new();
+    for publisher in imports.publisher.values().flatten() {
+        user_info
+            .entry(publisher.user_id)
+            .or_insert_with(|| CratesCacheUser {
+                login: publisher.user_login.clone(),
+                name: publisher.user_name.clone(),
+            });
+    }
+    user_info
+}
+
+struct CacheState {
+    /// The loaded DiffCache, will be written back on Drop
+    diff_cache: DiffCache,
+    /// Command history to provide some persistent magic smarts
+    command_history: CommandHistory,
+    /// Cache of fetched info from crates.io.
+    crates_cache: CratesCache,
+    /// Paths for unpacked packages from this version.
+    fetched_packages: FastMap<(String, VetVersion), Arc<tokio::sync::OnceCell<PathBuf>>>,
+    /// Computed diffstats from this version.
+    diffed: FastMap<(String, Delta), Arc<tokio::sync::OnceCell<DiffStat>>>,
+}
+
+/// The cache where we store globally shared artifacts like fetched packages and diffstats
+///
+/// All access to this directory should be managed by this type to avoid races.
+pub struct Cache {
+    /// System-global lock over the cache, will be None if we're mocking.
+    _lock: Option<FileLock>,
+    /// Path to the root of the cache
+    root: Option<PathBuf>,
+    /// Path to the DiffCache (for when we want to save it back)
+    diff_cache_path: Option<PathBuf>,
+    /// Path to the CommandHistory (for when we want to save it back)
+    command_history_path: Option<PathBuf>,
+    /// Path to the CratesCache (for when we want to save it back)
+    publisher_cache_path: Option<PathBuf>,
+    /// Semaphore preventing exceeding the maximum number of concurrent diffs.
+    diff_semaphore: tokio::sync::Semaphore,
+    /// The time to use as `now` when considering cache expiry.
+    now: chrono::DateTime<chrono::Utc>,
+    /// Common mutable state for the cache which can be mutated concurrently
+    /// from multiple tasks.
+    state: Mutex<CacheState>,
+}
+
+impl Drop for Cache {
+    fn drop(&mut self) {
+        let state = self.state.get_mut().unwrap();
+        if let Some(diff_cache_path) = &self.diff_cache_path {
+            // Write back the diff_cache
+            if let Err(err) = || -> Result<(), CacheCommitError> {
+                let diff_cache = store_diff_cache(mem::take(&mut state.diff_cache))?;
+                fs::write(diff_cache_path, diff_cache)?;
+                Ok(())
+            }() {
+                error!("error writing back changes to diff-cache: {:?}", err);
+            }
+        }
+        if let Some(command_history_path) = &self.command_history_path {
+            // Write back the command_history
+            if let Err(err) = || -> Result<(), CacheCommitError> {
+                let command_history = store_command_history(mem::take(&mut state.command_history))?;
+                fs::write(command_history_path, command_history)?;
+                Ok(())
+            }() {
+                error!("error writing back changes to command history: {:?}", err);
+            }
+        }
+        if let Some(publisher_cache_path) = &self.publisher_cache_path {
+            // Write back the publisher_cache
+            if let Err(err) = || -> Result<(), CacheCommitError> {
+                let publisher_cache = store_publisher_cache(mem::take(&mut state.crates_cache))?;
+                fs::write(publisher_cache_path, publisher_cache)?;
+                Ok(())
+            }() {
+                error!("error writing back changes to publisher-cache: {:?}", err);
+            }
+        }
+        // `_lock: FileLock` implicitly released here
+    }
+}
+
+impl Cache {
+    /// Acquire the cache
+    pub fn acquire(cfg: &PartialConfig) -> Result<Self, CacheAcquireError> {
+        #[cfg(test)]
+        if cfg.mock_cache {
+            // We're in unit tests, everything should be mocked and not touch real caches
+            return Ok(Cache {
+                _lock: None,
+                root: None,
+                diff_cache_path: None,
+                command_history_path: None,
+                publisher_cache_path: None,
+                diff_semaphore: tokio::sync::Semaphore::new(MAX_CONCURRENT_DIFFS),
+                now: cfg.now,
+                state: Mutex::new(CacheState {
+                    diff_cache: DiffCache::default(),
+                    command_history: CommandHistory::default(),
+                    crates_cache: CratesCache::default(),
+                    fetched_packages: FastMap::new(),
+                    diffed: FastMap::new(),
+                }),
+            });
+        }
+
+        // Make sure the cache directory exists, and acquire an exclusive lock on it.
+        let root = cfg.cache_dir.clone();
+        fs::create_dir_all(&root).map_err(|error| CacheAcquireError::Root {
+            target: root.clone(),
+            error,
+        })?;
+
+        let lock = Filesystem::new(root.clone()).open_rw(CACHE_VET_LOCK, "cache lock")?;
+
+        let empty = root.join(CACHE_EMPTY_PACKAGE);
+        fs::create_dir_all(&empty).map_err(|error| CacheAcquireError::Empty {
+            target: empty.clone(),
+            error,
+        })?;
+
+        let packages_src = root.join(CACHE_REGISTRY_SRC);
+        fs::create_dir_all(&packages_src).map_err(|error| CacheAcquireError::Src {
+            target: packages_src.clone(),
+            error,
+        })?;
+
+        let packages_cache = root.join(CACHE_REGISTRY_CACHE);
+        fs::create_dir_all(&packages_cache).map_err(|error| CacheAcquireError::Cache {
+            target: packages_cache.clone(),
+            error,
+        })?;
+
+        // Setup the diff_cache.
+        let diff_cache_path = root.join(CACHE_DIFF_CACHE);
+        let diff_cache: DiffCache = File::open(&diff_cache_path)
+            .ok()
+            .and_then(|f| load_toml(CACHE_DIFF_CACHE, f).map(|v| v.1).ok())
+            .unwrap_or_default();
+
+        // Setup the command_history.
+        let command_history_path = root.join(CACHE_COMMAND_HISTORY);
+        let command_history: CommandHistory = File::open(&command_history_path)
+            .ok()
+            .and_then(|f| load_json(f).ok())
+            .unwrap_or_default();
+
+        // Setup the publisher_cache.
+        let publisher_cache_path = root.join(CACHE_CRATES_IO_CACHE);
+        let publisher_cache: CratesCache = File::open(&publisher_cache_path)
+            .ok()
+            .and_then(|f| load_json(f).ok())
+            .unwrap_or_default();
+
+        Ok(Self {
+            _lock: Some(lock),
+            root: Some(root),
+            diff_cache_path: Some(diff_cache_path),
+            command_history_path: Some(command_history_path),
+            publisher_cache_path: Some(publisher_cache_path),
+            diff_semaphore: tokio::sync::Semaphore::new(MAX_CONCURRENT_DIFFS),
+            now: cfg.now,
+            state: Mutex::new(CacheState {
+                diff_cache,
+                command_history,
+                crates_cache: publisher_cache,
+                fetched_packages: FastMap::new(),
+                diffed: FastMap::new(),
+            }),
+        })
+    }
+
+    #[tracing::instrument(skip(self, metadata, network), err)]
+    pub async fn fetch_package(
+        &self,
+        metadata: &cargo_metadata::Metadata,
+        network: Option<&Network>,
+        package: PackageStr<'_>,
+        version: &VetVersion,
+    ) -> Result<PathBuf, FetchError> {
+        // Lock the mutex to extract a reference to the OnceCell which we'll use
+        // to asynchronously synchronize on and fetch the package only once in a
+        // single execution.
+        let once_cell = {
+            // NOTE: Don't .await while this is held, or we might deadlock!
+            let mut guard = self.state.lock().unwrap();
+            guard
+                .fetched_packages
+                .entry((package.to_owned(), version.clone()))
+                .or_default()
+                .clone()
+        };
+
+        let path_res: Result<_, FetchError> = once_cell
+            .get_or_try_init(|| async {
+                let root = self.root.as_ref().unwrap();
+
+                // crates.io won't have a copy of any crates with git revision
+                // versions, so they need to be found in local clones within the
+                // cargo metadata, otherwise we cannot find them.
+                if let Some(git_rev) = &version.git_rev {
+                    let repacked_src = root.join(CACHE_REGISTRY_SRC).join(format!(
+                        "{}-{}.git.{}",
+                        package,
+                        version.semver,
+                        version.git_rev.as_ref().unwrap()
+                    ));
+                    if fetch_is_ok(&repacked_src).await {
+                        return Ok(repacked_src);
+                    }
+
+                    // We don't have a cached re-pack - repack again ourselves.
+                    let checkout_path = locate_local_checkout(metadata, package, version)
+                        .ok_or_else(|| FetchError::UnknownGitRevision {
+                            package: package.to_owned(),
+                            git_rev: git_rev.to_owned(),
+                        })?;
+
+                    // We re-package any git checkouts into the cache in order
+                    // to maintain a consistent directory structure with crates
+                    // fetched from crates.io in diffs.
+                    unpack_checkout(&checkout_path, &repacked_src)
+                        .await
+                        .map_err(|error| FetchError::UnpackCheckout {
+                            src: checkout_path,
+                            error,
+                        })?;
+                    return Ok(repacked_src);
+                }
+
+                let version = &version.semver;
+
+                let dir_name = format!("{package}-{version}");
+
+                // First try to get a cached copy from cargo's registry.
+                if let Ok(cargo_home) = home::cargo_home() {
+                    // Check both the sparse and git registry caches.
+                    for registry in [CARGO_REGISTRY_CRATES_IO_HTTP, CARGO_REGISTRY_CRATES_IO_GIT] {
+                        let fetched_src = cargo_home
+                            .join(CARGO_REGISTRY)
+                            .join(CARGO_REGISTRY_SRC)
+                            .join(registry)
+                            .join(&dir_name);
+                        if fetch_is_ok(&fetched_src).await {
+                            return Ok(fetched_src);
+                        }
+                    }
+                }
+
+                // Paths for the fetched package and checkout in our local cache.
+                let fetched_package = root
+                    .join(CACHE_REGISTRY_CACHE)
+                    .join(format!("{dir_name}.crate"));
+                let fetched_src = root.join(CACHE_REGISTRY_SRC).join(&dir_name);
+
+                // Check if the resource is already available in our local cache.
+                let fetched_package_ = fetched_package.clone();
+                let now = filetime::FileTime::from_system_time(SystemTime::from(self.now));
+                let cached_file = tokio::task::spawn_blocking(move || {
+                    File::open(&fetched_package_).map(|file| {
+                        // Update the atime and mtime for this crate to ensure it isn't
+                        // collected by the gc.
+                        if let Err(err) =
+                            filetime::set_file_handle_times(&file, Some(now), Some(now))
+                        {
+                            warn!(
+                                "failed to update mtime for {}, gc may not function correctly: {}",
+                                fetched_package_.display(),
+                                err
+                            );
+                        }
+                        file
+                    })
+                })
+                .await
+                .expect("failed to join");
+
+                // If the file isn't in our local cache, make sure to download it.
+                let file = match cached_file {
+                    Ok(file) => file,
+                    Err(_) => {
+                        let network = network.ok_or_else(|| FetchError::Frozen {
+                            package: package.to_owned(),
+                            version: version.clone(),
+                        })?;
+
+                        // We don't have it, so download it
+                        let url =
+                            format!("https://crates.io/api/v1/crates/{package}/{version}/download");
+                        let url = Url::parse(&url).map_err(|error| FetchError::InvalidUrl {
+                            url: url.clone(),
+                            error,
+                        })?;
+                        info!(
+                            "downloading package {}:{} from {} to {}",
+                            package,
+                            version,
+                            url,
+                            fetched_package.display()
+                        );
+                        network.download_and_persist(url, &fetched_package).await?;
+
+                        let fetched_package_ = fetched_package.clone();
+                        tokio::task::spawn_blocking(move || File::open(fetched_package_))
+                            .await
+                            .expect("failed to join")
+                            .map_err(|error| FetchError::OpenCached {
+                                target: fetched_package.clone(),
+                                error,
+                            })?
+                    }
+                };
+
+                // TODO(#116): take the SHA2 of the bytes and compare it to what the registry says
+
+                if fetch_is_ok(&fetched_src).await {
+                    Ok(fetched_src)
+                } else {
+                    info!(
+                        "unpacking package {}:{} from {} to {}",
+                        package,
+                        version,
+                        fetched_package.display(),
+                        fetched_src.display()
+                    );
+                    // The tarball needs to be unpacked, so do so.
+                    tokio::task::spawn_blocking(move || {
+                        unpack_package(&file, &fetched_src)
+                            .map(|_| fetched_src)
+                            .map_err(|error| FetchError::Unpack {
+                                src: fetched_package.clone(),
+                                error,
+                            })
+                    })
+                    .await
+                    .expect("failed to join")
+                }
+            })
+            .await;
+        let path = path_res?;
+        Ok(path.to_owned())
+    }
+
+    #[tracing::instrument(skip_all, err)]
+    pub async fn diffstat_package(
+        &self,
+        version1: &Path,
+        version2: &Path,
+        has_git_rev: bool,
+    ) -> Result<(DiffStat, Vec<(PathBuf, PathBuf)>), DiffError> {
+        let _permit = self
+            .diff_semaphore
+            .acquire()
+            .await
+            .expect("Semaphore dropped?!");
+
+        // ERRORS: all of this is properly fallible internal workings, we can fail
+        // to diffstat some packages and still produce some useful output
+        trace!("diffstating {version1:#?} {version2:#?}");
+
+        let out = tokio::process::Command::new("git")
+            .arg("diff")
+            .arg("--ignore-cr-at-eol")
+            .arg("--no-index")
+            .arg("--numstat")
+            .arg("-z")
+            .arg(version1)
+            .arg(version2)
+            .output()
+            .await
+            .map_err(CommandError::CommandFailed)?;
+
+        let status = out.status.code().unwrap_or(-1);
+        // 0 = empty
+        // 1 = some diff
+        if status != 0 && status != 1 {
+            return Err(CommandError::BadStatus(status).into());
+        }
+
+        let mut diffstat = DiffStat {
+            files_changed: 0,
+            insertions: 0,
+            deletions: 0,
+        };
+        let mut to_compare = Vec::new();
+
+        // Thanks to the `-z` flag the output takes the rough format of:
+        // "{INSERTED}\t{DELETED}\t\0{FROM_PATH}\0{TO_PATH}\0" for each file
+        // being diffed. If the file was added or removed one of the sides will
+        // be "/dev/null", even on Windows. Binary files use "-" for the
+        // inserted & deleted counts.
+        let output = String::from_utf8(out.stdout).map_err(CommandError::BadOutput)?;
+        let mut chunks = output.split('\0');
+        while let (Some(changes_s), Some(from_s), Some(to_s)) =
+            (chunks.next(), chunks.next(), chunks.next())
+        {
+            // Check if the path is one of the files which is ignored.
+            let rel_path = if to_s != "/dev/null" {
+                Path::new(to_s)
+                    .strip_prefix(version2)
+                    .map_err(DiffError::UnexpectedPath)?
+            } else {
+                assert_ne!(
+                    from_s, "/dev/null",
+                    "unexpected diff from /dev/null to /dev/null"
+                );
+                Path::new(from_s)
+                    .strip_prefix(version1)
+                    .map_err(DiffError::UnexpectedPath)?
+            };
+            if DIFF_SKIP_PATHS.iter().any(|p| Path::new(p) == rel_path)
+                || (has_git_rev && Path::new(CARGO_TOML_FILE) == rel_path)
+            {
+                continue;
+            }
+
+            to_compare.push((from_s.into(), to_s.into()));
+
+            diffstat.files_changed += 1;
+
+            match changes_s.trim().split_once('\t') {
+                Some(("-", "-")) => {} // binary diff
+                Some((insertions_s, deletions_s)) => {
+                    diffstat.insertions += insertions_s
+                        .parse::<u64>()
+                        .map_err(|_| DiffError::InvalidOutput)?;
+                    diffstat.deletions += deletions_s
+                        .parse::<u64>()
+                        .map_err(|_| DiffError::InvalidOutput)?;
+                }
+                None => Err(DiffError::InvalidOutput)?,
+            };
+        }
+        Ok((diffstat, to_compare))
+    }
+
+    #[tracing::instrument(skip(self, metadata, network), err)]
+    pub async fn fetch_and_diffstat_package(
+        &self,
+        metadata: &cargo_metadata::Metadata,
+        network: Option<&Network>,
+        package: PackageStr<'_>,
+        delta: &Delta,
+    ) -> Result<DiffStat, FetchAndDiffError> {
+        // Lock the mutex to extract a reference to the OnceCell which we'll use
+        // to asynchronously synchronize on and diff the package only once in a
+        // single execution.
+        //
+        // While we have the mutex locked, we'll also check the DiffStat cache
+        // to return without any async steps if possible.
+        let once_cell = {
+            // NOTE: Don't .await while this is held, or we might deadlock!
+            let mut guard = self.state.lock().unwrap();
+
+            // Check if the value has already been cached.
+            let DiffCache::V2 { diffs } = &guard.diff_cache;
+            if let Some(cached) = diffs
+                .get(package)
+                .and_then(|cache| cache.get(delta))
+                .cloned()
+            {
+                return Ok(cached);
+            }
+
+            if self.root.is_none() {
+                // If we don't have a root, assume we want mocked results
+                // ERRORS: this warning really rides the line, I'm not sure if the user can/should care
+                warn!("Missing root, assuming we're in tests and mocking");
+
+                let from_len = match &delta.from {
+                    Some(from) => from.semver.major * from.semver.major,
+                    None => 0,
+                };
+                let to_len: u64 = delta.to.semver.major * delta.to.semver.major;
+                let diff = to_len as i64 - from_len as i64;
+                let count = diff.unsigned_abs();
+                return Ok(DiffStat {
+                    files_changed: 1,
+                    insertions: if diff > 0 { count } else { 0 },
+                    deletions: if diff < 0 { count } else { 0 },
+                });
+            }
+
+            guard
+                .diffed
+                .entry((package.to_owned(), delta.clone()))
+                .or_default()
+                .clone()
+        };
+
+        let diffstat = once_cell
+            .get_or_try_init(|| async {
+                let from = match &delta.from {
+                    Some(from) => self.fetch_package(metadata, network, package, from).await?,
+                    None => self.root.as_ref().unwrap().join(CACHE_EMPTY_PACKAGE),
+                };
+                let to = self
+                    .fetch_package(metadata, network, package, &delta.to)
+                    .await?;
+
+                // Have fetches, do a real diffstat
+                // NOTE: We'll never pick a 'from' version with a git_rev, so we
+                // don't need to check for that here.
+                let (diffstat, _) = self
+                    .diffstat_package(&from, &to, delta.to.git_rev.is_some())
+                    .await?;
+
+                // Record the cache result in the diffcache
+                {
+                    let mut guard = self.state.lock().unwrap();
+                    let DiffCache::V2 { diffs } = &mut guard.diff_cache;
+                    diffs
+                        .entry(package.to_string())
+                        .or_default()
+                        .insert(delta.clone(), diffstat.clone());
+                }
+
+                Ok::<_, FetchAndDiffError>(diffstat)
+            })
+            .await?;
+        Ok(diffstat.clone())
+    }
+
+    /// Run a garbage-collection pass over the cache, removing any files which
+    /// aren't supposed to be there, or which haven't been touched for an
+    /// extended period of time.
+    pub async fn gc(&self, max_package_age: Duration) {
+        if self.root.is_none() {
+            return;
+        }
+
+        let (root_rv, empty_rv, packages_rv) = tokio::join!(
+            self.gc_root(),
+            self.gc_empty(),
+            self.gc_packages(max_package_age)
+        );
+        if let Err(err) = root_rv {
+            error!("gc: performing gc on the cache root failed: {err}");
+        }
+        if let Err(err) = empty_rv {
+            error!("gc: performing gc on the empty package failed: {err}");
+        }
+        if let Err(err) = packages_rv {
+            error!("gc: performing gc on the package cache failed: {err}");
+        }
+    }
+
+    /// Sync version of `gc`
+    pub fn gc_sync(&self, max_package_age: Duration) {
+        tokio::runtime::Handle::current().block_on(self.gc(max_package_age));
+    }
+
+    /// Remove any unrecognized files from the root of the cargo-vet cache
+    /// directory.
+    async fn gc_root(&self) -> Result<(), io::Error> {
+        let root = self.root.as_ref().unwrap();
+        let mut root_entries = tokio::fs::read_dir(root).await?;
+        while let Some(entry) = root_entries.next_entry().await? {
+            if !entry
+                .file_name()
+                .to_str()
+                .map_or(false, |name| CACHE_ALLOWED_FILES.contains(&name))
+            {
+                remove_dir_entry(&entry).await?;
+            }
+        }
+        Ok(())
+    }
+
+    /// Remove all files located in the `cargo-vet/empty` directory, as it
+    /// should be empty.
+    async fn gc_empty(&self) -> Result<(), std::io::Error> {
+        let empty = self.root.as_ref().unwrap().join(CACHE_EMPTY_PACKAGE);
+        let mut empty_entries = tokio::fs::read_dir(&empty).await?;
+        while let Some(entry) = empty_entries.next_entry().await? {
+            remove_dir_entry(&entry).await?;
+        }
+        Ok(())
+    }
+
+    /// Remove any non '.crate' files from the registry cache, '.crate' files
+    /// which are older than `max_package_age`, and any source directories from
+    /// the registry src which no longer have a corresponding .crate.
+    async fn gc_packages(&self, max_package_age: Duration) -> Result<(), io::Error> {
+        let cache = self.root.as_ref().unwrap().join(CACHE_REGISTRY_CACHE);
+        let src = self.root.as_ref().unwrap().join(CACHE_REGISTRY_SRC);
+
+        let mut kept_packages = Vec::new();
+
+        let mut cache_entries = tokio::fs::read_dir(&cache).await?;
+        while let Some(entry) = cache_entries.next_entry().await? {
+            if let Some(to_keep) = self.should_keep_package(&entry, max_package_age).await {
+                kept_packages.push(to_keep);
+            } else {
+                remove_dir_entry(&entry).await?;
+            }
+        }
+
+        let mut src_entries = tokio::fs::read_dir(&src).await?;
+        while let Some(entry) = src_entries.next_entry().await? {
+            if !kept_packages.contains(&entry.file_name()) || !fetch_is_ok(&entry.path()).await {
+                remove_dir_entry(&entry).await?;
+            }
+        }
+        Ok(())
+    }
+
+    /// Given a directory entry for a file, returns how old it is. If there is an
+    /// issue (e.g. mtime >= now), will return `None` instead.
+    async fn get_file_age(&self, entry: &tokio::fs::DirEntry) -> Option<Duration> {
+        let meta = entry.metadata().await.ok()?;
+        SystemTime::from(self.now)
+            .duration_since(meta.modified().ok()?)
+            .ok()
+    }
+
+    /// Returns tne name of the crate if it should be preserved, or `None` if it shouldn't.
+    async fn should_keep_package(
+        &self,
+        entry: &tokio::fs::DirEntry,
+        max_package_age: Duration,
+    ) -> Option<OsString> {
+        // Get the stem and extension from the directory entry's path, and
+        // immediately remove it if something goes wrong.
+        let path = entry.path();
+        let stem = path.file_stem()?;
+        if path.extension()? != OsStr::new("crate") {
+            return None;
+        }
+
+        match self.get_file_age(entry).await {
+            Some(age) if age > max_package_age => None,
+            _ => Some(stem.to_owned()),
+        }
+    }
+
+    /// Delete every file in the cache directory other than the cache lock, and
+    /// clear out the command history and diff cache files.
+    ///
+    /// NOTE: The diff_cache, command_history, and publisher_cache files will be
+    /// re-created when the cache is unlocked, however they will be empty.
+    pub async fn clean(&self) -> Result<(), io::Error> {
+        let root = self.root.as_ref().expect("cannot clean a mocked cache");
+
+        // Make sure we don't write back the command history, diff cache, or
+        // publisher cache when dropping.
+        {
+            let mut guard = self.state.lock().unwrap();
+            guard.command_history = Default::default();
+            guard.diff_cache = Default::default();
+            guard.crates_cache = Default::default();
+        }
+
+        let mut root_entries = tokio::fs::read_dir(&root).await?;
+        while let Some(entry) = root_entries.next_entry().await? {
+            if entry.file_name() != Path::new(CACHE_VET_LOCK) {
+                remove_dir_entry(&entry).await?;
+            }
+        }
+        Ok(())
+    }
+
+    /// Sync version of `clean`
+    pub fn clean_sync(&self) -> Result<(), io::Error> {
+        tokio::runtime::Handle::current().block_on(self.clean())
+    }
+
+    pub fn get_last_fetch(&self) -> Option<FetchCommand> {
+        let guard = self.state.lock().unwrap();
+        guard.command_history.last_fetch.clone()
+    }
+
+    pub fn set_last_fetch(&self, last_fetch: FetchCommand) {
+        let mut guard = self.state.lock().unwrap();
+        guard.command_history.last_fetch = Some(last_fetch);
+    }
+
+    /// If `versions` is specified, the cached information will be used if all specified versions
+    /// are already present or if the missing versions are _not_ in the crates.io index and the
+    /// last fetched time is less than `NONINDEX_VERSION_PUBLISHER_REFRESH_DAYS`.
+    ///
+    /// If `versions` is None, the cached information is used _only_ when the last fetched time is
+    /// less than `NONINDEX_VERSION_PUBLISHER_REFRESH_DAYS`.
+    ///
+    /// When this function returns `Ok`, the returned state is guaranteed to have an entry for
+    /// `name` in `crates_cache.crates`.
+    fn update_crates_cache<'a>(&'a self, name: PackageStr<'a>) -> UpdateCratesCache<'a> {
+        UpdateCratesCache::new(self, name)
+    }
+
+    /// Synchronous routine to get whatever publisher information is cached
+    /// without hitting the network.
+    pub fn get_cached_publishers(
+        &self,
+        name: PackageStr<'_>,
+    ) -> SortedMap<semver::Version, CratesCacheVersionDetails> {
+        let guard = self.state.lock().unwrap();
+        guard
+            .crates_cache
+            .crates
+            .get(name)
+            .map(|c| {
+                c.versions
+                    .iter()
+                    .flat_map(|(version, details)| {
+                        details.clone().map(|details| (version.clone(), details))
+                    })
+                    .collect()
+            })
+            .unwrap_or_default()
+    }
+
+    /// Look up information about who published each version of the specified
+    /// crates. Versions for each crate are also specified in order to avoid
+    /// hitting the network in the case where the cache already has the relevant
+    /// information.
+    pub async fn get_publishers(
+        &self,
+        network: Option<&Network>,
+        name: PackageStr<'_>,
+        versions: FastSet<&semver::Version>,
+    ) -> Result<SortedMap<semver::Version, CratesCacheVersionDetails>, CrateInfoError> {
+        let guard = self
+            .update_crates_cache(name)
+            .versions(versions)
+            .need_version_details()
+            .update(network)
+            .await?;
+        Ok(guard
+            .crates_cache
+            .crates
+            .get(name)
+            .expect("publisher cache update failed")
+            .versions
+            .iter()
+            .flat_map(|(version, details)| {
+                details.clone().map(|details| (version.clone(), details))
+            })
+            .collect())
+    }
+
+    /// Look up crates.io metadata for the given crate.
+    pub async fn get_crate_metadata(
+        &self,
+        network: Option<&Network>,
+        name: PackageStr<'_>,
+    ) -> Result<CratesAPICrateMetadata, CrateInfoError> {
+        let guard = self
+            .update_crates_cache(name)
+            .need_crate_metadata()
+            .invalidate_after(chrono::Duration::days(METADATA_CACHE_EXPIRY_DAYS))
+            .update(network)
+            .await?;
+        Ok(guard
+            .crates_cache
+            .crates
+            .get(name)
+            .expect("crate cache update failed")
+            .metadata
+            .as_ref()
+            .expect("crate cache metadata missing")
+            .clone())
+    }
+
+    /// Get version information from the crates.io index for this package.
+    pub async fn get_versions(
+        &self,
+        network: Option<&Network>,
+        name: PackageStr<'_>,
+    ) -> Result<Vec<semver::Version>, CrateInfoError> {
+        let guard = self
+            .update_crates_cache(name)
+            .invalidate_after(chrono::Duration::days(VERSIONS_CACHE_EXPIRY_DAYS))
+            .update(network)
+            .await?;
+        Ok(guard
+            .crates_cache
+            .crates
+            .get(name)
+            .expect("crate cache update failed")
+            .versions
+            .keys()
+            .cloned()
+            .collect())
+    }
+
+    /// Look up user information for a crates.io user from the publisher cache.
+    pub fn get_crates_user_info(&self, user_id: u64) -> Option<CratesCacheUser> {
+        let guard = self.state.lock().unwrap();
+        guard.crates_cache.users.get(&user_id).cloned()
+    }
+}
+
+struct UpdateCratesCache<'a> {
+    cache: &'a Cache,
+    crate_name: PackageStr<'a>,
+    cache_expiration: Option<chrono::Duration>,
+    versions: Option<FastSet<&'a semver::Version>>,
+    need_version_details: bool,
+    need_crate_metadata: bool,
+}
+
+impl<'a> UpdateCratesCache<'a> {
+    pub fn new(cache: &'a Cache, crate_name: PackageStr<'a>) -> Self {
+        assert!(
+            !crate_name.is_empty() && !crate_name.contains('/'),
+            "invalid crate name"
+        );
+        UpdateCratesCache {
+            cache,
+            crate_name,
+            cache_expiration: None,
+            versions: None,
+            need_version_details: false,
+            need_crate_metadata: false,
+        }
+    }
+
+    pub fn invalidate_after(mut self, cache_expiration: chrono::Duration) -> Self {
+        self.cache_expiration = Some(cache_expiration);
+        self
+    }
+
+    pub fn versions(mut self, versions: FastSet<&'a semver::Version>) -> Self {
+        self.versions = Some(versions);
+        self
+    }
+
+    pub fn need_version_details(mut self) -> Self {
+        self.need_version_details = true;
+        self
+    }
+
+    pub fn need_crate_metadata(mut self) -> Self {
+        self.need_crate_metadata = true;
+        self
+    }
+
+    pub async fn update(
+        self,
+        network: Option<&Network>,
+    ) -> Result<std::sync::MutexGuard<'a, CacheState>, CrateInfoError> {
+        let Some(network) = network else {
+            let guard = self.cache.state.lock().unwrap();
+            match guard.crates_cache.crates.get(self.crate_name) {
+                Some(entry) if entry.exists() => return Ok(guard),
+                _ => return Err(CrateInfoError::DoesNotExist { name: self.crate_name.to_owned() }),
+            }
+        };
+        // Use the cached response if possible.
+        if let Some(guard) = self.can_use_cache()? {
+            return Ok(guard);
+        }
+
+        if self.need_version_details || self.need_crate_metadata {
+            // If we don't yet have an existing entry in the cache, first update using the index to
+            // check whether the crate exists at all.
+            if !self.crate_exists() {
+                // Returns Err if the crate doesn't exist.
+                drop(self.update_using_index(network).await?);
+            }
+            self.update_using_api(network).await
+        } else {
+            self.update_using_index(network).await
+        }
+    }
+
+    fn crate_exists(&self) -> bool {
+        let guard = self.cache.state.lock().unwrap();
+        guard
+            .crates_cache
+            .crates
+            .get(self.crate_name)
+            .map(|e| e.exists())
+            .unwrap_or(false)
+    }
+
+    /// Returns Ok(Some) if the cache can be used, Ok(None) if not, and Err for errors (such as non
+    /// existent crates).
+    pub fn can_use_cache(
+        &self,
+    ) -> Result<Option<std::sync::MutexGuard<'a, CacheState>>, CrateInfoError> {
+        let guard = self.cache.state.lock().unwrap();
+        if let Some(entry) = guard.crates_cache.crates.get(self.crate_name) {
+            let cache_age = self.cache.now - entry.last_fetched;
+            // If a crate was previously found to not exist...
+            if !entry.exists() {
+                if cache_age < chrono::Duration::days(NONEXISTENT_CRATE_EXPIRY_DAYS) {
+                    return Err(CrateInfoError::DoesNotExist {
+                        name: self.crate_name.to_owned(),
+                    });
+                } else {
+                    return Ok(None);
+                }
+            }
+
+            // If we're missing metadata, return immediately (need update).
+            if entry.metadata.is_none() && self.need_crate_metadata {
+                return Ok(None);
+            }
+
+            // Check if there are any relevant versions which are not present in
+            // the local cache. If none are missing, we have everything cached
+            // and can continue as normal.
+            if let Some(versions) = &self.versions {
+                let mut has_missing_versions = false;
+                for &v in versions {
+                    match entry.versions.get(v) {
+                        None => has_missing_versions = true,
+                        // If we're missing a known version's details, return immediately (need
+                        // update).
+                        Some(None) if self.need_version_details => {
+                            return Ok(None);
+                        }
+                        _ => (),
+                    }
+                }
+
+                // If versions were specified and there were no missing versions, return
+                // immediately.
+                if !has_missing_versions {
+                    info!(
+                        "using cached publisher info for {} - relevant versions in cache",
+                        self.crate_name
+                    );
+                    return Ok(Some(guard));
+                }
+            }
+
+            if let Some(expiration) = self.cache_expiration {
+                if cache_age < expiration {
+                    info!(
+                        "using cached info for {} - entry not expired",
+                        self.crate_name
+                    );
+                    return Ok(Some(guard));
+                }
+            }
+        }
+        Ok(None)
+    }
+
+    /// Use `crates.io/api` to get crate information.
+    ///
+    /// This fully replaces/updates the information in `crates_cache.crates` for `name`.
+    ///
+    /// When this function returns `Ok`, the returned state is guaranteed to have an entry for
+    /// `name` in `crates_cache.crates`.
+    ///
+    /// # Note
+    /// The official scraper policy requests a rate limit of 1 request per second
+    /// <https://crates.io/policies#crawlers>. This wouldn't be a very good user-experience to
+    /// require a multi-second wait to fetch each crate's information, however the local caching
+    /// and infrequent user-driven calls to the API should hopefully ensure we remain under the
+    /// 1 request per second limit over time.
+    ///
+    /// If this ends up being an issue, we can look into adding some form of cross-call tracking
+    /// in the cache to ensure that we don't exceed the rate over a slightly-extended period of
+    /// time, (e.g. by throttling requests from consecutive calls).
+    async fn update_using_api(
+        &self,
+        network: &Network,
+    ) -> Result<std::sync::MutexGuard<'a, CacheState>, CrateInfoError> {
+        let url = Url::parse(&format!(
+            "https://crates.io/api/v1/crates/{}",
+            self.crate_name
+        ))
+        .expect("invalid crate name");
+
+        let response = self.try_download(network, url).await?;
+        let result = load_json::<CratesAPICrate>(&response[..])?;
+
+        // Update the users cache and individual crates caches, and return our
+        // set of versions.
+        let mut guard = self.cache.state.lock().unwrap();
+        let versions: SortedMap<_, _> = result
+            .versions
+            .into_iter()
+            .map(|api_version| {
+                (
+                    api_version.num,
+                    Some(CratesCacheVersionDetails {
+                        created_at: api_version.created_at,
+                        published_by: api_version.published_by.map(|api_user| {
+                            info!("recording user info for {api_user:?}");
+                            guard.crates_cache.users.insert(
+                                api_user.id,
+                                CratesCacheUser {
+                                    login: api_user.login,
+                                    name: api_user.name,
+                                },
+                            );
+                            api_user.id
+                        }),
+                    }),
+                )
+            })
+            .collect();
+        info!(
+            "found {} versions for crate {}",
+            versions.len(),
+            self.crate_name
+        );
+        guard.crates_cache.crates.insert(
+            self.crate_name.to_owned(),
+            CratesCacheEntry {
+                last_fetched: self.cache.now,
+                versions,
+                metadata: Some(result.crate_data),
+            },
+        );
+
+        Ok(guard)
+    }
+
+    /// Use `index.crates.io` to get crate information.
+    ///
+    /// This will only add versions which aren't already present in `crates_cache.crates` for
+    /// `name`.
+    ///
+    /// When this function returns `Ok`, the returned state is guaranteed to have an entry for
+    /// `name` in `crates_cache.crates`.
+    async fn update_using_index(
+        &self,
+        network: &Network,
+    ) -> Result<std::sync::MutexGuard<'a, CacheState>, CrateInfoError> {
+        // Crate names can only be a subset of ascii (valid rust identifier characters and `-`), so
+        // using `len()` and indexing will result in valid counts/characters.
+        let mut url = String::from("https://index.crates.io/");
+        let name = self.crate_name;
+        use std::fmt::Write;
+        match name.len() {
+            1 => write!(url, "1/{name}"),
+            2 => write!(url, "2/{name}"),
+            3 => write!(url, "3/{}/{name}", &name[0..1]),
+            _ => write!(url, "{}/{}/{name}", &name[0..2], &name[2..4]),
+        }
+        .expect("writing to a String should not fail");
+        // Crate index always use lowercases, but crate name may contain uppercase characters.
+        url.make_ascii_lowercase();
+        let url = Url::parse(&url).expect("invalid crate name");
+
+        let response = self.try_download(network, url).await?;
+
+        let result = crates_index::Crate::from_slice(&response[..]).map_err(LoadJsonError::from)?;
+
+        // Update the crates cache with version info (if not already present).
+        let mut guard = self.cache.state.lock().unwrap();
+        info!(
+            "found {} versions for crate {}",
+            result.versions().len(),
+            name
+        );
+
+        let entry = guard
+            .crates_cache
+            .crates
+            .entry(name.to_owned())
+            .or_default();
+        entry.last_fetched = self.cache.now;
+        for version in result
+            .versions()
+            .iter()
+            .filter_map(|v| semver::Version::parse(v.version()).ok())
+        {
+            entry.versions.entry(version).or_default();
+        }
+
+        Ok(guard)
+    }
+
+    async fn try_download(&self, network: &Network, url: Url) -> Result<Vec<u8>, CrateInfoError> {
+        network.download(url).await.map_err(|e| match e {
+            DownloadError::FailedToStartDownload { error, .. }
+                if error.status() == Some(reqwest::StatusCode::NOT_FOUND) =>
+            {
+                self.non_existent_crate();
+                CrateInfoError::DoesNotExist {
+                    name: self.crate_name.to_owned(),
+                }
+            }
+            other => other.into(),
+        })
+    }
+
+    fn non_existent_crate(&self) {
+        let mut guard = self.cache.state.lock().unwrap();
+        info!("crate {} not found in crates.io", self.crate_name);
+        guard.crates_cache.crates.insert(
+            self.crate_name.to_owned(),
+            CratesCacheEntry {
+                last_fetched: self.cache.now,
+                versions: Default::default(),
+                metadata: None,
+            },
+        );
+    }
+}
+
+/// Queries a package in the crates.io registry for a specific published version
+pub fn exact_version<'a>(
+    this: &'a crates_index::Crate,
+    target_version: &semver::Version,
+) -> Option<&'a crates_index::Version> {
+    for index_version in this.versions() {
+        if let Ok(index_ver) = index_version.version().parse::<semver::Version>() {
+            if &index_ver == target_version {
+                return Some(index_version);
+            }
+        }
+    }
+    None
+}
+
+/// Locate the checkout path for the given package and version if it is part of
+/// the local build graph. Returns `None` if a local checkout cannot be found.
+pub fn locate_local_checkout(
+    metadata: &cargo_metadata::Metadata,
+    package: PackageStr<'_>,
+    version: &VetVersion,
+) -> Option<PathBuf> {
+    for pkg in &metadata.packages {
+        if pkg.name == package && &pkg.vet_version() == version {
+            assert_eq!(
+                pkg.manifest_path.file_name(),
+                Some(CARGO_TOML_FILE),
+                "unexpected manifest file name"
+            );
+            return Some(pkg.manifest_path.parent().map(PathBuf::from).unwrap());
+        }
+    }
+    None
+}
+
+#[tracing::instrument(err)]
+fn unpack_package(tarball: &File, unpack_dir: &Path) -> Result<(), UnpackError> {
+    // If we get here and the unpack_dir exists, this implies we had a previously failed fetch,
+    // blast it away so we can have a clean slate!
+    if unpack_dir.exists() {
+        fs::remove_dir_all(unpack_dir)?;
+    }
+    fs::create_dir(unpack_dir)?;
+    let gz = GzDecoder::new(tarball);
+    let mut tar = Archive::new(gz);
+    let prefix = unpack_dir.file_name().unwrap();
+    let parent = unpack_dir.parent().unwrap();
+    for entry in tar.entries()? {
+        let mut entry = entry.map_err(UnpackError::ArchiveIterate)?;
+        let entry_path = entry
+            .path()
+            .map_err(UnpackError::ArchiveEntry)?
+            .into_owned();
+
+        // We're going to unpack this tarball into the global source
+        // directory, but we want to make sure that it doesn't accidentally
+        // (or maliciously) overwrite source code from other crates. Cargo
+        // itself should never generate a tarball that hits this error, and
+        // crates.io should also block uploads with these sorts of tarballs,
+        // but be extra sure by adding a check here as well.
+        if !entry_path.starts_with(prefix) {
+            return Err(UnpackError::InvalidPaths {
+                entry_path,
+                prefix: prefix.to_owned(),
+            });
+        }
+
+        entry
+            .unpack_in(parent)
+            .map_err(|error| UnpackError::Unpack {
+                entry_path: entry_path.clone(),
+                error,
+            })?;
+    }
+
+    create_unpack_lock(unpack_dir).map_err(|error| UnpackError::LockCreate {
+        target: unpack_dir.to_owned(),
+        error,
+    })?;
+
+    Ok(())
+}
+
+fn create_unpack_lock(unpack_dir: &Path) -> Result<(), io::Error> {
+    let lockfile = unpack_dir.join(CARGO_OK_FILE);
+
+    // The lock file is created after unpacking so we overwrite a lock file
+    // which may have been extracted from the package.
+    let mut ok = OpenOptions::new()
+        .create(true)
+        .read(true)
+        .write(true)
+        .open(lockfile)?;
+
+    // Write to the lock file to indicate that unpacking was successful.
+    write!(ok, "ok")?;
+    ok.sync_all()?;
+
+    Ok(())
+}
+
+/// Unpack a non-crates.io package checkout in a format similar to what would be
+/// unpacked from a .crate file published on crates.io.
+///
+/// This is used in order to normalize the file and directory structure for git
+/// revisions to make them easier to work with when diffing.
+async fn unpack_checkout(
+    checkout_path: &Path,
+    unpack_path: &Path,
+) -> Result<(), UnpackCheckoutError> {
+    // Invoke `cargo package --list` to determine the list of files which
+    // should be copied to the repackaged directory.
+    let cargo_path = std::env::var_os(CARGO_ENV).expect("Cargo failed to set $CARGO, how?");
+    let out = tokio::process::Command::new(cargo_path)
+        .arg("package")
+        .arg("--list")
+        .arg("--allow-dirty")
+        .arg("--manifest-path")
+        .arg(checkout_path.join(CARGO_TOML_FILE))
+        .output()
+        .await
+        .map_err(CommandError::CommandFailed)?;
+
+    if !out.status.success() {
+        return Err(CommandError::BadStatus(out.status.code().unwrap_or(-1)).into());
+    }
+
+    let stdout = String::from_utf8(out.stdout).map_err(CommandError::BadOutput)?;
+
+    tokio::fs::create_dir_all(unpack_path)
+        .await
+        .map_err(|error| UnpackCheckoutError::CreateDirError {
+            path: unpack_path.to_owned(),
+            error,
+        })?;
+
+    // Asynchronously copy all required files to the target directory.
+    try_join_all(stdout.lines().map(|target| async move {
+        // We'll be ignoring diffs for each of the skipped paths, so we can
+        // ignore these if cargo reports them.
+        if DIFF_SKIP_PATHS.iter().any(|&p| p == target) {
+            return Ok(());
+        }
+
+        let to = unpack_path.join(target);
+        let from = match target {
+            // Copy the original Cargo.toml to Cargo.toml.orig for better
+            // comparisons.
+            "Cargo.toml.orig" => checkout_path.join(CARGO_TOML_FILE),
+            _ => checkout_path.join(target),
+        };
+
+        // Create the directory this file will be placed in.
+        let parent = to.parent().unwrap();
+        tokio::fs::create_dir_all(&parent).await.map_err(|error| {
+            UnpackCheckoutError::CreateDirError {
+                path: parent.to_owned(),
+                error,
+            }
+        })?;
+
+        match tokio::fs::copy(from, to).await {
+            Ok(_) => Ok(()),
+            Err(error) => match error.kind() {
+                // Cargo may tell us about files which don't exist (e.g. because
+                // they are generated). It's OK to ignore those files when
+                // copying.
+                io::ErrorKind::NotFound => Ok(()),
+                _ => Err(UnpackCheckoutError::CopyError {
+                    target: target.into(),
+                    error,
+                }),
+            },
+        }
+    }))
+    .await?;
+
+    let unpack_path_ = unpack_path.to_owned();
+    tokio::task::spawn_blocking(move || create_unpack_lock(&unpack_path_))
+        .await
+        .expect("failed to join")
+        .map_err(UnpackCheckoutError::LockCreate)?;
+
+    Ok(())
+}
+
+async fn fetch_is_ok(fetch: &Path) -> bool {
+    match tokio::fs::read_to_string(fetch.join(CARGO_OK_FILE)).await {
+        Ok(ok) => ok == CARGO_OK_BODY,
+        Err(_) => false,
+    }
+}
+
+/// Based on the type of file for an entry, either recursively remove the
+/// directory, or remove the file. This is intended to be roughly equivalent to
+/// `rm -r`.
+async fn remove_dir_entry(entry: &tokio::fs::DirEntry) -> Result<(), io::Error> {
+    info!("gc: removing {}", entry.path().display());
+    let file_type = entry.file_type().await?;
+    if file_type.is_dir() {
+        tokio::fs::remove_dir_all(entry.path()).await?;
+    } else {
+        tokio::fs::remove_file(entry.path()).await?;
+    }
+    Ok(())
+}
+
+fn load_toml<T>(file_name: &str, reader: impl Read) -> Result<(SourceFile, T), LoadTomlError>
+where
+    T: for<'a> Deserialize<'a>,
+{
+    let mut reader = BufReader::new(reader);
+    let mut string = String::new();
+    reader.read_to_string(&mut string)?;
+    let source_code = SourceFile::new(file_name, string);
+    let result = toml::de::from_str(source_code.source());
+    match result {
+        Ok(toml) => Ok((source_code, toml)),
+        Err(error) => {
+            let (line, col) = error.line_col().unwrap_or((0, 0));
+            let span = SourceOffset::from_location(source_code.source(), line + 1, col);
+            Err(TomlParseError {
+                source_code,
+                span,
+                error,
+            }
+            .into())
+        }
+    }
+}
+fn store_toml<T>(
+    heading: &str,
+    val: T,
+    user_info: Option<&FastMap<CratesUserId, CratesCacheUser>>,
+) -> Result<String, StoreTomlError>
+where
+    T: Serialize,
+{
+    let toml_document = to_formatted_toml(val, user_info)?;
+    Ok(format!("{heading}{toml_document}"))
+}
+fn load_json<T>(reader: impl Read) -> Result<T, LoadJsonError>
+where
+    T: for<'a> Deserialize<'a>,
+{
+    let mut reader = BufReader::new(reader);
+    let mut string = String::new();
+    reader.read_to_string(&mut string)?;
+    let json = serde_json::from_str(&string).map_err(|error| JsonParseError { error })?;
+    Ok(json)
+}
+fn store_json<T>(val: T) -> Result<String, StoreJsonError>
+where
+    T: Serialize,
+{
+    let json_string = serde_json::to_string(&val)?;
+    Ok(json_string)
+}
+fn store_audits(
+    mut audits: AuditsFile,
+    user_info: &FastMap<CratesUserId, CratesCacheUser>,
+) -> Result<String, StoreTomlError> {
+    let heading = r###"
+# cargo-vet audits file
+"###;
+    audits
+        .audits
+        .values_mut()
+        .for_each(|entries| entries.sort());
+
+    store_toml(heading, audits, Some(user_info))
+}
+fn store_config(mut config: ConfigFile) -> Result<String, StoreTomlError> {
+    config
+        .exemptions
+        .values_mut()
+        .for_each(|entries| entries.sort());
+
+    let heading = r###"
+# cargo-vet config file
+"###;
+
+    store_toml(heading, config, None)
+}
+fn store_imports(
+    imports: ImportsFile,
+    user_info: &FastMap<CratesUserId, CratesCacheUser>,
+) -> Result<String, StoreTomlError> {
+    let heading = r###"
+# cargo-vet imports lock
+"###;
+
+    store_toml(heading, imports, Some(user_info))
+}
+fn store_diff_cache(diff_cache: DiffCache) -> Result<String, StoreTomlError> {
+    let heading = "";
+
+    store_toml(heading, diff_cache, None)
+}
+fn store_command_history(command_history: CommandHistory) -> Result<String, StoreJsonError> {
+    store_json(command_history)
+}
+fn store_publisher_cache(publisher_cache: CratesCache) -> Result<String, StoreJsonError> {
+    store_json(publisher_cache)
+}
diff --git a/src/string_format.rs b/src/string_format.rs
new file mode 100644
index 0000000..8184432
--- /dev/null
+++ b/src/string_format.rs
@@ -0,0 +1,104 @@
+//! String formatting utilities.
+
+use std::fmt::{self, Display, Formatter, Write};
+
+/// Format a short list with commas and an "and" before the last item in a multi-item list.
+///
+/// If there are 2 or fewer items, they are always displayed (regardless of formatting width
+/// limit), and the first item is also always displayed.
+///
+/// The default width is 40 characters.
+pub struct FormatShortList<S> {
+    items: Vec<S>,
+}
+
+impl<S: AsRef<str>> FormatShortList<S> {
+    pub fn new(mut items: Vec<S>) -> Self {
+        // To keep the display compact, sort by name length and truncate long lists.
+        // We first sort by name because rust defaults to a stable sort and this will
+        // have by-name as the tie breaker.
+        items.sort_by(|a, b| {
+            let a = a.as_ref();
+            let b = b.as_ref();
+            a.len().cmp(&b.len()).then_with(|| a.cmp(b))
+        });
+        FormatShortList { items }
+    }
+
+    pub fn string(items: Vec<S>) -> String {
+        Self::new(items).to_string()
+    }
+}
+
+impl<S: AsRef<str>> Display for FormatShortList<S> {
+    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+        let width = f.width().unwrap_or(40);
+
+        // The character count for these constants is obtained with `len` (because we author this
+        // text and there's no reason to do anything more expensive to get the length), so keep
+        // these ASCII.
+        const CONJUNCTION: &str = " and ";
+        const REMAINDER: &str = " other";
+        const REMAINDER_PLURAL: &str = "s";
+
+        fn remainder_length(n: usize) -> usize {
+            let num_length = n / 10 + 1;
+            num_length + REMAINDER.len() + if n > 1 { REMAINDER_PLURAL.len() } else { 0 }
+        }
+
+        match self.items.as_slice() {
+            [] => Ok(()),
+            [a] => f.write_str(a.as_ref()),
+            [a, b] => write!(f, "{}{CONJUNCTION}{}", a.as_ref(), b.as_ref()),
+            items => {
+                // Decide how many items we can show based on the width limit.
+                let items_len = items.len();
+                let too_large_index = items
+                    .iter()
+                    .enumerate()
+                    .scan(0, |prior_length, (index, item)| {
+                        // prior_length represents the length of the previous items up to and including
+                        // the trailing comma.
+                        let item_chars = console::measure_text_width(item.as_ref());
+                        if index == items_len - 1 {
+                            return Some(*prior_length + CONJUNCTION.len() + item_chars);
+                        }
+                        *prior_length += item_chars + 1; // item and trailing comma
+                        Some(
+                            *prior_length - usize::from(index == 0) /* no comma in this case, "FOO and X others" */
+                                + CONJUNCTION.len()
+                                + remainder_length(items_len - (index + 1)),
+                        )
+                    })
+                    .position(|length| length > width);
+                let large_enough_index = too_large_index.unwrap_or(items_len).saturating_sub(1);
+
+                // Write out the items based on `large_enough_index`.
+                f.write_str(items[0].as_ref())?;
+                if large_enough_index == 0 {
+                    f.write_str(CONJUNCTION)?;
+                    write!(f, "{}{REMAINDER}{REMAINDER_PLURAL}", items_len - 1)?;
+                } else {
+                    for item in &items[1..=std::cmp::min(large_enough_index, items_len - 2)] {
+                        write!(f, ", {}", item.as_ref())?;
+                    }
+                    f.write_char(',')?;
+                    f.write_str(CONJUNCTION)?;
+                    if large_enough_index == items_len - 1 {
+                        f.write_str(items[large_enough_index].as_ref())?;
+                    } else {
+                        let remaining = items_len - (large_enough_index + 1);
+                        write!(
+                            f,
+                            "{}{REMAINDER}{}",
+                            remaining,
+                            if remaining > 1 { REMAINDER_PLURAL } else { "" }
+                        )?;
+                    }
+                }
+
+                Ok(())
+            }
+        }
+    }
+}
diff --git a/src/tests/aggregate.rs b/src/tests/aggregate.rs
new file mode 100644
index 0000000..1d47769
--- /dev/null
+++ b/src/tests/aggregate.rs
@@ -0,0 +1,259 @@
+use insta::assert_snapshot;
+
+use super::*;
+
+fn mock_aggregate(sources: Vec<(String, AuditsFile)>) -> String {
+    match crate::do_aggregate_audits(sources) {
+        Ok(merged) => crate::serialization::to_formatted_toml(merged, None)
+            .unwrap()
+            .to_string(),
+        Err(error) => format!("{:?}", miette::Report::new(error)),
+    }
+}
+
+#[test]
+fn test_merge_audits_files_basic() {
+    let _enter = TEST_RUNTIME.enter();
+
+    let audits_files = vec![
+        (
+            "https://source1.example.com/supply_chain/audits.toml".to_owned(),
+            AuditsFile {
+                criteria: [].into_iter().collect(),
+                wildcard_audits: [(
+                    "package2".to_owned(),
+                    vec![wildcard_audit(1, "safe-to-deploy")],
+                )]
+                .into_iter()
+                .collect(),
+                audits: [(
+                    "package1".to_owned(),
+                    vec![full_audit(ver(DEFAULT_VER), "safe-to-deploy")],
+                )]
+                .into_iter()
+                .collect(),
+                trusted: [].into_iter().collect(),
+            },
+        ),
+        (
+            "https://source2.example.com/supply_chain/audits.toml".to_owned(),
+            AuditsFile {
+                criteria: [].into_iter().collect(),
+                wildcard_audits: [
+                    (
+                        "package2".to_owned(),
+                        vec![wildcard_audit(2, "safe-to-deploy")],
+                    ),
+                    (
+                        "package3".to_owned(),
+                        vec![wildcard_audit(1, "safe-to-deploy")],
+                    ),
+                ]
+                .into_iter()
+                .collect(),
+                audits: [
+                    (
+                        "package1".to_owned(),
+                        vec![
+                            full_audit(ver(5), "safe-to-deploy"),
+                            full_audit(ver(DEFAULT_VER), "safe-to-deploy"),
+                        ],
+                    ),
+                    (
+                        "package2".to_owned(),
+                        vec![full_audit(ver(DEFAULT_VER), "safe-to-deploy")],
+                    ),
+                ]
+                .into_iter()
+                .collect(),
+                trusted: [].into_iter().collect(),
+            },
+        ),
+    ];
+
+    let output = mock_aggregate(audits_files);
+    assert_snapshot!(output);
+}
+
+#[test]
+fn test_merge_audits_files_custom_criteria() {
+    let _enter = TEST_RUNTIME.enter();
+
+    let audits_files = vec![
+        (
+            "https://source1.example.com/supply_chain/audits.toml".to_owned(),
+            AuditsFile {
+                criteria: [
+                    (
+                        "criteria1".to_owned(),
+                        CriteriaEntry {
+                            implies: vec![],
+                            description: Some("Criteria 1".to_owned()),
+                            description_url: None,
+                            aggregated_from: vec!["https://elsewhere.example.com/audits.toml"
+                                .to_owned()
+                                .into()],
+                        },
+                    ),
+                    (
+                        "criteria2".to_owned(),
+                        CriteriaEntry {
+                            implies: vec![],
+                            description: Some("Criteria 2".to_owned()),
+                            description_url: None,
+                            aggregated_from: vec![],
+                        },
+                    ),
+                ]
+                .into_iter()
+                .collect(),
+                wildcard_audits: [].into_iter().collect(),
+                audits: [(
+                    "package1".to_owned(),
+                    vec![
+                        full_audit(ver(DEFAULT_VER), "criteria1"),
+                        full_audit(ver(DEFAULT_VER), "criteria2"),
+                    ],
+                )]
+                .into_iter()
+                .collect(),
+                trusted: [].into_iter().collect(),
+            },
+        ),
+        (
+            "https://source2.example.com/supply_chain/audits.toml".to_owned(),
+            AuditsFile {
+                criteria: [
+                    (
+                        "criteria1".to_owned(),
+                        CriteriaEntry {
+                            implies: vec![],
+                            description: Some("Criteria 1".to_owned()),
+                            description_url: None,
+                            aggregated_from: vec!["https://beyond.example.com/audits.toml"
+                                .to_owned()
+                                .into()],
+                        },
+                    ),
+                    (
+                        "criteria3".to_owned(),
+                        CriteriaEntry {
+                            implies: vec![],
+                            description: Some("Criteria 3".to_owned()),
+                            description_url: None,
+                            aggregated_from: vec![],
+                        },
+                    ),
+                ]
+                .into_iter()
+                .collect(),
+                wildcard_audits: [].into_iter().collect(),
+                audits: [
+                    (
+                        "package1".to_owned(),
+                        vec![full_audit(ver(DEFAULT_VER), "criteria3")],
+                    ),
+                    (
+                        "package2".to_owned(),
+                        vec![full_audit(ver(DEFAULT_VER), "criteria1")],
+                    ),
+                ]
+                .into_iter()
+                .collect(),
+                trusted: [].into_iter().collect(),
+            },
+        ),
+    ];
+
+    let output = mock_aggregate(audits_files);
+    assert_snapshot!(output);
+}
+
+#[test]
+fn test_merge_audits_files_custom_criteria_conflict() {
+    let _enter = TEST_RUNTIME.enter();
+
+    let audits_files = vec![
+        (
+            "https://source1.example.com/supply_chain/audits.toml".to_owned(),
+            AuditsFile {
+                criteria: [
+                    (
+                        "criteria1".to_owned(),
+                        CriteriaEntry {
+                            implies: vec![],
+                            description: Some("Criteria 1".to_owned()),
+                            description_url: None,
+                            aggregated_from: vec![],
+                        },
+                    ),
+                    (
+                        "criteria2".to_owned(),
+                        CriteriaEntry {
+                            implies: vec!["criteria1".to_owned().into()],
+                            description: Some("Criteria 2".to_owned()),
+                            description_url: None,
+                            aggregated_from: vec![],
+                        },
+                    ),
+                    (
+                        "criteria3".to_owned(),
+                        CriteriaEntry {
+                            implies: vec!["criteria2".to_owned().into()],
+                            description: None,
+                            description_url: Some("https://criteria3".to_owned()),
+                            aggregated_from: vec![],
+                        },
+                    ),
+                ]
+                .into_iter()
+                .collect(),
+                wildcard_audits: [].into_iter().collect(),
+                audits: [].into_iter().collect(),
+                trusted: [].into_iter().collect(),
+            },
+        ),
+        (
+            "https://source2.example.com/supply_chain/audits.toml".to_owned(),
+            AuditsFile {
+                criteria: [
+                    (
+                        "criteria1".to_owned(),
+                        CriteriaEntry {
+                            implies: vec![],
+                            description: Some("Criteria 1 (alt)".to_owned()),
+                            description_url: None,
+                            aggregated_from: vec![],
+                        },
+                    ),
+                    (
+                        "criteria2".to_owned(),
+                        CriteriaEntry {
+                            implies: vec!["criteria1".to_owned().into()],
+                            description: None,
+                            description_url: Some("https://criteria2".to_owned()),
+                            aggregated_from: vec![],
+                        },
+                    ),
+                    (
+                        "criteria3".to_owned(),
+                        CriteriaEntry {
+                            implies: vec!["criteria1".to_owned().into()],
+                            description: None,
+                            description_url: Some("https://criteria3.alt".to_owned()),
+                            aggregated_from: vec![],
+                        },
+                    ),
+                ]
+                .into_iter()
+                .collect(),
+                wildcard_audits: [].into_iter().collect(),
+                audits: [].into_iter().collect(),
+                trusted: [].into_iter().collect(),
+            },
+        ),
+    ];
+
+    let output = mock_aggregate(audits_files);
+    assert_snapshot!(output);
+}
diff --git a/src/tests/audit_as_crates_io.rs b/src/tests/audit_as_crates_io.rs
new file mode 100644
index 0000000..85757ba
--- /dev/null
+++ b/src/tests/audit_as_crates_io.rs
@@ -0,0 +1,336 @@
+use super::*;
+
+fn build_registry(network: &mut Network, extra_packages: &[&str]) {
+    let mut packages = vec![
+        "root-package",
+        "first-party",
+        "firstA",
+        "firstAB",
+        "firstB",
+        "firstB-nodeps",
+        "descriptive",
+    ];
+    packages.extend_from_slice(extra_packages);
+
+    let mut registry = MockRegistryBuilder::new();
+    registry.user(1, "user1", "User One");
+    for package in packages {
+        registry.package_m(
+            package,
+            CratesAPICrateMetadata {
+                description: Some(if package == "descriptive" {
+                    "descriptive".to_owned()
+                } else {
+                    "whatever".to_owned()
+                }),
+                repository: None,
+            },
+            &[reg_published_by(ver(DEFAULT_VER), Some(1), "2022-01-01")],
+        );
+    }
+    registry.serve(network);
+}
+
+fn get_audit_as_crates_io(cfg: &Config, store: &Store, add_packages_to_index: bool) -> String {
+    let mut cache = crate::storage::Cache::acquire(cfg).unwrap();
+    let mut network = crate::network::Network::new_mock();
+    build_registry(
+        &mut network,
+        if add_packages_to_index {
+            &["first", "root"]
+        } else {
+            &[]
+        },
+    );
+    let res = tokio::runtime::Handle::current().block_on(crate::check_audit_as_crates_io(
+        cfg,
+        store,
+        Some(&network),
+        &mut cache,
+    ));
+    match res {
+        Ok(()) => String::new(),
+        Err(e) => format!("{:?}", miette::Report::new(e)),
+    }
+}
+
+fn get_audit_as_crates_io_json(cfg: &Config, store: &Store) -> String {
+    let mut cache = crate::storage::Cache::acquire(cfg).unwrap();
+    let mut network = crate::network::Network::new_mock();
+    build_registry(&mut network, &[]);
+    let res = tokio::runtime::Handle::current().block_on(crate::check_audit_as_crates_io(
+        cfg,
+        store,
+        Some(&network),
+        &mut cache,
+    ));
+    match res {
+        Ok(()) => String::new(),
+        Err(e) => {
+            let handler = miette::JSONReportHandler::new();
+            let mut output = String::new();
+            handler.render_report(&mut output, &e).unwrap();
+            output
+        }
+    }
+}
+
+#[test]
+fn simple_audit_as_crates_io() {
+    let _enter = TEST_RUNTIME.enter();
+
+    let mock = MockMetadata::simple();
+    let metadata = mock.metadata();
+    let (config, audits, imports) = builtin_files_full_audited(&metadata);
+    let store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+
+    let output = get_audit_as_crates_io(&cfg, &store, false);
+    insta::assert_snapshot!("simple-audit-as-crates-io", output);
+}
+
+#[test]
+fn simple_audit_as_crates_io_all_true() {
+    let _enter = TEST_RUNTIME.enter();
+
+    let mock = MockMetadata::simple();
+    let metadata = mock.metadata();
+    let (mut config, audits, imports) = builtin_files_full_audited(&metadata);
+
+    for package in &mock.packages {
+        if package.is_first_party {
+            config
+                .policy
+                .insert(package.name.to_owned(), audit_as_policy(Some(true)));
+        }
+    }
+
+    let store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+
+    let output = get_audit_as_crates_io(&cfg, &store, false);
+    insta::assert_snapshot!("simple-audit-as-crates-io-all-true", output);
+}
+
+#[test]
+fn simple_audit_as_crates_io_all_false() {
+    let _enter = TEST_RUNTIME.enter();
+
+    let mock = MockMetadata::simple();
+    let metadata = mock.metadata();
+    let (mut config, audits, imports) = builtin_files_full_audited(&metadata);
+
+    for package in &mock.packages {
+        if package.is_first_party {
+            config
+                .policy
+                .insert(package.name.to_owned(), audit_as_policy(Some(false)));
+        }
+    }
+
+    let store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+
+    let output = get_audit_as_crates_io(&cfg, &store, false);
+    insta::assert_snapshot!("simple-audit-as-crates-io-all-false", output);
+}
+
+#[test]
+fn complex_audit_as_crates_io() {
+    let _enter = TEST_RUNTIME.enter();
+
+    let mock = MockMetadata::complex();
+    let metadata = mock.metadata();
+    let (config, audits, imports) = builtin_files_full_audited(&metadata);
+    let store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+
+    let output = get_audit_as_crates_io(&cfg, &store, false);
+    insta::assert_snapshot!("complex-audit-as-crates-io", output);
+}
+
+#[test]
+fn complex_audit_as_crates_io_all_true() {
+    let _enter = TEST_RUNTIME.enter();
+
+    let mock = MockMetadata::complex();
+    let metadata = mock.metadata();
+    let (mut config, audits, imports) = builtin_files_full_audited(&metadata);
+
+    for package in &mock.packages {
+        if package.is_first_party {
+            config
+                .policy
+                .insert(package.name.to_owned(), audit_as_policy(Some(true)));
+        }
+    }
+
+    let store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+
+    let output = get_audit_as_crates_io(&cfg, &store, false);
+    insta::assert_snapshot!("complex-audit-as-crates-io-all-true", output);
+}
+
+#[test]
+fn complex_audit_as_crates_io_all_false() {
+    let _enter = TEST_RUNTIME.enter();
+
+    let mock = MockMetadata::complex();
+    let metadata = mock.metadata();
+    let (mut config, audits, imports) = builtin_files_full_audited(&metadata);
+
+    for package in &mock.packages {
+        if package.is_first_party {
+            config
+                .policy
+                .insert(package.name.to_owned(), audit_as_policy(Some(false)));
+        }
+    }
+
+    let store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+
+    let output = get_audit_as_crates_io(&cfg, &store, false);
+    insta::assert_snapshot!("complex-audit-as-crates-io-all-false", output);
+}
+
+#[test]
+fn complex_audit_as_crates_io_max_wrong() {
+    let _enter = TEST_RUNTIME.enter();
+
+    let mock = MockMetadata::complex();
+    let metadata = mock.metadata();
+    let (mut config, audits, imports) = builtin_files_full_audited(&metadata);
+
+    config
+        .policy
+        .insert("rootA".to_owned(), audit_as_policy(Some(true)));
+    config
+        .policy
+        .insert("rootB".to_owned(), audit_as_policy(Some(true)));
+
+    let store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+
+    let output = get_audit_as_crates_io(&cfg, &store, false);
+    insta::assert_snapshot!("complex-audit-as-crates-io-max-wrong", output);
+}
+
+#[test]
+fn complex_audit_as_crates_io_max_wrong_json() {
+    let _enter = TEST_RUNTIME.enter();
+
+    let mock = MockMetadata::complex();
+    let metadata = mock.metadata();
+    let (mut config, audits, imports) = builtin_files_full_audited(&metadata);
+
+    config
+        .policy
+        .insert("rootA".to_owned(), audit_as_policy(Some(true)));
+    config
+        .policy
+        .insert("rootB".to_owned(), audit_as_policy(Some(true)));
+
+    let store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+
+    let output = get_audit_as_crates_io_json(&cfg, &store);
+    insta::assert_snapshot!("complex-audit-as-crates-io-max-wrong-json", output);
+}
+
+#[test]
+fn simple_deps_audit_as_crates_io() {
+    let _enter = TEST_RUNTIME.enter();
+
+    let mock = MockMetadata::simple_deps();
+    let metadata = mock.metadata();
+    let (config, audits, imports) = builtin_files_full_audited(&metadata);
+    let store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+
+    let output = get_audit_as_crates_io(&cfg, &store, false);
+    insta::assert_snapshot!("simple-deps-audit-as-crates-io", output);
+}
+
+#[test]
+fn dev_detection_audit_as_crates_io() {
+    let _enter = TEST_RUNTIME.enter();
+
+    let mock = MockMetadata::dev_detection();
+    let metadata = mock.metadata();
+    let (config, audits, imports) = builtin_files_full_audited(&metadata);
+    let store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+
+    let output = get_audit_as_crates_io(&cfg, &store, false);
+    insta::assert_snapshot!("dev-detection-audit-as-crates-io", output);
+}
+
+#[test]
+fn haunted_audit_as_crates_io() {
+    let _enter = TEST_RUNTIME.enter();
+
+    let mock = MockMetadata::haunted_tree();
+    let metadata = mock.metadata();
+    let (config, audits, imports) = builtin_files_full_audited(&metadata);
+    let store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+
+    let output = get_audit_as_crates_io(&cfg, &store, true);
+    insta::assert_snapshot!("haunted-audit-as-crates-io", output);
+}
+
+#[test]
+fn cycle_audit_as_crates_io() {
+    let _enter = TEST_RUNTIME.enter();
+
+    let mock = MockMetadata::cycle();
+    let metadata = mock.metadata();
+    let (config, audits, imports) = builtin_files_full_audited(&metadata);
+    let store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+
+    let output = get_audit_as_crates_io(&cfg, &store, false);
+    insta::assert_snapshot!("cycle-audit-as-crates-io", output);
+}
+
+#[test]
+fn audit_as_crates_io_non_first_party() {
+    let _enter = TEST_RUNTIME.enter();
+
+    let mock = MockMetadata::complex();
+    let metadata = mock.metadata();
+    let (mut config, audits, imports) = builtin_files_full_audited(&metadata);
+
+    for package in &mock.packages {
+        config
+            .policy
+            .insert(package.name.to_owned(), audit_as_policy(Some(false)));
+    }
+
+    let store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+
+    let output = get_audit_as_crates_io(&cfg, &store, false);
+    insta::assert_snapshot!("audit-as-crates-io-non-first-party", output);
+}
+
+#[test]
+fn audit_as_crates_io_metadata_mismatch() {
+    let _enter = TEST_RUNTIME.enter();
+
+    let mock = MockMetadata::descriptive();
+    let metadata = mock.metadata();
+    let (mut config, audits, imports) = builtin_files_full_audited(&metadata);
+
+    config
+        .policy
+        .insert("descriptive".to_owned(), audit_as_policy(Some(true)));
+
+    let store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+
+    let output = get_audit_as_crates_io(&cfg, &store, true);
+    insta::assert_snapshot!("audit-as-crates-io-metadata-mismatch", output);
+}
diff --git a/src/tests/certify.rs b/src/tests/certify.rs
new file mode 100644
index 0000000..ae05777
--- /dev/null
+++ b/src/tests/certify.rs
@@ -0,0 +1,587 @@
+use super::*;
+use std::fmt::Write;
+
+#[test]
+fn mock_simple_suggested_criteria() {
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+
+    let (mut config, mut audits, imports) = files_no_exemptions(&metadata);
+
+    config.policy.insert(
+        "first-party".to_string(),
+        dep_policy([("third-party1", ["strong-reviewed"])]),
+    );
+
+    audits.audits.insert(
+        "third-party1".to_owned(),
+        vec![
+            full_audit(ver(2), "weak-reviewed"),
+            full_audit(ver(3), "reviewed"),
+            full_audit(ver(4), "strong-reviewed"),
+            delta_audit(ver(6), ver(DEFAULT_VER), "strong-reviewed"),
+            delta_audit(ver(7), ver(DEFAULT_VER), "reviewed"),
+            delta_audit(ver(8), ver(DEFAULT_VER), "weak-reviewed"),
+        ],
+    );
+    audits.audits.insert(
+        "third-party2".to_owned(),
+        vec![
+            full_audit(ver(2), "weak-reviewed"),
+            full_audit(ver(3), "reviewed"),
+            full_audit(ver(4), "strong-reviewed"),
+            delta_audit(ver(6), ver(DEFAULT_VER), "strong-reviewed"),
+            delta_audit(ver(7), ver(DEFAULT_VER), "reviewed"),
+            delta_audit(ver(8), ver(DEFAULT_VER), "weak-reviewed"),
+        ],
+    );
+
+    let store = Store::mock(config, audits, imports);
+    let report = crate::resolver::resolve(&metadata, None, &store);
+
+    let mut output = String::new();
+    for (from, to, descr) in [
+        (None, DEFAULT_VER, "full audit"),
+        // from existing audit
+        (Some(2), DEFAULT_VER, "from weak-reviewed"),
+        (Some(3), DEFAULT_VER, "from reviewed"),
+        (Some(4), DEFAULT_VER, "from strong-reviewed"),
+        // to existing audit
+        (None, 6, "to strong-reviewed"),
+        (None, 7, "to reviewed"),
+        (None, 8, "to weak-reviewed"),
+        // bridge existing audits
+        (Some(2), 6, "from weak-reviewed to strong-reviewed"),
+        (Some(2), 7, "from weak-reviewed to reviewed"),
+        (Some(2), 8, "from weak-reviewed to weak-reviewed"),
+        (Some(3), 6, "from reviewed to strong-reviewed"),
+        (Some(3), 7, "from reviewed to reviewed"),
+        (Some(3), 8, "from reviewed to weak-reviewed"),
+        (Some(4), 6, "from strong-reviewed to strong-reviewed"),
+        (Some(4), 7, "from strong-reviewed to reviewed"),
+        (Some(4), 8, "from strong-reviewed to weak-reviewed"),
+    ] {
+        let from = from.map(ver);
+        let to = ver(to);
+        writeln!(
+            output,
+            "{} ({} -> {})",
+            descr,
+            from.as_ref().map_or("root".to_owned(), |v| v.to_string()),
+            to
+        )
+        .unwrap();
+        writeln!(
+            output,
+            "  third-party1: {:?}",
+            report.compute_suggested_criteria("third-party1", from.as_ref(), &to)
+        )
+        .unwrap();
+        writeln!(
+            output,
+            "  third-party2: {:?}",
+            report.compute_suggested_criteria("third-party2", from.as_ref(), &to)
+        )
+        .unwrap();
+    }
+
+    insta::assert_snapshot!("mock-simple-suggested-criteria", output);
+}
+
+#[test]
+fn mock_simple_certify_flow() {
+    let mock = MockMetadata::simple();
+
+    let _enter = TEST_RUNTIME.enter();
+    let metadata = mock.metadata();
+
+    let (config, audits, imports) = files_inited(&metadata);
+
+    let mut store = Store::mock(config, audits, imports);
+
+    let output = BasicTestOutput::with_callbacks(
+        |_| Ok("\n".to_owned()),
+        |_| {
+            Ok("\
+            I, testing, certify that I have audited version 10.0.0 of third-party1 in accordance with the above criteria.\n\
+            \n\
+            These are testing notes. They contain some\n\
+            newlines. Trailing whitespace        \n    \
+            and leading whitespace\n\
+            \n".to_owned())
+        },
+    );
+
+    let cfg = mock_cfg_args(
+        &metadata,
+        [
+            "cargo",
+            "vet",
+            "certify",
+            "third-party1",
+            "10.0.0",
+            "--who",
+            "testing",
+        ],
+    );
+    let sub_args = if let Some(crate::cli::Commands::Certify(sub_args)) = &cfg.cli.command {
+        sub_args
+    } else {
+        unreachable!();
+    };
+
+    crate::do_cmd_certify(
+        &output.clone().as_dyn(),
+        &cfg,
+        sub_args,
+        &mut store,
+        None,
+        None,
+    )
+    .expect("do_cmd_certify failed");
+
+    let audits = crate::serialization::to_formatted_toml(&store.audits, None).unwrap();
+
+    let result = format!("OUTPUT:\n{output}\nAUDITS:\n{audits}");
+
+    insta::assert_snapshot!("mock-simple-certify-flow", result);
+}
+
+#[test]
+fn mock_delta_certify_flow() {
+    let mock = MockMetadata::simple();
+
+    let _enter = TEST_RUNTIME.enter();
+    let metadata = mock.metadata();
+
+    let (config, audits, imports) = files_inited(&metadata);
+
+    let mut store = Store::mock(config, audits, imports);
+
+    let output = BasicTestOutput::with_callbacks(
+        |_| Ok("\n".to_owned()),
+        |_| {
+            Ok("\
+            I, testing, certify that I have audited the changes from version 10.0.0 to 10.0.1 of third-party1 in accordance with the above criteria.\n\
+            \n\
+            These are testing notes. They contain some\n\
+            newlines. Trailing whitespace        \n    \
+            and leading whitespace\n\
+            \n".to_owned())
+        },
+    );
+
+    let cfg = mock_cfg_args(
+        &metadata,
+        [
+            "cargo",
+            "vet",
+            "certify",
+            "third-party1",
+            "10.0.0",
+            "10.0.1",
+            "--who",
+            "testing",
+            "--criteria",
+            "safe-to-deploy",
+        ],
+    );
+    let sub_args = if let Some(crate::cli::Commands::Certify(sub_args)) = &cfg.cli.command {
+        sub_args
+    } else {
+        unreachable!();
+    };
+
+    crate::do_cmd_certify(
+        &output.clone().as_dyn(),
+        &cfg,
+        sub_args,
+        &mut store,
+        None,
+        None,
+    )
+    .expect("do_cmd_certify failed");
+
+    let audits = crate::serialization::to_formatted_toml(&store.audits, None).unwrap();
+
+    let result = format!("OUTPUT:\n{output}\nAUDITS:\n{audits}");
+
+    insta::assert_snapshot!("mock-delta-certify-flow", result);
+}
+
+#[test]
+fn mock_wildcard_certify_flow() {
+    let mock = MockMetadata::simple();
+
+    let _enter = TEST_RUNTIME.enter();
+    let metadata = mock.metadata();
+
+    let (config, audits, imports) = files_inited(&metadata);
+
+    let output = BasicTestOutput::with_callbacks(
+        |_| Ok("\n".to_owned()),
+        |_| {
+            Ok("\
+            I, testing, certify that any version of third-party1 published by 'testuser' between 2022-12-12 and 2024-01-01 will satisfy the above criteria.\n\
+            \n\
+            These are testing notes. They contain some\n\
+            newlines. Trailing whitespace        \n    \
+            and leading whitespace\n\
+            \n".to_owned())
+        },
+    );
+
+    let cfg = mock_cfg_args(
+        &metadata,
+        [
+            "cargo",
+            "vet",
+            "certify",
+            "third-party1",
+            "--wildcard",
+            "testuser",
+            "--who",
+            "testing",
+        ],
+    );
+    let sub_args = if let Some(crate::cli::Commands::Certify(sub_args)) = &cfg.cli.command {
+        sub_args
+    } else {
+        unreachable!();
+    };
+
+    let mut network = Network::new_mock();
+
+    MockRegistryBuilder::new()
+        .user(2, "testuser", "Test user")
+        .user(5, "otheruser", "Other User")
+        .package(
+            "third-party1",
+            &[
+                reg_published_by(ver(9), Some(5), "2022-10-12"),
+                reg_published_by(ver(DEFAULT_VER), Some(2), "2022-12-12"),
+            ],
+        )
+        .serve(&mut network);
+
+    let mut store = Store::mock_online(&cfg, config, audits, imports, &network, true)
+        .expect("store acquisition failed");
+
+    crate::do_cmd_certify(
+        &output.clone().as_dyn(),
+        &cfg,
+        sub_args,
+        &mut store,
+        Some(&network),
+        None,
+    )
+    .expect("do_cmd_certify failed");
+
+    let audits = crate::serialization::to_formatted_toml(
+        &store.audits,
+        Some(&crate::storage::user_info_map(&store.imports)),
+    )
+    .unwrap();
+
+    let result = format!("OUTPUT:\n{output}\nAUDITS:\n{audits}");
+
+    insta::assert_snapshot!(result);
+}
+
+#[test]
+fn mock_trust_flow_simple() {
+    let mock = MockMetadata::simple();
+
+    let _enter = TEST_RUNTIME.enter();
+    let metadata = mock.metadata();
+
+    let (config, audits, imports) = files_inited(&metadata);
+
+    let output = BasicTestOutput::with_callbacks(|_| Ok("\n".to_owned()), |_| unimplemented!());
+
+    let cfg = mock_cfg_args(&metadata, ["cargo", "vet", "trust", "third-party1"]);
+    let sub_args = if let Some(crate::cli::Commands::Trust(sub_args)) = &cfg.cli.command {
+        sub_args
+    } else {
+        unreachable!();
+    };
+
+    let mut network = Network::new_mock();
+    MockRegistryBuilder::new()
+        .user(2, "testuser", "Test user")
+        .package(
+            "third-party1",
+            &[
+                reg_published_by(ver(1), None, "2022-10-12"),
+                reg_published_by(ver(9), Some(2), "2022-10-12"),
+                reg_published_by(ver(DEFAULT_VER), Some(2), "2022-12-12"),
+            ],
+        )
+        .serve(&mut network);
+
+    let mut store = Store::mock_online(&cfg, config, audits, imports, &network, true)
+        .expect("store acquisition failed");
+
+    crate::do_cmd_trust(
+        &output.clone().as_dyn(),
+        &cfg,
+        sub_args,
+        &mut store,
+        Some(&network),
+    )
+    .expect("do_cmd_trust failed");
+
+    let audits = crate::serialization::to_formatted_toml(
+        &store.audits,
+        Some(&crate::storage::user_info_map(&store.imports)),
+    )
+    .unwrap();
+
+    let result = format!("OUTPUT:\n{output}\nAUDITS:\n{audits}");
+
+    insta::assert_snapshot!(result);
+}
+
+#[test]
+fn mock_trust_flow_ambiguous() {
+    let mock = MockMetadata::simple();
+
+    let _enter = TEST_RUNTIME.enter();
+    let metadata = mock.metadata();
+
+    let (config, audits, imports) = files_inited(&metadata);
+
+    let output = BasicTestOutput::with_callbacks(|_| Ok("\n".to_owned()), |_| unimplemented!());
+
+    let cfg = mock_cfg_args(&metadata, ["cargo", "vet", "trust", "third-party1"]);
+    let sub_args = if let Some(crate::cli::Commands::Trust(sub_args)) = &cfg.cli.command {
+        sub_args
+    } else {
+        unreachable!();
+    };
+
+    let mut network = Network::new_mock();
+    MockRegistryBuilder::new()
+        .user(2, "testuser", "Test user")
+        .user(5, "otheruser", "Other user")
+        .package(
+            "third-party1",
+            &[
+                reg_published_by(ver(1), None, "2022-10-12"),
+                reg_published_by(ver(9), Some(5), "2022-10-12"),
+                reg_published_by(ver(DEFAULT_VER), Some(2), "2022-12-12"),
+            ],
+        )
+        .serve(&mut network);
+
+    let mut store = Store::mock_online(&cfg, config, audits, imports, &network, true)
+        .expect("store acquisition failed");
+
+    let error = crate::do_cmd_trust(&output.as_dyn(), &cfg, sub_args, &mut store, Some(&network))
+        .expect_err("do_cmd_trust succeeded");
+
+    insta::assert_snapshot!(format!("{error:?}"));
+}
+
+#[test]
+fn mock_trust_flow_explicit() {
+    let mock = MockMetadata::simple();
+
+    let _enter = TEST_RUNTIME.enter();
+    let metadata = mock.metadata();
+
+    let (config, audits, imports) = files_inited(&metadata);
+
+    let output = BasicTestOutput::with_callbacks(|_| Ok("\n".to_owned()), |_| unimplemented!());
+
+    let cfg = mock_cfg_args(
+        &metadata,
+        ["cargo", "vet", "trust", "third-party1", "testuser"],
+    );
+    let sub_args = if let Some(crate::cli::Commands::Trust(sub_args)) = &cfg.cli.command {
+        sub_args
+    } else {
+        unreachable!();
+    };
+
+    let mut network = Network::new_mock();
+    MockRegistryBuilder::new()
+        .user(2, "testuser", "Test user")
+        .user(5, "otheruser", "Other user")
+        .package(
+            "third-party1",
+            &[
+                reg_published_by(ver(1), None, "2022-10-12"),
+                reg_published_by(ver(9), Some(5), "2022-10-12"),
+                reg_published_by(ver(DEFAULT_VER), Some(2), "2022-12-12"),
+            ],
+        )
+        .serve(&mut network);
+
+    let mut store = Store::mock_online(&cfg, config, audits, imports, &network, true)
+        .expect("store acquisition failed");
+
+    crate::do_cmd_trust(
+        &output.clone().as_dyn(),
+        &cfg,
+        sub_args,
+        &mut store,
+        Some(&network),
+    )
+    .expect("do_cmd_trust failed");
+
+    let audits = crate::serialization::to_formatted_toml(
+        &store.audits,
+        Some(&crate::storage::user_info_map(&store.imports)),
+    )
+    .unwrap();
+
+    let result = format!("OUTPUT:\n{output}\nAUDITS:\n{audits}");
+
+    insta::assert_snapshot!(result);
+}
+
+#[test]
+fn mock_trust_flow_all() {
+    let mock = MockMetadata::simple();
+
+    let _enter = TEST_RUNTIME.enter();
+    let metadata = mock.metadata();
+
+    let (config, audits, imports) = files_inited(&metadata);
+
+    let output = BasicTestOutput::with_callbacks(|_| Ok("\n".to_owned()), |_| unimplemented!());
+
+    let cfg = mock_cfg_args(&metadata, ["cargo", "vet", "trust", "--all", "testuser"]);
+    let sub_args = if let Some(crate::cli::Commands::Trust(sub_args)) = &cfg.cli.command {
+        sub_args
+    } else {
+        unreachable!();
+    };
+
+    let mut network = Network::new_mock();
+    MockRegistryBuilder::new()
+        .user(2, "testuser", "Test user")
+        .user(5, "otheruser", "Other user")
+        .package(
+            "third-party1",
+            &[
+                reg_published_by(ver(1), None, "2022-10-12"),
+                reg_published_by(ver(9), Some(5), "2022-10-12"),
+                reg_published_by(ver(DEFAULT_VER), Some(2), "2022-12-12"),
+            ],
+        )
+        .package(
+            "transitive-third-party1",
+            &[
+                reg_published_by(ver(1), None, "2022-10-12"),
+                reg_published_by(ver(9), Some(2), "2022-10-12"),
+                reg_published_by(ver(DEFAULT_VER), Some(2), "2022-12-12"),
+            ],
+        )
+        .package(
+            "third-party2",
+            &[reg_published_by(ver(DEFAULT_VER), Some(2), "2022-12-12")],
+        )
+        .serve(&mut network);
+
+    let mut store = Store::mock_online(&cfg, config, audits, imports, &network, true)
+        .expect("store acquisition failed");
+
+    crate::do_cmd_trust(
+        &output.clone().as_dyn(),
+        &cfg,
+        sub_args,
+        &mut store,
+        Some(&network),
+    )
+    .expect("do_cmd_trust failed");
+
+    let audits = crate::serialization::to_formatted_toml(
+        &store.audits,
+        Some(&crate::storage::user_info_map(&store.imports)),
+    )
+    .unwrap();
+
+    let result = format!("OUTPUT:\n{output}\nAUDITS:\n{audits}");
+
+    insta::assert_snapshot!(result);
+}
+
+#[test]
+fn mock_trust_flow_all_allow_multiple() {
+    let mock = MockMetadata::simple();
+
+    let _enter = TEST_RUNTIME.enter();
+    let metadata = mock.metadata();
+
+    let (config, audits, imports) = files_inited(&metadata);
+
+    let output = BasicTestOutput::with_callbacks(|_| Ok("\n".to_owned()), |_| unimplemented!());
+
+    let cfg = mock_cfg_args(
+        &metadata,
+        [
+            "cargo",
+            "vet",
+            "trust",
+            "--all",
+            "testuser",
+            "--allow-multiple-publishers",
+        ],
+    );
+    let sub_args = if let Some(crate::cli::Commands::Trust(sub_args)) = &cfg.cli.command {
+        sub_args
+    } else {
+        unreachable!();
+    };
+
+    let mut network = Network::new_mock();
+    MockRegistryBuilder::new()
+        .user(2, "testuser", "Test user")
+        .user(5, "otheruser", "Other user")
+        .package(
+            "third-party1",
+            &[
+                reg_published_by(ver(1), None, "2022-10-12"),
+                reg_published_by(ver(9), Some(5), "2022-10-12"),
+                reg_published_by(ver(DEFAULT_VER), Some(2), "2022-12-12"),
+            ],
+        )
+        .package(
+            "transitive-third-party1",
+            &[
+                reg_published_by(ver(1), None, "2022-10-12"),
+                reg_published_by(ver(9), Some(2), "2022-10-12"),
+                reg_published_by(ver(DEFAULT_VER), Some(2), "2022-12-12"),
+            ],
+        )
+        .package(
+            "third-party2",
+            &[reg_published_by(ver(DEFAULT_VER), Some(2), "2022-12-12")],
+        )
+        .serve(&mut network);
+
+    let mut store = Store::mock_online(&cfg, config, audits, imports, &network, true)
+        .expect("store acquisition failed");
+
+    crate::do_cmd_trust(
+        &output.clone().as_dyn(),
+        &cfg,
+        sub_args,
+        &mut store,
+        Some(&network),
+    )
+    .expect("do_cmd_trust failed");
+
+    let audits = crate::serialization::to_formatted_toml(
+        &store.audits,
+        Some(&crate::storage::user_info_map(&store.imports)),
+    )
+    .unwrap();
+
+    let result = format!("OUTPUT:\n{output}\nAUDITS:\n{audits}");
+
+    insta::assert_snapshot!(result);
+}
diff --git a/src/tests/crate_policies.rs b/src/tests/crate_policies.rs
new file mode 100644
index 0000000..88dc5a2
--- /dev/null
+++ b/src/tests/crate_policies.rs
@@ -0,0 +1,141 @@
+use crate::errors::CratePolicyErrors;
+
+use super::*;
+
+struct CratePolicyTest(pub MockMetadata);
+
+impl CratePolicyTest {
+    pub fn no_errors<F>(&self, alter_config: F)
+    where
+        F: FnOnce(&mut ConfigFile),
+    {
+        self.check_crate_policies(alter_config)
+            .expect("crate policy check should succeed");
+    }
+
+    pub fn insta_crate_policy_errors<N: AsRef<str>, F>(&self, name: N, alter_config: F)
+    where
+        F: FnOnce(&mut ConfigFile),
+    {
+        let e = self
+            .check_crate_policies(alter_config)
+            .expect_err("crate policy check should have failed");
+        insta::assert_snapshot!(name.as_ref(), format!("{:?}", miette::Report::new(e)));
+    }
+
+    fn check_crate_policies<F>(&self, alter_config: F) -> Result<(), CratePolicyErrors>
+    where
+        F: FnOnce(&mut ConfigFile),
+    {
+        let metadata = self.0.metadata();
+        let (mut config, audits, imports) = builtin_files_full_audited(&metadata);
+        alter_config(&mut config);
+        let store = Store::mock(config, audits, imports);
+        let cfg = mock_cfg(&metadata);
+
+        crate::check_crate_policies(&cfg, &store)
+    }
+}
+
+/// Checks that if a third-party crate is present, and an unversioned policy is used _without_
+/// `dependency-criteria`, no error occurs.
+#[test]
+fn simple_crate_policies_third_party_crates_dont_need_versions() {
+    let _enter = TEST_RUNTIME.enter();
+
+    CratePolicyTest(MockMetadata::overlapping()).no_errors(|config| {
+        config.policy.insert(
+            "third-party".into(),
+            PackagePolicyEntry::Unversioned(Default::default()),
+        );
+    });
+}
+
+fn dep_criteria_policy_entry() -> PolicyEntry {
+    PolicyEntry {
+        dependency_criteria: [("foo".to_owned().into(), vec!["bar".to_owned().into()])].into(),
+        ..Default::default()
+    }
+}
+
+/// Checks that if a third-party crate is present, and an unversioned policy is used with
+/// `dependency-criteria`, an error occurs indicating that versions need to be specified.
+#[test]
+fn simple_crate_policies_third_party_crates_imply_versions() {
+    let _enter = TEST_RUNTIME.enter();
+
+    CratePolicyTest(MockMetadata::overlapping()).insta_crate_policy_errors(
+        "third_party_crates_imply_versions",
+        |config| {
+            config.policy.insert(
+                "third-party".into(),
+                PackagePolicyEntry::Unversioned(dep_criteria_policy_entry()),
+            );
+        },
+    );
+}
+
+/// Checks that if a third-party crate is present and a versioned policy with `dependency-criteria`
+/// is used for one version, an error occurs indicating that versions are needed for other versions
+/// (regardless of whether they are considered first- or third-party).
+#[test]
+fn simple_crate_policies_third_party_crates_need_all_versions() {
+    let _enter = TEST_RUNTIME.enter();
+
+    let test = CratePolicyTest(MockMetadata::overlapping());
+
+    for which in [1, 2] {
+        test.insta_crate_policy_errors(
+            format!("third_party_crates_need_all_versions_{which}"),
+            |config| {
+                config.policy.insert(
+                    "third-party".into(),
+                    PackagePolicyEntry::Versioned {
+                        version: [(ver(which), dep_criteria_policy_entry())].into(),
+                    },
+                );
+            },
+        );
+    }
+}
+
+/// If crate policies are provided for versions which aren't present in the graph, an error should
+/// occur.
+#[test]
+fn simple_crate_policies_extraneous_crate_versions() {
+    let _enter = TEST_RUNTIME.enter();
+
+    CratePolicyTest(MockMetadata::overlapping()).insta_crate_policy_errors(
+        "extraneous_crate_versions",
+        |config| {
+            config.policy.insert(
+                "third-party".into(),
+                PackagePolicyEntry::Versioned {
+                    version: [
+                        (ver(1), Default::default()),
+                        (ver(2), Default::default()),
+                        (ver(3), Default::default()),
+                    ]
+                    .into(),
+                },
+            );
+        },
+    );
+}
+
+/// If crate policies are provided for crates which aren't present in the graph, an error should
+/// occur.
+#[test]
+fn simple_crate_policies_extraneous_crates() {
+    let _enter = TEST_RUNTIME.enter();
+
+    CratePolicyTest(MockMetadata::overlapping()).insta_crate_policy_errors(
+        "extraneous_crates",
+        |config| {
+            config.policy.insert(
+                "non-existent".into(),
+                PackagePolicyEntry::Unversioned(Default::default()),
+            );
+        },
+    );
+}
diff --git a/src/tests/import.rs b/src/tests/import.rs
new file mode 100644
index 0000000..1c1b96e
--- /dev/null
+++ b/src/tests/import.rs
@@ -0,0 +1,1916 @@
+use super::*;
+
+// Helper function for imports tests. Performs a vet and updates imports based
+// on it, returning a diff of the two.
+fn get_imports_file_changes(
+    metadata: &Metadata,
+    store: &Store,
+    mode: impl FnMut(PackageStr<'_>) -> crate::resolver::UpdateMode,
+) -> String {
+    let (new_imports, _new_exemptions) =
+        crate::resolver::get_store_updates(&mock_cfg(metadata), store, mode);
+
+    // Format the old and new files as TOML, and write out a diff using `similar`.
+    let old_imports = crate::serialization::to_formatted_toml(
+        &store.imports,
+        Some(&crate::storage::user_info_map(&store.imports)),
+    )
+    .unwrap()
+    .to_string();
+    let new_imports = crate::serialization::to_formatted_toml(
+        &new_imports,
+        Some(&crate::storage::user_info_map(&new_imports)),
+    )
+    .unwrap()
+    .to_string();
+
+    generate_diff(&old_imports, &new_imports)
+}
+
+fn get_imports_file_changes_prune(metadata: &Metadata, store: &Store) -> String {
+    get_imports_file_changes(metadata, store, |_| crate::resolver::UpdateMode {
+        search_mode: crate::resolver::SearchMode::PreferExemptions,
+        prune_exemptions: false,
+        prune_imports: true,
+    })
+}
+
+fn get_imports_file_changes_noprune(metadata: &Metadata, store: &Store) -> String {
+    get_imports_file_changes(metadata, store, |_| crate::resolver::UpdateMode {
+        search_mode: crate::resolver::SearchMode::PreferExemptions,
+        prune_exemptions: false,
+        prune_imports: false,
+    })
+}
+
+// Test cases:
+
+#[test]
+fn new_peer_import() {
+    // (Pass) We don't import any audits from a brand-new peer as we're fully
+    // audited, however we do add an entry to the table for it.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, audits, mut imports) = builtin_files_full_audited(&metadata);
+
+    let new_foreign_audits = AuditsFile {
+        criteria: SortedMap::new(),
+        wildcard_audits: SortedMap::new(),
+        audits: [
+            (
+                "third-party2".to_owned(),
+                vec![full_audit(ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+            ),
+            (
+                "unused-package".to_owned(),
+                vec![full_audit(ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+            ),
+        ]
+        .into_iter()
+        .collect(),
+        trusted: SortedMap::new(),
+    };
+
+    let old_other_foreign_audits = AuditsFile {
+        criteria: SortedMap::new(),
+        wildcard_audits: SortedMap::new(),
+        audits: SortedMap::new(),
+        trusted: SortedMap::new(),
+    };
+
+    let new_other_foreign_audits = AuditsFile {
+        criteria: SortedMap::new(),
+        wildcard_audits: SortedMap::new(),
+        audits: [(
+            "third-party2".to_owned(),
+            vec![full_audit(ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+        )]
+        .into_iter()
+        .collect(),
+        trusted: SortedMap::new(),
+    };
+
+    config.imports.insert(
+        FOREIGN.to_owned(),
+        crate::format::RemoteImport {
+            url: vec![FOREIGN_URL.to_owned()],
+            ..Default::default()
+        },
+    );
+
+    imports
+        .audits
+        .insert(OTHER_FOREIGN.to_owned(), old_other_foreign_audits);
+
+    config.imports.insert(
+        OTHER_FOREIGN.to_owned(),
+        crate::format::RemoteImport {
+            url: vec![OTHER_FOREIGN_URL.to_owned()],
+            ..Default::default()
+        },
+    );
+
+    let cfg = mock_cfg(&metadata);
+
+    let mut network = Network::new_mock();
+    network.mock_serve_toml(FOREIGN_URL, &new_foreign_audits);
+    network.mock_serve_toml(OTHER_FOREIGN_URL, &new_other_foreign_audits);
+
+    let store = Store::mock_online(&cfg, config, audits, imports, &network, true).unwrap();
+
+    let output = get_imports_file_changes_prune(&metadata, &store);
+    insta::assert_snapshot!(output);
+}
+
+#[test]
+fn existing_peer_skip_import() {
+    // (Pass) If we've previously imported from a peer, we don't import
+    // audits for a package unless it's useful.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, audits, mut imports) = builtin_files_full_audited(&metadata);
+
+    let old_foreign_audits = AuditsFile {
+        criteria: SortedMap::new(),
+        wildcard_audits: SortedMap::new(),
+        audits: SortedMap::new(),
+        trusted: SortedMap::new(),
+    };
+
+    let new_foreign_audits = AuditsFile {
+        criteria: SortedMap::new(),
+        wildcard_audits: [
+            (
+                "third-party2".to_owned(),
+                vec![wildcard_audit(1, SAFE_TO_DEPLOY)],
+            ),
+            (
+                "unused-package".to_owned(),
+                vec![wildcard_audit(1, SAFE_TO_DEPLOY)],
+            ),
+        ]
+        .into_iter()
+        .collect(),
+        audits: [
+            (
+                "third-party2".to_owned(),
+                vec![full_audit(ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+            ),
+            (
+                "unused-package".to_owned(),
+                vec![full_audit(ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+            ),
+        ]
+        .into_iter()
+        .collect(),
+        trusted: SortedMap::new(),
+    };
+
+    imports
+        .audits
+        .insert(FOREIGN.to_owned(), old_foreign_audits);
+
+    config.imports.insert(
+        FOREIGN.to_owned(),
+        crate::format::RemoteImport {
+            url: vec![FOREIGN_URL.to_owned()],
+            ..Default::default()
+        },
+    );
+
+    let cfg = mock_cfg(&metadata);
+
+    let mut network = Network::new_mock();
+    network.mock_serve_toml(FOREIGN_URL, &new_foreign_audits);
+    MockRegistryBuilder::new()
+        .user(1, "user1", "User One")
+        .package(
+            "third-party2",
+            &[reg_published_by(ver(DEFAULT_VER), Some(1), "2022-12-12")],
+        )
+        .serve(&mut network);
+
+    let store = Store::mock_online(&cfg, config, audits, imports, &network, true).unwrap();
+
+    let output = get_imports_file_changes_prune(&metadata, &store);
+    insta::assert_snapshot!("existing_peer_skip_import", output);
+
+    let output = get_imports_file_changes_noprune(&metadata, &store);
+    insta::assert_snapshot!("existing_peer_skip_import_noprune", output);
+}
+
+#[test]
+fn existing_peer_remove_unused() {
+    // (Pass) When pruning, we'll remove unused audits (including violations)
+    // when unlocked, even if our peer hasn't changed. These audits will be
+    // preserved when not pruning.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, mut imports) = builtin_files_full_audited(&metadata);
+
+    audits.audits.remove("third-party2");
+
+    let old_foreign_audits = AuditsFile {
+        criteria: SortedMap::new(),
+        wildcard_audits: SortedMap::new(),
+        audits: [
+            (
+                "third-party2".to_owned(),
+                vec![
+                    full_audit(ver(5), SAFE_TO_DEPLOY),
+                    full_audit(ver(10), SAFE_TO_RUN),
+                    delta_audit(ver(5), ver(DEFAULT_VER), SAFE_TO_DEPLOY),
+                    delta_audit(ver(100), ver(200), SAFE_TO_DEPLOY),
+                ],
+            ),
+            (
+                "unused-package".to_owned(),
+                vec![full_audit(ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+            ),
+            (
+                "unused-violation".to_owned(),
+                vec![violation(VersionReq::parse("1.*").unwrap(), SAFE_TO_DEPLOY)],
+            ),
+        ]
+        .into_iter()
+        .collect(),
+        trusted: SortedMap::new(),
+    };
+
+    let new_foreign_audits = old_foreign_audits.clone();
+
+    imports
+        .audits
+        .insert(FOREIGN.to_owned(), old_foreign_audits);
+
+    config.imports.insert(
+        FOREIGN.to_owned(),
+        crate::format::RemoteImport {
+            url: vec![FOREIGN_URL.to_owned()],
+            ..Default::default()
+        },
+    );
+
+    let cfg = mock_cfg(&metadata);
+
+    let mut network = Network::new_mock();
+    network.mock_serve_toml(FOREIGN_URL, &new_foreign_audits);
+
+    let store = Store::mock_online(&cfg, config, audits, imports, &network, true).unwrap();
+
+    let output = get_imports_file_changes_prune(&metadata, &store);
+    insta::assert_snapshot!("existing_peer_remove_unused", output);
+
+    let output = get_imports_file_changes_noprune(&metadata, &store);
+    insta::assert_snapshot!("existing_peer_remove_unused_noprune", output);
+}
+
+#[test]
+fn existing_peer_import_delta_audit() {
+    // (Pass) If a new delta audit from a peer is useful, we'll import only that
+    // audit.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, mut imports) = builtin_files_full_audited(&metadata);
+
+    audits.audits.remove("third-party2");
+
+    let old_foreign_audits = AuditsFile {
+        criteria: SortedMap::new(),
+        wildcard_audits: SortedMap::new(),
+        audits: [(
+            "third-party2".to_owned(),
+            vec![full_audit(ver(9), SAFE_TO_DEPLOY)],
+        )]
+        .into_iter()
+        .collect(),
+        trusted: SortedMap::new(),
+    };
+
+    let new_foreign_audits = AuditsFile {
+        criteria: SortedMap::new(),
+        wildcard_audits: SortedMap::new(),
+        audits: [
+            // A new audit for third-party2 should fix our audit, so we should
+            // import it, but not other useless audits.
+            (
+                "third-party2".to_owned(),
+                vec![
+                    full_audit(ver(9), SAFE_TO_DEPLOY),
+                    delta_audit(ver(9), ver(DEFAULT_VER), SAFE_TO_DEPLOY),
+                    delta_audit(ver(100), ver(200), SAFE_TO_DEPLOY),
+                ],
+            ),
+            // This audit won't change things for us, so we won't import it to
+            // avoid churn.
+            (
+                "third-party1".to_owned(),
+                vec![full_audit(ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+            ),
+        ]
+        .into_iter()
+        .collect(),
+        trusted: SortedMap::new(),
+    };
+
+    let old_other_foreign_audits = AuditsFile {
+        criteria: SortedMap::new(),
+        wildcard_audits: SortedMap::new(),
+        audits: SortedMap::new(),
+        trusted: SortedMap::new(),
+    };
+
+    let new_other_foreign_audits = AuditsFile {
+        criteria: SortedMap::new(),
+        wildcard_audits: SortedMap::new(),
+        // We won't import unrelated audits from other sources.
+        audits: [(
+            "third-party2".to_owned(),
+            vec![delta_audit(ver(200), ver(300), SAFE_TO_DEPLOY)],
+        )]
+        .into_iter()
+        .collect(),
+        trusted: SortedMap::new(),
+    };
+
+    imports
+        .audits
+        .insert(FOREIGN.to_owned(), old_foreign_audits);
+
+    config.imports.insert(
+        FOREIGN.to_owned(),
+        crate::format::RemoteImport {
+            url: vec![FOREIGN_URL.to_owned()],
+            ..Default::default()
+        },
+    );
+
+    imports
+        .audits
+        .insert(OTHER_FOREIGN.to_owned(), old_other_foreign_audits);
+
+    config.imports.insert(
+        OTHER_FOREIGN.to_owned(),
+        crate::format::RemoteImport {
+            url: vec![OTHER_FOREIGN_URL.to_owned()],
+            ..Default::default()
+        },
+    );
+
+    let cfg = mock_cfg(&metadata);
+
+    let mut network = Network::new_mock();
+    network.mock_serve_toml(FOREIGN_URL, &new_foreign_audits);
+    network.mock_serve_toml(OTHER_FOREIGN_URL, &new_other_foreign_audits);
+
+    let store = Store::mock_online(&cfg, config, audits, imports, &network, true).unwrap();
+
+    let output = get_imports_file_changes_prune(&metadata, &store);
+    insta::assert_snapshot!(output);
+}
+
+#[test]
+fn existing_peer_import_custom_criteria() {
+    // (Pass) We'll immediately import criteria changes for mapped criteria when
+    // unlocked, even if our peer hasn't changed or we aren't mapping them
+    // locally. Only the criteria will be updated.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, mut imports) = builtin_files_full_audited(&metadata);
+
+    audits.audits.remove("third-party2");
+
+    let old_foreign_audits = AuditsFile {
+        criteria: SortedMap::new(),
+        wildcard_audits: SortedMap::new(),
+        audits: [(
+            "third-party2".to_owned(),
+            vec![full_audit(ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+        )]
+        .into_iter()
+        .collect(),
+        trusted: SortedMap::new(),
+    };
+
+    let new_foreign_audits = AuditsFile {
+        criteria: [
+            ("fuzzed".to_string(), criteria("fuzzed")),
+            (
+                "super-fuzzed".to_string(),
+                criteria_implies("super-fuzzed", ["fuzzed"]),
+            ),
+        ]
+        .into_iter()
+        .collect(),
+        wildcard_audits: SortedMap::new(),
+        audits: [(
+            "third-party2".to_owned(),
+            vec![
+                full_audit(ver(DEFAULT_VER), SAFE_TO_DEPLOY),
+                delta_audit(ver(DEFAULT_VER), ver(11), SAFE_TO_DEPLOY),
+            ],
+        )]
+        .into_iter()
+        .collect(),
+        trusted: SortedMap::new(),
+    };
+
+    imports
+        .audits
+        .insert(FOREIGN.to_owned(), old_foreign_audits);
+
+    config.imports.insert(
+        FOREIGN.to_owned(),
+        crate::format::RemoteImport {
+            url: vec![FOREIGN_URL.to_owned()],
+            criteria_map: [(
+                "fuzzed".to_string().into(),
+                vec![SAFE_TO_RUN.to_string().into()],
+            )]
+            .into_iter()
+            .collect(),
+            ..Default::default()
+        },
+    );
+
+    let cfg = mock_cfg(&metadata);
+
+    let mut network = Network::new_mock();
+    network.mock_serve_toml(FOREIGN_URL, &new_foreign_audits);
+
+    let store = Store::mock_online(&cfg, config, audits, imports, &network, true).unwrap();
+
+    let output = get_imports_file_changes_prune(&metadata, &store);
+
+    insta::assert_snapshot!(output);
+}
+
+#[test]
+fn new_audit_for_unused_criteria_basic() {
+    // (Pass) If a peer adds an audit for an unused criteria, we shouldn't
+    // vendor in the changes unnecessarily, even if the criteria is mapped.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, mut imports) = builtin_files_full_audited(&metadata);
+
+    audits.audits.remove("third-party2");
+
+    let old_foreign_audits = AuditsFile {
+        criteria: [("fuzzed".to_string(), criteria("fuzzed"))]
+            .into_iter()
+            .collect(),
+        wildcard_audits: SortedMap::new(),
+        audits: [(
+            "third-party2".to_owned(),
+            vec![full_audit(ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+        )]
+        .into_iter()
+        .collect(),
+        trusted: SortedMap::new(),
+    };
+
+    let mut new_foreign_audits = old_foreign_audits.clone();
+    new_foreign_audits
+        .audits
+        .get_mut("third-party2")
+        .unwrap()
+        .push(full_audit(ver(DEFAULT_VER), "fuzzed"));
+
+    imports
+        .audits
+        .insert(FOREIGN.to_owned(), old_foreign_audits);
+
+    config.imports.insert(
+        FOREIGN.to_owned(),
+        crate::format::RemoteImport {
+            url: vec![FOREIGN_URL.to_owned()],
+            criteria_map: [(
+                "fuzzed".to_string().into(),
+                vec![SAFE_TO_RUN.to_string().into()],
+            )]
+            .into_iter()
+            .collect(),
+            ..Default::default()
+        },
+    );
+
+    let cfg = mock_cfg(&metadata);
+
+    let mut network = Network::new_mock();
+    network.mock_serve_toml(FOREIGN_URL, &new_foreign_audits);
+
+    let store = Store::mock_online(&cfg, config, audits, imports, &network, true).unwrap();
+
+    let output = get_imports_file_changes_prune(&metadata, &store);
+
+    insta::assert_snapshot!(output);
+}
+
+#[test]
+fn new_audit_for_unused_criteria_transitive() {
+    // (Pass) If a peer adds an audit for an unused criteria of a transitive
+    // dependency, we shouldn't vendor in the changes unnecessarily.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, mut imports) = builtin_files_full_audited(&metadata);
+
+    audits.audits.remove("third-party1");
+
+    let old_foreign_audits = AuditsFile {
+        criteria: [("fuzzed".to_string(), criteria("fuzzed"))]
+            .into_iter()
+            .collect(),
+        wildcard_audits: SortedMap::new(),
+        audits: [(
+            "third-party1".to_owned(),
+            vec![full_audit(ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+        )]
+        .into_iter()
+        .collect(),
+        trusted: SortedMap::new(),
+    };
+
+    let mut new_foreign_audits = old_foreign_audits.clone();
+    new_foreign_audits
+        .audits
+        .get_mut("third-party1")
+        .unwrap()
+        .push(full_audit(ver(DEFAULT_VER), "fuzzed"));
+    new_foreign_audits.audits.insert(
+        "transitive-third-party1".to_owned(),
+        vec![full_audit(ver(DEFAULT_VER), "fuzzed")],
+    );
+
+    imports
+        .audits
+        .insert(FOREIGN.to_owned(), old_foreign_audits);
+
+    config.imports.insert(
+        FOREIGN.to_owned(),
+        crate::format::RemoteImport {
+            url: vec![FOREIGN_URL.to_owned()],
+            criteria_map: [(
+                "fuzzed".to_string().into(),
+                vec![SAFE_TO_RUN.to_string().into()],
+            )]
+            .into_iter()
+            .collect(),
+            ..Default::default()
+        },
+    );
+
+    let cfg = mock_cfg(&metadata);
+
+    let mut network = Network::new_mock();
+    network.mock_serve_toml(FOREIGN_URL, &new_foreign_audits);
+
+    let store = Store::mock_online(&cfg, config, audits, imports, &network, true).unwrap();
+
+    let output = get_imports_file_changes_prune(&metadata, &store);
+
+    insta::assert_snapshot!(output);
+}
+
+#[test]
+fn existing_peer_revoked_audit() {
+    // (Pass) If a previously-imported audit is removed, we should also remove
+    // it locally, even if doing so would cause vet to fail.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, mut imports) = builtin_files_full_audited(&metadata);
+
+    audits.audits.remove("third-party2");
+
+    let old_foreign_audits = AuditsFile {
+        criteria: SortedMap::new(),
+        wildcard_audits: SortedMap::new(),
+        audits: [(
+            "third-party2".to_owned(),
+            vec![full_audit(ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+        )]
+        .into_iter()
+        .collect(),
+        trusted: SortedMap::new(),
+    };
+
+    let new_foreign_audits = AuditsFile {
+        criteria: SortedMap::new(),
+        wildcard_audits: SortedMap::new(),
+        audits: SortedMap::new(),
+        trusted: SortedMap::new(),
+    };
+
+    imports
+        .audits
+        .insert(FOREIGN.to_owned(), old_foreign_audits);
+
+    config.imports.insert(
+        FOREIGN.to_owned(),
+        crate::format::RemoteImport {
+            url: vec![FOREIGN_URL.to_owned()],
+            ..Default::default()
+        },
+    );
+
+    let cfg = mock_cfg(&metadata);
+
+    let mut network = Network::new_mock();
+    network.mock_serve_toml(FOREIGN_URL, &new_foreign_audits);
+
+    let store = Store::mock_online(&cfg, config, audits, imports, &network, true).unwrap();
+
+    let output = get_imports_file_changes_prune(&metadata, &store);
+    insta::assert_snapshot!("existing_peer_revoked_audit", output);
+
+    let output = get_imports_file_changes_noprune(&metadata, &store);
+    insta::assert_snapshot!("existing_peer_revoked_audit_noprune", output);
+}
+
+#[test]
+fn existing_peer_add_violation() {
+    // (Pass) If a peer adds a violation for any version of a crate we use, we
+    // should immediately import it. We won't immediately import other audits
+    // added for that crate, however.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, mut imports) = builtin_files_full_audited(&metadata);
+
+    audits.audits.remove("third-party2");
+
+    let old_foreign_audits = AuditsFile {
+        criteria: SortedMap::new(),
+        wildcard_audits: SortedMap::new(),
+        audits: [(
+            "third-party2".to_owned(),
+            vec![full_audit(ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+        )]
+        .into_iter()
+        .collect(),
+        trusted: SortedMap::new(),
+    };
+
+    let new_foreign_audits = AuditsFile {
+        criteria: SortedMap::new(),
+        wildcard_audits: SortedMap::new(),
+        audits: [(
+            "third-party2".to_owned(),
+            vec![
+                full_audit(ver(DEFAULT_VER), SAFE_TO_DEPLOY),
+                delta_audit(ver(DEFAULT_VER), ver(20), SAFE_TO_DEPLOY),
+                violation(VersionReq::parse("99.*").unwrap(), SAFE_TO_DEPLOY),
+            ],
+        )]
+        .into_iter()
+        .collect(),
+        trusted: SortedMap::new(),
+    };
+
+    imports
+        .audits
+        .insert(FOREIGN.to_owned(), old_foreign_audits);
+
+    config.imports.insert(
+        FOREIGN.to_owned(),
+        crate::format::RemoteImport {
+            url: vec![FOREIGN_URL.to_owned()],
+            ..Default::default()
+        },
+    );
+
+    let cfg = mock_cfg(&metadata);
+
+    let mut network = Network::new_mock();
+    network.mock_serve_toml(FOREIGN_URL, &new_foreign_audits);
+
+    let store = Store::mock_online(&cfg, config, audits, imports, &network, true).unwrap();
+
+    let output = get_imports_file_changes_prune(&metadata, &store);
+    insta::assert_snapshot!(output);
+}
+
+#[test]
+fn new_audit_needed_violation() {
+    // (Pass) If a peer provides a violation for a crate we use (even if there are no related
+    // audits), we should import it.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = builtin_files_full_audited(&metadata);
+
+    audits.audits.remove("third-party2");
+
+    let new_foreign_audits = AuditsFile {
+        criteria: SortedMap::new(),
+        wildcard_audits: SortedMap::new(),
+        audits: [(
+            "third-party2".to_owned(),
+            vec![violation(
+                VersionReq::parse("10.*").unwrap(),
+                SAFE_TO_DEPLOY,
+            )],
+        )]
+        .into_iter()
+        .collect(),
+        trusted: SortedMap::new(),
+    };
+
+    config.imports.insert(
+        FOREIGN.to_owned(),
+        crate::format::RemoteImport {
+            url: vec![FOREIGN_URL.to_owned()],
+            ..Default::default()
+        },
+    );
+
+    let cfg = mock_cfg(&metadata);
+
+    let mut network = Network::new_mock();
+    network.mock_serve_toml(FOREIGN_URL, &new_foreign_audits);
+
+    let store = Store::mock_online(&cfg, config, audits, imports, &network, true).unwrap();
+
+    let output = get_imports_file_changes_prune(&metadata, &store);
+    insta::assert_snapshot!(output);
+}
+
+#[test]
+fn new_audit_unneeded_violation() {
+    // (Pass) If a peer provides a violation for a crate we don't use, we should not import it.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = builtin_files_full_audited(&metadata);
+
+    audits.audits.remove("third-party2");
+
+    let new_foreign_audits = AuditsFile {
+        criteria: SortedMap::new(),
+        wildcard_audits: SortedMap::new(),
+        audits: [
+            (
+                "third-party2".to_owned(),
+                vec![full_audit(ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+            ),
+            (
+                "third-party3".to_owned(),
+                vec![violation(
+                    VersionReq::parse("10.*").unwrap(),
+                    SAFE_TO_DEPLOY,
+                )],
+            ),
+        ]
+        .into_iter()
+        .collect(),
+        trusted: SortedMap::new(),
+    };
+
+    config.imports.insert(
+        FOREIGN.to_owned(),
+        crate::format::RemoteImport {
+            url: vec![FOREIGN_URL.to_owned()],
+            ..Default::default()
+        },
+    );
+
+    let cfg = mock_cfg(&metadata);
+
+    let mut network = Network::new_mock();
+    network.mock_serve_toml(FOREIGN_URL, &new_foreign_audits);
+
+    let store = Store::mock_online(&cfg, config, audits, imports, &network, true).unwrap();
+
+    let output = get_imports_file_changes_prune(&metadata, &store);
+    insta::assert_snapshot!(output);
+}
+
+#[test]
+fn peer_audits_exemption_no_minimize() {
+    // (Pass) We don't import audits for a package which would replace an
+    // exemption unless we're regenerating exemptions.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, audits, mut imports) = builtin_files_inited(&metadata);
+
+    let old_foreign_audits = AuditsFile {
+        criteria: SortedMap::new(),
+        wildcard_audits: SortedMap::new(),
+        audits: SortedMap::new(),
+        trusted: SortedMap::new(),
+    };
+
+    let new_foreign_audits = AuditsFile {
+        criteria: SortedMap::new(),
+        wildcard_audits: SortedMap::new(),
+        audits: [(
+            "third-party2".to_owned(),
+            vec![full_audit(ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+        )]
+        .into_iter()
+        .collect(),
+        trusted: SortedMap::new(),
+    };
+
+    imports
+        .audits
+        .insert(FOREIGN.to_owned(), old_foreign_audits);
+
+    config.imports.insert(
+        FOREIGN.to_owned(),
+        crate::format::RemoteImport {
+            url: vec![FOREIGN_URL.to_owned()],
+            ..Default::default()
+        },
+    );
+
+    let cfg = mock_cfg(&metadata);
+
+    let mut network = Network::new_mock();
+    network.mock_serve_toml(FOREIGN_URL, &new_foreign_audits);
+
+    let store = Store::mock_online(&cfg, config, audits, imports, &network, true).unwrap();
+
+    let output = get_imports_file_changes_prune(&metadata, &store);
+    insta::assert_snapshot!(output);
+}
+
+#[test]
+fn peer_audits_exemption_minimize() {
+    // (Pass) We do import audits for a package which would replace an exemption
+    // when we're regenerating exemptions.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, mut imports) = builtin_files_inited(&metadata);
+
+    audits.audits.insert(
+        "transitive-third-party1".to_owned(),
+        vec![full_audit(ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+    );
+
+    let old_foreign_audits = AuditsFile {
+        criteria: SortedMap::new(),
+        wildcard_audits: SortedMap::new(),
+        audits: [
+            (
+                "unused-crate".to_owned(),
+                vec![full_audit(ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+            ),
+            (
+                "third-party1".to_owned(),
+                vec![delta_audit(ver(DEFAULT_VER), ver(100), SAFE_TO_DEPLOY)],
+            ),
+        ]
+        .into_iter()
+        .collect(),
+        trusted: SortedMap::new(),
+    };
+
+    let new_foreign_audits = AuditsFile {
+        criteria: SortedMap::new(),
+        wildcard_audits: SortedMap::new(),
+        audits: [
+            (
+                "unused-crate".to_owned(),
+                vec![full_audit(ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+            ),
+            (
+                "third-party1".to_owned(),
+                vec![
+                    full_audit(ver(DEFAULT_VER), SAFE_TO_DEPLOY),
+                    delta_audit(ver(DEFAULT_VER), ver(100), SAFE_TO_DEPLOY),
+                ],
+            ),
+            (
+                "third-party2".to_owned(),
+                vec![full_audit(ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+            ),
+        ]
+        .into_iter()
+        .collect(),
+        trusted: SortedMap::new(),
+    };
+
+    imports
+        .audits
+        .insert(FOREIGN.to_owned(), old_foreign_audits);
+
+    config.imports.insert(
+        FOREIGN.to_owned(),
+        crate::format::RemoteImport {
+            url: vec![FOREIGN_URL.to_owned()],
+            ..Default::default()
+        },
+    );
+
+    let cfg = mock_cfg(&metadata);
+
+    let mut network = Network::new_mock();
+    network.mock_serve_toml(FOREIGN_URL, &new_foreign_audits);
+
+    #[allow(clippy::type_complexity)]
+    let configs: [(&str, fn(&str) -> crate::resolver::UpdateMode); 3] = [
+        ("prune", |_| crate::resolver::UpdateMode {
+            search_mode: crate::resolver::SearchMode::PreferFreshImports,
+            prune_exemptions: true,
+            prune_imports: true,
+        }),
+        ("certify", |name| {
+            if name == "third-party2" {
+                crate::resolver::UpdateMode {
+                    search_mode: crate::resolver::SearchMode::PreferFreshImports,
+                    prune_exemptions: true,
+                    prune_imports: false,
+                }
+            } else {
+                crate::resolver::UpdateMode {
+                    search_mode: crate::resolver::SearchMode::PreferExemptions,
+                    prune_exemptions: false,
+                    prune_imports: false,
+                }
+            }
+        }),
+        ("vet", |_| crate::resolver::UpdateMode {
+            search_mode: crate::resolver::SearchMode::PreferExemptions,
+            prune_exemptions: false,
+            prune_imports: false,
+        }),
+    ];
+
+    for (name, mode) in configs {
+        let mut store = Store::mock_online(
+            &cfg,
+            config.clone(),
+            audits.clone(),
+            imports.clone(),
+            &network,
+            true,
+        )
+        .unwrap();
+
+        // Capture the old imports before minimizing exemptions
+        let old = store.mock_commit();
+
+        crate::resolver::update_store(&mock_cfg(&metadata), &mut store, mode);
+
+        // Capture after minimizing exemptions, and generate a diff.
+        let new = store.mock_commit();
+
+        let output = diff_store_commits(&old, &new);
+        insta::assert_snapshot!(format!("peer_audits_exemption_minimize_{name}"), output);
+    }
+}
+
+#[test]
+fn peer_audits_import_exclusion() {
+    // (Pass) Exclusions in the config should make a crate's audits and
+    // violations appear to be revoked upstream, but audits for other crates
+    // shouldn't be impacted.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, mut imports) = builtin_files_full_audited(&metadata);
+
+    audits.audits.remove("transitive-third-party1");
+
+    let old_foreign_audits = AuditsFile {
+        criteria: SortedMap::new(),
+        wildcard_audits: SortedMap::new(),
+        audits: [
+            (
+                "third-party2".to_owned(),
+                vec![full_audit(ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+            ),
+            (
+                "third-party1".to_owned(),
+                vec![violation("*".parse().unwrap(), SAFE_TO_DEPLOY)],
+            ),
+            (
+                "transitive-third-party1".to_owned(),
+                vec![full_audit(ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+            ),
+        ]
+        .into_iter()
+        .collect(),
+        trusted: SortedMap::new(),
+    };
+
+    let new_foreign_audits = old_foreign_audits.clone();
+
+    imports
+        .audits
+        .insert(FOREIGN.to_owned(), old_foreign_audits);
+
+    config.imports.insert(
+        FOREIGN.to_owned(),
+        crate::format::RemoteImport {
+            url: vec![FOREIGN_URL.to_owned()],
+            exclude: vec!["third-party1".to_owned(), "third-party2".to_owned()],
+            ..Default::default()
+        },
+    );
+
+    let cfg = mock_cfg(&metadata);
+
+    let mut network = Network::new_mock();
+    network.mock_serve_toml(FOREIGN_URL, &new_foreign_audits);
+
+    let store = Store::mock_online(&cfg, config, audits, imports, &network, true).unwrap();
+
+    let imported = store
+        .imported_audits()
+        .get(FOREIGN)
+        .expect("The remote should be present in `imported_audits`");
+
+    assert!(
+        !imported.audits.contains_key("third-party1"),
+        "The `third-party1` crate should be completely missing from `imported_audits`"
+    );
+    assert!(
+        !imported.audits.contains_key("third-party2"),
+        "The `third-party2` crate should be completely missing from `imported_audits`"
+    );
+    assert!(
+        imported.audits.contains_key("transitive-third-party1"),
+        "The `transitive-third-party1` crate should still be present in `imported_audits`"
+    );
+
+    let output = get_imports_file_changes_noprune(&metadata, &store);
+    insta::assert_snapshot!(output);
+}
+
+#[test]
+fn existing_peer_updated_description() {
+    // (Pass) If we've previously imported from a peer, and a criteria
+    // description changed, we get an error with details.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, audits, mut imports) = builtin_files_full_audited(&metadata);
+
+    let old_foreign_audits = AuditsFile {
+        criteria: [(
+            "example".to_string(),
+            criteria(
+                "Example criteria description\n\
+                First line\n\
+                Second line\n\
+                Third line\n",
+            ),
+        )]
+        .into_iter()
+        .collect(),
+        wildcard_audits: SortedMap::new(),
+        audits: SortedMap::new(),
+        trusted: SortedMap::new(),
+    };
+
+    let new_foreign_audits = AuditsFile {
+        criteria: [(
+            "example".to_string(),
+            criteria(
+                "Example criteria description\n\
+                First new line\n\
+                Third line\n\
+                Fourth line\n",
+            ),
+        )]
+        .into_iter()
+        .collect(),
+        wildcard_audits: SortedMap::new(),
+        audits: SortedMap::new(),
+        trusted: SortedMap::new(),
+    };
+
+    imports
+        .audits
+        .insert(FOREIGN.to_owned(), old_foreign_audits);
+
+    config.imports.insert(
+        FOREIGN.to_owned(),
+        crate::format::RemoteImport {
+            url: vec![FOREIGN_URL.to_owned()],
+            criteria_map: [(
+                "example".to_string().into(),
+                vec![SAFE_TO_DEPLOY.to_string().into()],
+            )]
+            .into_iter()
+            .collect(),
+            ..Default::default()
+        },
+    );
+
+    let cfg = mock_cfg(&metadata);
+
+    let mut network = Network::new_mock();
+    network.mock_serve_toml(FOREIGN_URL, &new_foreign_audits);
+
+    let error = match Store::mock_online(&cfg, config, audits, imports, &network, false) {
+        Ok(_) => panic!("expected store creation to fail due to updated criteria"),
+        Err(err) => miette::Report::from(err),
+    };
+    insta::assert_snapshot!("existing_peer_updated_description", format!("{error:?}"));
+}
+
+#[test]
+fn fresh_import_preferred_audits() {
+    // (Pass) We prefer shorter audit chains over longer ones.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = builtin_files_full_audited(&metadata);
+
+    audits.audits.remove("third-party2");
+
+    let new_foreign_audits = AuditsFile {
+        criteria: SortedMap::new(),
+        audits: [(
+            "third-party2".to_owned(),
+            vec![
+                full_audit(ver(5), SAFE_TO_DEPLOY),
+                delta_audit(ver(5), ver(6), SAFE_TO_DEPLOY),
+                delta_audit(ver(6), ver(DEFAULT_VER), SAFE_TO_DEPLOY),
+            ],
+        )]
+        .into_iter()
+        .collect(),
+        wildcard_audits: SortedMap::new(),
+        trusted: SortedMap::new(),
+    };
+
+    let new_other_foreign_audits = AuditsFile {
+        criteria: SortedMap::new(),
+        audits: [(
+            "third-party2".to_owned(),
+            vec![delta_audit(ver(5), ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+        )]
+        .into_iter()
+        .collect(),
+        wildcard_audits: SortedMap::new(),
+        trusted: SortedMap::new(),
+    };
+
+    config.imports.insert(
+        FOREIGN.to_owned(),
+        crate::format::RemoteImport {
+            url: vec![FOREIGN_URL.to_owned()],
+            ..Default::default()
+        },
+    );
+    config.imports.insert(
+        OTHER_FOREIGN.to_owned(),
+        crate::format::RemoteImport {
+            url: vec![OTHER_FOREIGN_URL.to_owned()],
+            ..Default::default()
+        },
+    );
+
+    let cfg = mock_cfg(&metadata);
+
+    let mut network = Network::new_mock();
+    network.mock_serve_toml(FOREIGN_URL, &new_foreign_audits);
+    network.mock_serve_toml(OTHER_FOREIGN_URL, &new_other_foreign_audits);
+
+    let store = Store::mock_online(&cfg, config, audits, imports, &network, true).unwrap();
+
+    let output = get_imports_file_changes_prune(&metadata, &store);
+    insta::assert_snapshot!(output);
+}
+
+#[test]
+fn old_import_preferred_audits() {
+    // (Pass) we don't switch to a shorter audit path if we already have a longer one imported.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, mut imports) = builtin_files_full_audited(&metadata);
+
+    audits.audits.remove("third-party2");
+
+    let old_foreign_audits = AuditsFile {
+        criteria: SortedMap::new(),
+        audits: [(
+            "third-party2".to_owned(),
+            vec![
+                full_audit(ver(5), SAFE_TO_DEPLOY),
+                delta_audit(ver(5), ver(6), SAFE_TO_DEPLOY),
+                delta_audit(ver(6), ver(DEFAULT_VER), SAFE_TO_DEPLOY),
+            ],
+        )]
+        .into_iter()
+        .collect(),
+        wildcard_audits: SortedMap::new(),
+        trusted: SortedMap::new(),
+    };
+
+    let new_foreign_audits = old_foreign_audits.clone();
+
+    let new_other_foreign_audits = AuditsFile {
+        criteria: SortedMap::new(),
+        audits: [(
+            "third-party2".to_owned(),
+            vec![delta_audit(ver(5), ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+        )]
+        .into_iter()
+        .collect(),
+        wildcard_audits: SortedMap::new(),
+        trusted: SortedMap::new(),
+    };
+
+    imports
+        .audits
+        .insert(FOREIGN.to_owned(), old_foreign_audits);
+
+    config.imports.insert(
+        FOREIGN.to_owned(),
+        crate::format::RemoteImport {
+            url: vec![FOREIGN_URL.to_owned()],
+            ..Default::default()
+        },
+    );
+    config.imports.insert(
+        OTHER_FOREIGN.to_owned(),
+        crate::format::RemoteImport {
+            url: vec![OTHER_FOREIGN_URL.to_owned()],
+            ..Default::default()
+        },
+    );
+
+    let cfg = mock_cfg(&metadata);
+
+    let mut network = Network::new_mock();
+    network.mock_serve_toml(FOREIGN_URL, &new_foreign_audits);
+    network.mock_serve_toml(OTHER_FOREIGN_URL, &new_other_foreign_audits);
+
+    let store = Store::mock_online(&cfg, config, audits, imports, &network, true).unwrap();
+
+    let output = get_imports_file_changes_prune(&metadata, &store);
+    insta::assert_snapshot!(output);
+}
+
+#[test]
+fn equal_length_preferred_audits() {
+    // (Pass) Between two audit paths of the same length, we prefer one
+    // arbitrarily (the output of this test documents our preference, which is
+    // based on how quickly the edges approach the start node).
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = builtin_files_full_audited(&metadata);
+
+    audits.audits.remove("third-party2");
+
+    let new_foreign_audits = AuditsFile {
+        criteria: SortedMap::new(),
+        audits: [(
+            "third-party2".to_owned(),
+            vec![
+                full_audit(ver(2), SAFE_TO_DEPLOY),
+                full_audit(ver(8), SAFE_TO_DEPLOY),
+                delta_audit(ver(2), ver(DEFAULT_VER), SAFE_TO_DEPLOY),
+                delta_audit(ver(8), ver(DEFAULT_VER), SAFE_TO_DEPLOY),
+            ],
+        )]
+        .into_iter()
+        .collect(),
+        wildcard_audits: SortedMap::new(),
+        trusted: SortedMap::new(),
+    };
+
+    config.imports.insert(
+        FOREIGN.to_owned(),
+        crate::format::RemoteImport {
+            url: vec![FOREIGN_URL.to_owned()],
+            ..Default::default()
+        },
+    );
+
+    let cfg = mock_cfg(&metadata);
+
+    let mut network = Network::new_mock();
+    network.mock_serve_toml(FOREIGN_URL, &new_foreign_audits);
+
+    let store = Store::mock_online(&cfg, config, audits, imports, &network, true).unwrap();
+
+    let output = get_imports_file_changes_prune(&metadata, &store);
+    insta::assert_snapshot!(output);
+}
+
+#[test]
+fn import_multiple_versions() {
+    // (Pass) If multiple versions of a crate in the graph need to import
+    // audits, we need to import the required audits for all versions, not just
+    // one of them.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::complex();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = builtin_files_full_audited(&metadata);
+
+    audits.audits.remove("third-core");
+
+    let new_foreign_audits = AuditsFile {
+        criteria: SortedMap::new(),
+        audits: [(
+            "third-core".to_owned(),
+            vec![
+                full_audit(ver(5), "safe-to-deploy"),
+                full_audit(ver(DEFAULT_VER), "safe-to-deploy"),
+            ],
+        )]
+        .into_iter()
+        .collect(),
+        wildcard_audits: SortedMap::new(),
+        trusted: SortedMap::new(),
+    };
+
+    config.imports.insert(
+        FOREIGN.to_owned(),
+        crate::format::RemoteImport {
+            url: vec![FOREIGN_URL.to_owned()],
+            ..Default::default()
+        },
+    );
+
+    let cfg = mock_cfg(&metadata);
+
+    let mut network = Network::new_mock();
+    network.mock_serve_toml(FOREIGN_URL, &new_foreign_audits);
+
+    let store = Store::mock_online(&cfg, config, audits, imports, &network, true).unwrap();
+
+    let output = get_imports_file_changes_prune(&metadata, &store);
+    insta::assert_snapshot!(output);
+}
+
+#[test]
+fn foreign_audit_file_to_local() {
+    let _enter = TEST_RUNTIME.enter();
+
+    let foreign_audit_file = crate::format::ForeignAuditsFile {
+        criteria: [
+            (
+                "example".to_string(),
+                toml::toml! {
+                    description = "Example criteria description"
+                },
+            ),
+            (
+                "example2".to_string(),
+                toml::toml! {
+                    implies = "unknown-criteria"
+                    description = "example2"
+                },
+            ),
+            (
+                "example3".to_string(),
+                toml::toml! {
+                    description = "example2"
+                    implies = ["safe-to-deploy", "will-not-parse"]
+                },
+            ),
+            (
+                "will-not-parse".to_string(),
+                toml::toml! {
+                    implies = [{ not = "a string" }]
+                    description = "will be ignored"
+                },
+            ),
+            (
+                "will-not-parse2".to_string(),
+                toml::toml! {
+                    description = "example2"
+                    implies = "safe-to-deploy"
+                    unknown = "invalid unknown field"
+                },
+            ),
+        ]
+        .into_iter()
+        .collect(),
+        wildcard_audits: [
+            (
+                "crate-a".to_string(),
+                vec![toml::toml! {
+                    criteria = "safe-to-deploy"
+                    user-id = 1
+                    start = "2022-12-25"
+                    end = "2023-12-25"
+                    notes = "should parse correctly"
+                }],
+            ),
+            (
+                "crate-b".to_string(),
+                vec![toml::toml! {
+                    criteria = "example"
+                    user-id = "invalid"
+                    start = "2022-12-25"
+                    end = "2023-12-25"
+                    notes = "will be removed, along with the entire crate"
+                }],
+            ),
+            (
+                "crate-c".to_string(),
+                vec![
+                    toml::toml! {
+                        criteria = "example2"
+                        user-id = 1
+                        start = "2022-12-25"
+                        end = "2023-12-25"
+                        notes = "will not be removed"
+                    },
+                    toml::toml! {
+                        criteria = ["example2", "example3"]
+                        user-id = 1
+                        start = "2022-12-25"
+                        end = "2023-12-25"
+                        notes = "will not be removed"
+                    },
+                ],
+            ),
+        ]
+        .into_iter()
+        .collect(),
+        audits: [
+            (
+                "crate-a".to_string(),
+                vec![
+                    toml::toml! {
+                        criteria = "safe-to-deploy"
+                        version = "10.0.0"
+                        notes = "should parse correctly"
+                    },
+                    toml::toml! {
+                        criteria = "unknown-criteria"
+                        version = "10.0.0"
+                        notes = "will be removed"
+                    },
+                    toml::toml! {
+                        criteria = "example"
+                        version = "invalid"
+                        notes = "will be removed"
+                    },
+                    toml::toml! {
+                        criteria = "will-not-parse"
+                        version = "10.0.0"
+                        notes = "will be removed"
+                    },
+                    toml::toml! {
+                        criteria = "safe-to-deploy"
+                        violation = "invalid"
+                        notes = "will be removed"
+                    },
+                    toml::toml! {
+                        criteria = "safe-to-deploy"
+                        version = "20.0.0"
+                        unknown = "invalid unknown field"
+                    },
+                ],
+            ),
+            (
+                "crate-b".to_string(),
+                vec![toml::toml! {
+                    criteria = "example"
+                    version = "invalid"
+                    notes = "will be removed, along with the entire crate"
+                }],
+            ),
+            (
+                "crate-c".to_string(),
+                vec![
+                    toml::toml! {
+                        criteria = "example2"
+                        version = "10.0.0"
+                        notes = "will not be removed"
+                    },
+                    toml::toml! {
+                        criteria = ["example2", "example3"]
+                        version = "10.0.0"
+                        notes = "will not be removed"
+                    },
+                    toml::toml! {
+                        criteria = "example2"
+                        delta = "1.0.0 -> 10.0.0"
+                        notes = "will not be removed"
+                    },
+                    toml::toml! {
+                        criteria = "safe-to-deploy"
+                        violation = "=5.0.0"
+                        notes = "will not be removed"
+                    },
+                ],
+            ),
+        ]
+        .into_iter()
+        .collect(),
+        trusted: SortedMap::new(),
+    };
+
+    let mut result = crate::storage::foreign_audit_file_to_local(foreign_audit_file);
+    result.ignored_criteria.sort();
+    result.ignored_audits.sort();
+
+    assert_eq!(
+        result.ignored_criteria,
+        &["will-not-parse", "will-not-parse2"]
+    );
+    assert_eq!(
+        result.ignored_audits,
+        &["crate-a", "crate-a", "crate-a", "crate-a", "crate-a", "crate-b", "crate-b"]
+    );
+
+    insta::assert_snapshot!(
+        "foreign_audit_file_to_local",
+        crate::serialization::to_formatted_toml(&result.audit_file, None)
+            .unwrap()
+            .to_string()
+    );
+}
+
+#[test]
+fn import_wildcard_audit_publisher() {
+    // (Pass) We should fetch information from the crates.io API about crates
+    // with wildcard audits both locally and from peers, though preferring local
+    // wildcard audits if they are sufficient.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, mut imports) = builtin_files_full_audited(&metadata);
+
+    audits.audits.remove("third-party2");
+    audits.audits.remove("third-party1");
+
+    audits.wildcard_audits.insert(
+        "third-party2".to_owned(),
+        vec![wildcard_audit(1, SAFE_TO_DEPLOY)],
+    );
+
+    let old_foreign_audits = AuditsFile {
+        criteria: SortedMap::new(),
+        audits: SortedMap::new(),
+        wildcard_audits: SortedMap::new(),
+        trusted: SortedMap::new(),
+    };
+
+    let mut new_foreign_audits = old_foreign_audits.clone();
+    new_foreign_audits.wildcard_audits.insert(
+        "third-party2".to_owned(),
+        vec![wildcard_audit(1, SAFE_TO_DEPLOY)],
+    );
+    new_foreign_audits.wildcard_audits.insert(
+        "third-party1".to_owned(),
+        vec![wildcard_audit(2, SAFE_TO_DEPLOY)],
+    );
+
+    imports
+        .audits
+        .insert(FOREIGN.to_owned(), old_foreign_audits);
+
+    config.imports.insert(
+        FOREIGN.to_owned(),
+        crate::format::RemoteImport {
+            url: vec![FOREIGN_URL.to_owned()],
+            ..Default::default()
+        },
+    );
+
+    let cfg = mock_cfg(&metadata);
+
+    let mut network = Network::new_mock();
+    MockRegistryBuilder::new()
+        .user(1, "user1", "User One")
+        .user(2, "user2", "User Two")
+        .package(
+            "third-party1",
+            &[
+                reg_published_by(ver(DEFAULT_VER), Some(2), "2022-12-12"),
+                reg_published_by(ver(5), Some(2), "2022-12-12"),
+            ],
+        )
+        .package(
+            "third-party2",
+            &[
+                reg_published_by(ver(DEFAULT_VER), Some(1), "2022-12-12"),
+                reg_published_by(ver(5), Some(2), "2022-12-12"),
+            ],
+        )
+        .serve(&mut network);
+    network.mock_serve_toml(FOREIGN_URL, &new_foreign_audits);
+
+    let store = Store::mock_online(&cfg, config, audits, imports, &network, true).unwrap();
+
+    let output = get_imports_file_changes_prune(&metadata, &store);
+    insta::assert_snapshot!(output);
+}
+
+#[test]
+fn import_criteria_map() {
+    // (Pass)
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, audits, imports) = files_no_exemptions(&metadata);
+
+    config.imports.insert(
+        FOREIGN.to_owned(),
+        crate::format::RemoteImport {
+            url: vec![FOREIGN_URL.to_owned()],
+            criteria_map: [
+                (
+                    "foreign-reviewed".to_owned().into(),
+                    vec!["reviewed".to_owned().into()],
+                ),
+                (
+                    "safe-to-deploy".to_owned().into(),
+                    vec!["strong-reviewed".to_owned().into()],
+                ),
+                (
+                    "safe-to-run".to_owned().into(),
+                    vec!["weak-reviewed".to_owned().into()],
+                ),
+            ]
+            .into_iter()
+            .collect(),
+            ..Default::default()
+        },
+    );
+
+    config.policy.package.insert(
+        "first-party".to_owned(),
+        PackagePolicyEntry::Unversioned(PolicyEntry {
+            dependency_criteria: [
+                (
+                    "third-party1".to_owned().into(),
+                    vec!["reviewed".to_owned().into()],
+                ),
+                (
+                    "third-party2".to_owned().into(),
+                    vec!["weak-reviewed".to_owned().into()],
+                ),
+            ]
+            .into_iter()
+            .collect(),
+            ..Default::default()
+        }),
+    );
+    config.policy.package.insert(
+        "third-party1".to_owned(),
+        PackagePolicyEntry::Versioned {
+            version: [(
+                ver(DEFAULT_VER),
+                PolicyEntry {
+                    dependency_criteria: [
+                        (
+                            "third-party1".to_owned().into(),
+                            vec!["strong-reviewed".to_owned().into()],
+                        ),
+                        (
+                            "third-party2".to_owned().into(),
+                            vec!["weak-reviewed".to_owned().into()],
+                        ),
+                    ]
+                    .into_iter()
+                    .collect(),
+                    ..Default::default()
+                },
+            )]
+            .into_iter()
+            .collect(),
+        },
+    );
+
+    let new_foreign_audits = AuditsFile {
+        criteria: [
+            (
+                "foreign-strong-reviewed".to_string(),
+                criteria_implies("foreign strongly reviewed", ["foreign-reviewed"]),
+            ),
+            (
+                "foreign-reviewed".to_string(),
+                criteria_implies("foreign reviewed", ["foreign-weak-reviewed"]),
+            ),
+            (
+                "foreign-weak-reviewed".to_string(),
+                criteria("foreign weakly reviewed"),
+            ),
+        ]
+        .into_iter()
+        .collect(),
+        audits: [
+            (
+                "third-party1".to_owned(),
+                vec![full_audit(ver(DEFAULT_VER), "foreign-strong-reviewed")],
+            ),
+            (
+                "transitive-third-party1".to_owned(),
+                vec![full_audit(ver(DEFAULT_VER), "safe-to-deploy")],
+            ),
+            (
+                "third-party2".to_owned(),
+                vec![full_audit(ver(DEFAULT_VER), "safe-to-run")],
+            ),
+        ]
+        .into_iter()
+        .collect(),
+        wildcard_audits: SortedMap::new(),
+        trusted: SortedMap::new(),
+    };
+
+    let cfg = mock_cfg(&metadata);
+
+    let mut network = Network::new_mock();
+    network.mock_serve_toml(FOREIGN_URL, &new_foreign_audits);
+
+    let store = Store::mock_online(&cfg, config, audits, imports, &network, true).unwrap();
+
+    let output = get_imports_file_changes_prune(&metadata, &store);
+    insta::assert_snapshot!(output);
+}
+
+#[test]
+fn import_criteria_map_aggregated() {
+    // (Pass) When importing multiple sources, they should be aggregated using
+    // independent mapping of criteria. Because the foreign-criteria audit isn't
+    // mapped, there is no criteria mapping error.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = builtin_files_full_audited(&metadata);
+
+    config.imports.insert(
+        FOREIGN.to_owned(),
+        crate::format::RemoteImport {
+            url: vec![FOREIGN_URL.to_owned(), OTHER_FOREIGN_URL.to_owned()],
+            ..Default::default()
+        },
+    );
+
+    audits.audits.remove("third-party1");
+
+    let new_foreign_audits = AuditsFile {
+        criteria: [(
+            "foreign-reviewed".to_string(),
+            criteria_implies("foreign reviewed A", [SAFE_TO_DEPLOY]),
+        )]
+        .into_iter()
+        .collect(),
+        audits: [(
+            "third-party1".to_owned(),
+            vec![full_audit(ver(9), SAFE_TO_DEPLOY)],
+        )]
+        .into_iter()
+        .collect(),
+        ..Default::default()
+    };
+
+    let new_other_foreign_audits = AuditsFile {
+        criteria: [(
+            "foreign-reviewed".to_string(),
+            criteria_implies("foreign reviewed B", [SAFE_TO_DEPLOY]),
+        )]
+        .into_iter()
+        .collect(),
+        audits: [(
+            "third-party1".to_owned(),
+            vec![delta_audit(ver(9), ver(DEFAULT_VER), "foreign-reviewed")],
+        )]
+        .into_iter()
+        .collect(),
+        ..Default::default()
+    };
+
+    let cfg = mock_cfg(&metadata);
+
+    let mut network = Network::new_mock();
+    network.mock_serve_toml(FOREIGN_URL, &new_foreign_audits);
+    network.mock_serve_toml(OTHER_FOREIGN_URL, &new_other_foreign_audits);
+
+    let store = Store::mock_online(&cfg, config, audits, imports, &network, true).unwrap();
+
+    let output = get_imports_file_changes_prune(&metadata, &store);
+    insta::assert_snapshot!(output);
+}
+
+#[test]
+fn import_criteria_map_aggregated_error() {
+    // (Pass) If a foreign criteria is mapped, and has different descriptions it
+    // should produce an error when importing.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = files_full_audited(&metadata);
+
+    config.imports.insert(
+        FOREIGN.to_owned(),
+        crate::format::RemoteImport {
+            url: vec![FOREIGN_URL.to_owned(), OTHER_FOREIGN_URL.to_owned()],
+            criteria_map: [(
+                "foreign-reviewed".to_owned().into(),
+                vec!["reviewed".to_owned().into()],
+            )]
+            .into_iter()
+            .collect(),
+            ..Default::default()
+        },
+    );
+
+    audits.audits.remove("third-party1");
+
+    let new_foreign_audits = AuditsFile {
+        criteria: [(
+            "foreign-reviewed".to_string(),
+            criteria_implies("foreign reviewed A", [SAFE_TO_DEPLOY]),
+        )]
+        .into_iter()
+        .collect(),
+        audits: [(
+            "third-party1".to_owned(),
+            vec![full_audit(ver(9), SAFE_TO_DEPLOY)],
+        )]
+        .into_iter()
+        .collect(),
+        ..Default::default()
+    };
+
+    let new_other_foreign_audits = AuditsFile {
+        criteria: [(
+            "foreign-reviewed".to_string(),
+            criteria_implies("foreign reviewed B", [SAFE_TO_DEPLOY]),
+        )]
+        .into_iter()
+        .collect(),
+        audits: [(
+            "third-party1".to_owned(),
+            vec![delta_audit(ver(9), ver(DEFAULT_VER), "foreign-reviewed")],
+        )]
+        .into_iter()
+        .collect(),
+        ..Default::default()
+    };
+
+    let cfg = mock_cfg(&metadata);
+
+    let mut network = Network::new_mock();
+    network.mock_serve_toml(FOREIGN_URL, &new_foreign_audits);
+    network.mock_serve_toml(OTHER_FOREIGN_URL, &new_other_foreign_audits);
+
+    let output = match Store::mock_online(&cfg, config, audits, imports, &network, true) {
+        Ok(_) => panic!("unexpected success"),
+        Err(err) => format!("{:?}", miette::Report::new(err)),
+    };
+
+    insta::assert_snapshot!(output);
+}
diff --git a/src/tests/mod.rs b/src/tests/mod.rs
new file mode 100644
index 0000000..adfff59
--- /dev/null
+++ b/src/tests/mod.rs
@@ -0,0 +1,1364 @@
+use std::{
+    collections::BTreeMap,
+    ffi::OsString,
+    fmt, fs, io,
+    path::PathBuf,
+    sync::{Arc, Mutex},
+};
+
+use cargo_metadata::{semver, Metadata};
+use clap::Parser;
+use serde_json::{json, Value};
+
+use crate::{
+    format::{
+        AuditEntry, AuditKind, AuditsFile, ConfigFile, CratesAPICrate, CratesAPICrateMetadata,
+        CratesAPIUser, CratesAPIVersion, CratesPublisher, CratesUserId, CriteriaEntry, CriteriaMap,
+        CriteriaName, CriteriaStr, ExemptedDependency, FastMap, ImportsFile, MetaConfig,
+        PackageName, PackagePolicyEntry, PackageStr, PolicyEntry, SortedMap, SortedSet, TrustEntry,
+        VersionReq, VetVersion, WildcardEntry, SAFE_TO_DEPLOY, SAFE_TO_RUN,
+    },
+    git_tool::Editor,
+    network::Network,
+    out::Out,
+    resolver::ResolveReport,
+    storage::Store,
+    Config, PackageExt, PartialConfig,
+};
+
+/// Helper for performing an `assert_snapshot!` for the report output of a
+/// resolver invocation. This will generate both human and JSON reports for the
+/// given resolve report, and snapshot both. The JSON reports will have the
+/// suffix `.json`.
+///
+/// Unlike a normal `assert_snapshot!` the snapshot name isn't inferred by this
+/// macro, as multiple snapshots with different names need to be generated.
+macro_rules! assert_report_snapshot {
+    ($name:expr, $metadata:expr, $store:expr) => {
+        assert_report_snapshot!($name, $metadata, $store, None);
+    };
+    ($name:expr, $metadata:expr, $store:expr, $network:expr) => {{
+        let report = $crate::resolver::resolve(&$metadata, None, &$store);
+        let (human, json) = $crate::tests::get_reports(&$metadata, report, &$store, $network);
+        insta::assert_snapshot!($name, human);
+        insta::assert_snapshot!(concat!($name, ".json"), json);
+    }};
+}
+
+mod aggregate;
+mod audit_as_crates_io;
+mod certify;
+mod crate_policies;
+mod import;
+mod regenerate_unaudited;
+mod registry;
+mod renew;
+mod store_parsing;
+mod trusted;
+mod unpublished;
+mod vet;
+mod violations;
+mod wildcard;
+
+// Some room above and below
+const DEFAULT_VER: u64 = 10;
+const DEFAULT_CRIT: CriteriaStr = "reviewed";
+
+// Some strings for imports
+const FOREIGN: &str = "peer-company";
+const FOREIGN_URL: &str = "https://peercompany.co.uk";
+const OTHER_FOREIGN: &str = "rival-company";
+const OTHER_FOREIGN_URL: &str = "https://rivalcompany.ca";
+
+lazy_static::lazy_static! {
+    static ref TEST_RUNTIME: tokio::runtime::Runtime = {
+        let error_colors_enabled = false;
+        miette::set_hook(Box::new(move |_| {
+            let graphical_theme = if error_colors_enabled {
+                miette::GraphicalTheme::unicode()
+            } else {
+                miette::GraphicalTheme::unicode_nocolor()
+            };
+            Box::new(
+                miette::MietteHandlerOpts::new()
+                    .graphical_theme(graphical_theme)
+                    .width(80)
+                    .build()
+            )
+        })).expect("Failed to initialize error handler");
+
+        tracing_subscriber::fmt::fmt()
+            .with_max_level(tracing::level_filters::LevelFilter::TRACE)
+            .with_target(false)
+            .without_time()
+            .with_writer(tracing_subscriber::fmt::writer::TestWriter::new())
+            .init();
+
+        tokio::runtime::Runtime::new().unwrap()
+    };
+
+}
+
+struct MockMetadata {
+    packages: Vec<MockPackage>,
+    pkgids: Vec<String>,
+    idx_by_name_and_ver: BTreeMap<PackageStr<'static>, BTreeMap<VetVersion, usize>>,
+}
+
+struct MockPackage {
+    name: &'static str,
+    version: VetVersion,
+    deps: Vec<MockDependency>,
+    dev_deps: Vec<MockDependency>,
+    build_deps: Vec<MockDependency>,
+    targets: Vec<&'static str>,
+    is_workspace: bool,
+    is_first_party: bool,
+}
+
+struct MockDependency {
+    name: &'static str,
+    version: VetVersion,
+}
+
+impl Default for MockPackage {
+    fn default() -> Self {
+        Self {
+            name: "",
+            version: ver(DEFAULT_VER),
+            deps: vec![],
+            dev_deps: vec![],
+            build_deps: vec![],
+            targets: vec!["lib"],
+            is_workspace: false,
+            is_first_party: false,
+        }
+    }
+}
+
+fn ver(major: u64) -> VetVersion {
+    VetVersion {
+        semver: semver::Version {
+            major,
+            minor: 0,
+            patch: 0,
+            pre: Default::default(),
+            build: Default::default(),
+        },
+        git_rev: None,
+    }
+}
+
+fn dep(name: &'static str) -> MockDependency {
+    dep_ver(name, DEFAULT_VER)
+}
+
+fn dep_ver(name: &'static str, version: u64) -> MockDependency {
+    MockDependency {
+        name,
+        version: ver(version),
+    }
+}
+
+#[allow(dead_code)]
+fn default_exemptions(version: VetVersion, config: &ConfigFile) -> ExemptedDependency {
+    ExemptedDependency {
+        version,
+        criteria: vec![config.default_criteria.clone().into()],
+        notes: None,
+        suggest: true,
+    }
+}
+fn exemptions(version: VetVersion, criteria: CriteriaStr) -> ExemptedDependency {
+    ExemptedDependency {
+        version,
+        criteria: vec![criteria.to_string().into()],
+        notes: None,
+        suggest: true,
+    }
+}
+
+fn delta_audit(from: VetVersion, to: VetVersion, criteria: CriteriaStr) -> AuditEntry {
+    AuditEntry {
+        who: vec![],
+        notes: None,
+        criteria: vec![criteria.to_string().into()],
+        kind: AuditKind::Delta { from, to },
+        aggregated_from: vec![],
+        is_fresh_import: false,
+    }
+}
+
+fn full_audit(version: VetVersion, criteria: CriteriaStr) -> AuditEntry {
+    AuditEntry {
+        who: vec![],
+        notes: None,
+        criteria: vec![criteria.to_string().into()],
+        kind: AuditKind::Full { version },
+        aggregated_from: vec![],
+        is_fresh_import: false,
+    }
+}
+
+fn full_audit_m(
+    version: VetVersion,
+    criteria: impl IntoIterator<Item = impl Into<CriteriaName>>,
+) -> AuditEntry {
+    AuditEntry {
+        who: vec![],
+        notes: None,
+        criteria: criteria.into_iter().map(|s| s.into().into()).collect(),
+        kind: AuditKind::Full { version },
+        aggregated_from: vec![],
+        is_fresh_import: false,
+    }
+}
+
+fn violation_hard(version: VersionReq) -> AuditEntry {
+    AuditEntry {
+        who: vec![],
+        notes: None,
+        criteria: vec![SAFE_TO_RUN.to_string().into()],
+        kind: AuditKind::Violation { violation: version },
+        aggregated_from: vec![],
+        is_fresh_import: false,
+    }
+}
+#[allow(dead_code)]
+fn violation(version: VersionReq, criteria: CriteriaStr) -> AuditEntry {
+    AuditEntry {
+        who: vec![],
+        notes: None,
+        criteria: vec![criteria.to_string().into()],
+        kind: AuditKind::Violation { violation: version },
+        aggregated_from: vec![],
+        is_fresh_import: false,
+    }
+}
+#[allow(dead_code)]
+fn violation_m(
+    version: VersionReq,
+    criteria: impl IntoIterator<Item = impl Into<CriteriaName>>,
+) -> AuditEntry {
+    AuditEntry {
+        who: vec![],
+        notes: None,
+        criteria: criteria.into_iter().map(|s| s.into().into()).collect(),
+        kind: AuditKind::Violation { violation: version },
+        aggregated_from: vec![],
+        is_fresh_import: false,
+    }
+}
+
+fn wildcard_audit(user_id: u64, criteria: CriteriaStr) -> WildcardEntry {
+    WildcardEntry {
+        who: vec![],
+        notes: None,
+        criteria: vec![criteria.to_string().into()],
+        user_id,
+        start: chrono::NaiveDate::from_ymd_opt(2022, 12, 1).unwrap().into(),
+        end: chrono::NaiveDate::from_ymd_opt(2023, 1, 1).unwrap().into(),
+        renew: None,
+        aggregated_from: vec![],
+        is_fresh_import: false,
+    }
+}
+
+fn wildcard_audit_m(
+    user_id: u64,
+    criteria: impl IntoIterator<Item = impl Into<CriteriaName>>,
+) -> WildcardEntry {
+    WildcardEntry {
+        who: vec![],
+        notes: None,
+        criteria: criteria.into_iter().map(|s| s.into().into()).collect(),
+        user_id,
+        start: chrono::NaiveDate::from_ymd_opt(2022, 12, 1).unwrap().into(),
+        end: chrono::NaiveDate::from_ymd_opt(2023, 1, 1).unwrap().into(),
+        renew: None,
+        aggregated_from: vec![],
+        is_fresh_import: false,
+    }
+}
+
+fn trusted_entry(user_id: u64, criteria: CriteriaStr) -> TrustEntry {
+    TrustEntry {
+        notes: None,
+        criteria: vec![criteria.to_string().into()],
+        user_id,
+        start: chrono::NaiveDate::from_ymd_opt(2022, 12, 1).unwrap().into(),
+        end: chrono::NaiveDate::from_ymd_opt(2023, 1, 1).unwrap().into(),
+        aggregated_from: vec![],
+    }
+}
+
+fn publisher_entry(version: VetVersion, user_id: u64) -> CratesPublisher {
+    CratesPublisher {
+        version,
+        when: chrono::NaiveDate::from_ymd_opt(2022, 12, 15).unwrap(),
+        user_id,
+        user_login: format!("user{user_id}"),
+        user_name: None,
+        is_fresh_import: false,
+    }
+}
+
+fn publisher_entry_named(
+    version: VetVersion,
+    user_id: u64,
+    login: &str,
+    name: &str,
+) -> CratesPublisher {
+    CratesPublisher {
+        version,
+        when: chrono::NaiveDate::from_ymd_opt(2022, 12, 15).unwrap(),
+        user_id,
+        user_login: login.to_owned(),
+        user_name: Some(name.to_owned()),
+        is_fresh_import: false,
+    }
+}
+
+fn default_policy() -> PolicyEntry {
+    PolicyEntry {
+        audit_as_crates_io: None,
+        criteria: None,
+        dev_criteria: None,
+        dependency_criteria: SortedMap::new(),
+        notes: None,
+    }
+}
+
+fn audit_as_policy(audit_as_crates_io: Option<bool>) -> PackagePolicyEntry {
+    PackagePolicyEntry::Unversioned(PolicyEntry {
+        audit_as_crates_io,
+        ..default_policy()
+    })
+}
+
+fn audit_as_policy_with<F: Fn(&mut PolicyEntry)>(
+    audit_as_crates_io: Option<bool>,
+    alter: F,
+) -> PackagePolicyEntry {
+    let mut entry = PolicyEntry {
+        audit_as_crates_io,
+        ..default_policy()
+    };
+    alter(&mut entry);
+    PackagePolicyEntry::Unversioned(entry)
+}
+
+fn self_policy(criteria: impl IntoIterator<Item = impl Into<CriteriaName>>) -> PackagePolicyEntry {
+    PackagePolicyEntry::Unversioned(PolicyEntry {
+        criteria: Some(criteria.into_iter().map(|s| s.into().into()).collect()),
+        ..default_policy()
+    })
+}
+
+fn dep_policy(
+    dependency_criteria: impl IntoIterator<
+        Item = (
+            impl Into<PackageName>,
+            impl IntoIterator<Item = impl Into<CriteriaName>>,
+        ),
+    >,
+) -> PackagePolicyEntry {
+    PackagePolicyEntry::Unversioned(PolicyEntry {
+        dependency_criteria: dependency_criteria
+            .into_iter()
+            .map(|(k, v)| {
+                (
+                    k.into().into(),
+                    v.into_iter().map(|s| s.into().into()).collect::<Vec<_>>(),
+                )
+            })
+            .collect(),
+        ..default_policy()
+    })
+}
+
+fn criteria(description: &str) -> CriteriaEntry {
+    CriteriaEntry {
+        description: Some(description.to_owned()),
+        description_url: None,
+        implies: vec![],
+        aggregated_from: vec![],
+    }
+}
+
+fn criteria_implies(
+    description: &str,
+    implies: impl IntoIterator<Item = impl Into<CriteriaName>>,
+) -> CriteriaEntry {
+    CriteriaEntry {
+        description: Some(description.to_owned()),
+        description_url: None,
+        implies: implies.into_iter().map(|s| s.into().into()).collect(),
+        aggregated_from: vec![],
+    }
+}
+
+impl MockMetadata {
+    fn simple() -> Self {
+        // A simple dependency tree to test basic functionality on.
+        //
+        //                                    Graph
+        // =======================================================================================
+        //
+        //                                 root-package
+        //                                       |
+        //                                 first-party
+        //                                /           \
+        //                       third-party1       third-party2
+        //                            |
+        //                  transitive-third-party1
+        //
+        MockMetadata::new(vec![
+            MockPackage {
+                name: "root-package",
+                is_workspace: true,
+                is_first_party: true,
+                deps: vec![dep("first-party")],
+                ..Default::default()
+            },
+            MockPackage {
+                name: "first-party",
+                is_first_party: true,
+                deps: vec![dep("third-party1"), dep("third-party2")],
+                ..Default::default()
+            },
+            MockPackage {
+                name: "third-party1",
+                deps: vec![dep("transitive-third-party1")],
+                ..Default::default()
+            },
+            MockPackage {
+                name: "third-party2",
+                ..Default::default()
+            },
+            MockPackage {
+                name: "transitive-third-party1",
+                ..Default::default()
+            },
+        ])
+    }
+
+    fn complex() -> Self {
+        // A Complex dependency tree to test more weird interactions and corner cases:
+        //
+        // * firstAB: first-party shared between two roots
+        // * firstB-nodeps: first-party with no third-parties
+        // * third-core: third-party used by everything, has two versions in-tree
+        //
+        //                                      Graph
+        // =======================================================================================
+        //
+        //                         rootA                rootB
+        //                        -------       ---------------------
+        //                       /       \     /          |          \
+        //                      /         \   /           |           \
+        //                    firstA     firstAB       firstB     firstB-nodeps
+        //                   /      \         \           |
+        //                  /        \         \          |
+        //                 /        thirdA    thirdAB     +
+        //                /             \        |       /
+        //               /               \       |      /
+        //        third-core:v5           third-core:v10
+        //
+        MockMetadata::new(vec![
+            MockPackage {
+                name: "rootA",
+                is_workspace: true,
+                is_first_party: true,
+                deps: vec![dep("firstA"), dep("firstAB")],
+                ..Default::default()
+            },
+            MockPackage {
+                name: "rootB",
+                is_workspace: true,
+                is_first_party: true,
+                deps: vec![dep("firstB"), dep("firstAB"), dep("firstB-nodeps")],
+                ..Default::default()
+            },
+            MockPackage {
+                name: "firstA",
+                is_first_party: true,
+                deps: vec![dep("thirdA"), dep_ver("third-core", 5)],
+                ..Default::default()
+            },
+            MockPackage {
+                name: "firstAB",
+                is_first_party: true,
+                deps: vec![dep("thirdAB")],
+                ..Default::default()
+            },
+            MockPackage {
+                name: "firstB",
+                is_first_party: true,
+                deps: vec![dep("third-core")],
+                ..Default::default()
+            },
+            MockPackage {
+                name: "firstB-nodeps",
+                is_first_party: true,
+                ..Default::default()
+            },
+            MockPackage {
+                name: "thirdA",
+                deps: vec![dep("third-core")],
+                ..Default::default()
+            },
+            MockPackage {
+                name: "thirdAB",
+                deps: vec![dep("third-core")],
+                ..Default::default()
+            },
+            MockPackage {
+                name: "third-core",
+                ..Default::default()
+            },
+            MockPackage {
+                name: "third-core",
+                version: ver(5),
+                ..Default::default()
+            },
+        ])
+    }
+
+    /// The `third-party` crate is used as both a first- and third-party crate (with different
+    /// versions).
+    fn overlapping() -> Self {
+        MockMetadata::new(vec![
+            MockPackage {
+                name: "root-package",
+                is_workspace: true,
+                is_first_party: true,
+                deps: vec![dep("first-party"), dep_ver("third-party", 1)],
+                ..Default::default()
+            },
+            MockPackage {
+                name: "first-party",
+                is_first_party: true,
+                deps: vec![dep_ver("third-party", 2)],
+                ..Default::default()
+            },
+            MockPackage {
+                name: "third-party",
+                is_first_party: true,
+                version: ver(1),
+                ..Default::default()
+            },
+            MockPackage {
+                name: "third-party",
+                version: ver(2),
+                ..Default::default()
+            },
+        ])
+    }
+
+    fn simple_deps() -> Self {
+        // Different dependency cases
+        MockMetadata::new(vec![
+            MockPackage {
+                name: "root",
+                is_workspace: true,
+                is_first_party: true,
+                deps: vec![dep("normal"), dep("proc-macro")],
+                dev_deps: vec![dep("dev"), dep("dev-proc-macro")],
+                build_deps: vec![dep("build"), dep("build-proc-macro")],
+                ..Default::default()
+            },
+            MockPackage {
+                name: "normal",
+                ..Default::default()
+            },
+            MockPackage {
+                name: "dev",
+                ..Default::default()
+            },
+            MockPackage {
+                name: "build",
+                ..Default::default()
+            },
+            MockPackage {
+                name: "proc-macro",
+                targets: vec!["proc-macro"],
+                ..Default::default()
+            },
+            MockPackage {
+                name: "dev-proc-macro",
+                targets: vec!["proc-macro"],
+                ..Default::default()
+            },
+            MockPackage {
+                name: "build-proc-macro",
+                targets: vec!["proc-macro"],
+                ..Default::default()
+            },
+        ])
+    }
+
+    fn cycle() -> Self {
+        // Different dependency cases
+        MockMetadata::new(vec![
+            MockPackage {
+                name: "root",
+                is_workspace: true,
+                is_first_party: true,
+                deps: vec![dep("normal")],
+                dev_deps: vec![dep("dev-cycle")],
+                ..Default::default()
+            },
+            MockPackage {
+                name: "normal",
+                ..Default::default()
+            },
+            MockPackage {
+                name: "dev-cycle",
+                deps: vec![dep("root")],
+                ..Default::default()
+            },
+        ])
+    }
+
+    fn dev_detection() -> Self {
+        MockMetadata::new(vec![
+            MockPackage {
+                name: "root",
+                is_workspace: true,
+                is_first_party: true,
+                deps: vec![dep("normal"), dep("both")],
+                dev_deps: vec![dep("dev-cycle-direct"), dep("both"), dep("simple-dev")],
+                ..Default::default()
+            },
+            MockPackage {
+                name: "normal",
+                ..Default::default()
+            },
+            MockPackage {
+                name: "both",
+                ..Default::default()
+            },
+            MockPackage {
+                name: "simple-dev",
+                deps: vec![dep("simple-dev-indirect")],
+                ..Default::default()
+            },
+            MockPackage {
+                name: "simple-dev-indirect",
+                ..Default::default()
+            },
+            MockPackage {
+                name: "dev-cycle-direct",
+                deps: vec![dep("dev-cycle-indirect")],
+                ..Default::default()
+            },
+            MockPackage {
+                name: "dev-cycle-indirect",
+                deps: vec![dep("root")],
+                ..Default::default()
+            },
+        ])
+    }
+
+    fn haunted_tree() -> Self {
+        MockMetadata::new(vec![
+            MockPackage {
+                name: "root",
+                is_workspace: true,
+                is_first_party: true,
+                deps: vec![dep("first")],
+                ..Default::default()
+            },
+            MockPackage {
+                name: "first",
+                is_workspace: true,
+                is_first_party: true,
+                deps: vec![dep("third-normal")],
+                dev_deps: vec![dep("third-dev")],
+                ..Default::default()
+            },
+            MockPackage {
+                name: "third-normal",
+                ..Default::default()
+            },
+            MockPackage {
+                name: "third-dev",
+                ..Default::default()
+            },
+        ])
+    }
+
+    fn descriptive() -> Self {
+        MockMetadata::new(vec![MockPackage {
+            name: "descriptive",
+            is_workspace: true,
+            is_first_party: true,
+            ..Default::default()
+        }])
+    }
+
+    fn new(packages: Vec<MockPackage>) -> Self {
+        let mut pkgids = vec![];
+        let mut idx_by_name_and_ver = BTreeMap::<PackageStr, BTreeMap<VetVersion, usize>>::new();
+
+        for (idx, package) in packages.iter().enumerate() {
+            let pkgid = if package.is_first_party {
+                format!(
+                    "{} {} (path+file:///C:/FAKE/{})",
+                    package.name, package.version, package.name
+                )
+            } else {
+                format!(
+                    "{} {} (registry+https://github.com/rust-lang/crates.io-index)",
+                    package.name, package.version
+                )
+            };
+            pkgids.push(pkgid);
+            let old = idx_by_name_and_ver
+                .entry(package.name)
+                .or_default()
+                .insert(package.version.clone(), idx);
+            assert!(
+                old.is_none(),
+                "duplicate version {} {}",
+                package.name,
+                package.version
+            );
+        }
+
+        Self {
+            packages,
+            pkgids,
+            idx_by_name_and_ver,
+        }
+    }
+
+    fn pkgid(&self, package: &MockPackage) -> &str {
+        self.pkgid_by(package.name, &package.version)
+    }
+
+    fn pkgid_by(&self, name: PackageStr, version: &VetVersion) -> &str {
+        &self.pkgids[self.idx_by_name_and_ver[name][version]]
+    }
+
+    fn package_by(&self, name: PackageStr, version: &VetVersion) -> &MockPackage {
+        &self.packages[self.idx_by_name_and_ver[name][version]]
+    }
+
+    fn source(&self, package: &MockPackage) -> Value {
+        if package.is_first_party {
+            json!(null)
+        } else {
+            json!("registry+https://github.com/rust-lang/crates.io-index")
+        }
+    }
+
+    fn metadata(&self) -> Metadata {
+        let meta_json = json!({
+            "packages": self.packages.iter().map(|package| json!({
+                "name": package.name,
+                "version": package.version.to_string(),
+                "id": self.pkgid(package),
+                "license": "MIT",
+                "license_file": null,
+                "description": "whatever",
+                "source": self.source(package),
+                "dependencies": package.deps.iter().chain(&package.dev_deps).chain(&package.build_deps).map(|dep| json!({
+                    "name": dep.name,
+                    "source": self.source(self.package_by(dep.name, &dep.version)),
+                    "req": format!("={}", dep.version),
+                    "kind": null,
+                    "rename": null,
+                    "optional": false,
+                    "uses_default_features": true,
+                    "features": [],
+                    "target": null,
+                    "registry": null
+                })).collect::<Vec<_>>(),
+                "targets": package.targets.iter().map(|target| json!({
+                    "kind": [
+                        target
+                    ],
+                    "crate_types": [
+                        target
+                    ],
+                    "name": package.name,
+                    "src_path": "C:\\Users\\fake_user\\.cargo\\registry\\src\\github.com-1ecc6299db9ec823\\DUMMY\\src\\lib.rs",
+                    "edition": "2015",
+                    "doc": true,
+                    "doctest": true,
+                    "test": true
+                })).collect::<Vec<_>>(),
+                "features": {},
+                "manifest_path": "C:\\Users\\fake_user\\.cargo\\registry\\src\\github.com-1ecc6299db9ec823\\DUMMY\\Cargo.toml",
+                "metadata": null,
+                "publish": null,
+                "authors": [],
+                "categories": [],
+                "keywords": [],
+                "readme": "README.md",
+                "repository": null,
+                "homepage": null,
+                "documentation": null,
+                "edition": "2015",
+                "links": null,
+                "default_run": null,
+                "rust_version": null
+            })).collect::<Vec<_>>(),
+            "workspace_members": self.packages.iter().filter_map(|package| {
+                if package.is_workspace {
+                    Some(self.pkgid(package))
+                } else {
+                    None
+                }
+            }).collect::<Vec<_>>(),
+            "resolve": {
+                "nodes": self.packages.iter().map(|package| {
+                    let mut all_deps = BTreeMap::<(PackageStr, &VetVersion), Vec<Option<&str>>>::new();
+                    for dep in &package.deps {
+                        all_deps.entry((dep.name, &dep.version)).or_default().push(None);
+                    }
+                    for dep in &package.build_deps {
+                        all_deps.entry((dep.name, &dep.version)).or_default().push(Some("build"));
+                    }
+                    for dep in &package.dev_deps {
+                        all_deps.entry((dep.name, &dep.version)).or_default().push(Some("dev"));
+                    }
+                    json!({
+                        "id": self.pkgid(package),
+                        "dependencies": all_deps.keys().map(|(name, version)| self.pkgid_by(name, version)).collect::<Vec<_>>(),
+                        "deps": all_deps.iter().map(|((name, version), kinds)| json!({
+                            "name": name,
+                            "pkg": self.pkgid_by(name, version),
+                            "dep_kinds": kinds.iter().map(|kind| json!({
+                                "kind": kind,
+                                "target": null,
+                            })).collect::<Vec<_>>(),
+                        })).collect::<Vec<_>>(),
+                    })
+                }).collect::<Vec<_>>(),
+                "root": null,
+            },
+            "target_directory": "C:\\FAKE\\target",
+            "version": 1,
+            "workspace_root": "C:\\FAKE\\",
+            "metadata": null,
+        });
+        serde_json::from_value(meta_json).unwrap()
+    }
+}
+
+fn init_files(
+    metadata: &Metadata,
+    criteria: impl IntoIterator<Item = (CriteriaName, CriteriaEntry)>,
+    default_criteria: &str,
+) -> (ConfigFile, AuditsFile, ImportsFile) {
+    let mut config = ConfigFile {
+        cargo_vet: Default::default(),
+        default_criteria: default_criteria.to_owned(),
+        imports: Default::default(),
+        policy: Default::default(),
+        exemptions: Default::default(),
+    };
+    let audits = AuditsFile {
+        criteria: criteria.into_iter().collect(),
+        wildcard_audits: SortedMap::new(),
+        audits: SortedMap::new(),
+        trusted: SortedMap::new(),
+    };
+    let imports = ImportsFile {
+        unpublished: SortedMap::new(),
+        publisher: SortedMap::new(),
+        audits: SortedMap::new(),
+    };
+
+    // Make the root packages use our custom criteria instead of the builtins
+    if default_criteria != SAFE_TO_DEPLOY {
+        for pkgid in &metadata.workspace_members {
+            for package in &metadata.packages {
+                if package.id == *pkgid {
+                    config.policy.insert(
+                        package.name.clone(),
+                        PackagePolicyEntry::Unversioned(PolicyEntry {
+                            audit_as_crates_io: None,
+                            criteria: Some(vec![default_criteria.to_string().into()]),
+                            dev_criteria: Some(vec![default_criteria.to_string().into()]),
+                            dependency_criteria: CriteriaMap::new(),
+                            notes: None,
+                        }),
+                    );
+                }
+            }
+        }
+    }
+
+    // Use `update_store` to generate exemptions which would allow the tree to
+    // be mocked, then deconstruct the store again. Callers may want to
+    // initialize the store differently during their tests.
+    let mut store = Store::mock(config, audits, imports);
+    crate::resolver::update_store(&mock_cfg(metadata), &mut store, |_| {
+        crate::resolver::UpdateMode {
+            search_mode: crate::resolver::SearchMode::RegenerateExemptions,
+            prune_exemptions: true,
+            prune_imports: true,
+        }
+    });
+
+    (store.config, store.audits, store.imports)
+}
+
+fn files_inited(metadata: &Metadata) -> (ConfigFile, AuditsFile, ImportsFile) {
+    // Criteria hierarchy:
+    //
+    // * strong-reviewed
+    //   * reviewed (default)
+    //      * weak-reviewed
+    // * fuzzed
+    //
+    // This lets use mess around with "strong reqs", "weaker reqs", and "unrelated reqs"
+    // with "reviewed" as the implicit default everything cares about.
+
+    init_files(
+        metadata,
+        [
+            (
+                "strong-reviewed".to_string(),
+                criteria_implies("strongly reviewed", ["reviewed"]),
+            ),
+            (
+                "reviewed".to_string(),
+                criteria_implies("reviewed", ["weak-reviewed"]),
+            ),
+            ("weak-reviewed".to_string(), criteria("weakly reviewed")),
+            ("fuzzed".to_string(), criteria("fuzzed")),
+        ],
+        DEFAULT_CRIT,
+    )
+}
+
+fn files_no_exemptions(metadata: &Metadata) -> (ConfigFile, AuditsFile, ImportsFile) {
+    let (mut config, audits, imports) = files_inited(metadata);
+
+    // Just clear all the exemptions out
+    config.exemptions.clear();
+
+    (config, audits, imports)
+}
+
+fn files_full_audited(metadata: &Metadata) -> (ConfigFile, AuditsFile, ImportsFile) {
+    let (config, mut audits, imports) = files_no_exemptions(metadata);
+
+    let mut audited = SortedMap::<PackageName, Vec<AuditEntry>>::new();
+    for package in &metadata.packages {
+        if package.is_third_party(&config.policy) {
+            audited
+                .entry(package.name.clone())
+                .or_default()
+                .push(full_audit(package.vet_version(), DEFAULT_CRIT));
+        }
+    }
+    audits.audits = audited;
+
+    (config, audits, imports)
+}
+
+fn builtin_files_inited(metadata: &Metadata) -> (ConfigFile, AuditsFile, ImportsFile) {
+    init_files(metadata, [], SAFE_TO_DEPLOY)
+}
+
+fn builtin_files_no_exemptions(metadata: &Metadata) -> (ConfigFile, AuditsFile, ImportsFile) {
+    let (mut config, audits, imports) = builtin_files_inited(metadata);
+
+    // Just clear all the exemptions out
+    config.exemptions.clear();
+
+    (config, audits, imports)
+}
+fn builtin_files_full_audited(metadata: &Metadata) -> (ConfigFile, AuditsFile, ImportsFile) {
+    let (config, mut audits, imports) = builtin_files_no_exemptions(metadata);
+
+    let mut audited = SortedMap::<PackageName, Vec<AuditEntry>>::new();
+    for package in &metadata.packages {
+        if package.is_third_party(&config.policy) {
+            audited
+                .entry(package.name.clone())
+                .or_default()
+                .push(full_audit(package.vet_version(), SAFE_TO_DEPLOY));
+        }
+    }
+    audits.audits = audited;
+
+    (config, audits, imports)
+}
+fn builtin_files_minimal_audited(metadata: &Metadata) -> (ConfigFile, AuditsFile, ImportsFile) {
+    let (mut config, mut audits, imports) = builtin_files_inited(metadata);
+
+    let mut audited = SortedMap::<PackageName, Vec<AuditEntry>>::new();
+    for (name, entries) in std::mem::take(&mut config.exemptions) {
+        for entry in entries {
+            audited.entry(name.clone()).or_default().push(full_audit_m(
+                entry.version,
+                entry.criteria.iter().map(|s| &**s).collect::<Vec<_>>(),
+            ));
+        }
+    }
+    audits.audits = audited;
+
+    (config, audits, imports)
+}
+
+/// Returns a fixed datetime that should be considered `now`: 2023-01-01 12:00 UTC.
+fn mock_now() -> chrono::DateTime<chrono::Utc> {
+    chrono::DateTime::from_utc(
+        chrono::NaiveDateTime::new(
+            chrono::NaiveDate::from_ymd_opt(2023, 1, 1).unwrap(),
+            chrono::NaiveTime::from_hms_opt(12, 0, 0).unwrap(),
+        ),
+        chrono::Utc,
+    )
+}
+
+/// Returns a fixed datetime that should be considered `today`: 2023-01-01.
+///
+/// This is derived from `mock_now()`.
+fn mock_today() -> chrono::NaiveDate {
+    mock_now().date_naive()
+}
+
+fn mock_cfg(metadata: &Metadata) -> Config {
+    mock_cfg_args(metadata, ["cargo", "vet"])
+}
+
+fn mock_cfg_args<I, T>(metadata: &Metadata, itr: I) -> Config
+where
+    I: IntoIterator<Item = T>,
+    T: Into<OsString> + Clone,
+{
+    let crate::cli::FakeCli::Vet(cli) =
+        crate::cli::FakeCli::try_parse_from(itr).expect("Parsing arguments for mock_cfg failed!");
+    Config {
+        metacfg: MetaConfig(vec![]),
+        metadata: metadata.clone(),
+        _rest: PartialConfig {
+            cli,
+            now: mock_now(),
+            cache_dir: PathBuf::new(),
+            mock_cache: true,
+        },
+    }
+}
+
+fn get_reports(
+    metadata: &Metadata,
+    report: ResolveReport,
+    store: &Store,
+    network: Option<&Network>,
+) -> (String, String) {
+    // FIXME: Figure out how to handle disabling output colours better in tests.
+    console::set_colors_enabled(false);
+    console::set_colors_enabled_stderr(false);
+
+    let cfg = mock_cfg(metadata);
+    let suggest = report.compute_suggest(&cfg, store, network).unwrap();
+
+    let human_output = BasicTestOutput::new();
+    report
+        .print_human(&human_output.clone().as_dyn(), &cfg, suggest.as_ref())
+        .unwrap();
+    let json_output = BasicTestOutput::new();
+    report
+        .print_json(&json_output.clone().as_dyn(), suggest.as_ref())
+        .unwrap();
+    (human_output.to_string(), json_output.to_string())
+}
+
+#[allow(clippy::type_complexity)]
+struct BasicTestOutput {
+    output: Mutex<Vec<u8>>,
+    on_read_line: Option<Box<dyn Fn(&str) -> io::Result<String> + Send + Sync + 'static>>,
+    on_edit: Option<Box<dyn Fn(String) -> io::Result<String> + Send + Sync + 'static>>,
+}
+
+impl BasicTestOutput {
+    fn new() -> Arc<Self> {
+        Arc::new(BasicTestOutput {
+            output: Mutex::new(Vec::new()),
+            on_read_line: None,
+            on_edit: None,
+        })
+    }
+
+    fn with_callbacks(
+        on_read_line: impl Fn(&str) -> io::Result<String> + Send + Sync + 'static,
+        on_edit: impl Fn(String) -> io::Result<String> + Send + Sync + 'static,
+    ) -> Arc<Self> {
+        Arc::new(BasicTestOutput {
+            output: Mutex::new(Vec::new()),
+            on_read_line: Some(Box::new(on_read_line)),
+            on_edit: Some(Box::new(on_edit)),
+        })
+    }
+
+    fn as_dyn(self: Arc<Self>) -> Arc<dyn Out> {
+        self
+    }
+}
+
+impl fmt::Display for BasicTestOutput {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        std::str::from_utf8(&self.output.lock().unwrap())
+            .unwrap()
+            .fmt(f)
+    }
+}
+
+impl Out for BasicTestOutput {
+    fn write(&self, buf: &[u8]) -> io::Result<usize> {
+        self.output.lock().unwrap().extend_from_slice(buf);
+        Ok(buf.len())
+    }
+
+    fn clear_screen(&self) -> io::Result<()> {
+        writeln!(self, "<<<CLEAR SCREEN>>>");
+        Ok(())
+    }
+
+    fn read_line_with_prompt(&self, initial: &str) -> io::Result<String> {
+        write!(self, "{initial}");
+        if let Some(on_read_line) = &self.on_read_line {
+            let response = on_read_line(initial)?;
+            writeln!(self, "{response}");
+            Ok(response)
+        } else {
+            Err(io::ErrorKind::Unsupported.into())
+        }
+    }
+
+    fn editor<'b>(&'b self, name: &'b str) -> io::Result<Editor<'b>> {
+        if let Some(on_edit) = &self.on_edit {
+            let mut editor = Editor::new(name)?;
+            editor.set_run_editor(move |path| {
+                let original = fs::read_to_string(path)?;
+                writeln!(self, "<<<EDITING {name}>>>\n{original}");
+                match on_edit(original) {
+                    Ok(contents) => {
+                        writeln!(self, "<<<EDIT OK>>>\n{contents}\n<<<END EDIT>>>");
+                        fs::write(path, contents)?;
+                        Ok(true)
+                    }
+                    Err(err) => {
+                        writeln!(self, "<<<EDIT ERROR>>>");
+                        Err(err)
+                    }
+                }
+            });
+            Ok(editor)
+        } else {
+            panic!("Unexpected editor call without on_edit configured!");
+        }
+    }
+}
+
+/// Format a diff between the old and new strings for reporting.
+fn generate_diff(old: &str, new: &str) -> String {
+    similar::utils::diff_lines(similar::Algorithm::Myers, old, new)
+        .into_iter()
+        .map(|(tag, line)| format!("{tag}{line}"))
+        .collect()
+}
+
+/// Generate a diff between two values returned from `Store::mock_commit`.
+fn diff_store_commits(old: &SortedMap<String, String>, new: &SortedMap<String, String>) -> String {
+    use std::fmt::Write;
+    let mut result = String::new();
+    let keys = old.keys().chain(new.keys()).collect::<SortedSet<&String>>();
+    for key in keys {
+        let old = old.get(key).map(|s| &s[..]).unwrap_or("");
+        let new = new.get(key).map(|s| &s[..]).unwrap_or("");
+        if old == new {
+            writeln!(&mut result, "{key}: (unchanged)").unwrap();
+            continue;
+        }
+        let diff = generate_diff(old, new);
+        writeln!(&mut result, "{key}:\n{diff}").unwrap();
+    }
+    result
+}
+
+#[derive(Clone)]
+struct MockRegistryVersion {
+    version: semver::Version,
+    published_by: Option<CratesUserId>,
+    created_at: chrono::DateTime<chrono::Utc>,
+}
+
+fn reg_published_by(
+    version: VetVersion,
+    published_by: Option<CratesUserId>,
+    when: &str,
+) -> MockRegistryVersion {
+    assert!(
+        version.git_rev.is_none(),
+        "cannot publish a git version to registry"
+    );
+    MockRegistryVersion {
+        version: version.semver,
+        published_by,
+        created_at: chrono::DateTime::from_utc(
+            chrono::NaiveDateTime::new(
+                when.parse::<chrono::NaiveDate>().unwrap(),
+                chrono::NaiveTime::from_hms_opt(12, 0, 0).unwrap(),
+            ),
+            chrono::Utc,
+        ),
+    }
+}
+
+struct MockRegistryPackage {
+    versions: Vec<MockRegistryVersion>,
+    metadata: CratesAPICrateMetadata,
+}
+
+#[derive(Default)]
+struct MockRegistryBuilder {
+    users: FastMap<CratesUserId, CratesAPIUser>,
+    packages: FastMap<PackageName, MockRegistryPackage>,
+}
+
+impl MockRegistryBuilder {
+    fn new() -> Self {
+        Default::default()
+    }
+
+    fn user(&mut self, id: CratesUserId, login: &str, name: &str) -> &mut Self {
+        self.users.insert(
+            id,
+            CratesAPIUser {
+                id,
+                login: login.to_owned(),
+                name: Some(name.to_owned()),
+            },
+        );
+        self
+    }
+
+    fn package(&mut self, name: PackageStr<'_>, versions: &[MockRegistryVersion]) -> &mut Self {
+        self.package_m(
+            name,
+            CratesAPICrateMetadata {
+                description: None,
+                repository: None,
+            },
+            versions,
+        )
+    }
+
+    fn package_m(
+        &mut self,
+        name: PackageStr<'_>,
+        metadata: CratesAPICrateMetadata,
+        versions: &[MockRegistryVersion],
+    ) -> &mut Self {
+        // To keep things simple, only handle the URL for 4+ characters in package names for now.
+        assert!(name.len() >= 4);
+        self.packages.insert(
+            name.to_owned(),
+            MockRegistryPackage {
+                metadata,
+                versions: versions.to_owned(),
+            },
+        );
+        self
+    }
+
+    fn serve(&self, network: &mut Network) {
+        for (name, pkg) in &self.packages {
+            // Serve the index entry as part of the http index.
+            network.mock_serve(
+                format!(
+                    "https://index.crates.io/{}/{}/{name}",
+                    &name[0..2],
+                    &name[2..4]
+                ).to_ascii_lowercase(),
+               pkg.versions
+                    .iter()
+                    .map(|v| {
+                        serde_json::to_string(&json!({
+                            "name": name,
+                            "vers": &v.version,
+                            "deps": [],
+                            "cksum": "90527ab4abff2f0608cdb1a78e2349180e1d92059f59b5a65ce2a1a15a499b73",
+                            "features": {},
+                            "yanked": false
+                        }))
+                        .unwrap()
+                    })
+                    .collect::<Vec<_>>()
+                    .join("\n"),
+            );
+
+            // Serve the crates.io API to match the http index and host extra metadata.
+            //
+            // NOTE: crates.io actually serves the API case-insensitively,
+            // unlike the http index, which is case-sensitive (and lowercase).
+            // Preserving case here matches how we currently construct the API
+            // url internally, but may need to be changed in the future.
+            network.mock_serve_json(
+                format!("https://crates.io/api/v1/crates/{name}"),
+                &CratesAPICrate {
+                    crate_data: pkg.metadata.clone(),
+                    versions: pkg
+                        .versions
+                        .iter()
+                        .map(|v| CratesAPIVersion {
+                            created_at: v.created_at,
+                            num: v.version.clone(),
+                            published_by: v.published_by.map(|id| {
+                                let user = &self.users[&id];
+                                CratesAPIUser {
+                                    id,
+                                    login: user.login.clone(),
+                                    name: user.name.clone(),
+                                }
+                            }),
+                        })
+                        .collect(),
+                },
+            )
+        }
+    }
+}
+
+// TESTING BACKLOG:
+//
+// * custom policies
+//   * basic
+//   * custom criteria to third-party
+//   * custom criteria to first-party
+//   * two first-parties depending on the same thing
+//      * which is itself first-party
+//      * which is a third-party
+//      * with different policies
+//         * where only the weaker one is satisfied (fail but give good diagnostic)
+//
+// * misc
+//   * git deps are first party but not in workspace
+//   * path deps are first party but not in workspace
+//   * multiple root packages
+//   * weird workspaces
+//   * running from weird directories
+//   * a node explicitly setting all its dependency_criteria to "no reqs"
+//     * ...should this just be an error? that feels wrong to do. otherwise:
+//       * with perfectly fine children
+//       * with children that fail to validate at all
+//
+// * malformed inputs:
+//   * no default criteria specified
+//   * referring to non-existent criteria
+//   * referring to non-existent crates (in crates.io? or just in our dep graph?)
+//   * referring to non-existent versions?
+//   * Bad delta syntax
+//   * Bad version syntax
+//   * entries in tomls that don't map to anything (at least warn to catch typos?)
+//     * might be running an old version of cargo-vet on a newer repo?
diff --git a/src/tests/regenerate_unaudited.rs b/src/tests/regenerate_unaudited.rs
new file mode 100644
index 0000000..04bb622
--- /dev/null
+++ b/src/tests/regenerate_unaudited.rs
@@ -0,0 +1,797 @@
+use super::*;
+
+fn get_exemptions(store: &Store) -> String {
+    for (name, exemptions) in &store.config.exemptions {
+        assert!(
+            // `is_sorted` is unstable
+            exemptions.windows(2).all(|elts| elts[0] <= elts[1]),
+            "exemptions for {name} aren't sorted"
+        );
+    }
+    toml_edit::ser::to_string_pretty(&store.config.exemptions).unwrap()
+}
+
+fn basic_regenerate(cfg: &Config, store: &mut Store) {
+    crate::resolver::update_store(cfg, store, |_| crate::resolver::UpdateMode {
+        search_mode: crate::resolver::SearchMode::RegenerateExemptions,
+        prune_exemptions: true,
+        prune_imports: true,
+    });
+}
+
+fn basic_minimize(cfg: &Config, store: &mut Store) {
+    crate::resolver::update_store(cfg, store, |_| crate::resolver::UpdateMode {
+        search_mode: crate::resolver::SearchMode::PreferFreshImports,
+        prune_exemptions: true,
+        prune_imports: true,
+    });
+}
+
+#[test]
+fn builtin_simple_exemptions_not_a_real_dep_regenerate() {
+    // (Pass) there's an exemptions entry for a package that isn't in our tree at all.
+    // Should strip the result and produce an empty exemptions file.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, audits, imports) = builtin_files_full_audited(&metadata);
+
+    config.exemptions.insert(
+        "fake-dep".to_string(),
+        vec![exemptions(ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+    );
+
+    let mut store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+    basic_regenerate(&cfg, &mut store);
+
+    let exemptions = get_exemptions(&store);
+    insta::assert_snapshot!("builtin-simple-not-a-real-dep-regenerate", exemptions);
+}
+
+#[test]
+fn builtin_simple_deps_exemptions_overbroad_regenerate() {
+    // (Pass) the exemptions entry is needed but it's overbroad
+    // Should downgrade from safe-to-deploy to safe-to-run
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple_deps();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = builtin_files_full_audited(&metadata);
+
+    audits.audits.insert("dev".to_string(), vec![]);
+
+    config.exemptions.insert(
+        "dev".to_string(),
+        vec![exemptions(ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+    );
+
+    let mut store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+    basic_regenerate(&cfg, &mut store);
+
+    let exemptions = get_exemptions(&store);
+    insta::assert_snapshot!("builtin-simple-unaudited-overbroad-regenerate", exemptions);
+}
+
+#[test]
+fn builtin_complex_exemptions_twins_regenerate() {
+    // (Pass) two versions of a crate exist and both are exemptions and they're needed
+    // Should be a no-op and both entries should remain
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::complex();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = builtin_files_full_audited(&metadata);
+
+    audits.audits.insert("third-core".to_string(), vec![]);
+
+    config.exemptions.insert(
+        "third-core".to_string(),
+        vec![
+            exemptions(ver(DEFAULT_VER), SAFE_TO_DEPLOY),
+            exemptions(ver(5), SAFE_TO_DEPLOY),
+        ],
+    );
+
+    let mut store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+    basic_regenerate(&cfg, &mut store);
+
+    let exemptions = get_exemptions(&store);
+    insta::assert_snapshot!("builtin-simple-unaudited-twins-regenerate", exemptions);
+}
+
+#[test]
+fn builtin_complex_exemptions_partial_twins_regenerate() {
+    // (Pass) two versions of a crate exist and one is exemptions and one is audited
+    // Should be a no-op and both entries should remain
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::complex();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = builtin_files_full_audited(&metadata);
+
+    audits.audits.insert(
+        "third-core".to_string(),
+        vec![full_audit(ver(5), SAFE_TO_DEPLOY)],
+    );
+
+    config.exemptions.insert(
+        "third-core".to_string(),
+        vec![exemptions(ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+    );
+
+    let mut store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+    basic_regenerate(&cfg, &mut store);
+
+    let exemptions = get_exemptions(&store);
+    insta::assert_snapshot!(
+        "builtin-simple-unaudited-partial-twins-regenerate",
+        exemptions
+    );
+}
+
+#[test]
+fn builtin_simple_exemptions_in_delta_regenerate() {
+    // (Pass) An audited entry overlaps a delta and isn't needed
+    // Should emit an empty exemptions file
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = builtin_files_full_audited(&metadata);
+
+    audits.audits.insert(
+        "third-party1".to_string(),
+        vec![
+            full_audit(ver(3), SAFE_TO_DEPLOY),
+            delta_audit(ver(3), ver(5), SAFE_TO_DEPLOY),
+            delta_audit(ver(5), ver(DEFAULT_VER), SAFE_TO_DEPLOY),
+        ],
+    );
+
+    config.exemptions.insert(
+        "third-party1".to_string(),
+        vec![exemptions(ver(5), SAFE_TO_DEPLOY)],
+    );
+
+    let mut store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+    basic_regenerate(&cfg, &mut store);
+
+    let exemptions = get_exemptions(&store);
+    insta::assert_snapshot!("builtin-simple-unaudited-in-delta-regenerate", exemptions);
+}
+
+#[test]
+fn builtin_simple_exemptions_in_full_regenerate() {
+    // (Pass) An audited entry overlaps a full audit and isn't needed
+    // Should emit an empty exemptions file
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = builtin_files_full_audited(&metadata);
+
+    audits.audits.insert(
+        "third-party1".to_string(),
+        vec![
+            full_audit(ver(3), SAFE_TO_DEPLOY),
+            delta_audit(ver(3), ver(5), SAFE_TO_DEPLOY),
+            delta_audit(ver(5), ver(DEFAULT_VER), SAFE_TO_DEPLOY),
+        ],
+    );
+
+    config.exemptions.insert(
+        "third-party1".to_string(),
+        vec![exemptions(ver(3), SAFE_TO_DEPLOY)],
+    );
+
+    let mut store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+    basic_regenerate(&cfg, &mut store);
+
+    let exemptions = get_exemptions(&store);
+    insta::assert_snapshot!("builtin-simple-unaudited-in-full-regenerate", exemptions);
+}
+
+#[test]
+fn builtin_simple_deps_exemptions_adds_uneeded_criteria_regenerate() {
+    // (Pass) An audited entry overlaps a full audit which is the cur version and isn't needed
+    // Should produce an empty exemptions
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple_deps();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = builtin_files_full_audited(&metadata);
+
+    audits.audits.insert(
+        "dev".to_string(),
+        vec![
+            full_audit(ver(5), SAFE_TO_RUN),
+            delta_audit(ver(5), ver(DEFAULT_VER), SAFE_TO_DEPLOY),
+        ],
+    );
+
+    config
+        .exemptions
+        .insert("dev".to_string(), vec![exemptions(ver(5), SAFE_TO_DEPLOY)]);
+
+    let mut store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+    basic_regenerate(&cfg, &mut store);
+
+    let exemptions = get_exemptions(&store);
+    insta::assert_snapshot!(
+        "builtin-simple-deps-unaudited-adds-uneeded-criteria-regenerate",
+        exemptions
+    );
+}
+
+#[test]
+fn builtin_dev_detection_exemptions_adds_uneeded_criteria_indirect_regenerate() {
+    // (Pass) An audited entry overlaps a full audit which is the cur version and isn't needed
+    // Should result in an empty exemptions file
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::dev_detection();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = builtin_files_minimal_audited(&metadata);
+
+    audits.audits.insert(
+        "simple-dev-indirect".to_string(),
+        vec![
+            full_audit(ver(5), SAFE_TO_RUN),
+            delta_audit(ver(5), ver(DEFAULT_VER), SAFE_TO_RUN),
+            delta_audit(ver(5), ver(DEFAULT_VER), SAFE_TO_DEPLOY),
+        ],
+    );
+
+    config.exemptions.insert(
+        "simple-dev-indirect".to_string(),
+        vec![exemptions(ver(5), SAFE_TO_DEPLOY)],
+    );
+
+    let mut store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+    basic_regenerate(&cfg, &mut store);
+
+    let exemptions = get_exemptions(&store);
+    insta::assert_snapshot!(
+        "builtin-dev-detection-unaudited-adds-uneeded-criteria-indirect-regenerate",
+        exemptions
+    );
+}
+
+#[test]
+fn builtin_simple_exemptions_extra_regenerate() {
+    // (Pass) there's an extra unused exemptions entry, but the other is needed.
+    // Should result in only the v10 exemptions entry remaining.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = builtin_files_full_audited(&metadata);
+
+    audits.audits.insert("third-party1".to_string(), vec![]);
+
+    config.exemptions.insert(
+        "third-party1".to_string(),
+        vec![
+            exemptions(ver(5), SAFE_TO_DEPLOY),
+            exemptions(ver(DEFAULT_VER), SAFE_TO_DEPLOY),
+        ],
+    );
+
+    let mut store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+    basic_regenerate(&cfg, &mut store);
+
+    let exemptions = get_exemptions(&store);
+    insta::assert_snapshot!("builtin-simple-unaudited-extra-regenerate", exemptions);
+}
+
+#[test]
+fn builtin_simple_exemptions_in_direct_full_regenerate() {
+    // (Pass) An audited entry overlaps a full audit which is the cur version and isn't needed
+    // Should produce an empty exemptions
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = builtin_files_full_audited(&metadata);
+
+    audits.audits.insert(
+        "third-party1".to_string(),
+        vec![full_audit(ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+    );
+
+    config.exemptions.insert(
+        "third-party1".to_string(),
+        vec![exemptions(ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+    );
+
+    let mut store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+    basic_regenerate(&cfg, &mut store);
+
+    let exemptions = get_exemptions(&store);
+    insta::assert_snapshot!(
+        "builtin-simple-unaudited-in-direct-full-regenerate",
+        exemptions
+    );
+}
+
+#[test]
+fn builtin_simple_exemptions_nested_weaker_req_regenerate() {
+    // (Pass) A dep that has weaker requirements on its dep
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = builtin_files_full_audited(&metadata);
+
+    audits.audits.insert(
+        "third-party1".to_string(),
+        vec![
+            delta_audit(ver(3), ver(6), SAFE_TO_DEPLOY),
+            delta_audit(ver(6), ver(DEFAULT_VER), SAFE_TO_DEPLOY),
+        ],
+    );
+    audits.audits.insert(
+        "transitive-third-party1".to_string(),
+        vec![
+            delta_audit(ver(4), ver(8), SAFE_TO_RUN),
+            delta_audit(ver(8), ver(DEFAULT_VER), SAFE_TO_RUN),
+        ],
+    );
+
+    config.policy.insert(
+        "third-party1".to_string(),
+        dep_policy([("transitive-third-party1", [SAFE_TO_RUN])]),
+    );
+
+    config.exemptions.insert(
+        "third-party1".to_string(),
+        vec![exemptions(ver(3), SAFE_TO_DEPLOY)],
+    );
+
+    config.exemptions.insert(
+        "transitive-third-party1".to_string(),
+        vec![exemptions(ver(4), SAFE_TO_RUN)],
+    );
+
+    let mut store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+    basic_regenerate(&cfg, &mut store);
+
+    let exemptions = get_exemptions(&store);
+    insta::assert_snapshot!(
+        "builtin-simple-unaudited-nested-weaker-req-regenerate",
+        exemptions
+    );
+}
+
+#[test]
+fn builtin_simple_exemptions_nested_stronger_req_regenerate() {
+    // (Pass) A dep that has stronger requirements on its dep
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = builtin_files_full_audited(&metadata);
+
+    config.policy.insert(
+        "first-party".to_string(),
+        dep_policy([("third-party1", [SAFE_TO_RUN])]),
+    );
+
+    audits.audits.insert(
+        "third-party1".to_string(),
+        vec![
+            delta_audit(ver(3), ver(6), SAFE_TO_RUN),
+            delta_audit(ver(6), ver(DEFAULT_VER), SAFE_TO_RUN),
+        ],
+    );
+    audits.audits.insert(
+        "transitive-third-party1".to_string(),
+        vec![
+            delta_audit(ver(4), ver(8), SAFE_TO_DEPLOY),
+            delta_audit(ver(8), ver(DEFAULT_VER), SAFE_TO_DEPLOY),
+        ],
+    );
+
+    config.policy.insert(
+        "third-party1".to_string(),
+        dep_policy([("transitive-third-party1", [SAFE_TO_DEPLOY])]),
+    );
+
+    config.exemptions.insert(
+        "third-party1".to_string(),
+        vec![exemptions(ver(3), SAFE_TO_RUN)],
+    );
+
+    config.exemptions.insert(
+        "transitive-third-party1".to_string(),
+        vec![exemptions(ver(4), SAFE_TO_DEPLOY)],
+    );
+
+    let mut store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+    basic_regenerate(&cfg, &mut store);
+
+    let exemptions = get_exemptions(&store);
+    insta::assert_snapshot!(
+        "builtin-simple-unaudited-nested-stronger-req-regenerate",
+        exemptions
+    );
+}
+
+#[test]
+fn builtin_simple_audit_as_default_root_regenerate() {
+    // (Pass) the root is audit-as-crates-io with a default root policy
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, audits, imports) = builtin_files_inited(&metadata);
+
+    config
+        .policy
+        .insert("root-package".to_string(), audit_as_policy(Some(true)));
+    config.exemptions.insert(
+        "root-package".to_string(),
+        vec![exemptions(ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+    );
+
+    let mut store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+    basic_regenerate(&cfg, &mut store);
+
+    let exemptions = get_exemptions(&store);
+    insta::assert_snapshot!(
+        "builtin-simple-audit-as-default-root-regenerate",
+        exemptions
+    );
+}
+
+#[test]
+fn builtin_simple_audit_as_weaker_root_regenerate() {
+    // (Pass) the root is audit-as-crates-io with an explicit root policy
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, audits, imports) = builtin_files_inited(&metadata);
+
+    config.policy.insert(
+        "root-package".to_string(),
+        audit_as_policy_with(Some(true), |policy| {
+            policy.criteria = Some(vec![SAFE_TO_RUN.to_string().into()]);
+        }),
+    );
+    config.exemptions.insert(
+        "root-package".to_string(),
+        vec![exemptions(ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+    );
+
+    let mut store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+    basic_regenerate(&cfg, &mut store);
+
+    let exemptions = get_exemptions(&store);
+    insta::assert_snapshot!("builtin-simple-audit-as-weaker-root-regenerate", exemptions);
+}
+
+#[test]
+fn builtin_simple_exemptions_larger_diff_regenerate() {
+    // (Pass) if an exemption is for a larger diff than would be required for a
+    // full audit, it should still be preserved.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = builtin_files_inited(&metadata);
+
+    audits.audits.insert(
+        "third-party1".to_owned(),
+        vec![delta_audit(ver(11), ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+    );
+    config.exemptions.insert(
+        "third-party1".to_owned(),
+        vec![exemptions(ver(11), SAFE_TO_DEPLOY)],
+    );
+
+    let mut store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+    basic_regenerate(&cfg, &mut store);
+
+    let exemptions = get_exemptions(&store);
+    insta::assert_snapshot!(
+        "builtin-simple-exemptions-larger-diff-regenerate",
+        exemptions
+    );
+}
+
+#[test]
+fn builtin_simple_exemptions_broaden_basic() {
+    // (Pass) minimize_exemptions prefers broadening an existing exemption to
+    // generating a new one, but won't broaden if it won't help.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = builtin_files_inited(&metadata);
+
+    audits.audits.insert(
+        "third-party1".to_owned(),
+        vec![delta_audit(ver(5), ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+    );
+    // This exemption can be broadened due to the above audit.
+    config.exemptions.insert(
+        "third-party1".to_owned(),
+        vec![exemptions(ver(5), SAFE_TO_RUN)],
+    );
+
+    audits.audits.insert(
+        "third-party2".to_owned(),
+        vec![delta_audit(ver(5), ver(DEFAULT_VER), SAFE_TO_RUN)],
+    );
+    // This exemption cannot be broadened as there's no delta-audit for
+    // `safe-to-deploy`, so it will be dropped and replaced with a
+    // full-exemption.
+    config.exemptions.insert(
+        "third-party2".to_owned(),
+        vec![exemptions(ver(5), SAFE_TO_RUN)],
+    );
+
+    audits.audits.insert(
+        "transitive-third-party1".to_owned(),
+        vec![delta_audit(ver(5), ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+    );
+    // Exemptions with `suggest` cannot be expanded, but will be preserved, and
+    // a new `SAFE_TO_DEPLOY` exemption will be added for the same version so
+    // audits pass.
+    config.exemptions.insert(
+        "transitive-third-party1".to_owned(),
+        vec![{
+            let mut exemption = exemptions(ver(5), SAFE_TO_RUN);
+            exemption.suggest = false;
+            exemption
+        }],
+    );
+
+    let mut store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+    basic_regenerate(&cfg, &mut store);
+
+    let exemptions = get_exemptions(&store);
+    insta::assert_snapshot!("builtin-simple-exemptions-broaden-basic", exemptions);
+}
+
+#[test]
+fn builtin_simple_exemptions_regenerate_merge() {
+    // (Pass) minimize_exemptions will merge exemptions if it's allowed to.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = builtin_files_full_audited(&metadata);
+
+    audits.criteria.insert(
+        "criteria1".to_owned(),
+        criteria_implies("", [SAFE_TO_DEPLOY]),
+    );
+    audits.criteria.insert(
+        "criteria2".to_owned(),
+        criteria_implies("", [SAFE_TO_DEPLOY]),
+    );
+
+    audits.audits.insert(
+        "third-party1".to_owned(),
+        vec![full_audit(ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+    );
+
+    audits.audits.insert(
+        "transitive-third-party1".to_owned(),
+        vec![
+            delta_audit(ver(5), ver(DEFAULT_VER), "criteria1"),
+            delta_audit(ver(5), ver(DEFAULT_VER), "criteria2"),
+        ],
+    );
+
+    config.policy.insert(
+        "third-party1".to_string(),
+        dep_policy([("transitive-third-party1", ["criteria1", "criteria2"])]),
+    );
+
+    config.exemptions.insert(
+        "transitive-third-party1".to_owned(),
+        vec![
+            exemptions(ver(5), "criteria1"),
+            exemptions(ver(5), "criteria2"),
+        ],
+    );
+
+    let mut store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+    basic_regenerate(&cfg, &mut store);
+
+    let exemptions = get_exemptions(&store);
+    insta::assert_snapshot!("builtin-simple-exemptions-regenerate-merge", exemptions);
+}
+
+#[test]
+fn builtin_simple_exemptions_regenerate_merge_nonew() {
+    // (Pass) minimize_exemptions will not merge exemptions if it's not allowed to.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = builtin_files_full_audited(&metadata);
+
+    audits.criteria.insert(
+        "criteria1".to_owned(),
+        criteria_implies("", [SAFE_TO_DEPLOY]),
+    );
+    audits.criteria.insert(
+        "criteria2".to_owned(),
+        criteria_implies("", [SAFE_TO_DEPLOY]),
+    );
+
+    audits.audits.insert(
+        "third-party1".to_owned(),
+        vec![full_audit(ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+    );
+
+    audits.audits.insert(
+        "transitive-third-party1".to_owned(),
+        vec![
+            delta_audit(ver(5), ver(DEFAULT_VER), "criteria1"),
+            delta_audit(ver(5), ver(DEFAULT_VER), "criteria2"),
+        ],
+    );
+
+    config.policy.insert(
+        "third-party1".to_string(),
+        dep_policy([("transitive-third-party1", ["criteria1", "criteria2"])]),
+    );
+
+    config.exemptions.insert(
+        "transitive-third-party1".to_owned(),
+        vec![
+            exemptions(ver(5), "criteria1"),
+            exemptions(ver(5), "criteria2"),
+        ],
+    );
+
+    let mut store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+    basic_minimize(&cfg, &mut store);
+
+    let exemptions = get_exemptions(&store);
+    insta::assert_snapshot!(
+        "builtin-simple-exemptions-regenerate-merge-nonew",
+        exemptions
+    );
+}
+
+#[test]
+fn builtin_simple_exemptions_regenerate_nonew_failed() {
+    // (Pass) minimize_exemptions will only remove unknown packages if no new
+    // exemptions are allowed, and vet is failing.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, audits, imports) = builtin_files_inited(&metadata);
+
+    config.exemptions.clear();
+
+    config.exemptions.insert(
+        "transitive-third-party1".to_owned(),
+        vec![
+            exemptions(ver(300), SAFE_TO_DEPLOY),
+            exemptions(ver(400), SAFE_TO_DEPLOY),
+        ],
+    );
+    config.exemptions.insert(
+        "third-party1".to_owned(),
+        vec![exemptions(ver(300), SAFE_TO_DEPLOY)],
+    );
+    config.exemptions.insert(
+        "random-crate".to_owned(),
+        vec![exemptions(ver(300), SAFE_TO_DEPLOY)],
+    );
+
+    let mut store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+    basic_minimize(&cfg, &mut store);
+
+    let exemptions = get_exemptions(&store);
+    insta::assert_snapshot!(
+        "builtin-simple-exemptions-regenerate-nonew-failed",
+        exemptions
+    );
+}
+
+#[test]
+fn builtin_complex_exemptions_preferred_path() {
+    // (Pass) minimizing exemptions will remove a now-unnecessary full exemption
+    // of another exemption already exists for an earlier version, with a valid
+    // delta-audit.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::complex();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = builtin_files_inited(&metadata);
+
+    audits.audits.insert(
+        "third-core".to_owned(),
+        vec![delta_audit(ver(5), ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+    );
+
+    config.exemptions.insert(
+        "third-core".to_owned(),
+        vec![
+            exemptions(ver(5), SAFE_TO_DEPLOY),
+            exemptions(ver(DEFAULT_VER), SAFE_TO_DEPLOY),
+        ],
+    );
+
+    let mut store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+    basic_minimize(&cfg, &mut store);
+
+    let exemptions = get_exemptions(&store);
+    insta::assert_snapshot!(exemptions);
+}
+
+#[test]
+fn builtin_complex_exemptions_preferred_path_fresh() {
+    // (Pass) regenerating exemptions will choose to only add a full exemption
+    // to an earlier version if a delta audit exists.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::complex();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = builtin_files_inited(&metadata);
+
+    audits.audits.insert(
+        "third-core".to_owned(),
+        vec![delta_audit(ver(5), ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+    );
+
+    config.exemptions.remove("third-core");
+
+    let mut store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg(&metadata);
+    basic_regenerate(&cfg, &mut store);
+
+    let exemptions = get_exemptions(&store);
+    insta::assert_snapshot!(exemptions);
+}
diff --git a/src/tests/registry.rs b/src/tests/registry.rs
new file mode 100644
index 0000000..a90db73
--- /dev/null
+++ b/src/tests/registry.rs
@@ -0,0 +1,36 @@
+use super::*;
+
+#[test]
+fn test_registry_parse_error() {
+    // Check that we can recover from an invalid registry with a useful error.
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, audits, imports) = builtin_files_no_exemptions(&metadata);
+
+    let mut network = Network::new_mock();
+    network.mock_serve(
+        crate::storage::REGISTRY_URL,
+        r#"
+[registry.remote]
+url = 10 # invalid!
+"#,
+    );
+
+    let cfg = mock_cfg(&metadata);
+
+    let store = Store::mock_online(&cfg, config, audits, imports, &network, false).unwrap();
+
+    let report = crate::resolver::resolve(&metadata, None, &store);
+    let suggest = report
+        .compute_suggest(&cfg, &store, Some(&network))
+        .unwrap();
+
+    let human_output = BasicTestOutput::new();
+    report
+        .print_human(&human_output.clone().as_dyn(), &cfg, suggest.as_ref())
+        .unwrap();
+
+    insta::assert_snapshot!(human_output.to_string());
+}
diff --git a/src/tests/renew.rs b/src/tests/renew.rs
new file mode 100644
index 0000000..f5de6c7
--- /dev/null
+++ b/src/tests/renew.rs
@@ -0,0 +1,285 @@
+use super::*;
+
+use crate::{do_cmd_renew, WildcardAuditRenewal};
+
+struct ExpireTest {
+    today: chrono::NaiveDate,
+    start: chrono::NaiveDate,
+    end: chrono::NaiveDate,
+}
+
+impl ExpireTest {
+    pub fn new(future: chrono::Duration) -> Self {
+        let today = mock_today();
+        let end = today + future;
+        ExpireTest {
+            today,
+            start: today,
+            end,
+        }
+    }
+
+    pub fn with_start(start: chrono::Duration, end: chrono::Duration) -> Self {
+        let today = mock_today();
+        let start = today + start;
+        let end = today + end;
+        ExpireTest { today, start, end }
+    }
+
+    pub fn test_complex<'a, F, I>(
+        &'a self,
+        args: I,
+        mock: MockMetadata,
+        f: F,
+    ) -> (Store, Arc<BasicTestOutput>)
+    where
+        F: FnOnce(&Self, &mut crate::format::WildcardAudits),
+        I: IntoIterator<Item = &'a str>,
+    {
+        let _enter = TEST_RUNTIME.enter();
+        let metadata = mock.metadata();
+        let (config, mut audits, imports) = builtin_files_no_exemptions(&metadata);
+
+        f(self, &mut audits.wildcard_audits);
+
+        let mut store = Store::mock(config, audits, imports);
+
+        let cfg = mock_cfg_args(&metadata, ["cargo", "vet", "renew"].into_iter().chain(args));
+        let sub_args = if let Some(crate::cli::Commands::Renew(sub_args)) = &cfg.cli.command {
+            sub_args
+        } else {
+            unreachable!();
+        };
+
+        let output = BasicTestOutput::new();
+        do_cmd_renew(&output.clone().as_dyn(), &cfg, &mut store, sub_args);
+        (store, output)
+    }
+
+    pub fn test_simple(&self) -> chrono::NaiveDate {
+        let (store, _) = self.test_complex(
+            ["--expiring"],
+            MockMetadata::haunted_tree(),
+            |me, audits| {
+                audits.insert(
+                    "third-normal".into(),
+                    vec![WildcardEntry {
+                        who: vec!["user".to_owned().into()],
+                        criteria: vec!["safe-to-deploy".to_owned().into()],
+                        user_id: 1,
+                        start: me.start.into(),
+                        end: me.end.into(),
+                        renew: None,
+                        notes: None,
+                        aggregated_from: Default::default(),
+                        is_fresh_import: false,
+                    }],
+                );
+            },
+        );
+        *store.audits.wildcard_audits["third-normal"][0].end
+    }
+
+    pub fn renew_date(&self) -> chrono::NaiveDate {
+        self.today + chrono::Months::new(12)
+    }
+}
+
+/// The renew command should update an expiring wildcard audit.
+#[test]
+fn renew_expiring_wildcard_audits() {
+    let expire = ExpireTest::new(chrono::Duration::weeks(2));
+    let end = expire.test_simple();
+    assert_eq!(end, expire.renew_date());
+}
+
+/// The renew command should update an already-expired wildcard audit.
+#[test]
+fn renew_already_expired_wildcard_audits() {
+    let expire = ExpireTest::with_start(chrono::Duration::weeks(-5), chrono::Duration::weeks(-3));
+    let end = expire.test_simple();
+    assert_eq!(end, expire.renew_date());
+}
+
+/// The renew command should not update anything if end dates are far enough in the future.
+#[test]
+fn renew_no_expiring_wildcard_audits() {
+    let expire = ExpireTest::new(chrono::Duration::weeks(7));
+    let end = expire.test_simple();
+    assert_eq!(end, expire.end);
+}
+
+/// Providing a specific crate name should only renew that crate.
+#[test]
+fn renew_specific_crate() {
+    let expire = ExpireTest::new(chrono::Duration::weeks(3));
+    let (store, _) =
+        expire.test_complex(["third-dev"], MockMetadata::haunted_tree(), |et, audits| {
+            audits.insert(
+                "third-normal".into(),
+                vec![WildcardEntry {
+                    who: vec!["user".to_owned().into()],
+                    criteria: vec!["safe-to-deploy".to_owned().into()],
+                    user_id: 1,
+                    start: et.start.into(),
+                    end: et.end.into(),
+                    renew: None,
+                    notes: None,
+                    aggregated_from: Default::default(),
+                    is_fresh_import: false,
+                }],
+            );
+            audits.insert(
+                "third-dev".into(),
+                vec![WildcardEntry {
+                    who: vec!["user".to_owned().into()],
+                    criteria: vec!["safe-to-deploy".to_owned().into()],
+                    user_id: 1,
+                    start: et.start.into(),
+                    end: et.end.into(),
+                    renew: None,
+                    notes: None,
+                    aggregated_from: Default::default(),
+                    is_fresh_import: false,
+                }],
+            );
+        });
+
+    assert_eq!(
+        *store.audits.wildcard_audits["third-normal"][0].end,
+        expire.end
+    );
+    assert_eq!(
+        *store.audits.wildcard_audits["third-dev"][0].end,
+        expire.renew_date()
+    );
+}
+
+/// A wildcard entry with `renew = false` shouldn't be updated by renew.
+#[test]
+fn renew_expiring_set_false() {
+    let expire = ExpireTest::new(chrono::Duration::weeks(3));
+    let (store, _) = expire.test_complex(
+        ["--expiring"],
+        MockMetadata::haunted_tree(),
+        |et, audits| {
+            audits.insert(
+                "third-normal".into(),
+                vec![WildcardEntry {
+                    who: vec!["user".to_owned().into()],
+                    criteria: vec!["safe-to-deploy".to_owned().into()],
+                    user_id: 1,
+                    start: et.start.into(),
+                    end: et.end.into(),
+                    renew: Some(false),
+                    notes: None,
+                    aggregated_from: Default::default(),
+                    is_fresh_import: false,
+                }],
+            );
+            audits.insert(
+                "third-dev".into(),
+                vec![WildcardEntry {
+                    who: vec!["user".to_owned().into()],
+                    criteria: vec!["safe-to-deploy".to_owned().into()],
+                    user_id: 1,
+                    start: et.start.into(),
+                    end: et.end.into(),
+                    renew: None,
+                    notes: None,
+                    aggregated_from: Default::default(),
+                    is_fresh_import: false,
+                }],
+            );
+        },
+    );
+
+    assert_eq!(
+        *store.audits.wildcard_audits["third-normal"][0].end,
+        expire.end
+    );
+    assert_eq!(
+        *store.audits.wildcard_audits["third-dev"][0].end,
+        expire.renew_date()
+    );
+}
+
+fn wildcard_audit_renewal_test<'a, Args, Create>(test_name: &str, args: Args, create: Create)
+where
+    Args: IntoIterator<Item = &'a str>,
+    Create: for<'s> FnOnce(&Config, &'s mut Store) -> WildcardAuditRenewal<'s>,
+{
+    let _enter = TEST_RUNTIME.enter();
+    let metadata = MockMetadata::simple().metadata();
+    let (config, mut audits, imports) = builtin_files_no_exemptions(&metadata);
+
+    let today = mock_today();
+    use chrono::Duration;
+    let start = today - Duration::weeks(10);
+    let expired = today - Duration::weeks(1);
+    let expiring = today + Duration::weeks(1);
+    let not_expiring = today + Duration::weeks(7);
+
+    let entry = |user_id: u64, end: chrono::NaiveDate, renew: Option<bool>| -> WildcardEntry {
+        WildcardEntry {
+            who: vec!["user".to_owned().into()],
+            criteria: vec!["safe-to-deploy".to_owned().into()],
+            user_id,
+            start: start.into(),
+            end: end.into(),
+            renew,
+            notes: None,
+            aggregated_from: Default::default(),
+            is_fresh_import: false,
+        }
+    };
+
+    audits.wildcard_audits.insert(
+        "foo".into(),
+        vec![
+            entry(1, expired, None),
+            entry(2, expiring, None),
+            entry(3, expiring, Some(false)),
+            entry(4, not_expiring, Some(false)),
+            entry(5, expired, Some(true)),
+        ],
+    );
+    audits.wildcard_audits.insert(
+        "bar".into(),
+        vec![entry(3, expired, Some(false)), entry(6, not_expiring, None)],
+    );
+    audits
+        .wildcard_audits
+        .insert("baz".into(), vec![entry(7, expiring, None)]);
+    audits
+        .wildcard_audits
+        .insert("quux".into(), vec![entry(8, expired, None)]);
+
+    let mut store = Store::mock(config, audits, imports);
+    let cfg = mock_cfg_args(&metadata, ["cargo", "vet", "renew"].into_iter().chain(args));
+    let before = store.mock_commit();
+    create(&cfg, &mut store).renew(today + chrono::Months::new(12));
+    let after = store.mock_commit();
+    insta::assert_snapshot!(test_name, diff_store_commits(&before, &after));
+}
+
+#[test]
+fn renew_expiring_selection_logic() {
+    wildcard_audit_renewal_test(
+        "renew-expiring-selection-logic",
+        ["--expiring"],
+        |cfg, store| {
+            let renewal = WildcardAuditRenewal::expiring(cfg, store);
+            assert_eq!(renewal.expired_crates(), vec!["foo", "quux"]);
+            assert_eq!(renewal.expiring_crates(), vec!["baz", "foo"]);
+            renewal
+        },
+    );
+}
+
+#[test]
+fn renew_specific_selection_logic() {
+    wildcard_audit_renewal_test("renew-specific-selection-logic", ["foo"], |_, store| {
+        WildcardAuditRenewal::single_crate("foo", store).expect("store inconsistent")
+    });
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__aggregate__merge_audits_files_basic.snap b/src/tests/snapshots/cargo_vet__tests__aggregate__merge_audits_files_basic.snap
new file mode 100644
index 0000000..2ceadf2
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__aggregate__merge_audits_files_basic.snap
@@ -0,0 +1,46 @@
+---
+source: src/tests/aggregate.rs
+expression: output
+---
+
+[[wildcard-audits.package2]]
+criteria = "safe-to-deploy"
+user-id = 1
+start = "2022-12-01"
+end = "2023-01-01"
+aggregated-from = "https://source1.example.com/supply_chain/audits.toml"
+
+[[wildcard-audits.package2]]
+criteria = "safe-to-deploy"
+user-id = 2
+start = "2022-12-01"
+end = "2023-01-01"
+aggregated-from = "https://source2.example.com/supply_chain/audits.toml"
+
+[[wildcard-audits.package3]]
+criteria = "safe-to-deploy"
+user-id = 1
+start = "2022-12-01"
+end = "2023-01-01"
+aggregated-from = "https://source2.example.com/supply_chain/audits.toml"
+
+[[audits.package1]]
+criteria = "safe-to-deploy"
+version = "10.0.0"
+aggregated-from = "https://source1.example.com/supply_chain/audits.toml"
+
+[[audits.package1]]
+criteria = "safe-to-deploy"
+version = "5.0.0"
+aggregated-from = "https://source2.example.com/supply_chain/audits.toml"
+
+[[audits.package1]]
+criteria = "safe-to-deploy"
+version = "10.0.0"
+aggregated-from = "https://source2.example.com/supply_chain/audits.toml"
+
+[[audits.package2]]
+criteria = "safe-to-deploy"
+version = "10.0.0"
+aggregated-from = "https://source2.example.com/supply_chain/audits.toml"
+
diff --git a/src/tests/snapshots/cargo_vet__tests__aggregate__merge_audits_files_custom_criteria.snap b/src/tests/snapshots/cargo_vet__tests__aggregate__merge_audits_files_custom_criteria.snap
new file mode 100644
index 0000000..5cc3bc3
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__aggregate__merge_audits_files_custom_criteria.snap
@@ -0,0 +1,40 @@
+---
+source: src/tests/aggregate.rs
+expression: output
+---
+
+[criteria.criteria1]
+description = "Criteria 1"
+aggregated-from = [
+    "https://elsewhere.example.com/audits.toml",
+    "https://source1.example.com/supply_chain/audits.toml",
+]
+
+[criteria.criteria2]
+description = "Criteria 2"
+aggregated-from = "https://source1.example.com/supply_chain/audits.toml"
+
+[criteria.criteria3]
+description = "Criteria 3"
+aggregated-from = "https://source2.example.com/supply_chain/audits.toml"
+
+[[audits.package1]]
+criteria = "criteria1"
+version = "10.0.0"
+aggregated-from = "https://source1.example.com/supply_chain/audits.toml"
+
+[[audits.package1]]
+criteria = "criteria2"
+version = "10.0.0"
+aggregated-from = "https://source1.example.com/supply_chain/audits.toml"
+
+[[audits.package1]]
+criteria = "criteria3"
+version = "10.0.0"
+aggregated-from = "https://source2.example.com/supply_chain/audits.toml"
+
+[[audits.package2]]
+criteria = "criteria1"
+version = "10.0.0"
+aggregated-from = "https://source2.example.com/supply_chain/audits.toml"
+
diff --git a/src/tests/snapshots/cargo_vet__tests__aggregate__merge_audits_files_custom_criteria_conflict.snap b/src/tests/snapshots/cargo_vet__tests__aggregate__merge_audits_files_custom_criteria_conflict.snap
new file mode 100644
index 0000000..7d805bb
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__aggregate__merge_audits_files_custom_criteria_conflict.snap
@@ -0,0 +1,27 @@
+---
+source: src/tests/aggregate.rs
+expression: output
+---
+  × there were errors aggregating source audit files
+
+Error:   × criteria description mismatch for criteria1
+  │ https://source1.example.com/supply_chain/audits.toml:
+  │ Criteria 1
+  │ https://source2.example.com/supply_chain/audits.toml:
+  │ Criteria 1 (alt)
+Error:   × criteria description mismatch for criteria2
+  │ https://source1.example.com/supply_chain/audits.toml:
+  │ Criteria 2
+  │ https://source2.example.com/supply_chain/audits.toml:
+  │ (URL) https://criteria2
+Error:   × criteria description mismatch for criteria3
+  │ https://source1.example.com/supply_chain/audits.toml:
+  │ (URL) https://criteria3
+  │ https://source2.example.com/supply_chain/audits.toml:
+  │ (URL) https://criteria3.alt
+Error:   × implied criteria mismatch for criteria3
+  │ https://source1.example.com/supply_chain/audits.toml:
+  │  - criteria2
+  │ https://source2.example.com/supply_chain/audits.toml:
+  │  - criteria1
+
diff --git a/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__audit-as-crates-io-metadata-mismatch.snap b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__audit-as-crates-io-metadata-mismatch.snap
new file mode 100644
index 0000000..f3f20b5
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__audit-as-crates-io-metadata-mismatch.snap
@@ -0,0 +1,10 @@
+---
+source: src/tests/audit_as_crates_io.rs
+expression: output
+---
+  × There are some issues with your policy.audit-as-crates-io entries
+
+Error:   × some audit-as-crates-io packages don't match published crates.io versions
+  │   descriptive:10.0.0
+  help: Remove the audit-as-crates-io entries or make them `false`
+
diff --git a/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__audit-as-crates-io-need-update.snap b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__audit-as-crates-io-need-update.snap
new file mode 100644
index 0000000..14b0b25
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__audit-as-crates-io-need-update.snap
@@ -0,0 +1,5 @@
+---
+source: src/tests/audit_as_crates_io.rs
+expression: output
+---
+
diff --git a/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__audit-as-crates-io-non-first-party.snap b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__audit-as-crates-io-non-first-party.snap
new file mode 100644
index 0000000..6ec32c1
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__audit-as-crates-io-non-first-party.snap
@@ -0,0 +1,12 @@
+---
+source: src/tests/audit_as_crates_io.rs
+expression: output
+---
+  × There are some issues with your policy.audit-as-crates-io entries
+
+Error:   × some audit-as-crates-io policies don't match first-party crates
+  │   third-core
+  │   thirdA
+  │   thirdAB
+  help: Remove the audit-as-crates-io entries
+
diff --git a/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__complex-audit-as-crates-io-all-false.snap b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__complex-audit-as-crates-io-all-false.snap
new file mode 100644
index 0000000..14b0b25
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__complex-audit-as-crates-io-all-false.snap
@@ -0,0 +1,5 @@
+---
+source: src/tests/audit_as_crates_io.rs
+expression: output
+---
+
diff --git a/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__complex-audit-as-crates-io-all-true.snap b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__complex-audit-as-crates-io-all-true.snap
new file mode 100644
index 0000000..b61c5c4
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__complex-audit-as-crates-io-all-true.snap
@@ -0,0 +1,11 @@
+---
+source: src/tests/audit_as_crates_io.rs
+expression: output
+---
+  × There are some issues with your policy.audit-as-crates-io entries
+
+Error:   × some audit-as-crates-io packages don't match published crates.io versions
+  │   rootA:10.0.0
+  │   rootB:10.0.0
+  help: Remove the audit-as-crates-io entries or make them `false`
+
diff --git a/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__complex-audit-as-crates-io-max-wrong-json.snap b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__complex-audit-as-crates-io-max-wrong-json.snap
new file mode 100644
index 0000000..8f6330b
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__complex-audit-as-crates-io-max-wrong-json.snap
@@ -0,0 +1,5 @@
+---
+source: src/tests/audit_as_crates_io.rs
+expression: output
+---
+{"message": "There are some issues with your policy.audit-as-crates-io entries","severity": "error","causes": [],"labels": [],"related": [{"message": "Some non-crates.io-fetched packages match published crates.io versions\n  firstA:10.0.0\n  firstAB:10.0.0\n  firstB:10.0.0\n  firstB-nodeps:10.0.0","severity": "error","causes": [],"help": "Add a `policy.*.audit-as-crates-io` entry for them","labels": [],"related": []},{"message": "some audit-as-crates-io packages don't match published crates.io versions\n  rootA:10.0.0\n  rootB:10.0.0","severity": "error","causes": [],"help": "Remove the audit-as-crates-io entries or make them `false`","labels": [],"related": []}]}
diff --git a/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__complex-audit-as-crates-io-max-wrong.snap b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__complex-audit-as-crates-io-max-wrong.snap
new file mode 100644
index 0000000..18d47fc
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__complex-audit-as-crates-io-max-wrong.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/audit_as_crates_io.rs
+expression: output
+---
+  × There are some issues with your policy.audit-as-crates-io entries
+
+Error:   × Some non-crates.io-fetched packages match published crates.io versions
+  │   firstA:10.0.0
+  │   firstAB:10.0.0
+  │   firstB:10.0.0
+  │   firstB-nodeps:10.0.0
+  help: Add a `policy.*.audit-as-crates-io` entry for them
+Error:   × some audit-as-crates-io packages don't match published crates.io versions
+  │   rootA:10.0.0
+  │   rootB:10.0.0
+  help: Remove the audit-as-crates-io entries or make them `false`
+
diff --git a/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__complex-audit-as-crates-io.snap b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__complex-audit-as-crates-io.snap
new file mode 100644
index 0000000..9acdf01
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__complex-audit-as-crates-io.snap
@@ -0,0 +1,13 @@
+---
+source: src/tests/audit_as_crates_io.rs
+expression: output
+---
+  × There are some issues with your policy.audit-as-crates-io entries
+
+Error:   × Some non-crates.io-fetched packages match published crates.io versions
+  │   firstA:10.0.0
+  │   firstAB:10.0.0
+  │   firstB:10.0.0
+  │   firstB-nodeps:10.0.0
+  help: Add a `policy.*.audit-as-crates-io` entry for them
+
diff --git a/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__cycle-audit-as-crates-io.snap b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__cycle-audit-as-crates-io.snap
new file mode 100644
index 0000000..14b0b25
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__cycle-audit-as-crates-io.snap
@@ -0,0 +1,5 @@
+---
+source: src/tests/audit_as_crates_io.rs
+expression: output
+---
+
diff --git a/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__dev-detection-audit-as-crates-io.snap b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__dev-detection-audit-as-crates-io.snap
new file mode 100644
index 0000000..14b0b25
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__dev-detection-audit-as-crates-io.snap
@@ -0,0 +1,5 @@
+---
+source: src/tests/audit_as_crates_io.rs
+expression: output
+---
+
diff --git a/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__haunted-audit-as-crates-io.snap b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__haunted-audit-as-crates-io.snap
new file mode 100644
index 0000000..cde2045
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__haunted-audit-as-crates-io.snap
@@ -0,0 +1,11 @@
+---
+source: src/tests/audit_as_crates_io.rs
+expression: output
+---
+  × There are some issues with your policy.audit-as-crates-io entries
+
+Error:   × Some non-crates.io-fetched packages match published crates.io versions
+  │   root:10.0.0
+  │   first:10.0.0
+  help: Add a `policy.*.audit-as-crates-io` entry for them
+
diff --git a/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__simple-audit-as-crates-io-all-false.snap b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__simple-audit-as-crates-io-all-false.snap
new file mode 100644
index 0000000..14b0b25
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__simple-audit-as-crates-io-all-false.snap
@@ -0,0 +1,5 @@
+---
+source: src/tests/audit_as_crates_io.rs
+expression: output
+---
+
diff --git a/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__simple-audit-as-crates-io-all-true.snap b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__simple-audit-as-crates-io-all-true.snap
new file mode 100644
index 0000000..14b0b25
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__simple-audit-as-crates-io-all-true.snap
@@ -0,0 +1,5 @@
+---
+source: src/tests/audit_as_crates_io.rs
+expression: output
+---
+
diff --git a/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__simple-audit-as-crates-io.snap b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__simple-audit-as-crates-io.snap
new file mode 100644
index 0000000..e411397
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__simple-audit-as-crates-io.snap
@@ -0,0 +1,11 @@
+---
+source: src/tests/audit_as_crates_io.rs
+expression: output
+---
+  × There are some issues with your policy.audit-as-crates-io entries
+
+Error:   × Some non-crates.io-fetched packages match published crates.io versions
+  │   root-package:10.0.0
+  │   first-party:10.0.0
+  help: Add a `policy.*.audit-as-crates-io` entry for them
+
diff --git a/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__simple-deps-audit-as-crates-io.snap b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__simple-deps-audit-as-crates-io.snap
new file mode 100644
index 0000000..14b0b25
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__audit_as_crates_io__simple-deps-audit-as-crates-io.snap
@@ -0,0 +1,5 @@
+---
+source: src/tests/audit_as_crates_io.rs
+expression: output
+---
+
diff --git a/src/tests/snapshots/cargo_vet__tests__certify__mock-delta-certify-flow.snap b/src/tests/snapshots/cargo_vet__tests__certify__mock-delta-certify-flow.snap
new file mode 100644
index 0000000..2f30675
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__certify__mock-delta-certify-flow.snap
@@ -0,0 +1,74 @@
+---
+source: src/tests/certify.rs
+expression: result
+---
+OUTPUT:
+<<<EDITING VET_CERTIFY>>>
+# Please read the following criteria and then follow the instructions below:
+
+# === BEGIN CRITERIA "safe-to-deploy" ===
+#
+# This crate will not introduce a serious security vulnerability to production
+# software exposed to untrusted input.
+#
+# Auditors are not required to perform a full logic review of the entire crate.
+# Rather, they must review enough to fully reason about the behavior of all unsafe
+# blocks and usage of powerful imports. For any reasonable usage of the crate in
+# real-world software, an attacker must not be able to manipulate the runtime
+# behavior of these sections in an exploitable or surprising way.
+#
+# Ideally, all unsafe code is fully sound, and ambient capabilities (e.g.
+# filesystem access) are hardened against manipulation and consistent with the
+# advertised behavior of the crate. However, some discretion is permitted. In such
+# cases, the nature of the discretion should be recorded in the `notes` field of
+# the audit record.
+#
+# For crates which generate deployed code (e.g. build dependencies or procedural
+# macros), reasonable usage of the crate should output code which meets the above
+# criteria.
+#
+# === END CRITERIA ===
+#
+# Uncomment the following statement:
+
+# I, testing, certify that I have audited the changes from version 10.0.0 to 10.0.1 of third-party1 in accordance with the above criteria.
+
+# Add any notes about your audit below this line:
+
+
+<<<EDIT OK>>>
+I, testing, certify that I have audited the changes from version 10.0.0 to 10.0.1 of third-party1 in accordance with the above criteria.
+
+These are testing notes. They contain some
+newlines. Trailing whitespace        
+    and leading whitespace
+
+
+<<<END EDIT>>>
+
+AUDITS:
+
+[criteria.fuzzed]
+description = "fuzzed"
+
+[criteria.reviewed]
+description = "reviewed"
+implies = "weak-reviewed"
+
+[criteria.strong-reviewed]
+description = "strongly reviewed"
+implies = "reviewed"
+
+[criteria.weak-reviewed]
+description = "weakly reviewed"
+
+[[audits.third-party1]]
+who = "testing"
+criteria = "safe-to-deploy"
+delta = "10.0.0 -> 10.0.1"
+notes = """
+These are testing notes. They contain some
+newlines. Trailing whitespace
+    and leading whitespace
+"""
+
diff --git a/src/tests/snapshots/cargo_vet__tests__certify__mock-simple-certify-flow.snap b/src/tests/snapshots/cargo_vet__tests__certify__mock-simple-certify-flow.snap
new file mode 100644
index 0000000..a36e904
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__certify__mock-simple-certify-flow.snap
@@ -0,0 +1,70 @@
+---
+source: src/tests/certify.rs
+expression: result
+---
+OUTPUT:
+<<<CLEAR SCREEN>>>
+choose criteria to certify for third-party1:10.0.0
+  1. safe-to-run
+  2. safe-to-deploy
+  3. fuzzed
+  4. reviewed
+  5. strong-reviewed
+  6. weak-reviewed
+
+current selection: ["reviewed"]
+(press ENTER to accept the current criteria)
+> 
+
+<<<EDITING VET_CERTIFY>>>
+# Please read the following criteria and then follow the instructions below:
+
+# === BEGIN CRITERIA "reviewed" ===
+#
+# reviewed
+#
+# === END CRITERIA ===
+#
+# Uncomment the following statement:
+
+# I, testing, certify that I have audited version 10.0.0 of third-party1 in accordance with the above criteria.
+
+# Add any notes about your audit below this line:
+
+
+<<<EDIT OK>>>
+I, testing, certify that I have audited version 10.0.0 of third-party1 in accordance with the above criteria.
+
+These are testing notes. They contain some
+newlines. Trailing whitespace        
+    and leading whitespace
+
+
+<<<END EDIT>>>
+
+AUDITS:
+
+[criteria.fuzzed]
+description = "fuzzed"
+
+[criteria.reviewed]
+description = "reviewed"
+implies = "weak-reviewed"
+
+[criteria.strong-reviewed]
+description = "strongly reviewed"
+implies = "reviewed"
+
+[criteria.weak-reviewed]
+description = "weakly reviewed"
+
+[[audits.third-party1]]
+who = "testing"
+criteria = "reviewed"
+version = "10.0.0"
+notes = """
+These are testing notes. They contain some
+newlines. Trailing whitespace
+    and leading whitespace
+"""
+
diff --git a/src/tests/snapshots/cargo_vet__tests__certify__mock-simple-suggested-criteria.snap b/src/tests/snapshots/cargo_vet__tests__certify__mock-simple-suggested-criteria.snap
new file mode 100644
index 0000000..b4f5a9f
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__certify__mock-simple-suggested-criteria.snap
@@ -0,0 +1,53 @@
+---
+source: src/tests/certify.rs
+expression: output
+---
+full audit (root -> 10.0.0)
+  third-party1: ["strong-reviewed"]
+  third-party2: ["reviewed"]
+from weak-reviewed (2.0.0 -> 10.0.0)
+  third-party1: ["weak-reviewed"]
+  third-party2: ["weak-reviewed"]
+from reviewed (3.0.0 -> 10.0.0)
+  third-party1: ["reviewed"]
+  third-party2: ["reviewed"]
+from strong-reviewed (4.0.0 -> 10.0.0)
+  third-party1: ["strong-reviewed"]
+  third-party2: ["reviewed"]
+to strong-reviewed (root -> 6.0.0)
+  third-party1: ["strong-reviewed"]
+  third-party2: ["reviewed"]
+to reviewed (root -> 7.0.0)
+  third-party1: ["reviewed"]
+  third-party2: ["reviewed"]
+to weak-reviewed (root -> 8.0.0)
+  third-party1: ["weak-reviewed"]
+  third-party2: ["weak-reviewed"]
+from weak-reviewed to strong-reviewed (2.0.0 -> 6.0.0)
+  third-party1: ["weak-reviewed"]
+  third-party2: ["weak-reviewed"]
+from weak-reviewed to reviewed (2.0.0 -> 7.0.0)
+  third-party1: ["weak-reviewed"]
+  third-party2: ["weak-reviewed"]
+from weak-reviewed to weak-reviewed (2.0.0 -> 8.0.0)
+  third-party1: ["weak-reviewed"]
+  third-party2: ["weak-reviewed"]
+from reviewed to strong-reviewed (3.0.0 -> 6.0.0)
+  third-party1: ["reviewed"]
+  third-party2: ["reviewed"]
+from reviewed to reviewed (3.0.0 -> 7.0.0)
+  third-party1: ["reviewed"]
+  third-party2: ["reviewed"]
+from reviewed to weak-reviewed (3.0.0 -> 8.0.0)
+  third-party1: ["weak-reviewed"]
+  third-party2: ["weak-reviewed"]
+from strong-reviewed to strong-reviewed (4.0.0 -> 6.0.0)
+  third-party1: ["strong-reviewed"]
+  third-party2: ["reviewed"]
+from strong-reviewed to reviewed (4.0.0 -> 7.0.0)
+  third-party1: ["reviewed"]
+  third-party2: ["reviewed"]
+from strong-reviewed to weak-reviewed (4.0.0 -> 8.0.0)
+  third-party1: ["weak-reviewed"]
+  third-party2: ["weak-reviewed"]
+
diff --git a/src/tests/snapshots/cargo_vet__tests__certify__mock_trust_flow_all.snap b/src/tests/snapshots/cargo_vet__tests__certify__mock_trust_flow_all.snap
new file mode 100644
index 0000000..33859c7
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__certify__mock_trust_flow_all.snap
@@ -0,0 +1,49 @@
+---
+source: src/tests/certify.rs
+expression: result
+---
+OUTPUT:
+<<<CLEAR SCREEN>>>
+choose trusted criteria for packages published by testuser (third-party2 and transitive-third-party1)
+  1. safe-to-run
+  2. safe-to-deploy
+  3. fuzzed
+  4. reviewed
+  5. strong-reviewed
+  6. weak-reviewed
+
+current selection: ["reviewed"]
+(press ENTER to accept the current criteria)
+> 
+
+
+AUDITS:
+
+[criteria.fuzzed]
+description = "fuzzed"
+
+[criteria.reviewed]
+description = "reviewed"
+implies = "weak-reviewed"
+
+[criteria.strong-reviewed]
+description = "strongly reviewed"
+implies = "reviewed"
+
+[criteria.weak-reviewed]
+description = "weakly reviewed"
+
+[audits]
+
+[[trusted.third-party2]]
+criteria = "reviewed"
+user-id = 2 # Test user (testuser)
+start = "2022-12-12"
+end = "2024-01-01"
+
+[[trusted.transitive-third-party1]]
+criteria = "reviewed"
+user-id = 2 # Test user (testuser)
+start = "2022-10-12"
+end = "2024-01-01"
+
diff --git a/src/tests/snapshots/cargo_vet__tests__certify__mock_trust_flow_all_allow_multiple.snap b/src/tests/snapshots/cargo_vet__tests__certify__mock_trust_flow_all_allow_multiple.snap
new file mode 100644
index 0000000..7d27994
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__certify__mock_trust_flow_all_allow_multiple.snap
@@ -0,0 +1,55 @@
+---
+source: src/tests/certify.rs
+expression: result
+---
+OUTPUT:
+<<<CLEAR SCREEN>>>
+choose trusted criteria for packages published by testuser (third-party1, third-party2, and 1 other)
+  1. safe-to-run
+  2. safe-to-deploy
+  3. fuzzed
+  4. reviewed
+  5. strong-reviewed
+  6. weak-reviewed
+
+current selection: ["reviewed"]
+(press ENTER to accept the current criteria)
+> 
+
+
+AUDITS:
+
+[criteria.fuzzed]
+description = "fuzzed"
+
+[criteria.reviewed]
+description = "reviewed"
+implies = "weak-reviewed"
+
+[criteria.strong-reviewed]
+description = "strongly reviewed"
+implies = "reviewed"
+
+[criteria.weak-reviewed]
+description = "weakly reviewed"
+
+[audits]
+
+[[trusted.third-party1]]
+criteria = "reviewed"
+user-id = 2 # Test user (testuser)
+start = "2022-12-12"
+end = "2024-01-01"
+
+[[trusted.third-party2]]
+criteria = "reviewed"
+user-id = 2 # Test user (testuser)
+start = "2022-12-12"
+end = "2024-01-01"
+
+[[trusted.transitive-third-party1]]
+criteria = "reviewed"
+user-id = 2 # Test user (testuser)
+start = "2022-10-12"
+end = "2024-01-01"
+
diff --git a/src/tests/snapshots/cargo_vet__tests__certify__mock_trust_flow_ambiguous.snap b/src/tests/snapshots/cargo_vet__tests__certify__mock_trust_flow_ambiguous.snap
new file mode 100644
index 0000000..12fc8f0
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__certify__mock_trust_flow_ambiguous.snap
@@ -0,0 +1,7 @@
+---
+source: src/tests/certify.rs
+expression: "format!(\"{error:?}\")"
+---
+  × The package 'third-party1' has multiple known publishers, please
+  │ explicitly specify which publisher to trust
+
diff --git a/src/tests/snapshots/cargo_vet__tests__certify__mock_trust_flow_explicit.snap b/src/tests/snapshots/cargo_vet__tests__certify__mock_trust_flow_explicit.snap
new file mode 100644
index 0000000..b4124d5
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__certify__mock_trust_flow_explicit.snap
@@ -0,0 +1,43 @@
+---
+source: src/tests/certify.rs
+expression: result
+---
+OUTPUT:
+<<<CLEAR SCREEN>>>
+choose trusted criteria for third-party1:* published by testuser
+  1. safe-to-run
+  2. safe-to-deploy
+  3. fuzzed
+  4. reviewed
+  5. strong-reviewed
+  6. weak-reviewed
+
+current selection: ["safe-to-deploy"]
+(press ENTER to accept the current criteria)
+> 
+
+
+AUDITS:
+
+[criteria.fuzzed]
+description = "fuzzed"
+
+[criteria.reviewed]
+description = "reviewed"
+implies = "weak-reviewed"
+
+[criteria.strong-reviewed]
+description = "strongly reviewed"
+implies = "reviewed"
+
+[criteria.weak-reviewed]
+description = "weakly reviewed"
+
+[audits]
+
+[[trusted.third-party1]]
+criteria = "safe-to-deploy"
+user-id = 2
+start = "2022-12-12"
+end = "2024-01-01"
+
diff --git a/src/tests/snapshots/cargo_vet__tests__certify__mock_trust_flow_simple.snap b/src/tests/snapshots/cargo_vet__tests__certify__mock_trust_flow_simple.snap
new file mode 100644
index 0000000..e95a881
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__certify__mock_trust_flow_simple.snap
@@ -0,0 +1,43 @@
+---
+source: src/tests/certify.rs
+expression: result
+---
+OUTPUT:
+<<<CLEAR SCREEN>>>
+choose trusted criteria for third-party1:* published by testuser
+  1. safe-to-run
+  2. safe-to-deploy
+  3. fuzzed
+  4. reviewed
+  5. strong-reviewed
+  6. weak-reviewed
+
+current selection: ["safe-to-deploy"]
+(press ENTER to accept the current criteria)
+> 
+
+
+AUDITS:
+
+[criteria.fuzzed]
+description = "fuzzed"
+
+[criteria.reviewed]
+description = "reviewed"
+implies = "weak-reviewed"
+
+[criteria.strong-reviewed]
+description = "strongly reviewed"
+implies = "reviewed"
+
+[criteria.weak-reviewed]
+description = "weakly reviewed"
+
+[audits]
+
+[[trusted.third-party1]]
+criteria = "safe-to-deploy"
+user-id = 2
+start = "2022-10-12"
+end = "2024-01-01"
+
diff --git a/src/tests/snapshots/cargo_vet__tests__certify__mock_wildcard_certify_flow.snap b/src/tests/snapshots/cargo_vet__tests__certify__mock_wildcard_certify_flow.snap
new file mode 100644
index 0000000..8f98c64
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__certify__mock_wildcard_certify_flow.snap
@@ -0,0 +1,91 @@
+---
+source: src/tests/certify.rs
+expression: result
+---
+OUTPUT:
+<<<CLEAR SCREEN>>>
+choose criteria to certify for third-party1:*
+  1. safe-to-run
+  2. safe-to-deploy
+  3. fuzzed
+  4. reviewed
+  5. strong-reviewed
+  6. weak-reviewed
+
+current selection: ["safe-to-deploy"]
+(press ENTER to accept the current criteria)
+> 
+
+<<<EDITING VET_CERTIFY>>>
+# Please read the following criteria and then follow the instructions below:
+
+# === BEGIN CRITERIA "safe-to-deploy" ===
+#
+# This crate will not introduce a serious security vulnerability to production
+# software exposed to untrusted input.
+#
+# Auditors are not required to perform a full logic review of the entire crate.
+# Rather, they must review enough to fully reason about the behavior of all unsafe
+# blocks and usage of powerful imports. For any reasonable usage of the crate in
+# real-world software, an attacker must not be able to manipulate the runtime
+# behavior of these sections in an exploitable or surprising way.
+#
+# Ideally, all unsafe code is fully sound, and ambient capabilities (e.g.
+# filesystem access) are hardened against manipulation and consistent with the
+# advertised behavior of the crate. However, some discretion is permitted. In such
+# cases, the nature of the discretion should be recorded in the `notes` field of
+# the audit record.
+#
+# For crates which generate deployed code (e.g. build dependencies or procedural
+# macros), reasonable usage of the crate should output code which meets the above
+# criteria.
+#
+# === END CRITERIA ===
+#
+# Uncomment the following statement:
+
+# I, testing, certify that any version of third-party1 published by 'testuser' between 2022-12-12 and 2024-01-01 will satisfy the above criteria.
+
+# Add any notes about your audit below this line:
+
+
+<<<EDIT OK>>>
+I, testing, certify that any version of third-party1 published by 'testuser' between 2022-12-12 and 2024-01-01 will satisfy the above criteria.
+
+These are testing notes. They contain some
+newlines. Trailing whitespace        
+    and leading whitespace
+
+
+<<<END EDIT>>>
+
+AUDITS:
+
+[criteria.fuzzed]
+description = "fuzzed"
+
+[criteria.reviewed]
+description = "reviewed"
+implies = "weak-reviewed"
+
+[criteria.strong-reviewed]
+description = "strongly reviewed"
+implies = "reviewed"
+
+[criteria.weak-reviewed]
+description = "weakly reviewed"
+
+[[wildcard-audits.third-party1]]
+who = "testing"
+criteria = "safe-to-deploy"
+user-id = 2
+start = "2022-12-12"
+end = "2024-01-01"
+notes = """
+These are testing notes. They contain some
+newlines. Trailing whitespace
+    and leading whitespace
+"""
+
+[audits]
+
diff --git a/src/tests/snapshots/cargo_vet__tests__crate_policies__extraneous_crate_versions.snap b/src/tests/snapshots/cargo_vet__tests__crate_policies__extraneous_crate_versions.snap
new file mode 100644
index 0000000..463b43e
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__crate_policies__extraneous_crate_versions.snap
@@ -0,0 +1,10 @@
+---
+source: src/tests/crate_policies.rs
+expression: "format!(\"{:?}\", miette :: Report :: new(e))"
+---
+  × There are some issues with your third-party policy entries
+
+Error:   × some versioned policy entries don't correspond to crates being used
+  │   third-party:3.0.0
+  help: Remove the `policy` entries
+
diff --git a/src/tests/snapshots/cargo_vet__tests__crate_policies__extraneous_crates.snap b/src/tests/snapshots/cargo_vet__tests__crate_policies__extraneous_crates.snap
new file mode 100644
index 0000000..694d84d
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__crate_policies__extraneous_crates.snap
@@ -0,0 +1,10 @@
+---
+source: src/tests/crate_policies.rs
+expression: "format!(\"{:?}\", miette :: Report :: new(e))"
+---
+  × There are some issues with your third-party policy entries
+
+Error:   × some versioned policy entries don't correspond to crates being used
+  │   non-existent
+  help: Remove the `policy` entries
+
diff --git a/src/tests/snapshots/cargo_vet__tests__crate_policies__third_party_crates_imply_versions.snap b/src/tests/snapshots/cargo_vet__tests__crate_policies__third_party_crates_imply_versions.snap
new file mode 100644
index 0000000..b43fb62
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__crate_policies__third_party_crates_imply_versions.snap
@@ -0,0 +1,13 @@
+---
+source: src/tests/crate_policies.rs
+expression: "format!(\"{:?}\", miette :: Report :: new(e))"
+---
+  × There are some issues with your third-party policy entries
+
+Error:   × some crates have policies that are missing an associated version
+  │   third-party:1.0.0
+  │   third-party:2.0.0
+  help: Specifing `dependency-criteria` requires explicit policies for each
+        version of a crate. Add a `policy."<crate>:<version>"` entry for
+        them.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__crate_policies__third_party_crates_need_all_versions_1.snap b/src/tests/snapshots/cargo_vet__tests__crate_policies__third_party_crates_need_all_versions_1.snap
new file mode 100644
index 0000000..329ba4f
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__crate_policies__third_party_crates_need_all_versions_1.snap
@@ -0,0 +1,12 @@
+---
+source: src/tests/crate_policies.rs
+expression: "format!(\"{:?}\", miette :: Report :: new(e))"
+---
+  × There are some issues with your third-party policy entries
+
+Error:   × some crates have policies that are missing an associated version
+  │   third-party:2.0.0
+  help: Specifing `dependency-criteria` requires explicit policies for each
+        version of a crate. Add a `policy."<crate>:<version>"` entry for
+        them.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__crate_policies__third_party_crates_need_all_versions_2.snap b/src/tests/snapshots/cargo_vet__tests__crate_policies__third_party_crates_need_all_versions_2.snap
new file mode 100644
index 0000000..7067f78
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__crate_policies__third_party_crates_need_all_versions_2.snap
@@ -0,0 +1,12 @@
+---
+source: src/tests/crate_policies.rs
+expression: "format!(\"{:?}\", miette :: Report :: new(e))"
+---
+  × There are some issues with your third-party policy entries
+
+Error:   × some crates have policies that are missing an associated version
+  │   third-party:1.0.0
+  help: Specifing `dependency-criteria` requires explicit policies for each
+        version of a crate. Add a `policy."<crate>:<version>"` entry for
+        them.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__import__equal_length_preferred_audits.snap b/src/tests/snapshots/cargo_vet__tests__import__equal_length_preferred_audits.snap
new file mode 100644
index 0000000..192585e
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__import__equal_length_preferred_audits.snap
@@ -0,0 +1,13 @@
+---
+source: src/tests/import.rs
+expression: output
+---
++
++[[audits.peer-company.audits.third-party2]]
++criteria = "safe-to-deploy"
++version = "2.0.0"
++
++[[audits.peer-company.audits.third-party2]]
++criteria = "safe-to-deploy"
++delta = "2.0.0 -> 10.0.0"
+
diff --git a/src/tests/snapshots/cargo_vet__tests__import__existing_peer_add_violation.snap b/src/tests/snapshots/cargo_vet__tests__import__existing_peer_add_violation.snap
new file mode 100644
index 0000000..38410c4
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__import__existing_peer_add_violation.snap
@@ -0,0 +1,13 @@
+---
+source: src/tests/import.rs
+expression: output
+---
+ 
+ [[audits.peer-company.audits.third-party2]]
+ criteria = "safe-to-deploy"
+ version = "10.0.0"
++
++[[audits.peer-company.audits.third-party2]]
++criteria = "safe-to-deploy"
++violation = "99.*"
+
diff --git a/src/tests/snapshots/cargo_vet__tests__import__existing_peer_import_custom_criteria.snap b/src/tests/snapshots/cargo_vet__tests__import__existing_peer_import_custom_criteria.snap
new file mode 100644
index 0000000..f67fa8d
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__import__existing_peer_import_custom_criteria.snap
@@ -0,0 +1,12 @@
+---
+source: src/tests/import.rs
+expression: output
+---
+ 
++[audits.peer-company.criteria.fuzzed]
++description = "fuzzed"
++
+ [[audits.peer-company.audits.third-party2]]
+ criteria = "safe-to-deploy"
+ version = "10.0.0"
+
diff --git a/src/tests/snapshots/cargo_vet__tests__import__existing_peer_import_delta_audit.snap b/src/tests/snapshots/cargo_vet__tests__import__existing_peer_import_delta_audit.snap
new file mode 100644
index 0000000..a9f35af
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__import__existing_peer_import_delta_audit.snap
@@ -0,0 +1,15 @@
+---
+source: src/tests/import.rs
+expression: output
+---
+ 
+ [[audits.peer-company.audits.third-party2]]
+ criteria = "safe-to-deploy"
+ version = "9.0.0"
+ 
++[[audits.peer-company.audits.third-party2]]
++criteria = "safe-to-deploy"
++delta = "9.0.0 -> 10.0.0"
++
+ [audits.rival-company.audits]
+
diff --git a/src/tests/snapshots/cargo_vet__tests__import__existing_peer_remove_unused.snap b/src/tests/snapshots/cargo_vet__tests__import__existing_peer_remove_unused.snap
new file mode 100644
index 0000000..ec6f4b6
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__import__existing_peer_remove_unused.snap
@@ -0,0 +1,29 @@
+---
+source: src/tests/import.rs
+expression: output
+---
+ 
+ [[audits.peer-company.audits.third-party2]]
+ criteria = "safe-to-deploy"
+ version = "5.0.0"
+ 
+ [[audits.peer-company.audits.third-party2]]
+-criteria = "safe-to-run"
+-version = "10.0.0"
+-
+-[[audits.peer-company.audits.third-party2]]
+ criteria = "safe-to-deploy"
+ delta = "5.0.0 -> 10.0.0"
+-
+-[[audits.peer-company.audits.third-party2]]
+-criteria = "safe-to-deploy"
+-delta = "100.0.0 -> 200.0.0"
+-
+-[[audits.peer-company.audits.unused-package]]
+-criteria = "safe-to-deploy"
+-version = "10.0.0"
+-
+-[[audits.peer-company.audits.unused-violation]]
+-criteria = "safe-to-deploy"
+-violation = "1.*"
+
diff --git a/src/tests/snapshots/cargo_vet__tests__import__existing_peer_remove_unused_noprune.snap b/src/tests/snapshots/cargo_vet__tests__import__existing_peer_remove_unused_noprune.snap
new file mode 100644
index 0000000..d3d0724
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__import__existing_peer_remove_unused_noprune.snap
@@ -0,0 +1,29 @@
+---
+source: src/tests/import.rs
+expression: output
+---
+ 
+ [[audits.peer-company.audits.third-party2]]
+ criteria = "safe-to-deploy"
+ version = "5.0.0"
+ 
+ [[audits.peer-company.audits.third-party2]]
+ criteria = "safe-to-run"
+ version = "10.0.0"
+ 
+ [[audits.peer-company.audits.third-party2]]
+ criteria = "safe-to-deploy"
+ delta = "5.0.0 -> 10.0.0"
+ 
+ [[audits.peer-company.audits.third-party2]]
+ criteria = "safe-to-deploy"
+ delta = "100.0.0 -> 200.0.0"
+ 
+ [[audits.peer-company.audits.unused-package]]
+ criteria = "safe-to-deploy"
+ version = "10.0.0"
+ 
+ [[audits.peer-company.audits.unused-violation]]
+ criteria = "safe-to-deploy"
+ violation = "1.*"
+
diff --git a/src/tests/snapshots/cargo_vet__tests__import__existing_peer_revoked_audit.snap b/src/tests/snapshots/cargo_vet__tests__import__existing_peer_revoked_audit.snap
new file mode 100644
index 0000000..551cef4
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__import__existing_peer_revoked_audit.snap
@@ -0,0 +1,10 @@
+---
+source: src/tests/import.rs
+expression: output
+---
+ 
+-[[audits.peer-company.audits.third-party2]]
+-criteria = "safe-to-deploy"
+-version = "10.0.0"
++[audits.peer-company.audits]
+
diff --git a/src/tests/snapshots/cargo_vet__tests__import__existing_peer_revoked_audit_noprune.snap b/src/tests/snapshots/cargo_vet__tests__import__existing_peer_revoked_audit_noprune.snap
new file mode 100644
index 0000000..551cef4
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__import__existing_peer_revoked_audit_noprune.snap
@@ -0,0 +1,10 @@
+---
+source: src/tests/import.rs
+expression: output
+---
+ 
+-[[audits.peer-company.audits.third-party2]]
+-criteria = "safe-to-deploy"
+-version = "10.0.0"
++[audits.peer-company.audits]
+
diff --git a/src/tests/snapshots/cargo_vet__tests__import__existing_peer_skip_import.snap b/src/tests/snapshots/cargo_vet__tests__import__existing_peer_skip_import.snap
new file mode 100644
index 0000000..c24bb39
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__import__existing_peer_skip_import.snap
@@ -0,0 +1,7 @@
+---
+source: src/tests/import.rs
+expression: output
+---
+ 
+ [audits.peer-company.audits]
+
diff --git a/src/tests/snapshots/cargo_vet__tests__import__existing_peer_skip_import_noprune.snap b/src/tests/snapshots/cargo_vet__tests__import__existing_peer_skip_import_noprune.snap
new file mode 100644
index 0000000..c24bb39
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__import__existing_peer_skip_import_noprune.snap
@@ -0,0 +1,7 @@
+---
+source: src/tests/import.rs
+expression: output
+---
+ 
+ [audits.peer-company.audits]
+
diff --git a/src/tests/snapshots/cargo_vet__tests__import__existing_peer_updated_description.snap b/src/tests/snapshots/cargo_vet__tests__import__existing_peer_updated_description.snap
new file mode 100644
index 0000000..43ab745
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__import__existing_peer_updated_description.snap
@@ -0,0 +1,18 @@
+---
+source: src/tests/import.rs
+expression: "format!(\"{error:?}\")"
+---
+  × Some of your imported audits changed their criteria descriptions
+
+Error:   × peer-company's 'example' criteria changed:
+  │ 
+  │ @@ -1,4 +1,4 @@
+  │  Example criteria description
+  │ -First line
+  │ -Second line
+  │ +First new line
+  │  Third line
+  │ +Fourth line
+  │ 
+  help: Run `cargo vet regenerate imports` to accept this new definition
+
diff --git a/src/tests/snapshots/cargo_vet__tests__import__foreign_audit_file_to_local.snap b/src/tests/snapshots/cargo_vet__tests__import__foreign_audit_file_to_local.snap
new file mode 100644
index 0000000..8e3546e
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__import__foreign_audit_file_to_local.snap
@@ -0,0 +1,61 @@
+---
+source: src/tests/import.rs
+expression: "crate::serialization::to_formatted_toml(&result.audit_file).unwrap().to_string()"
+---
+
+[criteria.example]
+description = "Example criteria description"
+
+[criteria.example2]
+description = "example2"
+
+[criteria.example3]
+description = "example2"
+implies = "safe-to-deploy"
+
+[[wildcard-audits.crate-a]]
+criteria = "safe-to-deploy"
+user-id = 1
+start = "2022-12-25"
+end = "2023-12-25"
+notes = "should parse correctly"
+
+[[wildcard-audits.crate-c]]
+criteria = "example2"
+user-id = 1
+start = "2022-12-25"
+end = "2023-12-25"
+notes = "will not be removed"
+
+[[wildcard-audits.crate-c]]
+criteria = ["example2", "example3"]
+user-id = 1
+start = "2022-12-25"
+end = "2023-12-25"
+notes = "will not be removed"
+
+[[audits.crate-a]]
+criteria = "safe-to-deploy"
+version = "10.0.0"
+notes = "should parse correctly"
+
+[[audits.crate-c]]
+criteria = "example2"
+version = "10.0.0"
+notes = "will not be removed"
+
+[[audits.crate-c]]
+criteria = ["example2", "example3"]
+version = "10.0.0"
+notes = "will not be removed"
+
+[[audits.crate-c]]
+criteria = "example2"
+delta = "1.0.0 -> 10.0.0"
+notes = "will not be removed"
+
+[[audits.crate-c]]
+criteria = "safe-to-deploy"
+violation = "=5.0.0"
+notes = "will not be removed"
+
diff --git a/src/tests/snapshots/cargo_vet__tests__import__fresh_import_preferred_audits.snap b/src/tests/snapshots/cargo_vet__tests__import__fresh_import_preferred_audits.snap
new file mode 100644
index 0000000..7123674
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__import__fresh_import_preferred_audits.snap
@@ -0,0 +1,13 @@
+---
+source: src/tests/import.rs
+expression: output
+---
++
++[[audits.peer-company.audits.third-party2]]
++criteria = "safe-to-deploy"
++version = "5.0.0"
++
++[[audits.rival-company.audits.third-party2]]
++criteria = "safe-to-deploy"
++delta = "5.0.0 -> 10.0.0"
+
diff --git a/src/tests/snapshots/cargo_vet__tests__import__import_criteria_map.snap b/src/tests/snapshots/cargo_vet__tests__import__import_criteria_map.snap
new file mode 100644
index 0000000..5004301
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__import__import_criteria_map.snap
@@ -0,0 +1,20 @@
+---
+source: src/tests/import.rs
+expression: output
+---
++
++[audits.peer-company.criteria.foreign-reviewed]
++description = "foreign reviewed"
++
++[[audits.peer-company.audits.third-party1]]
++criteria = "reviewed"
++version = "10.0.0"
++
++[[audits.peer-company.audits.third-party2]]
++criteria = "weak-reviewed"
++version = "10.0.0"
++
++[[audits.peer-company.audits.transitive-third-party1]]
++criteria = "strong-reviewed"
++version = "10.0.0"
+
diff --git a/src/tests/snapshots/cargo_vet__tests__import__import_criteria_map_aggregated.snap b/src/tests/snapshots/cargo_vet__tests__import__import_criteria_map_aggregated.snap
new file mode 100644
index 0000000..d46bf63
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__import__import_criteria_map_aggregated.snap
@@ -0,0 +1,15 @@
+---
+source: src/tests/import.rs
+expression: output
+---
++
++[[audits.peer-company.audits.third-party1]]
++criteria = "safe-to-deploy"
++version = "9.0.0"
++aggregated-from = "https://peercompany.co.uk"
++
++[[audits.peer-company.audits.third-party1]]
++criteria = "safe-to-deploy"
++delta = "9.0.0 -> 10.0.0"
++aggregated-from = "https://rivalcompany.ca"
+
diff --git a/src/tests/snapshots/cargo_vet__tests__import__import_criteria_map_aggregated_error.snap b/src/tests/snapshots/cargo_vet__tests__import__import_criteria_map_aggregated_error.snap
new file mode 100644
index 0000000..0c53aa6
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__import__import_criteria_map_aggregated_error.snap
@@ -0,0 +1,15 @@
+---
+source: src/tests/import.rs
+expression: output
+---
+  × error when aggregating multiple sources for peer-company
+  help: all sources for mapped custom criteria must have identical
+        descriptions
+
+Error:   × criteria description mismatch for foreign-reviewed
+  │ https://peercompany.co.uk:
+  │ foreign reviewed A
+  │ https://rivalcompany.ca:
+  │ foreign reviewed B
+  help: foreign-reviewed is mapped to the local criteria ["reviewed"]
+
diff --git a/src/tests/snapshots/cargo_vet__tests__import__import_multiple_versions.snap b/src/tests/snapshots/cargo_vet__tests__import__import_multiple_versions.snap
new file mode 100644
index 0000000..a30635f
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__import__import_multiple_versions.snap
@@ -0,0 +1,13 @@
+---
+source: src/tests/import.rs
+expression: output
+---
++
++[[audits.peer-company.audits.third-core]]
++criteria = "safe-to-deploy"
++version = "5.0.0"
++
++[[audits.peer-company.audits.third-core]]
++criteria = "safe-to-deploy"
++version = "10.0.0"
+
diff --git a/src/tests/snapshots/cargo_vet__tests__import__import_wildcard_audit_publisher.snap b/src/tests/snapshots/cargo_vet__tests__import__import_wildcard_audit_publisher.snap
new file mode 100644
index 0000000..a2dc303
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__import__import_wildcard_audit_publisher.snap
@@ -0,0 +1,27 @@
+---
+source: src/tests/import.rs
+expression: output
+---
+ 
++[[publisher.third-party1]]
++version = "10.0.0"
++when = "2022-12-12"
++user-id = 2
++user-login = "user2"
++user-name = "User Two"
++
++[[publisher.third-party2]]
++version = "10.0.0"
++when = "2022-12-12"
++user-id = 1
++user-login = "user1"
++user-name = "User One"
++
++[[audits.peer-company.wildcard-audits.third-party1]]
++criteria = "safe-to-deploy"
++user-id = 2 # User Two (user2)
++start = "2022-12-01"
++end = "2023-01-01"
++
+ [audits.peer-company.audits]
+
diff --git a/src/tests/snapshots/cargo_vet__tests__import__new_audit_for_unused_criteria_basic.snap b/src/tests/snapshots/cargo_vet__tests__import__new_audit_for_unused_criteria_basic.snap
new file mode 100644
index 0000000..b37182c
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__import__new_audit_for_unused_criteria_basic.snap
@@ -0,0 +1,12 @@
+---
+source: src/tests/import.rs
+expression: output
+---
+ 
+ [audits.peer-company.criteria.fuzzed]
+ description = "fuzzed"
+ 
+ [[audits.peer-company.audits.third-party2]]
+ criteria = "safe-to-deploy"
+ version = "10.0.0"
+
diff --git a/src/tests/snapshots/cargo_vet__tests__import__new_audit_for_unused_criteria_transitive.snap b/src/tests/snapshots/cargo_vet__tests__import__new_audit_for_unused_criteria_transitive.snap
new file mode 100644
index 0000000..9627fdd
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__import__new_audit_for_unused_criteria_transitive.snap
@@ -0,0 +1,12 @@
+---
+source: src/tests/import.rs
+expression: output
+---
+ 
+ [audits.peer-company.criteria.fuzzed]
+ description = "fuzzed"
+ 
+ [[audits.peer-company.audits.third-party1]]
+ criteria = "safe-to-deploy"
+ version = "10.0.0"
+
diff --git a/src/tests/snapshots/cargo_vet__tests__import__new_audit_needed_violation.snap b/src/tests/snapshots/cargo_vet__tests__import__new_audit_needed_violation.snap
new file mode 100644
index 0000000..25b434f
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__import__new_audit_needed_violation.snap
@@ -0,0 +1,9 @@
+---
+source: src/tests/import.rs
+expression: output
+---
++
++[[audits.peer-company.audits.third-party2]]
++criteria = "safe-to-deploy"
++violation = "10.*"
+
diff --git a/src/tests/snapshots/cargo_vet__tests__import__new_audit_unneeded_violation.snap b/src/tests/snapshots/cargo_vet__tests__import__new_audit_unneeded_violation.snap
new file mode 100644
index 0000000..72eb21d
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__import__new_audit_unneeded_violation.snap
@@ -0,0 +1,9 @@
+---
+source: src/tests/import.rs
+expression: output
+---
++
++[[audits.peer-company.audits.third-party2]]
++criteria = "safe-to-deploy"
++version = "10.0.0"
+
diff --git a/src/tests/snapshots/cargo_vet__tests__import__new_peer_import.snap b/src/tests/snapshots/cargo_vet__tests__import__new_peer_import.snap
new file mode 100644
index 0000000..e030896
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__import__new_peer_import.snap
@@ -0,0 +1,9 @@
+---
+source: src/tests/import.rs
+expression: output
+---
+ 
++[audits.peer-company.audits]
++
+ [audits.rival-company.audits]
+
diff --git a/src/tests/snapshots/cargo_vet__tests__import__old_import_preferred_audits.snap b/src/tests/snapshots/cargo_vet__tests__import__old_import_preferred_audits.snap
new file mode 100644
index 0000000..abaee7c
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__import__old_import_preferred_audits.snap
@@ -0,0 +1,19 @@
+---
+source: src/tests/import.rs
+expression: output
+---
+ 
+ [[audits.peer-company.audits.third-party2]]
+ criteria = "safe-to-deploy"
+ version = "5.0.0"
+ 
+ [[audits.peer-company.audits.third-party2]]
+ criteria = "safe-to-deploy"
+ delta = "5.0.0 -> 6.0.0"
+ 
+ [[audits.peer-company.audits.third-party2]]
+ criteria = "safe-to-deploy"
+ delta = "6.0.0 -> 10.0.0"
++
++[audits.rival-company.audits]
+
diff --git a/src/tests/snapshots/cargo_vet__tests__import__peer_audits_exemption_minimize_certify.snap b/src/tests/snapshots/cargo_vet__tests__import__peer_audits_exemption_minimize_certify.snap
new file mode 100644
index 0000000..b31363a
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__import__peer_audits_exemption_minimize_certify.snap
@@ -0,0 +1,44 @@
+---
+source: src/tests/import.rs
+expression: output
+---
+audits.toml: (unchanged)
+config.toml:
+ 
+ # cargo-vet config file
+ 
+ [cargo-vet]
+ version = "1.0"
+ 
+ [imports.peer-company]
+ url = "https://peercompany.co.uk"
+ 
+ [[exemptions.third-party1]]
+ version = "10.0.0"
+ criteria = "safe-to-deploy"
+ 
+-[[exemptions.third-party2]]
+-version = "10.0.0"
+-criteria = "safe-to-deploy"
+-
+ [[exemptions.transitive-third-party1]]
+ version = "10.0.0"
+ criteria = "safe-to-deploy"
+
+imports.lock:
+ 
+ # cargo-vet imports lock
+ 
+ [[audits.peer-company.audits.third-party1]]
+ criteria = "safe-to-deploy"
+ delta = "10.0.0 -> 100.0.0"
+ 
++[[audits.peer-company.audits.third-party2]]
++criteria = "safe-to-deploy"
++version = "10.0.0"
++
+ [[audits.peer-company.audits.unused-crate]]
+ criteria = "safe-to-deploy"
+ version = "10.0.0"
+
+
diff --git a/src/tests/snapshots/cargo_vet__tests__import__peer_audits_exemption_minimize_prune.snap b/src/tests/snapshots/cargo_vet__tests__import__peer_audits_exemption_minimize_prune.snap
new file mode 100644
index 0000000..0f0c75c
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__import__peer_audits_exemption_minimize_prune.snap
@@ -0,0 +1,42 @@
+---
+source: src/tests/import.rs
+expression: output
+---
+audits.toml: (unchanged)
+config.toml:
+ 
+ # cargo-vet config file
+ 
+ [cargo-vet]
+ version = "1.0"
+ 
+ [imports.peer-company]
+ url = "https://peercompany.co.uk"
+-
+-[[exemptions.third-party1]]
+-version = "10.0.0"
+-criteria = "safe-to-deploy"
+-
+-[[exemptions.third-party2]]
+-version = "10.0.0"
+-criteria = "safe-to-deploy"
+-
+-[[exemptions.transitive-third-party1]]
+-version = "10.0.0"
+-criteria = "safe-to-deploy"
+
+imports.lock:
+ 
+ # cargo-vet imports lock
+ 
+ [[audits.peer-company.audits.third-party1]]
+ criteria = "safe-to-deploy"
+-delta = "10.0.0 -> 100.0.0"
++version = "10.0.0"
+ 
+-[[audits.peer-company.audits.unused-crate]]
++[[audits.peer-company.audits.third-party2]]
+ criteria = "safe-to-deploy"
+ version = "10.0.0"
+
+
diff --git a/src/tests/snapshots/cargo_vet__tests__import__peer_audits_exemption_minimize_vet.snap b/src/tests/snapshots/cargo_vet__tests__import__peer_audits_exemption_minimize_vet.snap
new file mode 100644
index 0000000..622fa2b
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__import__peer_audits_exemption_minimize_vet.snap
@@ -0,0 +1,8 @@
+---
+source: src/tests/import.rs
+expression: output
+---
+audits.toml: (unchanged)
+config.toml: (unchanged)
+imports.lock: (unchanged)
+
diff --git a/src/tests/snapshots/cargo_vet__tests__import__peer_audits_exemption_no_minimize.snap b/src/tests/snapshots/cargo_vet__tests__import__peer_audits_exemption_no_minimize.snap
new file mode 100644
index 0000000..c24bb39
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__import__peer_audits_exemption_no_minimize.snap
@@ -0,0 +1,7 @@
+---
+source: src/tests/import.rs
+expression: output
+---
+ 
+ [audits.peer-company.audits]
+
diff --git a/src/tests/snapshots/cargo_vet__tests__import__peer_audits_import_exclusion.snap b/src/tests/snapshots/cargo_vet__tests__import__peer_audits_import_exclusion.snap
new file mode 100644
index 0000000..0d8ddc3
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__import__peer_audits_import_exclusion.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/import.rs
+expression: output
+---
+ 
+-[[audits.peer-company.audits.third-party1]]
+-criteria = "safe-to-deploy"
+-violation = "*"
+-
+-[[audits.peer-company.audits.third-party2]]
+-criteria = "safe-to-deploy"
+-version = "10.0.0"
+-
+ [[audits.peer-company.audits.transitive-third-party1]]
+ criteria = "safe-to-deploy"
+ version = "10.0.0"
+
diff --git a/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-dev-detection-unaudited-adds-uneeded-criteria-indirect-regenerate.snap b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-dev-detection-unaudited-adds-uneeded-criteria-indirect-regenerate.snap
new file mode 100644
index 0000000..30bbf1a
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-dev-detection-unaudited-adds-uneeded-criteria-indirect-regenerate.snap
@@ -0,0 +1,5 @@
+---
+source: src/tests/regenerate_unaudited.rs
+expression: unaudited
+---
+
diff --git a/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-audit-as-default-root-regenerate.snap b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-audit-as-default-root-regenerate.snap
new file mode 100644
index 0000000..ffac36b
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-audit-as-default-root-regenerate.snap
@@ -0,0 +1,20 @@
+---
+source: src/tests/regenerate_unaudited.rs
+expression: unaudited
+---
+[[root-package]]
+version = "10.0.0"
+criteria = "safe-to-deploy"
+
+[[third-party1]]
+version = "10.0.0"
+criteria = "safe-to-deploy"
+
+[[third-party2]]
+version = "10.0.0"
+criteria = "safe-to-deploy"
+
+[[transitive-third-party1]]
+version = "10.0.0"
+criteria = "safe-to-deploy"
+
diff --git a/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-audit-as-weaker-root-regenerate.snap b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-audit-as-weaker-root-regenerate.snap
new file mode 100644
index 0000000..f2e8775
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-audit-as-weaker-root-regenerate.snap
@@ -0,0 +1,20 @@
+---
+source: src/tests/regenerate_unaudited.rs
+expression: unaudited
+---
+[[root-package]]
+version = "10.0.0"
+criteria = "safe-to-run"
+
+[[third-party1]]
+version = "10.0.0"
+criteria = "safe-to-run"
+
+[[third-party2]]
+version = "10.0.0"
+criteria = "safe-to-run"
+
+[[transitive-third-party1]]
+version = "10.0.0"
+criteria = "safe-to-run"
+
diff --git a/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-deps-unaudited-adds-uneeded-criteria-regenerate.snap b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-deps-unaudited-adds-uneeded-criteria-regenerate.snap
new file mode 100644
index 0000000..30bbf1a
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-deps-unaudited-adds-uneeded-criteria-regenerate.snap
@@ -0,0 +1,5 @@
+---
+source: src/tests/regenerate_unaudited.rs
+expression: unaudited
+---
+
diff --git a/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-exemptions-broaden-basic.snap b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-exemptions-broaden-basic.snap
new file mode 100644
index 0000000..a8655ba
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-exemptions-broaden-basic.snap
@@ -0,0 +1,21 @@
+---
+source: src/tests/regenerate_unaudited.rs
+expression: exemptions
+---
+[[third-party1]]
+version = "5.0.0"
+criteria = "safe-to-deploy"
+
+[[third-party2]]
+version = "10.0.0"
+criteria = "safe-to-deploy"
+
+[[transitive-third-party1]]
+version = "5.0.0"
+criteria = "safe-to-deploy"
+
+[[transitive-third-party1]]
+version = "5.0.0"
+criteria = "safe-to-run"
+suggest = false
+
diff --git a/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-exemptions-larger-diff-regenerate.snap b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-exemptions-larger-diff-regenerate.snap
new file mode 100644
index 0000000..c88f0ac
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-exemptions-larger-diff-regenerate.snap
@@ -0,0 +1,16 @@
+---
+source: src/tests/regenerate_unaudited.rs
+expression: exemptions
+---
+[[third-party1]]
+version = "11.0.0"
+criteria = "safe-to-deploy"
+
+[[third-party2]]
+version = "10.0.0"
+criteria = "safe-to-deploy"
+
+[[transitive-third-party1]]
+version = "10.0.0"
+criteria = "safe-to-deploy"
+
diff --git a/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-exemptions-regenerate-merge-nonew.snap b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-exemptions-regenerate-merge-nonew.snap
new file mode 100644
index 0000000..99db240
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-exemptions-regenerate-merge-nonew.snap
@@ -0,0 +1,12 @@
+---
+source: src/tests/regenerate_unaudited.rs
+expression: exemptions
+---
+[[transitive-third-party1]]
+version = "5.0.0"
+criteria = "criteria1"
+
+[[transitive-third-party1]]
+version = "5.0.0"
+criteria = "criteria2"
+
diff --git a/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-exemptions-regenerate-merge.snap b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-exemptions-regenerate-merge.snap
new file mode 100644
index 0000000..cde56de
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-exemptions-regenerate-merge.snap
@@ -0,0 +1,11 @@
+---
+source: src/tests/regenerate_unaudited.rs
+expression: exemptions
+---
+[[transitive-third-party1]]
+version = "5.0.0"
+criteria = [
+    "criteria1",
+    "criteria2",
+]
+
diff --git a/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-exemptions-regenerate-nonew-failed.snap b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-exemptions-regenerate-nonew-failed.snap
new file mode 100644
index 0000000..e67a132
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-exemptions-regenerate-nonew-failed.snap
@@ -0,0 +1,16 @@
+---
+source: src/tests/regenerate_unaudited.rs
+expression: exemptions
+---
+[[third-party1]]
+version = "300.0.0"
+criteria = "safe-to-deploy"
+
+[[transitive-third-party1]]
+version = "300.0.0"
+criteria = "safe-to-deploy"
+
+[[transitive-third-party1]]
+version = "400.0.0"
+criteria = "safe-to-deploy"
+
diff --git a/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-not-a-real-dep-regenerate.snap b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-not-a-real-dep-regenerate.snap
new file mode 100644
index 0000000..30bbf1a
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-not-a-real-dep-regenerate.snap
@@ -0,0 +1,5 @@
+---
+source: src/tests/regenerate_unaudited.rs
+expression: unaudited
+---
+
diff --git a/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-unaudited-extra-regenerate.snap b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-unaudited-extra-regenerate.snap
new file mode 100644
index 0000000..1aa5a6e
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-unaudited-extra-regenerate.snap
@@ -0,0 +1,8 @@
+---
+source: src/tests/regenerate_unaudited.rs
+expression: unaudited
+---
+[[third-party1]]
+version = "10.0.0"
+criteria = "safe-to-deploy"
+
diff --git a/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-unaudited-in-delta-regenerate.snap b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-unaudited-in-delta-regenerate.snap
new file mode 100644
index 0000000..30bbf1a
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-unaudited-in-delta-regenerate.snap
@@ -0,0 +1,5 @@
+---
+source: src/tests/regenerate_unaudited.rs
+expression: unaudited
+---
+
diff --git a/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-unaudited-in-direct-full-regenerate.snap b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-unaudited-in-direct-full-regenerate.snap
new file mode 100644
index 0000000..30bbf1a
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-unaudited-in-direct-full-regenerate.snap
@@ -0,0 +1,5 @@
+---
+source: src/tests/regenerate_unaudited.rs
+expression: unaudited
+---
+
diff --git a/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-unaudited-in-full-regenerate.snap b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-unaudited-in-full-regenerate.snap
new file mode 100644
index 0000000..30bbf1a
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-unaudited-in-full-regenerate.snap
@@ -0,0 +1,5 @@
+---
+source: src/tests/regenerate_unaudited.rs
+expression: unaudited
+---
+
diff --git a/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-unaudited-nested-stronger-req-regenerate.snap b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-unaudited-nested-stronger-req-regenerate.snap
new file mode 100644
index 0000000..45382b9
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-unaudited-nested-stronger-req-regenerate.snap
@@ -0,0 +1,12 @@
+---
+source: src/tests/regenerate_unaudited.rs
+expression: exemptions
+---
+[[third-party1]]
+version = "3.0.0"
+criteria = "safe-to-run"
+
+[[transitive-third-party1]]
+version = "4.0.0"
+criteria = "safe-to-deploy"
+
diff --git a/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-unaudited-nested-weaker-req-regenerate.snap b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-unaudited-nested-weaker-req-regenerate.snap
new file mode 100644
index 0000000..c36e6d9
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-unaudited-nested-weaker-req-regenerate.snap
@@ -0,0 +1,12 @@
+---
+source: src/tests/regenerate_unaudited.rs
+expression: exemptions
+---
+[[third-party1]]
+version = "3.0.0"
+criteria = "safe-to-deploy"
+
+[[transitive-third-party1]]
+version = "4.0.0"
+criteria = "safe-to-run"
+
diff --git a/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-unaudited-overbroad-regenerate.snap b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-unaudited-overbroad-regenerate.snap
new file mode 100644
index 0000000..e93118a
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-unaudited-overbroad-regenerate.snap
@@ -0,0 +1,8 @@
+---
+source: src/tests/regenerate_unaudited.rs
+expression: unaudited
+---
+[[dev]]
+version = "10.0.0"
+criteria = "safe-to-run"
+
diff --git a/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-unaudited-partial-twins-regenerate.snap b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-unaudited-partial-twins-regenerate.snap
new file mode 100644
index 0000000..3a1d8d7
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-unaudited-partial-twins-regenerate.snap
@@ -0,0 +1,8 @@
+---
+source: src/tests/regenerate_unaudited.rs
+expression: unaudited
+---
+[[third-core]]
+version = "10.0.0"
+criteria = "safe-to-deploy"
+
diff --git a/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-unaudited-twins-regenerate.snap b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-unaudited-twins-regenerate.snap
new file mode 100644
index 0000000..b45484c
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin-simple-unaudited-twins-regenerate.snap
@@ -0,0 +1,12 @@
+---
+source: src/tests/regenerate_unaudited.rs
+expression: exemptions
+---
+[[third-core]]
+version = "5.0.0"
+criteria = "safe-to-deploy"
+
+[[third-core]]
+version = "10.0.0"
+criteria = "safe-to-deploy"
+
diff --git a/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin_complex_exemptions_preferred_path.snap b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin_complex_exemptions_preferred_path.snap
new file mode 100644
index 0000000..e1c4fa1
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin_complex_exemptions_preferred_path.snap
@@ -0,0 +1,16 @@
+---
+source: src/tests/regenerate_unaudited.rs
+expression: exemptions
+---
+[[third-core]]
+version = "5.0.0"
+criteria = "safe-to-deploy"
+
+[[thirdA]]
+version = "10.0.0"
+criteria = "safe-to-deploy"
+
+[[thirdAB]]
+version = "10.0.0"
+criteria = "safe-to-deploy"
+
diff --git a/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin_complex_exemptions_preferred_path_fresh.snap b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin_complex_exemptions_preferred_path_fresh.snap
new file mode 100644
index 0000000..e1c4fa1
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__regenerate_unaudited__builtin_complex_exemptions_preferred_path_fresh.snap
@@ -0,0 +1,16 @@
+---
+source: src/tests/regenerate_unaudited.rs
+expression: exemptions
+---
+[[third-core]]
+version = "5.0.0"
+criteria = "safe-to-deploy"
+
+[[thirdA]]
+version = "10.0.0"
+criteria = "safe-to-deploy"
+
+[[thirdAB]]
+version = "10.0.0"
+criteria = "safe-to-deploy"
+
diff --git a/src/tests/snapshots/cargo_vet__tests__registry__registry_parse_error.snap b/src/tests/snapshots/cargo_vet__tests__registry__registry_parse_error.snap
new file mode 100644
index 0000000..f382af3
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__registry__registry_parse_error.snap
@@ -0,0 +1,23 @@
+---
+source: src/tests/registry.rs
+expression: human_output.to_string()
+---
+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 missing ["safe-to-deploy"]
+
+recommended audits for safe-to-deploy:
+    Command                                           Publisher  Used By       Audit Size
+    cargo vet inspect third-party1 10.0.0             UNKNOWN    first-party   100 lines
+    cargo vet inspect third-party2 10.0.0             UNKNOWN    first-party   100 lines
+    cargo vet inspect transitive-third-party1 10.0.0  UNKNOWN    third-party1  100 lines
+
+estimated audit backlog: 300 lines
+
+WARNING: Import suggestions are disabled due to an incompatible registry. Consider upgrading to the most recent release of cargo-vet.
+
+Use |cargo vet certify| to record the audits.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__registry__test_registry_parse_error.snap b/src/tests/snapshots/cargo_vet__tests__registry__test_registry_parse_error.snap
new file mode 100644
index 0000000..5b548af
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__registry__test_registry_parse_error.snap
@@ -0,0 +1,20 @@
+---
+source: src/tests/registry.rs
+expression: human
+---
+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 missing ["safe-to-deploy"]
+
+recommended audits for safe-to-deploy:
+    cargo vet inspect third-party1 10.0.0             (used by first-party)   (100 lines)
+    cargo vet inspect third-party2 10.0.0             (used by first-party)   (100 lines)
+    cargo vet inspect transitive-third-party1 10.0.0  (used by third-party1)  (100 lines)
+
+estimated audit backlog: 300 lines
+
+Use |cargo vet certify| to record the audits.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__renew__renew-expiring-selection-logic.snap b/src/tests/snapshots/cargo_vet__tests__renew__renew-expiring-selection-logic.snap
new file mode 100644
index 0000000..1b16292
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__renew__renew-expiring-selection-logic.snap
@@ -0,0 +1,85 @@
+---
+source: src/tests/renew.rs
+expression: "diff_store_commits(&before, &after)"
+---
+audits.toml:
+ 
+ # cargo-vet audits file
+ 
+ [[wildcard-audits.bar]]
+ who = "user"
+ criteria = "safe-to-deploy"
+ user-id = 3
+ start = "2022-10-23"
+ end = "2022-12-25"
+ renew = false
+ 
+ [[wildcard-audits.bar]]
+ who = "user"
+ criteria = "safe-to-deploy"
+ user-id = 6
+ start = "2022-10-23"
+ end = "2023-02-19"
+ 
+ [[wildcard-audits.baz]]
+ who = "user"
+ criteria = "safe-to-deploy"
+ user-id = 7
+ start = "2022-10-23"
+-end = "2023-01-08"
++end = "2024-01-01"
+ 
+ [[wildcard-audits.foo]]
+ who = "user"
+ criteria = "safe-to-deploy"
+ user-id = 1
+ start = "2022-10-23"
+-end = "2022-12-25"
++end = "2024-01-01"
+ 
+ [[wildcard-audits.foo]]
+ who = "user"
+ criteria = "safe-to-deploy"
+ user-id = 2
+ start = "2022-10-23"
+-end = "2023-01-08"
++end = "2024-01-01"
+ 
+ [[wildcard-audits.foo]]
+ who = "user"
+ criteria = "safe-to-deploy"
+ user-id = 3
+ start = "2022-10-23"
+ end = "2023-01-08"
+ renew = false
+ 
+ [[wildcard-audits.foo]]
+ who = "user"
+ criteria = "safe-to-deploy"
+ user-id = 4
+ start = "2022-10-23"
+ end = "2023-02-19"
+ renew = false
+ 
+ [[wildcard-audits.foo]]
+ who = "user"
+ criteria = "safe-to-deploy"
+ user-id = 5
+ start = "2022-10-23"
+-end = "2022-12-25"
++end = "2024-01-01"
+ renew = true
+ 
+ [[wildcard-audits.quux]]
+ who = "user"
+ criteria = "safe-to-deploy"
+ user-id = 8
+ start = "2022-10-23"
+-end = "2022-12-25"
++end = "2024-01-01"
+ 
+ [audits]
+
+config.toml: (unchanged)
+imports.lock: (unchanged)
+
diff --git a/src/tests/snapshots/cargo_vet__tests__renew__renew-specific-selection-logic.snap b/src/tests/snapshots/cargo_vet__tests__renew__renew-specific-selection-logic.snap
new file mode 100644
index 0000000..547800a
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__renew__renew-specific-selection-logic.snap
@@ -0,0 +1,83 @@
+---
+source: src/tests/renew.rs
+expression: "diff_store_commits(&before, &after)"
+---
+audits.toml:
+ 
+ # cargo-vet audits file
+ 
+ [[wildcard-audits.bar]]
+ who = "user"
+ criteria = "safe-to-deploy"
+ user-id = 3
+ start = "2022-10-23"
+ end = "2022-12-25"
+ renew = false
+ 
+ [[wildcard-audits.bar]]
+ who = "user"
+ criteria = "safe-to-deploy"
+ user-id = 6
+ start = "2022-10-23"
+ end = "2023-02-19"
+ 
+ [[wildcard-audits.baz]]
+ who = "user"
+ criteria = "safe-to-deploy"
+ user-id = 7
+ start = "2022-10-23"
+ end = "2023-01-08"
+ 
+ [[wildcard-audits.foo]]
+ who = "user"
+ criteria = "safe-to-deploy"
+ user-id = 1
+ start = "2022-10-23"
+-end = "2022-12-25"
++end = "2024-01-01"
+ 
+ [[wildcard-audits.foo]]
+ who = "user"
+ criteria = "safe-to-deploy"
+ user-id = 2
+ start = "2022-10-23"
+-end = "2023-01-08"
++end = "2024-01-01"
+ 
+ [[wildcard-audits.foo]]
+ who = "user"
+ criteria = "safe-to-deploy"
+ user-id = 3
+ start = "2022-10-23"
+ end = "2023-01-08"
+ renew = false
+ 
+ [[wildcard-audits.foo]]
+ who = "user"
+ criteria = "safe-to-deploy"
+ user-id = 4
+ start = "2022-10-23"
+ end = "2023-02-19"
+ renew = false
+ 
+ [[wildcard-audits.foo]]
+ who = "user"
+ criteria = "safe-to-deploy"
+ user-id = 5
+ start = "2022-10-23"
+-end = "2022-12-25"
++end = "2024-01-01"
+ renew = true
+ 
+ [[wildcard-audits.quux]]
+ who = "user"
+ criteria = "safe-to-deploy"
+ user-id = 8
+ start = "2022-10-23"
+ end = "2022-12-25"
+ 
+ [audits]
+
+config.toml: (unchanged)
+imports.lock: (unchanged)
+
diff --git a/src/tests/snapshots/cargo_vet__tests__store_parsing__all_empty.snap b/src/tests/snapshots/cargo_vet__tests__store_parsing__all_empty.snap
new file mode 100644
index 0000000..d40cc88
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__store_parsing__all_empty.snap
@@ -0,0 +1,12 @@
+---
+source: src/tests/store_parsing.rs
+expression: acquire_errors
+---
+  × Failed to parse toml file
+  ╰─▶ missing field `audits`
+   ╭─[audits.toml:1:1]
+ 1 │ 
+   · â–²
+   · ╰── here
+   ╰────
+
diff --git a/src/tests/snapshots/cargo_vet__tests__store_parsing__all_min.snap b/src/tests/snapshots/cargo_vet__tests__store_parsing__all_min.snap
new file mode 100644
index 0000000..802d949
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__store_parsing__all_min.snap
@@ -0,0 +1,5 @@
+---
+source: src/tests/store_parsing.rs
+expression: acquire_errors
+---
+
diff --git a/src/tests/snapshots/cargo_vet__tests__store_parsing__distant_future_end_date.snap b/src/tests/snapshots/cargo_vet__tests__store_parsing__distant_future_end_date.snap
new file mode 100644
index 0000000..87ac530
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__store_parsing__distant_future_end_date.snap
@@ -0,0 +1,15 @@
+---
+source: src/tests/store_parsing.rs
+expression: acquire_errors
+---
+  × Your cargo-vet store (supply-chain) has consistency errors
+
+Error:   × '2024-05-15' is more than a year in the future
+   ╭─[audits.toml:7:1]
+ 7 │ start = "2015-05-15"
+ 8 │ end = "2024-05-15"
+   ·       ────────────
+ 9 │ 
+   ╰────
+  help: wildcard audits must end at most a year in the future (2024-01-01)
+
diff --git a/src/tests/snapshots/cargo_vet__tests__store_parsing__distant_future_end_date_leap_year.snap b/src/tests/snapshots/cargo_vet__tests__store_parsing__distant_future_end_date_leap_year.snap
new file mode 100644
index 0000000..1f2cee7
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__store_parsing__distant_future_end_date_leap_year.snap
@@ -0,0 +1,15 @@
+---
+source: src/tests/store_parsing.rs
+expression: acquire_errors
+---
+  × Your cargo-vet store (supply-chain) has consistency errors
+
+Error:   × '2021-03-01' is more than a year in the future
+   ╭─[audits.toml:7:1]
+ 7 │ start = "2015-05-15"
+ 8 │ end = "2021-03-01"
+   ·       ────────────
+ 9 │ 
+   ╰────
+  help: wildcard audits must end at most a year in the future (2021-02-28)
+
diff --git a/src/tests/snapshots/cargo_vet__tests__store_parsing__invalid_formatting.snap b/src/tests/snapshots/cargo_vet__tests__store_parsing__invalid_formatting.snap
new file mode 100644
index 0000000..354be2d
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__store_parsing__invalid_formatting.snap
@@ -0,0 +1,50 @@
+---
+source: src/tests/store_parsing.rs
+expression: acquire_errors
+---
+  × Your cargo-vet store (supply-chain) has consistency errors
+
+Error:   × A file in the store is not correctly formatted:
+  │ 
+  │ --- old/config.toml
+  │ +++ new/config.toml
+  │ @@ -11,14 +11,14 @@
+  │  [imports.peer2]
+  │  url = "https://peer1.com"
+  │ 
+  │ -[[exemptions.zzz]]
+  │ +[[exemptions.aaa]]
+  │  version = "1.0.0"
+  │  criteria = "safe-to-deploy"
+  │ 
+  │  [[exemptions.bbb]]
+  │ +version = "1.0.0"
+  │  criteria = "safe-to-deploy"
+  │ -version = "1.0.0"
+  │ 
+  │ -[[exemptions.aaa]]
+  │ +[[exemptions.zzz]]
+  │  version = "1.0.0"
+  │  criteria = "safe-to-deploy"
+  │ 
+  help: run `cargo vet` without --locked to reformat files in the store
+Error:   × A file in the store is not correctly formatted:
+  │ 
+  │ --- old/audits.toml
+  │ +++ new/audits.toml
+  │ @@ -7,9 +7,9 @@
+  │ 
+  │  [[audits.serde]]
+  │  criteria = ["safe-to-deploy", "good"]
+  │ -version = "2.0.0"
+  │ +version = "1.0.0"
+  │ +notes = "valid field"
+  │ 
+  │  [[audits.serde]]
+  │  criteria = ["safe-to-deploy", "good"]
+  │ -version = "1.0.0"
+  │ -notes = "valid field"
+  │ +version = "2.0.0"
+  │ 
+  help: run `cargo vet` without --locked to reformat files in the store
+
diff --git a/src/tests/snapshots/cargo_vet__tests__store_parsing__many_bad_audits.snap b/src/tests/snapshots/cargo_vet__tests__store_parsing__many_bad_audits.snap
new file mode 100644
index 0000000..069cd59
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__store_parsing__many_bad_audits.snap
@@ -0,0 +1,55 @@
+---
+source: src/tests/store_parsing.rs
+expression: acquire_errors
+---
+  × Your cargo-vet store (supply-chain) has consistency errors
+
+Error:   × 'bad-imply' is not a valid criteria name
+   ╭─[audits.toml:5:1]
+ 5 │ description = "great"
+ 6 │ implies = ["safe-to-deploy", "bad-imply"]
+   ·                              ───────────
+ 7 │ 
+   ╰────
+  help: the possible criteria are ["good", "safe-to-run", "safe-to-deploy"]
+Error:   × 'bad' is not a valid criteria name
+    ╭─[audits.toml:8:1]
+  8 │ [[audits.serde]]
+  9 │ criteria = "bad"
+    ·            ─────
+ 10 │ version = "1.0.0"
+    ╰────
+  help: the possible criteria are ["good", "safe-to-run", "safe-to-deploy"]
+Error:   × 'safe-to-jog' is not a valid criteria name
+    ╭─[audits.toml:12:1]
+ 12 │ [[audits.serde]]
+ 13 │ criteria = "safe-to-jog"
+    ·            ─────────────
+ 14 │ version = "2.0.0"
+    ╰────
+  help: the possible criteria are ["good", "safe-to-run", "safe-to-deploy"]
+Error:   × 'oops' is not a valid criteria name
+    ╭─[audits.toml:16:1]
+ 16 │ [[audits.serde]]
+ 17 │ criteria = "oops"
+    ·            ──────
+ 18 │ delta = "1.0.0 -> 1.1.0"
+    ╰────
+  help: the possible criteria are ["good", "safe-to-run", "safe-to-deploy"]
+Error:   × 'dang' is not a valid criteria name
+    ╭─[audits.toml:20:1]
+ 20 │ [[audits.serde]]
+ 21 │ criteria = ["safe-to-run", "dang"]
+    ·                            ──────
+ 22 │ delta = "1.0.0 -> 1.1.0"
+    ╰────
+  help: the possible criteria are ["good", "safe-to-run", "safe-to-deploy"]
+Error:   × 'no-good-bad-bad' is not a valid criteria name
+    ╭─[audits.toml:24:1]
+ 24 │ [[audits.serde]]
+ 25 │ criteria = "no-good-bad-bad"
+    ·            ─────────────────
+ 26 │ violation = "^5.0.0"
+    ╰────
+  help: the possible criteria are ["good", "safe-to-run", "safe-to-deploy"]
+
diff --git a/src/tests/snapshots/cargo_vet__tests__store_parsing__many_bad_config.snap b/src/tests/snapshots/cargo_vet__tests__store_parsing__many_bad_config.snap
new file mode 100644
index 0000000..dd26cbb
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__store_parsing__many_bad_config.snap
@@ -0,0 +1,47 @@
+---
+source: src/tests/store_parsing.rs
+expression: acquire_errors
+---
+  × Your cargo-vet store (supply-chain) has consistency errors
+
+Error:   × 'oops' is not a valid criteria name
+    ╭─[config.toml:21:1]
+ 21 │ version = "1.0.0"
+ 22 │ criteria = "oops"
+    ·            ──────
+ 23 │ 
+    ╰────
+  help: the possible criteria are ["good", "safe-to-run", "safe-to-deploy"]
+Error:   × 'bad' is not a valid criteria name
+    ╭─[config.toml:15:1]
+ 15 │ [policy.serde]
+ 16 │ criteria = "bad"
+    ·            ─────
+ 17 │ dev-criteria = "nope"
+    ╰────
+  help: the possible criteria are ["good", "safe-to-run", "safe-to-deploy"]
+Error:   × 'nope' is not a valid criteria name
+    ╭─[config.toml:16:1]
+ 16 │ criteria = "bad"
+ 17 │ dev-criteria = "nope"
+    ·                ──────
+ 18 │ dependency-criteria = { clap = ["safe-to-run", "unsafe-for-all", "good"], serde_derive = "nada" }
+    ╰────
+  help: the possible criteria are ["good", "safe-to-run", "safe-to-deploy"]
+Error:   × 'unsafe-for-all' is not a valid criteria name
+    ╭─[config.toml:17:1]
+ 17 │ dev-criteria = "nope"
+ 18 │ dependency-criteria = { clap = ["safe-to-run", "unsafe-for-all", "good"], serde_derive = "nada" }
+    ·                                                ────────────────
+ 19 │ 
+    ╰────
+  help: the possible criteria are ["good", "safe-to-run", "safe-to-deploy"]
+Error:   × 'nada' is not a valid criteria name
+    ╭─[config.toml:17:1]
+ 17 │ dev-criteria = "nope"
+ 18 │ dependency-criteria = { clap = ["safe-to-run", "unsafe-for-all", "good"], serde_derive = "nada" }
+    ·                                                                                          ──────
+ 19 │ 
+    ╰────
+  help: the possible criteria are ["good", "safe-to-run", "safe-to-deploy"]
+
diff --git a/src/tests/snapshots/cargo_vet__tests__store_parsing__many_bad_policies.snap b/src/tests/snapshots/cargo_vet__tests__store_parsing__many_bad_policies.snap
new file mode 100644
index 0000000..77357fe
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__store_parsing__many_bad_policies.snap
@@ -0,0 +1,44 @@
+---
+source: src/tests/store_parsing.rs
+expression: acquire_errors
+---
+
+  × Your cargo-vet store (supply-chain) has consistency errors
+
+Error: 
+  × 'bad' is not a valid criteria name
+   ╭─[config.toml:2:1]
+ 2 │ [policy.serde]
+ 3 │ criteria = "bad"
+   ·            ─────
+ 4 │ dev-criteria = "nope"
+   ╰────
+  help: the possible criteria are ["good", "safe-to-run", "safe-to-deploy"]
+Error: 
+  × 'nope' is not a valid criteria name
+   ╭─[config.toml:3:1]
+ 3 │ criteria = "bad"
+ 4 │ dev-criteria = "nope"
+   ·                ──────
+ 5 │ dependency-criteria = { serde_derive = "nada", clap = ["safe-to-run", "unsafe-for-all", "good"] }
+   ╰────
+  help: the possible criteria are ["good", "safe-to-run", "safe-to-deploy"]
+Error: 
+  × 'unsafe-for-all' is not a valid criteria name
+   ╭─[config.toml:4:1]
+ 4 │ dev-criteria = "nope"
+ 5 │ dependency-criteria = { serde_derive = "nada", clap = ["safe-to-run", "unsafe-for-all", "good"] }
+   ·                                                                       ────────────────
+ 6 │ 
+   ╰────
+  help: the possible criteria are ["good", "safe-to-run", "safe-to-deploy"]
+Error: 
+  × 'nada' is not a valid criteria name
+   ╭─[config.toml:4:1]
+ 4 │ dev-criteria = "nope"
+ 5 │ dependency-criteria = { serde_derive = "nada", clap = ["safe-to-run", "unsafe-for-all", "good"] }
+   ·                                        ──────
+ 6 │ 
+   ╰────
+  help: the possible criteria are ["good", "safe-to-run", "safe-to-deploy"]
+
diff --git a/src/tests/snapshots/cargo_vet__tests__store_parsing__outdated_imports_lock_excluded_crate.snap b/src/tests/snapshots/cargo_vet__tests__store_parsing__outdated_imports_lock_excluded_crate.snap
new file mode 100644
index 0000000..86e7389
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__store_parsing__outdated_imports_lock_excluded_crate.snap
@@ -0,0 +1,9 @@
+---
+source: src/tests/store_parsing.rs
+expression: acquire_errors
+---
+  × Your cargo-vet store (supply-chain) has consistency errors
+
+Error:   × imports.lock is out-of-date with respect to configuration
+  help: run `cargo vet` without --locked to update imports
+
diff --git a/src/tests/snapshots/cargo_vet__tests__store_parsing__outdated_imports_lock_extra_peer.snap b/src/tests/snapshots/cargo_vet__tests__store_parsing__outdated_imports_lock_extra_peer.snap
new file mode 100644
index 0000000..86e7389
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__store_parsing__outdated_imports_lock_extra_peer.snap
@@ -0,0 +1,9 @@
+---
+source: src/tests/store_parsing.rs
+expression: acquire_errors
+---
+  × Your cargo-vet store (supply-chain) has consistency errors
+
+Error:   × imports.lock is out-of-date with respect to configuration
+  help: run `cargo vet` without --locked to update imports
+
diff --git a/src/tests/snapshots/cargo_vet__tests__store_parsing__outdated_imports_lock_missing_peer.snap b/src/tests/snapshots/cargo_vet__tests__store_parsing__outdated_imports_lock_missing_peer.snap
new file mode 100644
index 0000000..86e7389
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__store_parsing__outdated_imports_lock_missing_peer.snap
@@ -0,0 +1,9 @@
+---
+source: src/tests/store_parsing.rs
+expression: acquire_errors
+---
+  × Your cargo-vet store (supply-chain) has consistency errors
+
+Error:   × imports.lock is out-of-date with respect to configuration
+  help: run `cargo vet` without --locked to update imports
+
diff --git a/src/tests/snapshots/cargo_vet__tests__store_parsing__outdated_imports_lock_ok.snap b/src/tests/snapshots/cargo_vet__tests__store_parsing__outdated_imports_lock_ok.snap
new file mode 100644
index 0000000..802d949
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__store_parsing__outdated_imports_lock_ok.snap
@@ -0,0 +1,5 @@
+---
+source: src/tests/store_parsing.rs
+expression: acquire_errors
+---
+
diff --git a/src/tests/snapshots/cargo_vet__tests__store_parsing__parse_criteria_map.snap b/src/tests/snapshots/cargo_vet__tests__store_parsing__parse_criteria_map.snap
new file mode 100644
index 0000000..802d949
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__store_parsing__parse_criteria_map.snap
@@ -0,0 +1,5 @@
+---
+source: src/tests/store_parsing.rs
+expression: acquire_errors
+---
+
diff --git a/src/tests/snapshots/cargo_vet__tests__store_parsing__simple_bad_audit.snap b/src/tests/snapshots/cargo_vet__tests__store_parsing__simple_bad_audit.snap
new file mode 100644
index 0000000..5ab1f64
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__store_parsing__simple_bad_audit.snap
@@ -0,0 +1,15 @@
+---
+source: src/tests/store_parsing.rs
+expression: acquire_errors
+---
+  × Your cargo-vet store (supply-chain) has consistency errors
+
+Error:   × 'bad' is not a valid criteria name
+   ╭─[audits.toml:4:1]
+ 4 │ [[audits.serde]]
+ 5 │ criteria = "bad"
+   ·            ─────
+ 6 │ version = "1.0.0"
+   ╰────
+  help: the possible criteria are ["safe-to-run", "safe-to-deploy"]
+
diff --git a/src/tests/snapshots/cargo_vet__tests__store_parsing__unknown_field_audit.snap b/src/tests/snapshots/cargo_vet__tests__store_parsing__unknown_field_audit.snap
new file mode 100644
index 0000000..3c373bd
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__store_parsing__unknown_field_audit.snap
@@ -0,0 +1,16 @@
+---
+source: src/tests/store_parsing.rs
+expression: acquire_errors
+---
+  × Failed to parse toml file
+  ╰─▶ unknown field `unknown-field`, expected one of `who`, `criteria`,
+      `version`, `delta`, `violation`, `notes`, `aggregated-from` for key
+      `audits.zzz` at line 4 column 1
+   ╭─[audits.toml:3:1]
+ 3 │ 
+ 4 │ [[audits.zzz]]
+   · â–²
+   · ╰── here
+ 5 │ criteria = "safe-to-deploy"
+   ╰────
+
diff --git a/src/tests/snapshots/cargo_vet__tests__store_parsing__unknown_field_config.snap b/src/tests/snapshots/cargo_vet__tests__store_parsing__unknown_field_config.snap
new file mode 100644
index 0000000..5fbcf3b
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__store_parsing__unknown_field_config.snap
@@ -0,0 +1,23 @@
+---
+source: src/tests/store_parsing.rs
+expression: acquire_errors
+---
+  × Your cargo-vet store (supply-chain) has consistency errors
+
+Error:   × A file in the store is not correctly formatted:
+  │ 
+  │ --- old/config.toml
+  │ +++ new/config.toml
+  │ @@ -7,9 +7,7 @@
+  │  [imports.peer1]
+  │  url = "https://peer1.com"
+  │  exclude = ["zzz", "aaa"]
+  │ -unknown-field = "hi"
+  │ 
+  │  [[exemptions.zzz]]
+  │  version = "1.0.0"
+  │  criteria = "safe-to-deploy"
+  │ -unknown-field = "hi"
+  │ 
+  help: run `cargo vet` without --locked to reformat files in the store
+
diff --git a/src/tests/snapshots/cargo_vet__tests__store_parsing__unknown_field_criteria.snap b/src/tests/snapshots/cargo_vet__tests__store_parsing__unknown_field_criteria.snap
new file mode 100644
index 0000000..47c6c24
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__store_parsing__unknown_field_criteria.snap
@@ -0,0 +1,15 @@
+---
+source: src/tests/store_parsing.rs
+expression: acquire_errors
+---
+  × Failed to parse toml file
+  ╰─▶ unknown field `unknown-field`, expected one of `description`,
+      `description-url`, `implies`, `aggregated-from` for key `criteria.good`
+      at line 9 column 1
+   ╭─[audits.toml:8:1]
+ 8 │ 
+ 9 │ [audits]
+   · â–²
+   · ╰── here
+   ╰────
+
diff --git a/src/tests/snapshots/cargo_vet__tests__trusted__trusted_delta_audit_locked.json.snap b/src/tests/snapshots/cargo_vet__tests__trusted__trusted_delta_audit_locked.json.snap
new file mode 100644
index 0000000..e64118f
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__trusted__trusted_delta_audit_locked.json.snap
@@ -0,0 +1,23 @@
+---
+source: src/tests/trusted.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0"
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__trusted__trusted_delta_audit_locked.snap b/src/tests/snapshots/cargo_vet__tests__trusted__trusted_delta_audit_locked.snap
new file mode 100644
index 0000000..2ba4734
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__trusted__trusted_delta_audit_locked.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/trusted.rs
+expression: human
+---
+Vetting Succeeded (3 fully audited)
+
diff --git a/src/tests/snapshots/cargo_vet__tests__trusted__trusted_delta_audit_wrong_user_id_locked.json.snap b/src/tests/snapshots/cargo_vet__tests__trusted__trusted_delta_audit_wrong_user_id_locked.json.snap
new file mode 100644
index 0000000..ee2469d
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__trusted__trusted_delta_audit_wrong_user_id_locked.json.snap
@@ -0,0 +1,57 @@
+---
+source: src/tests/trusted.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "transitive-third-party1",
+        "notable_parents": "third-party1",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "5.0.0",
+          "diffstat": {
+            "insertions": 25,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "safe-to-deploy": [
+        {
+          "name": "transitive-third-party1",
+          "notable_parents": "third-party1",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "5.0.0",
+            "diffstat": {
+              "insertions": 25,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 25
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__trusted__trusted_delta_audit_wrong_user_id_locked.snap b/src/tests/snapshots/cargo_vet__tests__trusted__trusted_delta_audit_wrong_user_id_locked.snap
new file mode 100644
index 0000000..ebaae5b
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__trusted__trusted_delta_audit_wrong_user_id_locked.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/trusted.rs
+expression: human
+---
+Vetting Failed!
+
+1 unvetted dependencies:
+  transitive-third-party1:10.0.0 missing ["safe-to-deploy"]
+
+recommended audits for safe-to-deploy:
+    Command                                          Publisher  Used By       Audit Size
+    cargo vet inspect transitive-third-party1 5.0.0  UNKNOWN    third-party1  25 lines
+
+estimated audit backlog: 25 lines
+
+Use |cargo vet certify| to record the audits.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__trusted__trusted_full_audit_locked.json.snap b/src/tests/snapshots/cargo_vet__tests__trusted__trusted_full_audit_locked.json.snap
new file mode 100644
index 0000000..e64118f
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__trusted__trusted_full_audit_locked.json.snap
@@ -0,0 +1,23 @@
+---
+source: src/tests/trusted.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0"
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__trusted__trusted_full_audit_locked.snap b/src/tests/snapshots/cargo_vet__tests__trusted__trusted_full_audit_locked.snap
new file mode 100644
index 0000000..2ba4734
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__trusted__trusted_full_audit_locked.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/trusted.rs
+expression: human
+---
+Vetting Succeeded (3 fully audited)
+
diff --git a/src/tests/snapshots/cargo_vet__tests__trusted__trusted_suggest_import.json.snap b/src/tests/snapshots/cargo_vet__tests__trusted__trusted_suggest_import.json.snap
new file mode 100644
index 0000000..290021a
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__trusted__trusted_suggest_import.json.snap
@@ -0,0 +1,57 @@
+---
+source: src/tests/trusted.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "transitive-third-party1",
+        "notable_parents": "third-party1",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "safe-to-deploy": [
+        {
+          "name": "transitive-third-party1",
+          "notable_parents": "third-party1",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 100
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__trusted__trusted_suggest_import.snap b/src/tests/snapshots/cargo_vet__tests__trusted__trusted_suggest_import.snap
new file mode 100644
index 0000000..bf58380
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__trusted__trusted_suggest_import.snap
@@ -0,0 +1,18 @@
+---
+source: src/tests/trusted.rs
+expression: human
+---
+Vetting Failed!
+
+1 unvetted dependencies:
+  transitive-third-party1:10.0.0 missing ["safe-to-deploy"]
+
+recommended audits for safe-to-deploy:
+    Command                                           Publisher  Used By       Audit Size
+    cargo vet inspect transitive-third-party1 10.0.0  testuser   third-party1  100 lines
+      NOTE: peer-company trusts Test user (testuser) - consider cargo vet trust transitive-third-party1 or cargo vet trust --all testuser
+
+estimated audit backlog: 100 lines
+
+Use |cargo vet certify| to record the audits.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__trusted__trusted_suggest_import_multiple.json.snap b/src/tests/snapshots/cargo_vet__tests__trusted__trusted_suggest_import_multiple.json.snap
new file mode 100644
index 0000000..290021a
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__trusted__trusted_suggest_import_multiple.json.snap
@@ -0,0 +1,57 @@
+---
+source: src/tests/trusted.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "transitive-third-party1",
+        "notable_parents": "third-party1",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "safe-to-deploy": [
+        {
+          "name": "transitive-third-party1",
+          "notable_parents": "third-party1",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 100
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__trusted__trusted_suggest_import_multiple.snap b/src/tests/snapshots/cargo_vet__tests__trusted__trusted_suggest_import_multiple.snap
new file mode 100644
index 0000000..faf65f6
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__trusted__trusted_suggest_import_multiple.snap
@@ -0,0 +1,18 @@
+---
+source: src/tests/trusted.rs
+expression: human
+---
+Vetting Failed!
+
+1 unvetted dependencies:
+  transitive-third-party1:10.0.0 missing ["safe-to-deploy"]
+
+recommended audits for safe-to-deploy:
+    Command                                           Publisher  Used By       Audit Size
+    cargo vet inspect transitive-third-party1 10.0.0  testuser   third-party1  100 lines
+      NOTE: peer-company and rival-company trust Test user (testuser) - consider cargo vet trust transitive-third-party1 or cargo vet trust --all testuser
+
+estimated audit backlog: 100 lines
+
+Use |cargo vet certify| to record the audits.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__trusted__trusted_suggest_local.json.snap b/src/tests/snapshots/cargo_vet__tests__trusted__trusted_suggest_local.json.snap
new file mode 100644
index 0000000..290021a
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__trusted__trusted_suggest_local.json.snap
@@ -0,0 +1,57 @@
+---
+source: src/tests/trusted.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "transitive-third-party1",
+        "notable_parents": "third-party1",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "safe-to-deploy": [
+        {
+          "name": "transitive-third-party1",
+          "notable_parents": "third-party1",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 100
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__trusted__trusted_suggest_local.snap b/src/tests/snapshots/cargo_vet__tests__trusted__trusted_suggest_local.snap
new file mode 100644
index 0000000..8515e97
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__trusted__trusted_suggest_local.snap
@@ -0,0 +1,18 @@
+---
+source: src/tests/trusted.rs
+expression: human
+---
+Vetting Failed!
+
+1 unvetted dependencies:
+  transitive-third-party1:10.0.0 missing ["safe-to-deploy"]
+
+recommended audits for safe-to-deploy:
+    Command                                           Publisher  Used By       Audit Size
+    cargo vet inspect transitive-third-party1 10.0.0  testuser   third-party1  100 lines
+      NOTE: this project trusts Test user (testuser) - consider cargo vet trust transitive-third-party1 or cargo vet trust --all testuser
+
+estimated audit backlog: 100 lines
+
+Use |cargo vet certify| to record the audits.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__trusted__trusted_suggest_local_ambiguous.json.snap b/src/tests/snapshots/cargo_vet__tests__trusted__trusted_suggest_local_ambiguous.json.snap
new file mode 100644
index 0000000..290021a
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__trusted__trusted_suggest_local_ambiguous.json.snap
@@ -0,0 +1,57 @@
+---
+source: src/tests/trusted.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "transitive-third-party1",
+        "notable_parents": "third-party1",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "safe-to-deploy": [
+        {
+          "name": "transitive-third-party1",
+          "notable_parents": "third-party1",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 100
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__trusted__trusted_suggest_local_ambiguous.snap b/src/tests/snapshots/cargo_vet__tests__trusted__trusted_suggest_local_ambiguous.snap
new file mode 100644
index 0000000..5939218
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__trusted__trusted_suggest_local_ambiguous.snap
@@ -0,0 +1,18 @@
+---
+source: src/tests/trusted.rs
+expression: human
+---
+Vetting Failed!
+
+1 unvetted dependencies:
+  transitive-third-party1:10.0.0 missing ["safe-to-deploy"]
+
+recommended audits for safe-to-deploy:
+    Command                                           Publisher  Used By       Audit Size
+    cargo vet inspect transitive-third-party1 10.0.0  testuser   third-party1  100 lines
+      NOTE: this project trusts Test user (testuser) - consider cargo vet trust transitive-third-party1 testuser
+
+estimated audit backlog: 100 lines
+
+Use |cargo vet certify| to record the audits.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__trusted__trusted_wrong_user_id_locked.json.snap b/src/tests/snapshots/cargo_vet__tests__trusted__trusted_wrong_user_id_locked.json.snap
new file mode 100644
index 0000000..290021a
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__trusted__trusted_wrong_user_id_locked.json.snap
@@ -0,0 +1,57 @@
+---
+source: src/tests/trusted.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "transitive-third-party1",
+        "notable_parents": "third-party1",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "safe-to-deploy": [
+        {
+          "name": "transitive-third-party1",
+          "notable_parents": "third-party1",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 100
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__trusted__trusted_wrong_user_id_locked.snap b/src/tests/snapshots/cargo_vet__tests__trusted__trusted_wrong_user_id_locked.snap
new file mode 100644
index 0000000..01a64dd
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__trusted__trusted_wrong_user_id_locked.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/trusted.rs
+expression: human
+---
+Vetting Failed!
+
+1 unvetted dependencies:
+  transitive-third-party1:10.0.0 missing ["safe-to-deploy"]
+
+recommended audits for safe-to-deploy:
+    Command                                           Publisher  Used By       Audit Size
+    cargo vet inspect transitive-third-party1 10.0.0  UNKNOWN    third-party1  100 lines
+
+estimated audit backlog: 100 lines
+
+Use |cargo vet certify| to record the audits.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__unpublished__audit_as_crates_io_unpublished_blank_regenerate_exemptions.snap b/src/tests/snapshots/cargo_vet__tests__unpublished__audit_as_crates_io_unpublished_blank_regenerate_exemptions.snap
new file mode 100644
index 0000000..0c71fd2
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__unpublished__audit_as_crates_io_unpublished_blank_regenerate_exemptions.snap
@@ -0,0 +1,28 @@
+---
+source: src/tests/unpublished.rs
+expression: output
+---
+audits.toml: (unchanged)
+config.toml:
+ 
+ # cargo-vet config file
+ 
+ [cargo-vet]
+ version = "1.0"
+ 
+ [policy.descriptive]
+ audit-as-crates-io = true
++
++[[exemptions.descriptive]]
++version = "9.0.0"
++criteria = "safe-to-deploy"
+
+imports.lock:
+ 
+ # cargo-vet imports lock
++
++[[unpublished.descriptive]]
++version = "10.0.0"
++audited_as = "9.0.0"
+
+
diff --git a/src/tests/snapshots/cargo_vet__tests__unpublished__audit_as_crates_io_unpublished_full_prefer_exemptions.snap b/src/tests/snapshots/cargo_vet__tests__unpublished__audit_as_crates_io_unpublished_full_prefer_exemptions.snap
new file mode 100644
index 0000000..82d4a3a
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__unpublished__audit_as_crates_io_unpublished_full_prefer_exemptions.snap
@@ -0,0 +1,8 @@
+---
+source: src/tests/unpublished.rs
+expression: output
+---
+audits.toml: (unchanged)
+config.toml: (unchanged)
+imports.lock: (unchanged)
+
diff --git a/src/tests/snapshots/cargo_vet__tests__unpublished__audit_as_crates_io_unpublished_full_prefer_fresh.snap b/src/tests/snapshots/cargo_vet__tests__unpublished__audit_as_crates_io_unpublished_full_prefer_fresh.snap
new file mode 100644
index 0000000..82d4a3a
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__unpublished__audit_as_crates_io_unpublished_full_prefer_fresh.snap
@@ -0,0 +1,8 @@
+---
+source: src/tests/unpublished.rs
+expression: output
+---
+audits.toml: (unchanged)
+config.toml: (unchanged)
+imports.lock: (unchanged)
+
diff --git a/src/tests/snapshots/cargo_vet__tests__unpublished__audit_as_crates_io_unpublished_full_prune.snap b/src/tests/snapshots/cargo_vet__tests__unpublished__audit_as_crates_io_unpublished_full_prune.snap
new file mode 100644
index 0000000..82d4a3a
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__unpublished__audit_as_crates_io_unpublished_full_prune.snap
@@ -0,0 +1,8 @@
+---
+source: src/tests/unpublished.rs
+expression: output
+---
+audits.toml: (unchanged)
+config.toml: (unchanged)
+imports.lock: (unchanged)
+
diff --git a/src/tests/snapshots/cargo_vet__tests__unpublished__audit_as_crates_io_unpublished_full_regenerate_exemptions.snap b/src/tests/snapshots/cargo_vet__tests__unpublished__audit_as_crates_io_unpublished_full_regenerate_exemptions.snap
new file mode 100644
index 0000000..82d4a3a
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__unpublished__audit_as_crates_io_unpublished_full_regenerate_exemptions.snap
@@ -0,0 +1,8 @@
+---
+source: src/tests/unpublished.rs
+expression: output
+---
+audits.toml: (unchanged)
+config.toml: (unchanged)
+imports.lock: (unchanged)
+
diff --git a/src/tests/snapshots/cargo_vet__tests__unpublished__audit_as_crates_io_unpublished_wildcard_nounpublished_prefer_exemptions.snap b/src/tests/snapshots/cargo_vet__tests__unpublished__audit_as_crates_io_unpublished_wildcard_nounpublished_prefer_exemptions.snap
new file mode 100644
index 0000000..145db01
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__unpublished__audit_as_crates_io_unpublished_wildcard_nounpublished_prefer_exemptions.snap
@@ -0,0 +1,29 @@
+---
+source: src/tests/unpublished.rs
+expression: output
+---
+audits.toml: (unchanged)
+config.toml: (unchanged)
+imports.lock:
+ 
+ # cargo-vet imports lock
+ 
++[[unpublished.descriptive]]
++version = "10.0.0"
++audited_as = "9.0.0"
++
+ [[publisher.descriptive]]
+ version = "8.0.0"
+ when = "2022-12-15"
+ user-id = 1
+ user-login = "user1"
+ user-name = "User One"
++
++[[publisher.descriptive]]
++version = "9.0.0"
++when = "2022-12-15"
++user-id = 1
++user-login = "user1"
++user-name = "User One"
+
+
diff --git a/src/tests/snapshots/cargo_vet__tests__unpublished__audit_as_crates_io_unpublished_wildcard_nounpublished_prune.snap b/src/tests/snapshots/cargo_vet__tests__unpublished__audit_as_crates_io_unpublished_wildcard_nounpublished_prune.snap
new file mode 100644
index 0000000..d5ad564
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__unpublished__audit_as_crates_io_unpublished_wildcard_nounpublished_prune.snap
@@ -0,0 +1,23 @@
+---
+source: src/tests/unpublished.rs
+expression: output
+---
+audits.toml: (unchanged)
+config.toml: (unchanged)
+imports.lock:
+ 
+ # cargo-vet imports lock
+ 
++[[unpublished.descriptive]]
++version = "10.0.0"
++audited_as = "9.0.0"
++
+ [[publisher.descriptive]]
+-version = "8.0.0"
++version = "9.0.0"
+ when = "2022-12-15"
+ user-id = 1
+ user-login = "user1"
+ user-name = "User One"
+
+
diff --git a/src/tests/snapshots/cargo_vet__tests__unpublished__audit_as_crates_io_unpublished_wildcard_nounpublished_regenerate_exemptions.snap b/src/tests/snapshots/cargo_vet__tests__unpublished__audit_as_crates_io_unpublished_wildcard_nounpublished_regenerate_exemptions.snap
new file mode 100644
index 0000000..d5ad564
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__unpublished__audit_as_crates_io_unpublished_wildcard_nounpublished_regenerate_exemptions.snap
@@ -0,0 +1,23 @@
+---
+source: src/tests/unpublished.rs
+expression: output
+---
+audits.toml: (unchanged)
+config.toml: (unchanged)
+imports.lock:
+ 
+ # cargo-vet imports lock
+ 
++[[unpublished.descriptive]]
++version = "10.0.0"
++audited_as = "9.0.0"
++
+ [[publisher.descriptive]]
+-version = "8.0.0"
++version = "9.0.0"
+ when = "2022-12-15"
+ user-id = 1
+ user-login = "user1"
+ user-name = "User One"
+
+
diff --git a/src/tests/snapshots/cargo_vet__tests__unpublished__audit_as_crates_io_unpublished_wildcard_prefer_exemptions.snap b/src/tests/snapshots/cargo_vet__tests__unpublished__audit_as_crates_io_unpublished_wildcard_prefer_exemptions.snap
new file mode 100644
index 0000000..82d4a3a
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__unpublished__audit_as_crates_io_unpublished_wildcard_prefer_exemptions.snap
@@ -0,0 +1,8 @@
+---
+source: src/tests/unpublished.rs
+expression: output
+---
+audits.toml: (unchanged)
+config.toml: (unchanged)
+imports.lock: (unchanged)
+
diff --git a/src/tests/snapshots/cargo_vet__tests__unpublished__audit_as_crates_io_unpublished_wildcard_prefer_fresh.snap b/src/tests/snapshots/cargo_vet__tests__unpublished__audit_as_crates_io_unpublished_wildcard_prefer_fresh.snap
new file mode 100644
index 0000000..2208d9c
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__unpublished__audit_as_crates_io_unpublished_wildcard_prefer_fresh.snap
@@ -0,0 +1,24 @@
+---
+source: src/tests/unpublished.rs
+expression: output
+---
+audits.toml: (unchanged)
+config.toml: (unchanged)
+imports.lock:
+ 
+ # cargo-vet imports lock
+ 
+ [[unpublished.descriptive]]
+ version = "10.0.0"
+-audited_as = "8.0.0"
++audited_as = "9.0.0"
+ 
+ [[publisher.descriptive]]
+-version = "8.0.0"
++version = "9.0.0"
+ when = "2022-12-15"
+ user-id = 1
+ user-login = "user1"
+ user-name = "User One"
+
+
diff --git a/src/tests/snapshots/cargo_vet__tests__unpublished__audit_as_crates_io_unpublished_wildcard_prune.snap b/src/tests/snapshots/cargo_vet__tests__unpublished__audit_as_crates_io_unpublished_wildcard_prune.snap
new file mode 100644
index 0000000..2208d9c
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__unpublished__audit_as_crates_io_unpublished_wildcard_prune.snap
@@ -0,0 +1,24 @@
+---
+source: src/tests/unpublished.rs
+expression: output
+---
+audits.toml: (unchanged)
+config.toml: (unchanged)
+imports.lock:
+ 
+ # cargo-vet imports lock
+ 
+ [[unpublished.descriptive]]
+ version = "10.0.0"
+-audited_as = "8.0.0"
++audited_as = "9.0.0"
+ 
+ [[publisher.descriptive]]
+-version = "8.0.0"
++version = "9.0.0"
+ when = "2022-12-15"
+ user-id = 1
+ user-login = "user1"
+ user-name = "User One"
+
+
diff --git a/src/tests/snapshots/cargo_vet__tests__unpublished__audit_as_crates_io_unpublished_wildcard_regenerate_exemptions.snap b/src/tests/snapshots/cargo_vet__tests__unpublished__audit_as_crates_io_unpublished_wildcard_regenerate_exemptions.snap
new file mode 100644
index 0000000..2208d9c
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__unpublished__audit_as_crates_io_unpublished_wildcard_regenerate_exemptions.snap
@@ -0,0 +1,24 @@
+---
+source: src/tests/unpublished.rs
+expression: output
+---
+audits.toml: (unchanged)
+config.toml: (unchanged)
+imports.lock:
+ 
+ # cargo-vet imports lock
+ 
+ [[unpublished.descriptive]]
+ version = "10.0.0"
+-audited_as = "8.0.0"
++audited_as = "9.0.0"
+ 
+ [[publisher.descriptive]]
+-version = "8.0.0"
++version = "9.0.0"
+ when = "2022-12-15"
+ user-id = 1
+ user-login = "user1"
+ user-name = "User One"
+
+
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
new file mode 100644
index 0000000..4502744
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-complex-full-audited.json.snap
@@ -0,0 +1,27 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-core",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-core",
+      "version": "5.0.0"
+    },
+    {
+      "name": "thirdA",
+      "version": "10.0.0"
+    },
+    {
+      "name": "thirdAB",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-complex-full-audited.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-complex-full-audited.snap
new file mode 100644
index 0000000..1085e91
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-complex-full-audited.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (4 fully audited)
+
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
new file mode 100644
index 0000000..8a3c620
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-complex-inited.json.snap
@@ -0,0 +1,27 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [],
+  "vetted_partially": [],
+  "vetted_with_exemptions": [
+    {
+      "name": "third-core",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-core",
+      "version": "5.0.0"
+    },
+    {
+      "name": "thirdA",
+      "version": "10.0.0"
+    },
+    {
+      "name": "thirdAB",
+      "version": "10.0.0"
+    }
+  ]
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-complex-inited.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-complex-inited.snap
new file mode 100644
index 0000000..5eb6a7e
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-complex-inited.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (4 exempted)
+
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
new file mode 100644
index 0000000..4502744
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-complex-minimal-audited.json.snap
@@ -0,0 +1,27 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-core",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-core",
+      "version": "5.0.0"
+    },
+    {
+      "name": "thirdA",
+      "version": "10.0.0"
+    },
+    {
+      "name": "thirdAB",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-complex-minimal-audited.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-complex-minimal-audited.snap
new file mode 100644
index 0000000..1085e91
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-complex-minimal-audited.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (4 fully audited)
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-complex-no-unaudited.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-complex-no-unaudited.json.snap
new file mode 100644
index 0000000..9cb0004
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-complex-no-unaudited.json.snap
@@ -0,0 +1,174 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "third-core",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "third-core",
+      "version": "5.0.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "thirdA",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "thirdAB",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "third-core",
+        "notable_parents": "firstA",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "5.0.0",
+          "diffstat": {
+            "insertions": 25,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "third-core",
+        "notable_parents": "firstB, thirdA, and thirdAB",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "thirdA",
+        "notable_parents": "firstA",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "thirdAB",
+        "notable_parents": "firstAB",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "safe-to-deploy": [
+        {
+          "name": "third-core",
+          "notable_parents": "firstA",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "5.0.0",
+            "diffstat": {
+              "insertions": 25,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        },
+        {
+          "name": "third-core",
+          "notable_parents": "firstB, thirdA, and thirdAB",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        },
+        {
+          "name": "thirdA",
+          "notable_parents": "firstA",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        },
+        {
+          "name": "thirdAB",
+          "notable_parents": "firstAB",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 325
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-complex-no-unaudited.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-complex-no-unaudited.snap
new file mode 100644
index 0000000..cb4e3c6
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-complex-no-unaudited.snap
@@ -0,0 +1,23 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+Vetting Failed!
+
+4 unvetted dependencies:
+  third-core:5.0.0 missing ["safe-to-deploy"]
+  third-core:10.0.0 missing ["safe-to-deploy"]
+  thirdA:10.0.0 missing ["safe-to-deploy"]
+  thirdAB:10.0.0 missing ["safe-to-deploy"]
+
+recommended audits for safe-to-deploy:
+    Command                              Publisher  Used By                      Audit Size
+    cargo vet inspect third-core 5.0.0   UNKNOWN    firstA                       25 lines
+    cargo vet inspect third-core 10.0.0  UNKNOWN    firstB, thirdA, and thirdAB  100 lines
+    cargo vet inspect thirdA 10.0.0      UNKNOWN    firstA                       100 lines
+    cargo vet inspect thirdAB 10.0.0     UNKNOWN    firstAB                      100 lines
+
+estimated audit backlog: 325 lines
+
+Use |cargo vet certify| to record the audits.
+
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
new file mode 100644
index 0000000..7135feb
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-cycle-full-audited.json.snap
@@ -0,0 +1,19 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "dev-cycle",
+      "version": "10.0.0"
+    },
+    {
+      "name": "normal",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-cycle-full-audited.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-cycle-full-audited.snap
new file mode 100644
index 0000000..bcff706
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-cycle-full-audited.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (2 fully audited)
+
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
new file mode 100644
index 0000000..65f97b3
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-cycle-inited.json.snap
@@ -0,0 +1,19 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [],
+  "vetted_partially": [],
+  "vetted_with_exemptions": [
+    {
+      "name": "dev-cycle",
+      "version": "10.0.0"
+    },
+    {
+      "name": "normal",
+      "version": "10.0.0"
+    }
+  ]
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-cycle-inited.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-cycle-inited.snap
new file mode 100644
index 0000000..91ebce5
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-cycle-inited.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (2 exempted)
+
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
new file mode 100644
index 0000000..7135feb
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-cycle-minimal-audited.json.snap
@@ -0,0 +1,19 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "dev-cycle",
+      "version": "10.0.0"
+    },
+    {
+      "name": "normal",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-cycle-minimal-audited.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-cycle-minimal-audited.snap
new file mode 100644
index 0000000..bcff706
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-cycle-minimal-audited.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (2 fully audited)
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-cycle-unaudited.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-cycle-unaudited.json.snap
new file mode 100644
index 0000000..6b20202
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-cycle-unaudited.json.snap
@@ -0,0 +1,98 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "dev-cycle",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-run"
+      ]
+    },
+    {
+      "name": "normal",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "dev-cycle",
+        "notable_parents": "root",
+        "suggested_criteria": [
+          "safe-to-run"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "normal",
+        "notable_parents": "root",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "safe-to-deploy": [
+        {
+          "name": "normal",
+          "notable_parents": "root",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ],
+      "safe-to-run": [
+        {
+          "name": "dev-cycle",
+          "notable_parents": "root",
+          "suggested_criteria": [
+            "safe-to-run"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 200
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-cycle-unaudited.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-cycle-unaudited.snap
new file mode 100644
index 0000000..b7452f9
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-cycle-unaudited.snap
@@ -0,0 +1,22 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+Vetting Failed!
+
+2 unvetted dependencies:
+  dev-cycle:10.0.0 missing ["safe-to-run"]
+  normal:10.0.0 missing ["safe-to-deploy"]
+
+recommended audits for safe-to-deploy:
+    Command                          Publisher  Used By  Audit Size
+    cargo vet inspect normal 10.0.0  UNKNOWN    root     100 lines
+
+recommended audits for safe-to-run:
+    Command                             Publisher  Used By  Audit Size
+    cargo vet inspect dev-cycle 10.0.0  UNKNOWN    root     100 lines
+
+estimated audit backlog: 200 lines
+
+Use |cargo vet certify| to record the audits.
+
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
new file mode 100644
index 0000000..b1be8f4
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-cursed-full.json.snap
@@ -0,0 +1,35 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "both",
+      "version": "10.0.0"
+    },
+    {
+      "name": "dev-cycle-direct",
+      "version": "10.0.0"
+    },
+    {
+      "name": "dev-cycle-indirect",
+      "version": "10.0.0"
+    },
+    {
+      "name": "normal",
+      "version": "10.0.0"
+    },
+    {
+      "name": "simple-dev",
+      "version": "10.0.0"
+    },
+    {
+      "name": "simple-dev-indirect",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-cursed-full.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-cursed-full.snap
new file mode 100644
index 0000000..358cbdf
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-cursed-full.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (6 fully audited)
+
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
new file mode 100644
index 0000000..b1be8f4
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-cursed-minimal.json.snap
@@ -0,0 +1,35 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "both",
+      "version": "10.0.0"
+    },
+    {
+      "name": "dev-cycle-direct",
+      "version": "10.0.0"
+    },
+    {
+      "name": "dev-cycle-indirect",
+      "version": "10.0.0"
+    },
+    {
+      "name": "normal",
+      "version": "10.0.0"
+    },
+    {
+      "name": "simple-dev",
+      "version": "10.0.0"
+    },
+    {
+      "name": "simple-dev-indirect",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-cursed-minimal.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-cursed-minimal.snap
new file mode 100644
index 0000000..358cbdf
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-cursed-minimal.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (6 fully audited)
+
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
new file mode 100644
index 0000000..645292c
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-empty-deeper.json.snap
@@ -0,0 +1,254 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "both",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "dev-cycle-direct",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-run"
+      ]
+    },
+    {
+      "name": "dev-cycle-indirect",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-run"
+      ]
+    },
+    {
+      "name": "normal",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "simple-dev",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-run"
+      ]
+    },
+    {
+      "name": "simple-dev-indirect",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-run"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "both",
+        "notable_parents": "root",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "dev-cycle-direct",
+        "notable_parents": "root",
+        "suggested_criteria": [
+          "safe-to-run"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "dev-cycle-indirect",
+        "notable_parents": "dev-cycle-direct",
+        "suggested_criteria": [
+          "safe-to-run"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "normal",
+        "notable_parents": "root",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "simple-dev",
+        "notable_parents": "root",
+        "suggested_criteria": [
+          "safe-to-run"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "simple-dev-indirect",
+        "notable_parents": "simple-dev",
+        "suggested_criteria": [
+          "safe-to-run"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "safe-to-deploy": [
+        {
+          "name": "both",
+          "notable_parents": "root",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        },
+        {
+          "name": "normal",
+          "notable_parents": "root",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ],
+      "safe-to-run": [
+        {
+          "name": "dev-cycle-direct",
+          "notable_parents": "root",
+          "suggested_criteria": [
+            "safe-to-run"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        },
+        {
+          "name": "dev-cycle-indirect",
+          "notable_parents": "dev-cycle-direct",
+          "suggested_criteria": [
+            "safe-to-run"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        },
+        {
+          "name": "simple-dev",
+          "notable_parents": "root",
+          "suggested_criteria": [
+            "safe-to-run"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        },
+        {
+          "name": "simple-dev-indirect",
+          "notable_parents": "simple-dev",
+          "suggested_criteria": [
+            "safe-to-run"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 600
+  }
+}
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
new file mode 100644
index 0000000..c64965a
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-empty-deeper.snap
@@ -0,0 +1,30 @@
+---
+source: src/tests/vet.rs
+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"]
+  simple-dev-indirect:10.0.0 missing ["safe-to-run"]
+
+recommended audits for safe-to-deploy:
+    Command                          Publisher  Used By  Audit Size
+    cargo vet inspect both 10.0.0    UNKNOWN    root     100 lines
+    cargo vet inspect normal 10.0.0  UNKNOWN    root     100 lines
+
+recommended audits for safe-to-run:
+    Command                                       Publisher  Used By           Audit Size
+    cargo vet inspect dev-cycle-direct 10.0.0     UNKNOWN    root              100 lines
+    cargo vet inspect dev-cycle-indirect 10.0.0   UNKNOWN    dev-cycle-direct  100 lines
+    cargo vet inspect simple-dev 10.0.0           UNKNOWN    root              100 lines
+    cargo vet inspect simple-dev-indirect 10.0.0  UNKNOWN    simple-dev        100 lines
+
+estimated audit backlog: 600 lines
+
+Use |cargo vet certify| to record the audits.
+
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
new file mode 100644
index 0000000..645292c
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-empty.json.snap
@@ -0,0 +1,254 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "both",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "dev-cycle-direct",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-run"
+      ]
+    },
+    {
+      "name": "dev-cycle-indirect",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-run"
+      ]
+    },
+    {
+      "name": "normal",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "simple-dev",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-run"
+      ]
+    },
+    {
+      "name": "simple-dev-indirect",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-run"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "both",
+        "notable_parents": "root",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "dev-cycle-direct",
+        "notable_parents": "root",
+        "suggested_criteria": [
+          "safe-to-run"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "dev-cycle-indirect",
+        "notable_parents": "dev-cycle-direct",
+        "suggested_criteria": [
+          "safe-to-run"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "normal",
+        "notable_parents": "root",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "simple-dev",
+        "notable_parents": "root",
+        "suggested_criteria": [
+          "safe-to-run"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "simple-dev-indirect",
+        "notable_parents": "simple-dev",
+        "suggested_criteria": [
+          "safe-to-run"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "safe-to-deploy": [
+        {
+          "name": "both",
+          "notable_parents": "root",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        },
+        {
+          "name": "normal",
+          "notable_parents": "root",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ],
+      "safe-to-run": [
+        {
+          "name": "dev-cycle-direct",
+          "notable_parents": "root",
+          "suggested_criteria": [
+            "safe-to-run"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        },
+        {
+          "name": "dev-cycle-indirect",
+          "notable_parents": "dev-cycle-direct",
+          "suggested_criteria": [
+            "safe-to-run"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        },
+        {
+          "name": "simple-dev",
+          "notable_parents": "root",
+          "suggested_criteria": [
+            "safe-to-run"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        },
+        {
+          "name": "simple-dev-indirect",
+          "notable_parents": "simple-dev",
+          "suggested_criteria": [
+            "safe-to-run"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 600
+  }
+}
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
new file mode 100644
index 0000000..c64965a
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-empty.snap
@@ -0,0 +1,30 @@
+---
+source: src/tests/vet.rs
+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"]
+  simple-dev-indirect:10.0.0 missing ["safe-to-run"]
+
+recommended audits for safe-to-deploy:
+    Command                          Publisher  Used By  Audit Size
+    cargo vet inspect both 10.0.0    UNKNOWN    root     100 lines
+    cargo vet inspect normal 10.0.0  UNKNOWN    root     100 lines
+
+recommended audits for safe-to-run:
+    Command                                       Publisher  Used By           Audit Size
+    cargo vet inspect dev-cycle-direct 10.0.0     UNKNOWN    root              100 lines
+    cargo vet inspect dev-cycle-indirect 10.0.0   UNKNOWN    dev-cycle-direct  100 lines
+    cargo vet inspect simple-dev 10.0.0           UNKNOWN    root              100 lines
+    cargo vet inspect simple-dev-indirect 10.0.0  UNKNOWN    simple-dev        100 lines
+
+estimated audit backlog: 600 lines
+
+Use |cargo vet certify| to record the audits.
+
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
new file mode 100644
index 0000000..b1be8f4
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-unaudited-adds-uneeded-criteria-indirect.json.snap
@@ -0,0 +1,35 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "both",
+      "version": "10.0.0"
+    },
+    {
+      "name": "dev-cycle-direct",
+      "version": "10.0.0"
+    },
+    {
+      "name": "dev-cycle-indirect",
+      "version": "10.0.0"
+    },
+    {
+      "name": "normal",
+      "version": "10.0.0"
+    },
+    {
+      "name": "simple-dev",
+      "version": "10.0.0"
+    },
+    {
+      "name": "simple-dev-indirect",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-unaudited-adds-uneeded-criteria-indirect.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-unaudited-adds-uneeded-criteria-indirect.snap
new file mode 100644
index 0000000..358cbdf
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection-unaudited-adds-uneeded-criteria-indirect.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (6 fully audited)
+
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
new file mode 100644
index 0000000..b1be8f4
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection.json.snap
@@ -0,0 +1,35 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "both",
+      "version": "10.0.0"
+    },
+    {
+      "name": "dev-cycle-direct",
+      "version": "10.0.0"
+    },
+    {
+      "name": "dev-cycle-indirect",
+      "version": "10.0.0"
+    },
+    {
+      "name": "normal",
+      "version": "10.0.0"
+    },
+    {
+      "name": "simple-dev",
+      "version": "10.0.0"
+    },
+    {
+      "name": "simple-dev-indirect",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection.snap
new file mode 100644
index 0000000..358cbdf
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-dev-detection.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (6 fully audited)
+
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
new file mode 100644
index 0000000..41edbc2
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-haunted-full-audited.json.snap
@@ -0,0 +1,19 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-dev",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-normal",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-haunted-full-audited.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-haunted-full-audited.snap
new file mode 100644
index 0000000..bcff706
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-haunted-full-audited.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (2 fully audited)
+
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
new file mode 100644
index 0000000..77c62a4
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-haunted-init.json.snap
@@ -0,0 +1,19 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [],
+  "vetted_partially": [],
+  "vetted_with_exemptions": [
+    {
+      "name": "third-dev",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-normal",
+      "version": "10.0.0"
+    }
+  ]
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-haunted-init.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-haunted-init.snap
new file mode 100644
index 0000000..91ebce5
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-haunted-init.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (2 exempted)
+
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
new file mode 100644
index 0000000..41edbc2
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-haunted-minimal-audited.json.snap
@@ -0,0 +1,19 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-dev",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-normal",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-haunted-minimal-audited.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-haunted-minimal-audited.snap
new file mode 100644
index 0000000..bcff706
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-haunted-minimal-audited.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (2 fully audited)
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-haunted-no-unaudited-deeper.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-haunted-no-unaudited-deeper.json.snap
new file mode 100644
index 0000000..dbb3283
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-haunted-no-unaudited-deeper.json.snap
@@ -0,0 +1,98 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "third-dev",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-run"
+      ]
+    },
+    {
+      "name": "third-normal",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "third-dev",
+        "notable_parents": "first",
+        "suggested_criteria": [
+          "safe-to-run"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "third-normal",
+        "notable_parents": "first",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "safe-to-deploy": [
+        {
+          "name": "third-normal",
+          "notable_parents": "first",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ],
+      "safe-to-run": [
+        {
+          "name": "third-dev",
+          "notable_parents": "first",
+          "suggested_criteria": [
+            "safe-to-run"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 200
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-haunted-no-unaudited-deeper.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-haunted-no-unaudited-deeper.snap
new file mode 100644
index 0000000..33eba58
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-haunted-no-unaudited-deeper.snap
@@ -0,0 +1,22 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+Vetting Failed!
+
+2 unvetted dependencies:
+  third-dev:10.0.0 missing ["safe-to-run"]
+  third-normal:10.0.0 missing ["safe-to-deploy"]
+
+recommended audits for safe-to-deploy:
+    Command                                Publisher  Used By  Audit Size
+    cargo vet inspect third-normal 10.0.0  UNKNOWN    first    100 lines
+
+recommended audits for safe-to-run:
+    Command                             Publisher  Used By  Audit Size
+    cargo vet inspect third-dev 10.0.0  UNKNOWN    first    100 lines
+
+estimated audit backlog: 200 lines
+
+Use |cargo vet certify| to record the audits.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-haunted-no-unaudited.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-haunted-no-unaudited.json.snap
new file mode 100644
index 0000000..dbb3283
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-haunted-no-unaudited.json.snap
@@ -0,0 +1,98 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "third-dev",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-run"
+      ]
+    },
+    {
+      "name": "third-normal",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "third-dev",
+        "notable_parents": "first",
+        "suggested_criteria": [
+          "safe-to-run"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "third-normal",
+        "notable_parents": "first",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "safe-to-deploy": [
+        {
+          "name": "third-normal",
+          "notable_parents": "first",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ],
+      "safe-to-run": [
+        {
+          "name": "third-dev",
+          "notable_parents": "first",
+          "suggested_criteria": [
+            "safe-to-run"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 200
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-haunted-no-unaudited.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-haunted-no-unaudited.snap
new file mode 100644
index 0000000..33eba58
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-haunted-no-unaudited.snap
@@ -0,0 +1,22 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+Vetting Failed!
+
+2 unvetted dependencies:
+  third-dev:10.0.0 missing ["safe-to-run"]
+  third-normal:10.0.0 missing ["safe-to-deploy"]
+
+recommended audits for safe-to-deploy:
+    Command                                Publisher  Used By  Audit Size
+    cargo vet inspect third-normal 10.0.0  UNKNOWN    first    100 lines
+
+recommended audits for safe-to-run:
+    Command                             Publisher  Used By  Audit Size
+    cargo vet inspect third-dev 10.0.0  UNKNOWN    first    100 lines
+
+estimated audit backlog: 200 lines
+
+Use |cargo vet certify| to record the audits.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-no-deps.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-no-deps.json.snap
new file mode 100644
index 0000000..1ed6322
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-no-deps.json.snap
@@ -0,0 +1,10 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-no-deps.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-no-deps.snap
new file mode 100644
index 0000000..9bc517e
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-no-deps.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (because you have no third-party dependencies)
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-only-first-deps.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-only-first-deps.json.snap
new file mode 100644
index 0000000..1ed6322
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-only-first-deps.json.snap
@@ -0,0 +1,10 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-only-first-deps.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-only-first-deps.snap
new file mode 100644
index 0000000..9bc517e
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-only-first-deps.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (because you have no third-party dependencies)
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-audit-as-default-root-no-audit.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-audit-as-default-root-no-audit.json.snap
new file mode 100644
index 0000000..d7210cf
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-audit-as-default-root-no-audit.json.snap
@@ -0,0 +1,57 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "root-package",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "root-package",
+        "notable_parents": "",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "safe-to-deploy": [
+        {
+          "name": "root-package",
+          "notable_parents": "",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 100
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-audit-as-default-root-no-audit.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-audit-as-default-root-no-audit.snap
new file mode 100644
index 0000000..9a8d68b
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-audit-as-default-root-no-audit.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+Vetting Failed!
+
+1 unvetted dependencies:
+  root-package:10.0.0 missing ["safe-to-deploy"]
+
+recommended audits for safe-to-deploy:
+    Command                                Publisher  Used By  Audit Size
+    cargo vet inspect root-package 10.0.0  UNKNOWN             100 lines
+
+estimated audit backlog: 100 lines
+
+Use |cargo vet certify| to record the audits.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-audit-as-default-root-too-weak.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-audit-as-default-root-too-weak.json.snap
new file mode 100644
index 0000000..d7210cf
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-audit-as-default-root-too-weak.json.snap
@@ -0,0 +1,57 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "root-package",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "root-package",
+        "notable_parents": "",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "safe-to-deploy": [
+        {
+          "name": "root-package",
+          "notable_parents": "",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 100
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-audit-as-default-root-too-weak.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-audit-as-default-root-too-weak.snap
new file mode 100644
index 0000000..9a8d68b
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-audit-as-default-root-too-weak.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+Vetting Failed!
+
+1 unvetted dependencies:
+  root-package:10.0.0 missing ["safe-to-deploy"]
+
+recommended audits for safe-to-deploy:
+    Command                                Publisher  Used By  Audit Size
+    cargo vet inspect root-package 10.0.0  UNKNOWN             100 lines
+
+estimated audit backlog: 100 lines
+
+Use |cargo vet certify| to record the audits.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-audit-as-default-root.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-audit-as-default-root.json.snap
new file mode 100644
index 0000000..33c8b49
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-audit-as-default-root.json.snap
@@ -0,0 +1,27 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [],
+  "vetted_partially": [],
+  "vetted_with_exemptions": [
+    {
+      "name": "root-package",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-party1",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0"
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0"
+    }
+  ]
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-audit-as-default-root.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-audit-as-default-root.snap
new file mode 100644
index 0000000..5eb6a7e
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-audit-as-default-root.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (4 exempted)
+
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
new file mode 100644
index 0000000..33c8b49
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-audit-as-weaker-root.json.snap
@@ -0,0 +1,27 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [],
+  "vetted_partially": [],
+  "vetted_with_exemptions": [
+    {
+      "name": "root-package",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-party1",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0"
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0"
+    }
+  ]
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-audit-as-weaker-root.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-audit-as-weaker-root.snap
new file mode 100644
index 0000000..5eb6a7e
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-audit-as-weaker-root.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (4 exempted)
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-delta-broken-cycle.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-delta-broken-cycle.json.snap
new file mode 100644
index 0000000..d2eb61e
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-delta-broken-cycle.json.snap
@@ -0,0 +1,57 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "third-party1",
+        "notable_parents": "first-party",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": "7.0.0",
+          "to": "8.0.0",
+          "diffstat": {
+            "insertions": 15,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "safe-to-deploy": [
+        {
+          "name": "third-party1",
+          "notable_parents": "first-party",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": "7.0.0",
+            "to": "8.0.0",
+            "diffstat": {
+              "insertions": 15,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 15
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-delta-broken-cycle.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-delta-broken-cycle.snap
new file mode 100644
index 0000000..dd6a96b
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-delta-broken-cycle.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+Vetting Failed!
+
+1 unvetted dependencies:
+  third-party1:10.0.0 missing ["safe-to-deploy"]
+
+recommended audits for safe-to-deploy:
+    Command                                  Publisher  Used By      Audit Size
+    cargo vet diff third-party1 7.0.0 8.0.0  UNKNOWN    first-party  1 files changed, 15 insertions(+)
+
+estimated audit backlog: 15 lines
+
+Use |cargo vet certify| to record the audits.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-delta-broken-double-cycle.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-delta-broken-double-cycle.json.snap
new file mode 100644
index 0000000..99f2832
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-delta-broken-double-cycle.json.snap
@@ -0,0 +1,57 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "third-party1",
+        "notable_parents": "first-party",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": "4.0.0",
+          "to": "5.0.0",
+          "diffstat": {
+            "insertions": 9,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "safe-to-deploy": [
+        {
+          "name": "third-party1",
+          "notable_parents": "first-party",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": "4.0.0",
+            "to": "5.0.0",
+            "diffstat": {
+              "insertions": 9,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 9
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-delta-broken-double-cycle.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-delta-broken-double-cycle.snap
new file mode 100644
index 0000000..e226297
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-delta-broken-double-cycle.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+Vetting Failed!
+
+1 unvetted dependencies:
+  third-party1:10.0.0 missing ["safe-to-deploy"]
+
+recommended audits for safe-to-deploy:
+    Command                                  Publisher  Used By      Audit Size
+    cargo vet diff third-party1 4.0.0 5.0.0  UNKNOWN    first-party  1 files changed, 9 insertions(+)
+
+estimated audit backlog: 9 lines
+
+Use |cargo vet certify| to record the audits.
+
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
new file mode 100644
index 0000000..4818073
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-delta-cycle.json.snap
@@ -0,0 +1,23 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0"
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-delta-cycle.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-delta-cycle.snap
new file mode 100644
index 0000000..f229132
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-delta-cycle.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (3 fully audited)
+
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
new file mode 100644
index 0000000..4818073
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-delta-double-cycle.json.snap
@@ -0,0 +1,23 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0"
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-delta-double-cycle.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-delta-double-cycle.snap
new file mode 100644
index 0000000..f229132
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-delta-double-cycle.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (3 fully audited)
+
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
new file mode 100644
index 0000000..3e31252
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-deps-full-audited.json.snap
@@ -0,0 +1,35 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "build",
+      "version": "10.0.0"
+    },
+    {
+      "name": "build-proc-macro",
+      "version": "10.0.0"
+    },
+    {
+      "name": "dev",
+      "version": "10.0.0"
+    },
+    {
+      "name": "dev-proc-macro",
+      "version": "10.0.0"
+    },
+    {
+      "name": "normal",
+      "version": "10.0.0"
+    },
+    {
+      "name": "proc-macro",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-deps-full-audited.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-deps-full-audited.snap
new file mode 100644
index 0000000..358cbdf
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-deps-full-audited.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (6 fully audited)
+
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
new file mode 100644
index 0000000..96f48da
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-deps-init.json.snap
@@ -0,0 +1,35 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [],
+  "vetted_partially": [],
+  "vetted_with_exemptions": [
+    {
+      "name": "build",
+      "version": "10.0.0"
+    },
+    {
+      "name": "build-proc-macro",
+      "version": "10.0.0"
+    },
+    {
+      "name": "dev",
+      "version": "10.0.0"
+    },
+    {
+      "name": "dev-proc-macro",
+      "version": "10.0.0"
+    },
+    {
+      "name": "normal",
+      "version": "10.0.0"
+    },
+    {
+      "name": "proc-macro",
+      "version": "10.0.0"
+    }
+  ]
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-deps-init.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-deps-init.snap
new file mode 100644
index 0000000..dc87a0d
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-deps-init.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (6 exempted)
+
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
new file mode 100644
index 0000000..3e31252
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-deps-minimal-audited.json.snap
@@ -0,0 +1,35 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "build",
+      "version": "10.0.0"
+    },
+    {
+      "name": "build-proc-macro",
+      "version": "10.0.0"
+    },
+    {
+      "name": "dev",
+      "version": "10.0.0"
+    },
+    {
+      "name": "dev-proc-macro",
+      "version": "10.0.0"
+    },
+    {
+      "name": "normal",
+      "version": "10.0.0"
+    },
+    {
+      "name": "proc-macro",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-deps-minimal-audited.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-deps-minimal-audited.snap
new file mode 100644
index 0000000..358cbdf
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-deps-minimal-audited.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (6 fully audited)
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-deps-no-unaudited.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-deps-no-unaudited.json.snap
new file mode 100644
index 0000000..702c412
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-deps-no-unaudited.json.snap
@@ -0,0 +1,254 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "build",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "build-proc-macro",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "dev",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-run"
+      ]
+    },
+    {
+      "name": "dev-proc-macro",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-run"
+      ]
+    },
+    {
+      "name": "normal",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "proc-macro",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "build",
+        "notable_parents": "root",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "build-proc-macro",
+        "notable_parents": "root",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "dev",
+        "notable_parents": "root",
+        "suggested_criteria": [
+          "safe-to-run"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "dev-proc-macro",
+        "notable_parents": "root",
+        "suggested_criteria": [
+          "safe-to-run"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "normal",
+        "notable_parents": "root",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "proc-macro",
+        "notable_parents": "root",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "safe-to-deploy": [
+        {
+          "name": "build",
+          "notable_parents": "root",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        },
+        {
+          "name": "build-proc-macro",
+          "notable_parents": "root",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        },
+        {
+          "name": "normal",
+          "notable_parents": "root",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        },
+        {
+          "name": "proc-macro",
+          "notable_parents": "root",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ],
+      "safe-to-run": [
+        {
+          "name": "dev",
+          "notable_parents": "root",
+          "suggested_criteria": [
+            "safe-to-run"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        },
+        {
+          "name": "dev-proc-macro",
+          "notable_parents": "root",
+          "suggested_criteria": [
+            "safe-to-run"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 600
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-deps-no-unaudited.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-deps-no-unaudited.snap
new file mode 100644
index 0000000..dae9da3
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-deps-no-unaudited.snap
@@ -0,0 +1,30 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+Vetting Failed!
+
+6 unvetted dependencies:
+  build:10.0.0 missing ["safe-to-deploy"]
+  build-proc-macro:10.0.0 missing ["safe-to-deploy"]
+  dev:10.0.0 missing ["safe-to-run"]
+  dev-proc-macro:10.0.0 missing ["safe-to-run"]
+  normal:10.0.0 missing ["safe-to-deploy"]
+  proc-macro:10.0.0 missing ["safe-to-deploy"]
+
+recommended audits for safe-to-deploy:
+    Command                                    Publisher  Used By  Audit Size
+    cargo vet inspect build 10.0.0             UNKNOWN    root     100 lines
+    cargo vet inspect build-proc-macro 10.0.0  UNKNOWN    root     100 lines
+    cargo vet inspect normal 10.0.0            UNKNOWN    root     100 lines
+    cargo vet inspect proc-macro 10.0.0        UNKNOWN    root     100 lines
+
+recommended audits for safe-to-run:
+    Command                                  Publisher  Used By  Audit Size
+    cargo vet inspect dev 10.0.0             UNKNOWN    root     100 lines
+    cargo vet inspect dev-proc-macro 10.0.0  UNKNOWN    root     100 lines
+
+estimated audit backlog: 600 lines
+
+Use |cargo vet certify| to record the audits.
+
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
new file mode 100644
index 0000000..3e31252
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-deps-unaudited-adds-uneeded-criteria.json.snap
@@ -0,0 +1,35 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "build",
+      "version": "10.0.0"
+    },
+    {
+      "name": "build-proc-macro",
+      "version": "10.0.0"
+    },
+    {
+      "name": "dev",
+      "version": "10.0.0"
+    },
+    {
+      "name": "dev-proc-macro",
+      "version": "10.0.0"
+    },
+    {
+      "name": "normal",
+      "version": "10.0.0"
+    },
+    {
+      "name": "proc-macro",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-deps-unaudited-adds-uneeded-criteria.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-deps-unaudited-adds-uneeded-criteria.snap
new file mode 100644
index 0000000..358cbdf
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-deps-unaudited-adds-uneeded-criteria.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (6 fully audited)
+
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
new file mode 100644
index 0000000..4818073
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-full-audited.json.snap
@@ -0,0 +1,23 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0"
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-full-audited.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-full-audited.snap
new file mode 100644
index 0000000..f229132
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-full-audited.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (3 fully audited)
+
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
new file mode 100644
index 0000000..956caa3
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-init.json.snap
@@ -0,0 +1,23 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [],
+  "vetted_partially": [],
+  "vetted_with_exemptions": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0"
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0"
+    }
+  ]
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-init.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-init.snap
new file mode 100644
index 0000000..6a331f3
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-init.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (3 exempted)
+
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
new file mode 100644
index 0000000..4818073
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-long-cycle.json.snap
@@ -0,0 +1,23 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0"
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-long-cycle.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-long-cycle.snap
new file mode 100644
index 0000000..f229132
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-long-cycle.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (3 fully audited)
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-no-unaudited.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-no-unaudited.json.snap
new file mode 100644
index 0000000..a25557a
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-no-unaudited.json.snap
@@ -0,0 +1,135 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "third-party1",
+        "notable_parents": "first-party",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "third-party2",
+        "notable_parents": "first-party",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "transitive-third-party1",
+        "notable_parents": "third-party1",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "safe-to-deploy": [
+        {
+          "name": "third-party1",
+          "notable_parents": "first-party",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        },
+        {
+          "name": "third-party2",
+          "notable_parents": "first-party",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        },
+        {
+          "name": "transitive-third-party1",
+          "notable_parents": "third-party1",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 300
+  }
+}
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
new file mode 100644
index 0000000..4e14e76
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-no-unaudited.snap
@@ -0,0 +1,21 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+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 missing ["safe-to-deploy"]
+
+recommended audits for safe-to-deploy:
+    Command                                           Publisher  Used By       Audit Size
+    cargo vet inspect third-party1 10.0.0             UNKNOWN    first-party   100 lines
+    cargo vet inspect third-party2 10.0.0             UNKNOWN    first-party   100 lines
+    cargo vet inspect transitive-third-party1 10.0.0  UNKNOWN    third-party1  100 lines
+
+estimated audit backlog: 300 lines
+
+Use |cargo vet certify| to record the audits.
+
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
new file mode 100644
index 0000000..4818073
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-noop-delta.json.snap
@@ -0,0 +1,23 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0"
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-noop-delta.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-noop-delta.snap
new file mode 100644
index 0000000..f229132
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-noop-delta.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (3 fully audited)
+
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
new file mode 100644
index 0000000..4818073
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-not-a-real-dep.json.snap
@@ -0,0 +1,23 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0"
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-not-a-real-dep.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-not-a-real-dep.snap
new file mode 100644
index 0000000..f229132
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-not-a-real-dep.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (3 fully audited)
+
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
new file mode 100644
index 0000000..9a6450b
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-extra.json.snap
@@ -0,0 +1,24 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-party2",
+      "version": "10.0.0"
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0"
+    }
+  ]
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-extra.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-extra.snap
new file mode 100644
index 0000000..d7d7132
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-extra.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (2 fully audited, 1 exempted)
+
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
new file mode 100644
index 0000000..4818073
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-in-delta.json.snap
@@ -0,0 +1,23 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0"
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-in-delta.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-in-delta.snap
new file mode 100644
index 0000000..f229132
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-in-delta.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (3 fully audited)
+
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
new file mode 100644
index 0000000..4818073
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-in-direct-full.json.snap
@@ -0,0 +1,23 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0"
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-in-direct-full.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-in-direct-full.snap
new file mode 100644
index 0000000..f229132
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-in-direct-full.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (3 fully audited)
+
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
new file mode 100644
index 0000000..4818073
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-in-full.json.snap
@@ -0,0 +1,23 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0"
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-in-full.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-in-full.snap
new file mode 100644
index 0000000..f229132
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-in-full.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (3 fully audited)
+
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
new file mode 100644
index 0000000..6d8b8ee
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-nested-stronger-req.json.snap
@@ -0,0 +1,24 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-party2",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0"
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-nested-stronger-req.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-nested-stronger-req.snap
new file mode 100644
index 0000000..0284688
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-nested-stronger-req.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (1 fully audited, 2 partially audited)
+
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
new file mode 100644
index 0000000..6d8b8ee
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-nested-weaker-req.json.snap
@@ -0,0 +1,24 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-party2",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0"
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-nested-weaker-req.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-nested-weaker-req.snap
new file mode 100644
index 0000000..0284688
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-nested-weaker-req.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (1 fully audited, 2 partially audited)
+
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
new file mode 100644
index 0000000..faa9aa6
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-overbroad.json.snap
@@ -0,0 +1,36 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "build",
+      "version": "10.0.0"
+    },
+    {
+      "name": "build-proc-macro",
+      "version": "10.0.0"
+    },
+    {
+      "name": "dev-proc-macro",
+      "version": "10.0.0"
+    },
+    {
+      "name": "normal",
+      "version": "10.0.0"
+    },
+    {
+      "name": "proc-macro",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": [
+    {
+      "name": "dev",
+      "version": "10.0.0"
+    }
+  ]
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-overbroad.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-overbroad.snap
new file mode 100644
index 0000000..4696ef4
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-overbroad.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (5 fully audited, 1 exempted)
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-partial-twins.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-partial-twins.json.snap
new file mode 100644
index 0000000..b759fb9
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-partial-twins.json.snap
@@ -0,0 +1,28 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-core",
+      "version": "5.0.0"
+    },
+    {
+      "name": "thirdA",
+      "version": "10.0.0"
+    },
+    {
+      "name": "thirdAB",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": [
+    {
+      "name": "third-core",
+      "version": "10.0.0"
+    }
+  ]
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-partial-twins.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-partial-twins.snap
new file mode 100644
index 0000000..d1428e2
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-partial-twins.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (3 fully audited, 1 exempted)
+
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
new file mode 100644
index 0000000..3b47325
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-twins.json.snap
@@ -0,0 +1,28 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "thirdA",
+      "version": "10.0.0"
+    },
+    {
+      "name": "thirdAB",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": [
+    {
+      "name": "third-core",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-core",
+      "version": "5.0.0"
+    }
+  ]
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-twins.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-twins.snap
new file mode 100644
index 0000000..7261c1f
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-unaudited-twins.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (2 fully audited, 2 exempted)
+
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
new file mode 100644
index 0000000..4818073
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-useless-long-cycle.json.snap
@@ -0,0 +1,23 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0"
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-useless-long-cycle.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-useless-long-cycle.snap
new file mode 100644
index 0000000..f229132
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin-simple-useless-long-cycle.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (3 fully audited)
+
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
new file mode 100644
index 0000000..4818073
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin_simple_foreign_audited.json.snap
@@ -0,0 +1,23 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0"
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin_simple_foreign_audited.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin_simple_foreign_audited.snap
new file mode 100644
index 0000000..f229132
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin_simple_foreign_audited.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (3 fully audited)
+
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
new file mode 100644
index 0000000..4818073
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin_simple_foreign_tag_team.json.snap
@@ -0,0 +1,23 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0"
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin_simple_foreign_tag_team.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin_simple_foreign_tag_team.snap
new file mode 100644
index 0000000..f229132
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin_simple_foreign_tag_team.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (3 fully audited)
+
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
new file mode 100644
index 0000000..4818073
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin_simple_mega_foreign_tag_team.json.snap
@@ -0,0 +1,23 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0"
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin_simple_mega_foreign_tag_team.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin_simple_mega_foreign_tag_team.snap
new file mode 100644
index 0000000..f229132
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin_simple_mega_foreign_tag_team.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (3 fully audited)
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin_simple_registry_suggestions.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin_simple_registry_suggestions.json.snap
new file mode 100644
index 0000000..a25557a
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin_simple_registry_suggestions.json.snap
@@ -0,0 +1,135 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "third-party1",
+        "notable_parents": "first-party",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "third-party2",
+        "notable_parents": "first-party",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "transitive-third-party1",
+        "notable_parents": "third-party1",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "safe-to-deploy": [
+        {
+          "name": "third-party1",
+          "notable_parents": "first-party",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        },
+        {
+          "name": "third-party2",
+          "notable_parents": "first-party",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        },
+        {
+          "name": "transitive-third-party1",
+          "notable_parents": "third-party1",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 300
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__builtin_simple_registry_suggestions.snap b/src/tests/snapshots/cargo_vet__tests__vet__builtin_simple_registry_suggestions.snap
new file mode 100644
index 0000000..3b85ff1
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__builtin_simple_registry_suggestions.snap
@@ -0,0 +1,25 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+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 missing ["safe-to-deploy"]
+
+recommended audits for safe-to-deploy:
+    Command                                           Publisher  Used By       Audit Size
+    cargo vet inspect third-party1 10.0.0             UNKNOWN    first-party   100 lines
+      NOTE: cargo vet import peer-company would eliminate this
+    cargo vet inspect third-party2 10.0.0             UNKNOWN    first-party   100 lines
+      NOTE: cargo vet import rival-company would eliminate this
+      NOTE: cargo vet import peer-company would reduce this to a 1-line diff
+    cargo vet inspect transitive-third-party1 10.0.0  UNKNOWN    third-party1  100 lines
+      NOTE: cargo vet import rival-company would reduce this to a 36-line diff
+
+estimated audit backlog: 300 lines
+
+Use |cargo vet certify| to record the audits.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-core10-partially-too-weak-via-strong-delta.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-core10-partially-too-weak-via-strong-delta.json.snap
new file mode 100644
index 0000000..38c00cc
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-core10-partially-too-weak-via-strong-delta.json.snap
@@ -0,0 +1,57 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "third-core",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "reviewed"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "third-core",
+        "notable_parents": "firstB, thirdA, and thirdAB",
+        "suggested_criteria": [
+          "reviewed"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "5.0.0",
+          "diffstat": {
+            "insertions": 25,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "reviewed": [
+        {
+          "name": "third-core",
+          "notable_parents": "firstB, thirdA, and thirdAB",
+          "suggested_criteria": [
+            "reviewed"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "5.0.0",
+            "diffstat": {
+              "insertions": 25,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 25
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-core10-partially-too-weak-via-strong-delta.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-core10-partially-too-weak-via-strong-delta.snap
new file mode 100644
index 0000000..b861ca6
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-core10-partially-too-weak-via-strong-delta.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+Vetting Failed!
+
+1 unvetted dependencies:
+  third-core:10.0.0 missing ["reviewed"]
+
+recommended audits for reviewed:
+    Command                             Publisher  Used By                      Audit Size
+    cargo vet inspect third-core 5.0.0  UNKNOWN    firstB, thirdA, and thirdAB  25 lines
+
+estimated audit backlog: 25 lines
+
+Use |cargo vet certify| to record the audits.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-core10-partially-too-weak-via-weak-delta.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-core10-partially-too-weak-via-weak-delta.json.snap
new file mode 100644
index 0000000..c6c1d91
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-core10-partially-too-weak-via-weak-delta.json.snap
@@ -0,0 +1,57 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "third-core",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "reviewed"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "third-core",
+        "notable_parents": "firstB, thirdA, and thirdAB",
+        "suggested_criteria": [
+          "reviewed"
+        ],
+        "suggested_diff": {
+          "from": "5.0.0",
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 75,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "reviewed": [
+        {
+          "name": "third-core",
+          "notable_parents": "firstB, thirdA, and thirdAB",
+          "suggested_criteria": [
+            "reviewed"
+          ],
+          "suggested_diff": {
+            "from": "5.0.0",
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 75,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 75
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-core10-partially-too-weak-via-weak-delta.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-core10-partially-too-weak-via-weak-delta.snap
new file mode 100644
index 0000000..13300be
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-core10-partially-too-weak-via-weak-delta.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+Vetting Failed!
+
+1 unvetted dependencies:
+  third-core:10.0.0 missing ["reviewed"]
+
+recommended audits for reviewed:
+    Command                                 Publisher  Used By                      Audit Size
+    cargo vet diff third-core 5.0.0 10.0.0  UNKNOWN    firstB, thirdA, and thirdAB  1 files changed, 75 insertions(+)
+
+estimated audit backlog: 75 lines
+
+Use |cargo vet certify| to record the audits.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-core10-partially-too-weak.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-core10-partially-too-weak.json.snap
new file mode 100644
index 0000000..c6c1d91
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-core10-partially-too-weak.json.snap
@@ -0,0 +1,57 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "third-core",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "reviewed"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "third-core",
+        "notable_parents": "firstB, thirdA, and thirdAB",
+        "suggested_criteria": [
+          "reviewed"
+        ],
+        "suggested_diff": {
+          "from": "5.0.0",
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 75,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "reviewed": [
+        {
+          "name": "third-core",
+          "notable_parents": "firstB, thirdA, and thirdAB",
+          "suggested_criteria": [
+            "reviewed"
+          ],
+          "suggested_diff": {
+            "from": "5.0.0",
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 75,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 75
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-core10-partially-too-weak.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-core10-partially-too-weak.snap
new file mode 100644
index 0000000..13300be
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-core10-partially-too-weak.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+Vetting Failed!
+
+1 unvetted dependencies:
+  third-core:10.0.0 missing ["reviewed"]
+
+recommended audits for reviewed:
+    Command                                 Publisher  Used By                      Audit Size
+    cargo vet diff third-core 5.0.0 10.0.0  UNKNOWN    firstB, thirdA, and thirdAB  1 files changed, 75 insertions(+)
+
+estimated audit backlog: 75 lines
+
+Use |cargo vet certify| to record the audits.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-core10-too-weak.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-core10-too-weak.json.snap
new file mode 100644
index 0000000..c6c1d91
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-core10-too-weak.json.snap
@@ -0,0 +1,57 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "third-core",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "reviewed"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "third-core",
+        "notable_parents": "firstB, thirdA, and thirdAB",
+        "suggested_criteria": [
+          "reviewed"
+        ],
+        "suggested_diff": {
+          "from": "5.0.0",
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 75,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "reviewed": [
+        {
+          "name": "third-core",
+          "notable_parents": "firstB, thirdA, and thirdAB",
+          "suggested_criteria": [
+            "reviewed"
+          ],
+          "suggested_diff": {
+            "from": "5.0.0",
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 75,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 75
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-core10-too-weak.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-core10-too-weak.snap
new file mode 100644
index 0000000..13300be
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-core10-too-weak.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+Vetting Failed!
+
+1 unvetted dependencies:
+  third-core:10.0.0 missing ["reviewed"]
+
+recommended audits for reviewed:
+    Command                                 Publisher  Used By                      Audit Size
+    cargo vet diff third-core 5.0.0 10.0.0  UNKNOWN    firstB, thirdA, and thirdAB  1 files changed, 75 insertions(+)
+
+estimated audit backlog: 75 lines
+
+Use |cargo vet certify| to record the audits.
+
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
new file mode 100644
index 0000000..4502744
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-full-audited.json.snap
@@ -0,0 +1,27 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-core",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-core",
+      "version": "5.0.0"
+    },
+    {
+      "name": "thirdA",
+      "version": "10.0.0"
+    },
+    {
+      "name": "thirdAB",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-full-audited.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-full-audited.snap
new file mode 100644
index 0000000..1085e91
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-full-audited.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (4 fully audited)
+
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
new file mode 100644
index 0000000..8a3c620
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-inited.json.snap
@@ -0,0 +1,27 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [],
+  "vetted_partially": [],
+  "vetted_with_exemptions": [
+    {
+      "name": "third-core",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-core",
+      "version": "5.0.0"
+    },
+    {
+      "name": "thirdA",
+      "version": "10.0.0"
+    },
+    {
+      "name": "thirdAB",
+      "version": "10.0.0"
+    }
+  ]
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-inited.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-inited.snap
new file mode 100644
index 0000000..5eb6a7e
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-inited.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (4 exempted)
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-missing-core10.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-missing-core10.json.snap
new file mode 100644
index 0000000..c6c1d91
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-missing-core10.json.snap
@@ -0,0 +1,57 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "third-core",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "reviewed"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "third-core",
+        "notable_parents": "firstB, thirdA, and thirdAB",
+        "suggested_criteria": [
+          "reviewed"
+        ],
+        "suggested_diff": {
+          "from": "5.0.0",
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 75,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "reviewed": [
+        {
+          "name": "third-core",
+          "notable_parents": "firstB, thirdA, and thirdAB",
+          "suggested_criteria": [
+            "reviewed"
+          ],
+          "suggested_diff": {
+            "from": "5.0.0",
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 75,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 75
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-missing-core10.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-missing-core10.snap
new file mode 100644
index 0000000..13300be
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-missing-core10.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+Vetting Failed!
+
+1 unvetted dependencies:
+  third-core:10.0.0 missing ["reviewed"]
+
+recommended audits for reviewed:
+    Command                                 Publisher  Used By                      Audit Size
+    cargo vet diff third-core 5.0.0 10.0.0  UNKNOWN    firstB, thirdA, and thirdAB  1 files changed, 75 insertions(+)
+
+estimated audit backlog: 75 lines
+
+Use |cargo vet certify| to record the audits.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-missing-core5.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-missing-core5.json.snap
new file mode 100644
index 0000000..c480117
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-missing-core5.json.snap
@@ -0,0 +1,57 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "third-core",
+      "version": "5.0.0",
+      "missing_criteria": [
+        "reviewed"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "third-core",
+        "notable_parents": "firstA",
+        "suggested_criteria": [
+          "reviewed"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "5.0.0",
+          "diffstat": {
+            "insertions": 25,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "reviewed": [
+        {
+          "name": "third-core",
+          "notable_parents": "firstA",
+          "suggested_criteria": [
+            "reviewed"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "5.0.0",
+            "diffstat": {
+              "insertions": 25,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 25
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-missing-core5.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-missing-core5.snap
new file mode 100644
index 0000000..c6c480d
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-missing-core5.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+Vetting Failed!
+
+1 unvetted dependencies:
+  third-core:5.0.0 missing ["reviewed"]
+
+recommended audits for reviewed:
+    Command                             Publisher  Used By  Audit Size
+    cargo vet inspect third-core 5.0.0  UNKNOWN    firstA   25 lines
+
+estimated audit backlog: 25 lines
+
+Use |cargo vet certify| to record the audits.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-no-unaudited.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-no-unaudited.json.snap
new file mode 100644
index 0000000..fb5aa7e
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-no-unaudited.json.snap
@@ -0,0 +1,174 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "third-core",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "reviewed"
+      ]
+    },
+    {
+      "name": "third-core",
+      "version": "5.0.0",
+      "missing_criteria": [
+        "reviewed"
+      ]
+    },
+    {
+      "name": "thirdA",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "reviewed"
+      ]
+    },
+    {
+      "name": "thirdAB",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "reviewed"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "third-core",
+        "notable_parents": "firstA",
+        "suggested_criteria": [
+          "reviewed"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "5.0.0",
+          "diffstat": {
+            "insertions": 25,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "third-core",
+        "notable_parents": "firstB, thirdA, and thirdAB",
+        "suggested_criteria": [
+          "reviewed"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "thirdA",
+        "notable_parents": "firstA",
+        "suggested_criteria": [
+          "reviewed"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "thirdAB",
+        "notable_parents": "firstAB",
+        "suggested_criteria": [
+          "reviewed"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "reviewed": [
+        {
+          "name": "third-core",
+          "notable_parents": "firstA",
+          "suggested_criteria": [
+            "reviewed"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "5.0.0",
+            "diffstat": {
+              "insertions": 25,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        },
+        {
+          "name": "third-core",
+          "notable_parents": "firstB, thirdA, and thirdAB",
+          "suggested_criteria": [
+            "reviewed"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        },
+        {
+          "name": "thirdA",
+          "notable_parents": "firstA",
+          "suggested_criteria": [
+            "reviewed"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        },
+        {
+          "name": "thirdAB",
+          "notable_parents": "firstAB",
+          "suggested_criteria": [
+            "reviewed"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 325
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-no-unaudited.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-no-unaudited.snap
new file mode 100644
index 0000000..fd40be0
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-complex-no-unaudited.snap
@@ -0,0 +1,23 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+Vetting Failed!
+
+4 unvetted dependencies:
+  third-core:5.0.0 missing ["reviewed"]
+  third-core:10.0.0 missing ["reviewed"]
+  thirdA:10.0.0 missing ["reviewed"]
+  thirdAB:10.0.0 missing ["reviewed"]
+
+recommended audits for reviewed:
+    Command                              Publisher  Used By                      Audit Size
+    cargo vet inspect third-core 5.0.0   UNKNOWN    firstA                       25 lines
+    cargo vet inspect third-core 10.0.0  UNKNOWN    firstB, thirdA, and thirdAB  100 lines
+    cargo vet inspect thirdA 10.0.0      UNKNOWN    firstA                       100 lines
+    cargo vet inspect thirdAB 10.0.0     UNKNOWN    firstAB                      100 lines
+
+estimated audit backlog: 325 lines
+
+Use |cargo vet certify| to record the audits.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-full-audit-overshoot.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-full-audit-overshoot.json.snap
new file mode 100644
index 0000000..8b594ee
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-full-audit-overshoot.json.snap
@@ -0,0 +1,57 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "reviewed"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "third-party1",
+        "notable_parents": "first-party",
+        "suggested_criteria": [
+          "reviewed"
+        ],
+        "suggested_diff": {
+          "from": "5.0.0",
+          "to": "4.0.0",
+          "diffstat": {
+            "insertions": 0,
+            "deletions": 9,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "reviewed": [
+        {
+          "name": "third-party1",
+          "notable_parents": "first-party",
+          "suggested_criteria": [
+            "reviewed"
+          ],
+          "suggested_diff": {
+            "from": "5.0.0",
+            "to": "4.0.0",
+            "diffstat": {
+              "insertions": 0,
+              "deletions": 9,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 9
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-full-audit-overshoot.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-full-audit-overshoot.snap
new file mode 100644
index 0000000..23c1896
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-full-audit-overshoot.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+Vetting Failed!
+
+1 unvetted dependencies:
+  third-party1:10.0.0 missing ["reviewed"]
+
+recommended audits for reviewed:
+    Command                                  Publisher  Used By      Audit Size
+    cargo vet diff third-party1 5.0.0 4.0.0  UNKNOWN    first-party  1 files changed, 9 deletions(-)
+
+estimated audit backlog: 9 lines
+
+Use |cargo vet certify| to record the audits.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-full-audit-too-weak.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-full-audit-too-weak.json.snap
new file mode 100644
index 0000000..864db30
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-full-audit-too-weak.json.snap
@@ -0,0 +1,57 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "reviewed"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "third-party1",
+        "notable_parents": "first-party",
+        "suggested_criteria": [
+          "reviewed"
+        ],
+        "suggested_diff": {
+          "from": "5.0.0",
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 75,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "reviewed": [
+        {
+          "name": "third-party1",
+          "notable_parents": "first-party",
+          "suggested_criteria": [
+            "reviewed"
+          ],
+          "suggested_diff": {
+            "from": "5.0.0",
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 75,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 75
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-full-audit-too-weak.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-full-audit-too-weak.snap
new file mode 100644
index 0000000..bd43e31
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-full-audit-too-weak.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+Vetting Failed!
+
+1 unvetted dependencies:
+  third-party1:10.0.0 missing ["reviewed"]
+
+recommended audits for reviewed:
+    Command                                   Publisher  Used By      Audit Size
+    cargo vet diff third-party1 5.0.0 10.0.0  UNKNOWN    first-party  1 files changed, 75 insertions(+)
+
+estimated audit backlog: 75 lines
+
+Use |cargo vet certify| to record the audits.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-full-audit-undershoot.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-full-audit-undershoot.json.snap
new file mode 100644
index 0000000..43af8d4
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-full-audit-undershoot.json.snap
@@ -0,0 +1,57 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "reviewed"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "third-party1",
+        "notable_parents": "first-party",
+        "suggested_criteria": [
+          "reviewed"
+        ],
+        "suggested_diff": {
+          "from": "5.0.0",
+          "to": "7.0.0",
+          "diffstat": {
+            "insertions": 24,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "reviewed": [
+        {
+          "name": "third-party1",
+          "notable_parents": "first-party",
+          "suggested_criteria": [
+            "reviewed"
+          ],
+          "suggested_diff": {
+            "from": "5.0.0",
+            "to": "7.0.0",
+            "diffstat": {
+              "insertions": 24,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 24
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-full-audit-undershoot.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-full-audit-undershoot.snap
new file mode 100644
index 0000000..d7db8b3
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-full-audit-undershoot.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+Vetting Failed!
+
+1 unvetted dependencies:
+  third-party1:10.0.0 missing ["reviewed"]
+
+recommended audits for reviewed:
+    Command                                  Publisher  Used By      Audit Size
+    cargo vet diff third-party1 5.0.0 7.0.0  UNKNOWN    first-party  1 files changed, 24 insertions(+)
+
+estimated audit backlog: 24 lines
+
+Use |cargo vet certify| to record the audits.
+
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
new file mode 100644
index 0000000..4818073
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-full-audit.json.snap
@@ -0,0 +1,23 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0"
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-full-audit.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-full-audit.snap
new file mode 100644
index 0000000..f229132
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-full-audit.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (3 fully audited)
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-too-weak-full-audit.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-too-weak-full-audit.json.snap
new file mode 100644
index 0000000..20ff1ef
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-too-weak-full-audit.json.snap
@@ -0,0 +1,57 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "reviewed"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "third-party1",
+        "notable_parents": "first-party",
+        "suggested_criteria": [
+          "reviewed"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "5.0.0",
+          "diffstat": {
+            "insertions": 25,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "reviewed": [
+        {
+          "name": "third-party1",
+          "notable_parents": "first-party",
+          "suggested_criteria": [
+            "reviewed"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "5.0.0",
+            "diffstat": {
+              "insertions": 25,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 25
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-too-weak-full-audit.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-too-weak-full-audit.snap
new file mode 100644
index 0000000..55169df
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-too-weak-full-audit.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+Vetting Failed!
+
+1 unvetted dependencies:
+  third-party1:10.0.0 missing ["reviewed"]
+
+recommended audits for reviewed:
+    Command                               Publisher  Used By      Audit Size
+    cargo vet inspect third-party1 5.0.0  UNKNOWN    first-party  25 lines
+
+estimated audit backlog: 25 lines
+
+Use |cargo vet certify| to record the audits.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-unaudited-overshoot.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-unaudited-overshoot.json.snap
new file mode 100644
index 0000000..8b594ee
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-unaudited-overshoot.json.snap
@@ -0,0 +1,57 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "reviewed"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "third-party1",
+        "notable_parents": "first-party",
+        "suggested_criteria": [
+          "reviewed"
+        ],
+        "suggested_diff": {
+          "from": "5.0.0",
+          "to": "4.0.0",
+          "diffstat": {
+            "insertions": 0,
+            "deletions": 9,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "reviewed": [
+        {
+          "name": "third-party1",
+          "notable_parents": "first-party",
+          "suggested_criteria": [
+            "reviewed"
+          ],
+          "suggested_diff": {
+            "from": "5.0.0",
+            "to": "4.0.0",
+            "diffstat": {
+              "insertions": 0,
+              "deletions": 9,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 9
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-unaudited-overshoot.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-unaudited-overshoot.snap
new file mode 100644
index 0000000..23c1896
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-unaudited-overshoot.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+Vetting Failed!
+
+1 unvetted dependencies:
+  third-party1:10.0.0 missing ["reviewed"]
+
+recommended audits for reviewed:
+    Command                                  Publisher  Used By      Audit Size
+    cargo vet diff third-party1 5.0.0 4.0.0  UNKNOWN    first-party  1 files changed, 9 deletions(-)
+
+estimated audit backlog: 9 lines
+
+Use |cargo vet certify| to record the audits.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-unaudited-too-weak.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-unaudited-too-weak.json.snap
new file mode 100644
index 0000000..864db30
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-unaudited-too-weak.json.snap
@@ -0,0 +1,57 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "reviewed"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "third-party1",
+        "notable_parents": "first-party",
+        "suggested_criteria": [
+          "reviewed"
+        ],
+        "suggested_diff": {
+          "from": "5.0.0",
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 75,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "reviewed": [
+        {
+          "name": "third-party1",
+          "notable_parents": "first-party",
+          "suggested_criteria": [
+            "reviewed"
+          ],
+          "suggested_diff": {
+            "from": "5.0.0",
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 75,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 75
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-unaudited-too-weak.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-unaudited-too-weak.snap
new file mode 100644
index 0000000..bd43e31
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-unaudited-too-weak.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+Vetting Failed!
+
+1 unvetted dependencies:
+  third-party1:10.0.0 missing ["reviewed"]
+
+recommended audits for reviewed:
+    Command                                   Publisher  Used By      Audit Size
+    cargo vet diff third-party1 5.0.0 10.0.0  UNKNOWN    first-party  1 files changed, 75 insertions(+)
+
+estimated audit backlog: 75 lines
+
+Use |cargo vet certify| to record the audits.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-unaudited-undershoot.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-unaudited-undershoot.json.snap
new file mode 100644
index 0000000..43af8d4
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-unaudited-undershoot.json.snap
@@ -0,0 +1,57 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "reviewed"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "third-party1",
+        "notable_parents": "first-party",
+        "suggested_criteria": [
+          "reviewed"
+        ],
+        "suggested_diff": {
+          "from": "5.0.0",
+          "to": "7.0.0",
+          "diffstat": {
+            "insertions": 24,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "reviewed": [
+        {
+          "name": "third-party1",
+          "notable_parents": "first-party",
+          "suggested_criteria": [
+            "reviewed"
+          ],
+          "suggested_diff": {
+            "from": "5.0.0",
+            "to": "7.0.0",
+            "diffstat": {
+              "insertions": 24,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 24
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-unaudited-undershoot.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-unaudited-undershoot.snap
new file mode 100644
index 0000000..d7db8b3
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-unaudited-undershoot.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+Vetting Failed!
+
+1 unvetted dependencies:
+  third-party1:10.0.0 missing ["reviewed"]
+
+recommended audits for reviewed:
+    Command                                  Publisher  Used By      Audit Size
+    cargo vet diff third-party1 5.0.0 7.0.0  UNKNOWN    first-party  1 files changed, 24 insertions(+)
+
+estimated audit backlog: 24 lines
+
+Use |cargo vet certify| to record the audits.
+
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
new file mode 100644
index 0000000..81ce05a
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-unaudited.json.snap
@@ -0,0 +1,24 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-party2",
+      "version": "10.0.0"
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-unaudited.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-unaudited.snap
new file mode 100644
index 0000000..b29bb7e
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-delta-to-unaudited.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (2 fully audited, 1 partially audited)
+
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
new file mode 100644
index 0000000..4818073
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-full-audited.json.snap
@@ -0,0 +1,23 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0"
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-full-audited.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-full-audited.snap
new file mode 100644
index 0000000..f229132
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-full-audited.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (3 fully audited)
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-higher-and-lower-version-review.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-higher-and-lower-version-review.json.snap
new file mode 100644
index 0000000..b7919e8
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-higher-and-lower-version-review.json.snap
@@ -0,0 +1,57 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "reviewed"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "third-party1",
+        "notable_parents": "first-party",
+        "suggested_criteria": [
+          "reviewed"
+        ],
+        "suggested_diff": {
+          "from": "9.0.0",
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 19,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "reviewed": [
+        {
+          "name": "third-party1",
+          "notable_parents": "first-party",
+          "suggested_criteria": [
+            "reviewed"
+          ],
+          "suggested_diff": {
+            "from": "9.0.0",
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 19,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 19
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-higher-and-lower-version-review.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-higher-and-lower-version-review.snap
new file mode 100644
index 0000000..0e6472b
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-higher-and-lower-version-review.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+Vetting Failed!
+
+1 unvetted dependencies:
+  third-party1:10.0.0 missing ["reviewed"]
+
+recommended audits for reviewed:
+    Command                                   Publisher  Used By      Audit Size
+    cargo vet diff third-party1 9.0.0 10.0.0  UNKNOWN    first-party  1 files changed, 19 insertions(+)
+
+estimated audit backlog: 19 lines
+
+Use |cargo vet certify| to record the audits.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-higher-version-review.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-higher-version-review.json.snap
new file mode 100644
index 0000000..4c04f6d
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-higher-version-review.json.snap
@@ -0,0 +1,57 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "reviewed"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "third-party1",
+        "notable_parents": "first-party",
+        "suggested_criteria": [
+          "reviewed"
+        ],
+        "suggested_diff": {
+          "from": "11.0.0",
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 0,
+            "deletions": 21,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "reviewed": [
+        {
+          "name": "third-party1",
+          "notable_parents": "first-party",
+          "suggested_criteria": [
+            "reviewed"
+          ],
+          "suggested_diff": {
+            "from": "11.0.0",
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 0,
+              "deletions": 21,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 21
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-higher-version-review.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-higher-version-review.snap
new file mode 100644
index 0000000..5d61c87
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-higher-version-review.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+Vetting Failed!
+
+1 unvetted dependencies:
+  third-party1:10.0.0 missing ["reviewed"]
+
+recommended audits for reviewed:
+    Command                                    Publisher  Used By      Audit Size
+    cargo vet diff third-party1 11.0.0 10.0.0  UNKNOWN    first-party  1 files changed, 21 deletions(-)
+
+estimated audit backlog: 21 lines
+
+Use |cargo vet certify| to record the audits.
+
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
new file mode 100644
index 0000000..956caa3
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-init.json.snap
@@ -0,0 +1,23 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [],
+  "vetted_partially": [],
+  "vetted_with_exemptions": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0"
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0"
+    }
+  ]
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-init.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-init.snap
new file mode 100644
index 0000000..6a331f3
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-init.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (3 exempted)
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-lower-version-review.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-lower-version-review.json.snap
new file mode 100644
index 0000000..b7919e8
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-lower-version-review.json.snap
@@ -0,0 +1,57 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "reviewed"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "third-party1",
+        "notable_parents": "first-party",
+        "suggested_criteria": [
+          "reviewed"
+        ],
+        "suggested_diff": {
+          "from": "9.0.0",
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 19,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "reviewed": [
+        {
+          "name": "third-party1",
+          "notable_parents": "first-party",
+          "suggested_criteria": [
+            "reviewed"
+          ],
+          "suggested_diff": {
+            "from": "9.0.0",
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 19,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 19
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-lower-version-review.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-lower-version-review.snap
new file mode 100644
index 0000000..0e6472b
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-lower-version-review.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+Vetting Failed!
+
+1 unvetted dependencies:
+  third-party1:10.0.0 missing ["reviewed"]
+
+recommended audits for reviewed:
+    Command                                   Publisher  Used By      Audit Size
+    cargo vet diff third-party1 9.0.0 10.0.0  UNKNOWN    first-party  1 files changed, 19 insertions(+)
+
+estimated audit backlog: 19 lines
+
+Use |cargo vet certify| to record the audits.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-missing-direct-internal.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-missing-direct-internal.json.snap
new file mode 100644
index 0000000..f999ff2
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-missing-direct-internal.json.snap
@@ -0,0 +1,57 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "reviewed"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "third-party1",
+        "notable_parents": "first-party",
+        "suggested_criteria": [
+          "reviewed"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "reviewed": [
+        {
+          "name": "third-party1",
+          "notable_parents": "first-party",
+          "suggested_criteria": [
+            "reviewed"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 100
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-missing-direct-internal.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-missing-direct-internal.snap
new file mode 100644
index 0000000..eefd202
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-missing-direct-internal.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+Vetting Failed!
+
+1 unvetted dependencies:
+  third-party1:10.0.0 missing ["reviewed"]
+
+recommended audits for reviewed:
+    Command                                Publisher  Used By      Audit Size
+    cargo vet inspect third-party1 10.0.0  UNKNOWN    first-party  100 lines
+
+estimated audit backlog: 100 lines
+
+Use |cargo vet certify| to record the audits.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-missing-direct-leaf.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-missing-direct-leaf.json.snap
new file mode 100644
index 0000000..683c732
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-missing-direct-leaf.json.snap
@@ -0,0 +1,57 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "third-party2",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "reviewed"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "third-party2",
+        "notable_parents": "first-party",
+        "suggested_criteria": [
+          "reviewed"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "reviewed": [
+        {
+          "name": "third-party2",
+          "notable_parents": "first-party",
+          "suggested_criteria": [
+            "reviewed"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 100
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-missing-direct-leaf.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-missing-direct-leaf.snap
new file mode 100644
index 0000000..2be92eb
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-missing-direct-leaf.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+Vetting Failed!
+
+1 unvetted dependencies:
+  third-party2:10.0.0 missing ["reviewed"]
+
+recommended audits for reviewed:
+    Command                                Publisher  Used By      Audit Size
+    cargo vet inspect third-party2 10.0.0  UNKNOWN    first-party  100 lines
+
+estimated audit backlog: 100 lines
+
+Use |cargo vet certify| to record the audits.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-missing-leaves.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-missing-leaves.json.snap
new file mode 100644
index 0000000..b812b0e
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-missing-leaves.json.snap
@@ -0,0 +1,96 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "third-party2",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "reviewed"
+      ]
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "reviewed"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "third-party2",
+        "notable_parents": "first-party",
+        "suggested_criteria": [
+          "reviewed"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "transitive-third-party1",
+        "notable_parents": "third-party1",
+        "suggested_criteria": [
+          "reviewed"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "reviewed": [
+        {
+          "name": "third-party2",
+          "notable_parents": "first-party",
+          "suggested_criteria": [
+            "reviewed"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        },
+        {
+          "name": "transitive-third-party1",
+          "notable_parents": "third-party1",
+          "suggested_criteria": [
+            "reviewed"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 200
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-missing-leaves.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-missing-leaves.snap
new file mode 100644
index 0000000..a28b3fe
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-missing-leaves.snap
@@ -0,0 +1,19 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+Vetting Failed!
+
+2 unvetted dependencies:
+  third-party2:10.0.0 missing ["reviewed"]
+  transitive-third-party1:10.0.0 missing ["reviewed"]
+
+recommended audits for reviewed:
+    Command                                           Publisher  Used By       Audit Size
+    cargo vet inspect third-party2 10.0.0             UNKNOWN    first-party   100 lines
+    cargo vet inspect transitive-third-party1 10.0.0  UNKNOWN    third-party1  100 lines
+
+estimated audit backlog: 200 lines
+
+Use |cargo vet certify| to record the audits.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-missing-transitive.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-missing-transitive.json.snap
new file mode 100644
index 0000000..6c6255b
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-missing-transitive.json.snap
@@ -0,0 +1,57 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "reviewed"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "transitive-third-party1",
+        "notable_parents": "third-party1",
+        "suggested_criteria": [
+          "reviewed"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "reviewed": [
+        {
+          "name": "transitive-third-party1",
+          "notable_parents": "third-party1",
+          "suggested_criteria": [
+            "reviewed"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 100
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-missing-transitive.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-missing-transitive.snap
new file mode 100644
index 0000000..dc5a5d0
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-missing-transitive.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+Vetting Failed!
+
+1 unvetted dependencies:
+  transitive-third-party1:10.0.0 missing ["reviewed"]
+
+recommended audits for reviewed:
+    Command                                           Publisher  Used By       Audit Size
+    cargo vet inspect transitive-third-party1 10.0.0  UNKNOWN    third-party1  100 lines
+
+estimated audit backlog: 100 lines
+
+Use |cargo vet certify| to record the audits.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-needed-reversed-delta-to-unaudited.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-needed-reversed-delta-to-unaudited.json.snap
new file mode 100644
index 0000000..f999ff2
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-needed-reversed-delta-to-unaudited.json.snap
@@ -0,0 +1,57 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "reviewed"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "third-party1",
+        "notable_parents": "first-party",
+        "suggested_criteria": [
+          "reviewed"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "reviewed": [
+        {
+          "name": "third-party1",
+          "notable_parents": "first-party",
+          "suggested_criteria": [
+            "reviewed"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 100
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-needed-reversed-delta-to-unaudited.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-needed-reversed-delta-to-unaudited.snap
new file mode 100644
index 0000000..eefd202
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-needed-reversed-delta-to-unaudited.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+Vetting Failed!
+
+1 unvetted dependencies:
+  third-party1:10.0.0 missing ["reviewed"]
+
+recommended audits for reviewed:
+    Command                                Publisher  Used By      Audit Size
+    cargo vet inspect third-party1 10.0.0  UNKNOWN    first-party  100 lines
+
+estimated audit backlog: 100 lines
+
+Use |cargo vet certify| to record the audits.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-no-unaudited.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-no-unaudited.json.snap
new file mode 100644
index 0000000..8cc2124
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-no-unaudited.json.snap
@@ -0,0 +1,135 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "reviewed"
+      ]
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "reviewed"
+      ]
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "reviewed"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "third-party1",
+        "notable_parents": "first-party",
+        "suggested_criteria": [
+          "reviewed"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "third-party2",
+        "notable_parents": "first-party",
+        "suggested_criteria": [
+          "reviewed"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "transitive-third-party1",
+        "notable_parents": "third-party1",
+        "suggested_criteria": [
+          "reviewed"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "reviewed": [
+        {
+          "name": "third-party1",
+          "notable_parents": "first-party",
+          "suggested_criteria": [
+            "reviewed"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        },
+        {
+          "name": "third-party2",
+          "notable_parents": "first-party",
+          "suggested_criteria": [
+            "reviewed"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        },
+        {
+          "name": "transitive-third-party1",
+          "notable_parents": "third-party1",
+          "suggested_criteria": [
+            "reviewed"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 300
+  }
+}
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
new file mode 100644
index 0000000..4cc10ca
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-no-unaudited.snap
@@ -0,0 +1,21 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+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 missing ["reviewed"]
+
+recommended audits for reviewed:
+    Command                                           Publisher  Used By       Audit Size
+    cargo vet inspect third-party1 10.0.0             UNKNOWN    first-party   100 lines
+    cargo vet inspect third-party2 10.0.0             UNKNOWN    first-party   100 lines
+    cargo vet inspect transitive-third-party1 10.0.0  UNKNOWN    third-party1  100 lines
+
+estimated audit backlog: 300 lines
+
+Use |cargo vet certify| to record the audits.
+
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
new file mode 100644
index 0000000..4818073
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-reverse-delta-to-full-audit.json.snap
@@ -0,0 +1,23 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0"
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-reverse-delta-to-full-audit.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-reverse-delta-to-full-audit.snap
new file mode 100644
index 0000000..f229132
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-reverse-delta-to-full-audit.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (3 fully audited)
+
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
new file mode 100644
index 0000000..81ce05a
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-reverse-delta-to-unaudited.json.snap
@@ -0,0 +1,24 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-party2",
+      "version": "10.0.0"
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-reverse-delta-to-unaudited.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-reverse-delta-to-unaudited.snap
new file mode 100644
index 0000000..b29bb7e
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-reverse-delta-to-unaudited.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (2 fully audited, 1 partially audited)
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-reviewed-too-weakly.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-reviewed-too-weakly.json.snap
new file mode 100644
index 0000000..6c6255b
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-reviewed-too-weakly.json.snap
@@ -0,0 +1,57 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "reviewed"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "transitive-third-party1",
+        "notable_parents": "third-party1",
+        "suggested_criteria": [
+          "reviewed"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "reviewed": [
+        {
+          "name": "transitive-third-party1",
+          "notable_parents": "third-party1",
+          "suggested_criteria": [
+            "reviewed"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 100
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-reviewed-too-weakly.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-reviewed-too-weakly.snap
new file mode 100644
index 0000000..dc5a5d0
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-reviewed-too-weakly.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+Vetting Failed!
+
+1 unvetted dependencies:
+  transitive-third-party1:10.0.0 missing ["reviewed"]
+
+recommended audits for reviewed:
+    Command                                           Publisher  Used By       Audit Size
+    cargo vet inspect transitive-third-party1 10.0.0  UNKNOWN    third-party1  100 lines
+
+estimated audit backlog: 100 lines
+
+Use |cargo vet certify| to record the audits.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__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
new file mode 100644
index 0000000..4818073
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-weaker-transitive-req-using-implies.json.snap
@@ -0,0 +1,23 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0"
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-weaker-transitive-req-using-implies.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-weaker-transitive-req-using-implies.snap
new file mode 100644
index 0000000..f229132
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-weaker-transitive-req-using-implies.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (3 fully audited)
+
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
new file mode 100644
index 0000000..4818073
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-weaker-transitive-req.json.snap
@@ -0,0 +1,23 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0"
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-weaker-transitive-req.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-weaker-transitive-req.snap
new file mode 100644
index 0000000..f229132
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-weaker-transitive-req.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (3 fully audited)
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-wrongly-reversed-delta-to-full-audit.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-wrongly-reversed-delta-to-full-audit.json.snap
new file mode 100644
index 0000000..864db30
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-wrongly-reversed-delta-to-full-audit.json.snap
@@ -0,0 +1,57 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "reviewed"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "third-party1",
+        "notable_parents": "first-party",
+        "suggested_criteria": [
+          "reviewed"
+        ],
+        "suggested_diff": {
+          "from": "5.0.0",
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 75,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "reviewed": [
+        {
+          "name": "third-party1",
+          "notable_parents": "first-party",
+          "suggested_criteria": [
+            "reviewed"
+          ],
+          "suggested_diff": {
+            "from": "5.0.0",
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 75,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 75
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-wrongly-reversed-delta-to-full-audit.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-wrongly-reversed-delta-to-full-audit.snap
new file mode 100644
index 0000000..bd43e31
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-wrongly-reversed-delta-to-full-audit.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+Vetting Failed!
+
+1 unvetted dependencies:
+  third-party1:10.0.0 missing ["reviewed"]
+
+recommended audits for reviewed:
+    Command                                   Publisher  Used By      Audit Size
+    cargo vet diff third-party1 5.0.0 10.0.0  UNKNOWN    first-party  1 files changed, 75 insertions(+)
+
+estimated audit backlog: 75 lines
+
+Use |cargo vet certify| to record the audits.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-wrongly-reversed-delta-to-unaudited.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-wrongly-reversed-delta-to-unaudited.json.snap
new file mode 100644
index 0000000..864db30
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-wrongly-reversed-delta-to-unaudited.json.snap
@@ -0,0 +1,57 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "reviewed"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "third-party1",
+        "notable_parents": "first-party",
+        "suggested_criteria": [
+          "reviewed"
+        ],
+        "suggested_diff": {
+          "from": "5.0.0",
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 75,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "reviewed": [
+        {
+          "name": "third-party1",
+          "notable_parents": "first-party",
+          "suggested_criteria": [
+            "reviewed"
+          ],
+          "suggested_diff": {
+            "from": "5.0.0",
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 75,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 75
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-wrongly-reversed-delta-to-unaudited.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-wrongly-reversed-delta-to-unaudited.snap
new file mode 100644
index 0000000..bd43e31
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock-simple-wrongly-reversed-delta-to-unaudited.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+Vetting Failed!
+
+1 unvetted dependencies:
+  third-party1:10.0.0 missing ["reviewed"]
+
+recommended audits for reviewed:
+    Command                                   Publisher  Used By      Audit Size
+    cargo vet diff third-party1 5.0.0 10.0.0  UNKNOWN    first-party  1 files changed, 75 insertions(+)
+
+estimated audit backlog: 75 lines
+
+Use |cargo vet certify| to record the audits.
+
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
new file mode 100644
index 0000000..4818073
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock_simple_foreign_audited_pun_mapped.json.snap
@@ -0,0 +1,23 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0"
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock_simple_foreign_audited_pun_mapped.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock_simple_foreign_audited_pun_mapped.snap
new file mode 100644
index 0000000..f229132
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock_simple_foreign_audited_pun_mapped.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (3 fully audited)
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock_simple_foreign_audited_pun_no_mapping.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock_simple_foreign_audited_pun_no_mapping.json.snap
new file mode 100644
index 0000000..8cc2124
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock_simple_foreign_audited_pun_no_mapping.json.snap
@@ -0,0 +1,135 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "reviewed"
+      ]
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "reviewed"
+      ]
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "reviewed"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "third-party1",
+        "notable_parents": "first-party",
+        "suggested_criteria": [
+          "reviewed"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "third-party2",
+        "notable_parents": "first-party",
+        "suggested_criteria": [
+          "reviewed"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "transitive-third-party1",
+        "notable_parents": "third-party1",
+        "suggested_criteria": [
+          "reviewed"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "reviewed": [
+        {
+          "name": "third-party1",
+          "notable_parents": "first-party",
+          "suggested_criteria": [
+            "reviewed"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        },
+        {
+          "name": "third-party2",
+          "notable_parents": "first-party",
+          "suggested_criteria": [
+            "reviewed"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        },
+        {
+          "name": "transitive-third-party1",
+          "notable_parents": "third-party1",
+          "suggested_criteria": [
+            "reviewed"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 300
+  }
+}
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
new file mode 100644
index 0000000..4cc10ca
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock_simple_foreign_audited_pun_no_mapping.snap
@@ -0,0 +1,21 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+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 missing ["reviewed"]
+
+recommended audits for reviewed:
+    Command                                           Publisher  Used By       Audit Size
+    cargo vet inspect third-party1 10.0.0             UNKNOWN    first-party   100 lines
+    cargo vet inspect third-party2 10.0.0             UNKNOWN    first-party   100 lines
+    cargo vet inspect transitive-third-party1 10.0.0  UNKNOWN    third-party1  100 lines
+
+estimated audit backlog: 300 lines
+
+Use |cargo vet certify| to record the audits.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__mock_simple_foreign_audited_pun_wrong_mapped.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__mock_simple_foreign_audited_pun_wrong_mapped.json.snap
new file mode 100644
index 0000000..8cc2124
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock_simple_foreign_audited_pun_wrong_mapped.json.snap
@@ -0,0 +1,135 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "reviewed"
+      ]
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "reviewed"
+      ]
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "reviewed"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "third-party1",
+        "notable_parents": "first-party",
+        "suggested_criteria": [
+          "reviewed"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "third-party2",
+        "notable_parents": "first-party",
+        "suggested_criteria": [
+          "reviewed"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "transitive-third-party1",
+        "notable_parents": "third-party1",
+        "suggested_criteria": [
+          "reviewed"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "reviewed": [
+        {
+          "name": "third-party1",
+          "notable_parents": "first-party",
+          "suggested_criteria": [
+            "reviewed"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        },
+        {
+          "name": "third-party2",
+          "notable_parents": "first-party",
+          "suggested_criteria": [
+            "reviewed"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        },
+        {
+          "name": "transitive-third-party1",
+          "notable_parents": "third-party1",
+          "suggested_criteria": [
+            "reviewed"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 300
+  }
+}
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
new file mode 100644
index 0000000..4cc10ca
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__mock_simple_foreign_audited_pun_wrong_mapped.snap
@@ -0,0 +1,21 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+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 missing ["reviewed"]
+
+recommended audits for reviewed:
+    Command                                           Publisher  Used By       Audit Size
+    cargo vet inspect third-party1 10.0.0             UNKNOWN    first-party   100 lines
+    cargo vet inspect third-party2 10.0.0             UNKNOWN    first-party   100 lines
+    cargo vet inspect transitive-third-party1 10.0.0  UNKNOWN    third-party1  100 lines
+
+estimated audit backlog: 300 lines
+
+Use |cargo vet certify| to record the audits.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-extra-missing.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-extra-missing.json.snap
new file mode 100644
index 0000000..bfdecbb
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-extra-missing.json.snap
@@ -0,0 +1,57 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "third-party2",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "fuzzed"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "third-party2",
+        "notable_parents": "first-party",
+        "suggested_criteria": [
+          "fuzzed"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "fuzzed": [
+        {
+          "name": "third-party2",
+          "notable_parents": "first-party",
+          "suggested_criteria": [
+            "fuzzed"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 100
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-extra-missing.snap b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-extra-missing.snap
new file mode 100644
index 0000000..5d876dc
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-extra-missing.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+Vetting Failed!
+
+1 unvetted dependencies:
+  third-party2:10.0.0 missing ["fuzzed"]
+
+recommended audits for fuzzed:
+    Command                                Publisher  Used By      Audit Size
+    cargo vet inspect third-party2 10.0.0  UNKNOWN    first-party  100 lines
+
+estimated audit backlog: 100 lines
+
+Use |cargo vet certify| to record the audits.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-extra.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-extra.json.snap
new file mode 100644
index 0000000..4818073
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-extra.json.snap
@@ -0,0 +1,23 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0"
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-extra.snap b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-extra.snap
new file mode 100644
index 0000000..f229132
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-extra.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (3 fully audited)
+
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
new file mode 100644
index 0000000..4818073
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-stronger.json.snap
@@ -0,0 +1,23 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0"
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-stronger.snap b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-stronger.snap
new file mode 100644
index 0000000..f229132
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-stronger.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (3 fully audited)
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-too-strong.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-too-strong.json.snap
new file mode 100644
index 0000000..5462d9f
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-too-strong.json.snap
@@ -0,0 +1,96 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "strong-reviewed"
+      ]
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "strong-reviewed"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "third-party1",
+        "notable_parents": "first-party",
+        "suggested_criteria": [
+          "strong-reviewed"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "transitive-third-party1",
+        "notable_parents": "third-party1",
+        "suggested_criteria": [
+          "strong-reviewed"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "strong-reviewed": [
+        {
+          "name": "third-party1",
+          "notable_parents": "first-party",
+          "suggested_criteria": [
+            "strong-reviewed"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        },
+        {
+          "name": "transitive-third-party1",
+          "notable_parents": "third-party1",
+          "suggested_criteria": [
+            "strong-reviewed"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 200
+  }
+}
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
new file mode 100644
index 0000000..5ce4c8a
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-too-strong.snap
@@ -0,0 +1,19 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+Vetting Failed!
+
+2 unvetted dependencies:
+  third-party1:10.0.0 missing ["strong-reviewed"]
+  transitive-third-party1:10.0.0 missing ["strong-reviewed"]
+
+recommended audits for strong-reviewed:
+    Command                                           Publisher  Used By       Audit Size
+    cargo vet inspect third-party1 10.0.0             UNKNOWN    first-party   100 lines
+    cargo vet inspect transitive-third-party1 10.0.0  UNKNOWN    third-party1  100 lines
+
+estimated audit backlog: 200 lines
+
+Use |cargo vet certify| to record the audits.
+
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
new file mode 100644
index 0000000..4818073
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-weaker-needed.json.snap
@@ -0,0 +1,23 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0"
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-weaker-needed.snap b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-weaker-needed.snap
new file mode 100644
index 0000000..f229132
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-weaker-needed.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (3 fully audited)
+
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
new file mode 100644
index 0000000..4818073
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-weaker.json.snap
@@ -0,0 +1,23 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0"
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-weaker.snap b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-weaker.snap
new file mode 100644
index 0000000..f229132
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-dep-weaker.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (3 fully audited)
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-extra-partially-missing.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-extra-partially-missing.json.snap
new file mode 100644
index 0000000..11cd6e5
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-extra-partially-missing.json.snap
@@ -0,0 +1,96 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "fuzzed"
+      ]
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "fuzzed"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "third-party1",
+        "notable_parents": "first-party",
+        "suggested_criteria": [
+          "fuzzed"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "transitive-third-party1",
+        "notable_parents": "third-party1",
+        "suggested_criteria": [
+          "fuzzed"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "fuzzed": [
+        {
+          "name": "third-party1",
+          "notable_parents": "first-party",
+          "suggested_criteria": [
+            "fuzzed"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        },
+        {
+          "name": "transitive-third-party1",
+          "notable_parents": "third-party1",
+          "suggested_criteria": [
+            "fuzzed"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 200
+  }
+}
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
new file mode 100644
index 0000000..4d09c65
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-extra-partially-missing.snap
@@ -0,0 +1,19 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+Vetting Failed!
+
+2 unvetted dependencies:
+  third-party1:10.0.0 missing ["fuzzed"]
+  transitive-third-party1:10.0.0 missing ["fuzzed"]
+
+recommended audits for fuzzed:
+    Command                                           Publisher  Used By       Audit Size
+    cargo vet inspect third-party1 10.0.0             UNKNOWN    first-party   100 lines
+    cargo vet inspect transitive-third-party1 10.0.0  UNKNOWN    third-party1  100 lines
+
+estimated audit backlog: 200 lines
+
+Use |cargo vet certify| to record the audits.
+
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
new file mode 100644
index 0000000..4818073
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-policy-redundant.json.snap
@@ -0,0 +1,23 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0"
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-policy-redundant.snap b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-policy-redundant.snap
new file mode 100644
index 0000000..f229132
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-policy-redundant.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (3 fully audited)
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-too-strong.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-too-strong.json.snap
new file mode 100644
index 0000000..ca45438
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-too-strong.json.snap
@@ -0,0 +1,135 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "strong-reviewed"
+      ]
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "strong-reviewed"
+      ]
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "strong-reviewed"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "third-party1",
+        "notable_parents": "first-party",
+        "suggested_criteria": [
+          "strong-reviewed"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "third-party2",
+        "notable_parents": "first-party",
+        "suggested_criteria": [
+          "strong-reviewed"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "transitive-third-party1",
+        "notable_parents": "third-party1",
+        "suggested_criteria": [
+          "strong-reviewed"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "strong-reviewed": [
+        {
+          "name": "third-party1",
+          "notable_parents": "first-party",
+          "suggested_criteria": [
+            "strong-reviewed"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        },
+        {
+          "name": "third-party2",
+          "notable_parents": "first-party",
+          "suggested_criteria": [
+            "strong-reviewed"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        },
+        {
+          "name": "transitive-third-party1",
+          "notable_parents": "third-party1",
+          "suggested_criteria": [
+            "strong-reviewed"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 300
+  }
+}
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
new file mode 100644
index 0000000..75ceba0
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-too-strong.snap
@@ -0,0 +1,21 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+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 missing ["strong-reviewed"]
+
+recommended audits for strong-reviewed:
+    Command                                           Publisher  Used By       Audit Size
+    cargo vet inspect third-party1 10.0.0             UNKNOWN    first-party   100 lines
+    cargo vet inspect third-party2 10.0.0             UNKNOWN    first-party   100 lines
+    cargo vet inspect transitive-third-party1 10.0.0  UNKNOWN    third-party1  100 lines
+
+estimated audit backlog: 300 lines
+
+Use |cargo vet certify| to record the audits.
+
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
new file mode 100644
index 0000000..4818073
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-weaker.json.snap
@@ -0,0 +1,23 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0"
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-weaker.snap b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-weaker.snap
new file mode 100644
index 0000000..f229132
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-first-weaker.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (3 fully audited)
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-root-dep-too-strong.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-root-dep-too-strong.json.snap
new file mode 100644
index 0000000..ca45438
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-root-dep-too-strong.json.snap
@@ -0,0 +1,135 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "strong-reviewed"
+      ]
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "strong-reviewed"
+      ]
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "strong-reviewed"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "third-party1",
+        "notable_parents": "first-party",
+        "suggested_criteria": [
+          "strong-reviewed"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "third-party2",
+        "notable_parents": "first-party",
+        "suggested_criteria": [
+          "strong-reviewed"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "transitive-third-party1",
+        "notable_parents": "third-party1",
+        "suggested_criteria": [
+          "strong-reviewed"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "strong-reviewed": [
+        {
+          "name": "third-party1",
+          "notable_parents": "first-party",
+          "suggested_criteria": [
+            "strong-reviewed"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        },
+        {
+          "name": "third-party2",
+          "notable_parents": "first-party",
+          "suggested_criteria": [
+            "strong-reviewed"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        },
+        {
+          "name": "transitive-third-party1",
+          "notable_parents": "third-party1",
+          "suggested_criteria": [
+            "strong-reviewed"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 300
+  }
+}
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
new file mode 100644
index 0000000..75ceba0
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-root-dep-too-strong.snap
@@ -0,0 +1,21 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+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 missing ["strong-reviewed"]
+
+recommended audits for strong-reviewed:
+    Command                                           Publisher  Used By       Audit Size
+    cargo vet inspect third-party1 10.0.0             UNKNOWN    first-party   100 lines
+    cargo vet inspect third-party2 10.0.0             UNKNOWN    first-party   100 lines
+    cargo vet inspect transitive-third-party1 10.0.0  UNKNOWN    third-party1  100 lines
+
+estimated audit backlog: 300 lines
+
+Use |cargo vet certify| to record the audits.
+
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
new file mode 100644
index 0000000..4818073
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-root-dep-weaker.json.snap
@@ -0,0 +1,23 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0"
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-root-dep-weaker.snap b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-root-dep-weaker.snap
new file mode 100644
index 0000000..f229132
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-root-dep-weaker.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (3 fully audited)
+
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-root-too-strong.json.snap b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-root-too-strong.json.snap
new file mode 100644
index 0000000..ca45438
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-root-too-strong.json.snap
@@ -0,0 +1,135 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "strong-reviewed"
+      ]
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "strong-reviewed"
+      ]
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "strong-reviewed"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "third-party1",
+        "notable_parents": "first-party",
+        "suggested_criteria": [
+          "strong-reviewed"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "third-party2",
+        "notable_parents": "first-party",
+        "suggested_criteria": [
+          "strong-reviewed"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      },
+      {
+        "name": "transitive-third-party1",
+        "notable_parents": "third-party1",
+        "suggested_criteria": [
+          "strong-reviewed"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "strong-reviewed": [
+        {
+          "name": "third-party1",
+          "notable_parents": "first-party",
+          "suggested_criteria": [
+            "strong-reviewed"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        },
+        {
+          "name": "third-party2",
+          "notable_parents": "first-party",
+          "suggested_criteria": [
+            "strong-reviewed"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        },
+        {
+          "name": "transitive-third-party1",
+          "notable_parents": "third-party1",
+          "suggested_criteria": [
+            "strong-reviewed"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 300
+  }
+}
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
new file mode 100644
index 0000000..75ceba0
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-root-too-strong.snap
@@ -0,0 +1,21 @@
+---
+source: src/tests/vet.rs
+expression: human
+---
+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 missing ["strong-reviewed"]
+
+recommended audits for strong-reviewed:
+    Command                                           Publisher  Used By       Audit Size
+    cargo vet inspect third-party1 10.0.0             UNKNOWN    first-party   100 lines
+    cargo vet inspect third-party2 10.0.0             UNKNOWN    first-party   100 lines
+    cargo vet inspect transitive-third-party1 10.0.0  UNKNOWN    third-party1  100 lines
+
+estimated audit backlog: 300 lines
+
+Use |cargo vet certify| to record the audits.
+
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
new file mode 100644
index 0000000..4818073
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-root-weaker.json.snap
@@ -0,0 +1,23 @@
+---
+source: src/tests/vet.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0"
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-root-weaker.snap b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-root-weaker.snap
new file mode 100644
index 0000000..f229132
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__vet__simple-policy-root-weaker.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/vet.rs
+expression: output
+---
+Vetting Succeeded (3 fully audited)
+
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
new file mode 100644
index 0000000..0729c08
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__violations__builtin-simple-deps-violation-dodged.json.snap
@@ -0,0 +1,35 @@
+---
+source: src/tests/violations.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "build",
+      "version": "10.0.0"
+    },
+    {
+      "name": "build-proc-macro",
+      "version": "10.0.0"
+    },
+    {
+      "name": "dev",
+      "version": "10.0.0"
+    },
+    {
+      "name": "dev-proc-macro",
+      "version": "10.0.0"
+    },
+    {
+      "name": "normal",
+      "version": "10.0.0"
+    },
+    {
+      "name": "proc-macro",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__violations__builtin-simple-deps-violation-dodged.snap b/src/tests/snapshots/cargo_vet__tests__violations__builtin-simple-deps-violation-dodged.snap
new file mode 100644
index 0000000..d98700e
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__violations__builtin-simple-deps-violation-dodged.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/violations.rs
+expression: output
+---
+Vetting Succeeded (6 fully audited)
+
diff --git a/src/tests/snapshots/cargo_vet__tests__violations__builtin-simple-deps-violation-high-hit.json.snap b/src/tests/snapshots/cargo_vet__tests__violations__builtin-simple-deps-violation-high-hit.json.snap
new file mode 100644
index 0000000..4d99401
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__violations__builtin-simple-deps-violation-high-hit.json.snap
@@ -0,0 +1,31 @@
+---
+source: src/tests/violations.rs
+expression: json
+---
+{
+  "conclusion": "fail (violation)",
+  "violations": {
+    "dev:10.0.0": [
+      {
+        "AuditConflict": {
+          "violation_source": null,
+          "violation": {
+            "criteria": "safe-to-deploy",
+            "version": null,
+            "delta": null,
+            "violation": "*",
+            "notes": null
+          },
+          "audit_source": null,
+          "audit": {
+            "criteria": "safe-to-deploy",
+            "version": "10.0.0",
+            "delta": null,
+            "violation": null,
+            "notes": null
+          }
+        }
+      }
+    ]
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__violations__builtin-simple-deps-violation-high-hit.snap b/src/tests/snapshots/cargo_vet__tests__violations__builtin-simple-deps-violation-high-hit.snap
new file mode 100644
index 0000000..1a21253
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__violations__builtin-simple-deps-violation-high-hit.snap
@@ -0,0 +1,12 @@
+---
+source: src/tests/violations.rs
+expression: output
+---
+Violations Found!
+  dev:10.0.0
+    the own audit 10.0.0
+      criteria: ["safe-to-deploy"]
+    conflicts with own violation against *
+      criteria: ["safe-to-deploy"]
+
+
diff --git a/src/tests/snapshots/cargo_vet__tests__violations__builtin-simple-deps-violation-imply-hit.json.snap b/src/tests/snapshots/cargo_vet__tests__violations__builtin-simple-deps-violation-imply-hit.json.snap
new file mode 100644
index 0000000..cf618e8
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__violations__builtin-simple-deps-violation-imply-hit.json.snap
@@ -0,0 +1,31 @@
+---
+source: src/tests/violations.rs
+expression: json
+---
+{
+  "conclusion": "fail (violation)",
+  "violations": {
+    "dev:10.0.0": [
+      {
+        "AuditConflict": {
+          "violation_source": null,
+          "violation": {
+            "criteria": "safe-to-run",
+            "version": null,
+            "delta": null,
+            "violation": "*",
+            "notes": null
+          },
+          "audit_source": null,
+          "audit": {
+            "criteria": "safe-to-deploy",
+            "version": "10.0.0",
+            "delta": null,
+            "violation": null,
+            "notes": null
+          }
+        }
+      }
+    ]
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__violations__builtin-simple-deps-violation-imply-hit.snap b/src/tests/snapshots/cargo_vet__tests__violations__builtin-simple-deps-violation-imply-hit.snap
new file mode 100644
index 0000000..2770c61
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__violations__builtin-simple-deps-violation-imply-hit.snap
@@ -0,0 +1,12 @@
+---
+source: src/tests/violations.rs
+expression: output
+---
+Violations Found!
+  dev:10.0.0
+    the own audit 10.0.0
+      criteria: ["safe-to-deploy"]
+    conflicts with own violation against *
+      criteria: ["safe-to-run"]
+
+
diff --git a/src/tests/snapshots/cargo_vet__tests__violations__builtin-simple-deps-violation-low-hit.json.snap b/src/tests/snapshots/cargo_vet__tests__violations__builtin-simple-deps-violation-low-hit.json.snap
new file mode 100644
index 0000000..cc67224
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__violations__builtin-simple-deps-violation-low-hit.json.snap
@@ -0,0 +1,31 @@
+---
+source: src/tests/violations.rs
+expression: json
+---
+{
+  "conclusion": "fail (violation)",
+  "violations": {
+    "dev:10.0.0": [
+      {
+        "AuditConflict": {
+          "violation_source": null,
+          "violation": {
+            "criteria": "safe-to-run",
+            "version": null,
+            "delta": null,
+            "violation": "*",
+            "notes": null
+          },
+          "audit_source": null,
+          "audit": {
+            "criteria": "safe-to-run",
+            "version": "10.0.0",
+            "delta": null,
+            "violation": null,
+            "notes": null
+          }
+        }
+      }
+    ]
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__violations__builtin-simple-deps-violation-low-hit.snap b/src/tests/snapshots/cargo_vet__tests__violations__builtin-simple-deps-violation-low-hit.snap
new file mode 100644
index 0000000..28fb3ea
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__violations__builtin-simple-deps-violation-low-hit.snap
@@ -0,0 +1,12 @@
+---
+source: src/tests/violations.rs
+expression: output
+---
+Violations Found!
+  dev:10.0.0
+    the own audit 10.0.0
+      criteria: ["safe-to-run"]
+    conflicts with own violation against *
+      criteria: ["safe-to-run"]
+
+
diff --git a/src/tests/snapshots/cargo_vet__tests__violations__builtin-simple-deps-violation-redundant-low-hit.json.snap b/src/tests/snapshots/cargo_vet__tests__violations__builtin-simple-deps-violation-redundant-low-hit.json.snap
new file mode 100644
index 0000000..9e64225
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__violations__builtin-simple-deps-violation-redundant-low-hit.json.snap
@@ -0,0 +1,34 @@
+---
+source: src/tests/violations.rs
+expression: json
+---
+{
+  "conclusion": "fail (violation)",
+  "violations": {
+    "dev:10.0.0": [
+      {
+        "AuditConflict": {
+          "violation_source": null,
+          "violation": {
+            "criteria": [
+              "safe-to-run",
+              "safe-to-deploy"
+            ],
+            "version": null,
+            "delta": null,
+            "violation": "*",
+            "notes": null
+          },
+          "audit_source": null,
+          "audit": {
+            "criteria": "safe-to-run",
+            "version": "10.0.0",
+            "delta": null,
+            "violation": null,
+            "notes": null
+          }
+        }
+      }
+    ]
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__violations__builtin-simple-deps-violation-redundant-low-hit.snap b/src/tests/snapshots/cargo_vet__tests__violations__builtin-simple-deps-violation-redundant-low-hit.snap
new file mode 100644
index 0000000..ca56047
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__violations__builtin-simple-deps-violation-redundant-low-hit.snap
@@ -0,0 +1,12 @@
+---
+source: src/tests/violations.rs
+expression: output
+---
+Violations Found!
+  dev:10.0.0
+    the own audit 10.0.0
+      criteria: ["safe-to-run"]
+    conflicts with own violation against *
+      criteria: ["safe-to-run", "safe-to-deploy"]
+
+
diff --git a/src/tests/snapshots/cargo_vet__tests__violations__mock-simple-violation-cur-full-audit.json.snap b/src/tests/snapshots/cargo_vet__tests__violations__mock-simple-violation-cur-full-audit.json.snap
new file mode 100644
index 0000000..230f1f0
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__violations__mock-simple-violation-cur-full-audit.json.snap
@@ -0,0 +1,31 @@
+---
+source: src/tests/violations.rs
+expression: json
+---
+{
+  "conclusion": "fail (violation)",
+  "violations": {
+    "third-party1:10.0.0": [
+      {
+        "AuditConflict": {
+          "violation_source": null,
+          "violation": {
+            "criteria": "safe-to-run",
+            "version": null,
+            "delta": null,
+            "violation": "=10",
+            "notes": null
+          },
+          "audit_source": null,
+          "audit": {
+            "criteria": "safe-to-deploy",
+            "version": "10.0.0",
+            "delta": null,
+            "violation": null,
+            "notes": null
+          }
+        }
+      }
+    ]
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__violations__mock-simple-violation-cur-full-audit.snap b/src/tests/snapshots/cargo_vet__tests__violations__mock-simple-violation-cur-full-audit.snap
new file mode 100644
index 0000000..917a5d3
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__violations__mock-simple-violation-cur-full-audit.snap
@@ -0,0 +1,12 @@
+---
+source: src/tests/violations.rs
+expression: output
+---
+Violations Found!
+  third-party1:10.0.0
+    the own audit 10.0.0
+      criteria: ["safe-to-deploy"]
+    conflicts with own violation against =10
+      criteria: ["safe-to-run"]
+
+
diff --git a/src/tests/snapshots/cargo_vet__tests__violations__mock-simple-violation-cur-unaudited.json.snap b/src/tests/snapshots/cargo_vet__tests__violations__mock-simple-violation-cur-unaudited.json.snap
new file mode 100644
index 0000000..34e2aab
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__violations__mock-simple-violation-cur-unaudited.json.snap
@@ -0,0 +1,28 @@
+---
+source: src/tests/violations.rs
+expression: json
+---
+{
+  "conclusion": "fail (violation)",
+  "violations": {
+    "third-party1:10.0.0": [
+      {
+        "UnauditedConflict": {
+          "violation_source": null,
+          "violation": {
+            "criteria": "weak-reviewed",
+            "version": null,
+            "delta": null,
+            "violation": "=10",
+            "notes": null
+          },
+          "exemptions": {
+            "version": "10.0.0",
+            "criteria": "reviewed",
+            "notes": null
+          }
+        }
+      }
+    ]
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__violations__mock-simple-violation-cur-unaudited.snap b/src/tests/snapshots/cargo_vet__tests__violations__mock-simple-violation-cur-unaudited.snap
new file mode 100644
index 0000000..40b2538
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__violations__mock-simple-violation-cur-unaudited.snap
@@ -0,0 +1,12 @@
+---
+source: src/tests/violations.rs
+expression: output
+---
+Violations Found!
+  third-party1:10.0.0
+    the exemption 10.0.0
+      criteria: ["reviewed"]
+    conflicts with own violation against =10
+      criteria: ["weak-reviewed"]
+
+
diff --git a/src/tests/snapshots/cargo_vet__tests__violations__mock-simple-violation-delta.json.snap b/src/tests/snapshots/cargo_vet__tests__violations__mock-simple-violation-delta.json.snap
new file mode 100644
index 0000000..0128f75
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__violations__mock-simple-violation-delta.json.snap
@@ -0,0 +1,51 @@
+---
+source: src/tests/violations.rs
+expression: json
+---
+{
+  "conclusion": "fail (violation)",
+  "violations": {
+    "third-party1:10.0.0": [
+      {
+        "AuditConflict": {
+          "violation_source": null,
+          "violation": {
+            "criteria": "safe-to-run",
+            "version": null,
+            "delta": null,
+            "violation": "=5.0.0",
+            "notes": null
+          },
+          "audit_source": null,
+          "audit": {
+            "criteria": "safe-to-deploy",
+            "version": null,
+            "delta": "3.0.0 -> 5.0.0",
+            "violation": null,
+            "notes": null
+          }
+        }
+      },
+      {
+        "AuditConflict": {
+          "violation_source": null,
+          "violation": {
+            "criteria": "safe-to-run",
+            "version": null,
+            "delta": null,
+            "violation": "=5.0.0",
+            "notes": null
+          },
+          "audit_source": null,
+          "audit": {
+            "criteria": "safe-to-deploy",
+            "version": null,
+            "delta": "5.0.0 -> 10.0.0",
+            "violation": null,
+            "notes": null
+          }
+        }
+      }
+    ]
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__violations__mock-simple-violation-delta.snap b/src/tests/snapshots/cargo_vet__tests__violations__mock-simple-violation-delta.snap
new file mode 100644
index 0000000..86a80e1
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__violations__mock-simple-violation-delta.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/violations.rs
+expression: output
+---
+Violations Found!
+  third-party1:10.0.0
+    the own audit 3.0.0 -> 5.0.0
+      criteria: ["safe-to-deploy"]
+    conflicts with own violation against =5.0.0
+      criteria: ["safe-to-run"]
+
+    the own audit 5.0.0 -> 10.0.0
+      criteria: ["safe-to-deploy"]
+    conflicts with own violation against =5.0.0
+      criteria: ["safe-to-run"]
+
+
diff --git a/src/tests/snapshots/cargo_vet__tests__violations__mock-simple-violation-full-audit.json.snap b/src/tests/snapshots/cargo_vet__tests__violations__mock-simple-violation-full-audit.json.snap
new file mode 100644
index 0000000..0b9c9e3
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__violations__mock-simple-violation-full-audit.json.snap
@@ -0,0 +1,51 @@
+---
+source: src/tests/violations.rs
+expression: json
+---
+{
+  "conclusion": "fail (violation)",
+  "violations": {
+    "third-party1:10.0.0": [
+      {
+        "AuditConflict": {
+          "violation_source": null,
+          "violation": {
+            "criteria": "safe-to-run",
+            "version": null,
+            "delta": null,
+            "violation": "=3.0.0",
+            "notes": null
+          },
+          "audit_source": null,
+          "audit": {
+            "criteria": "safe-to-deploy",
+            "version": "3.0.0",
+            "delta": null,
+            "violation": null,
+            "notes": null
+          }
+        }
+      },
+      {
+        "AuditConflict": {
+          "violation_source": null,
+          "violation": {
+            "criteria": "safe-to-run",
+            "version": null,
+            "delta": null,
+            "violation": "=3.0.0",
+            "notes": null
+          },
+          "audit_source": null,
+          "audit": {
+            "criteria": "safe-to-deploy",
+            "version": null,
+            "delta": "3.0.0 -> 5.0.0",
+            "violation": null,
+            "notes": null
+          }
+        }
+      }
+    ]
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__violations__mock-simple-violation-full-audit.snap b/src/tests/snapshots/cargo_vet__tests__violations__mock-simple-violation-full-audit.snap
new file mode 100644
index 0000000..7b71405
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__violations__mock-simple-violation-full-audit.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/violations.rs
+expression: output
+---
+Violations Found!
+  third-party1:10.0.0
+    the own audit 3.0.0
+      criteria: ["safe-to-deploy"]
+    conflicts with own violation against =3.0.0
+      criteria: ["safe-to-run"]
+
+    the own audit 3.0.0 -> 5.0.0
+      criteria: ["safe-to-deploy"]
+    conflicts with own violation against =3.0.0
+      criteria: ["safe-to-run"]
+
+
diff --git a/src/tests/snapshots/cargo_vet__tests__violations__mock-simple-violation-hit-with-extra-junk.json.snap b/src/tests/snapshots/cargo_vet__tests__violations__mock-simple-violation-hit-with-extra-junk.json.snap
new file mode 100644
index 0000000..51bf8ca
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__violations__mock-simple-violation-hit-with-extra-junk.json.snap
@@ -0,0 +1,34 @@
+---
+source: src/tests/violations.rs
+expression: json
+---
+{
+  "conclusion": "fail (violation)",
+  "violations": {
+    "third-party1:10.0.0": [
+      {
+        "AuditConflict": {
+          "violation_source": null,
+          "violation": {
+            "criteria": [
+              "safe-to-run",
+              "fuzzed"
+            ],
+            "version": null,
+            "delta": null,
+            "violation": "*",
+            "notes": null
+          },
+          "audit_source": null,
+          "audit": {
+            "criteria": "safe-to-run",
+            "version": "10.0.0",
+            "delta": null,
+            "violation": null,
+            "notes": null
+          }
+        }
+      }
+    ]
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__violations__mock-simple-violation-hit-with-extra-junk.snap b/src/tests/snapshots/cargo_vet__tests__violations__mock-simple-violation-hit-with-extra-junk.snap
new file mode 100644
index 0000000..f50b742
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__violations__mock-simple-violation-hit-with-extra-junk.snap
@@ -0,0 +1,12 @@
+---
+source: src/tests/violations.rs
+expression: output
+---
+Violations Found!
+  third-party1:10.0.0
+    the own audit 10.0.0
+      criteria: ["safe-to-run"]
+    conflicts with own violation against *
+      criteria: ["safe-to-run", "fuzzed"]
+
+
diff --git a/src/tests/snapshots/cargo_vet__tests__violations__mock-simple-violation-wildcard.json.snap b/src/tests/snapshots/cargo_vet__tests__violations__mock-simple-violation-wildcard.json.snap
new file mode 100644
index 0000000..579eab5
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__violations__mock-simple-violation-wildcard.json.snap
@@ -0,0 +1,31 @@
+---
+source: src/tests/violations.rs
+expression: json
+---
+{
+  "conclusion": "fail (violation)",
+  "violations": {
+    "third-party1:10.0.0": [
+      {
+        "AuditConflict": {
+          "violation_source": null,
+          "violation": {
+            "criteria": "safe-to-run",
+            "version": null,
+            "delta": null,
+            "violation": "*",
+            "notes": null
+          },
+          "audit_source": null,
+          "audit": {
+            "criteria": "safe-to-deploy",
+            "version": "10.0.0",
+            "delta": null,
+            "violation": null,
+            "notes": null
+          }
+        }
+      }
+    ]
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__violations__mock-simple-violation-wildcard.snap b/src/tests/snapshots/cargo_vet__tests__violations__mock-simple-violation-wildcard.snap
new file mode 100644
index 0000000..2492663
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__violations__mock-simple-violation-wildcard.snap
@@ -0,0 +1,12 @@
+---
+source: src/tests/violations.rs
+expression: output
+---
+Violations Found!
+  third-party1:10.0.0
+    the own audit 10.0.0
+      criteria: ["safe-to-deploy"]
+    conflicts with own violation against *
+      criteria: ["safe-to-run"]
+
+
diff --git a/src/tests/snapshots/cargo_vet__tests__wildcard__imported_wildcard_audit.json.snap b/src/tests/snapshots/cargo_vet__tests__wildcard__imported_wildcard_audit.json.snap
new file mode 100644
index 0000000..f5a7976
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__wildcard__imported_wildcard_audit.json.snap
@@ -0,0 +1,23 @@
+---
+source: src/tests/wildcard.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0"
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__wildcard__imported_wildcard_audit.snap b/src/tests/snapshots/cargo_vet__tests__wildcard__imported_wildcard_audit.snap
new file mode 100644
index 0000000..79656dc
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__wildcard__imported_wildcard_audit.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/wildcard.rs
+expression: human
+---
+Vetting Succeeded (3 fully audited)
+
diff --git a/src/tests/snapshots/cargo_vet__tests__wildcard__wildcard_delta_audit_locked.json.snap b/src/tests/snapshots/cargo_vet__tests__wildcard__wildcard_delta_audit_locked.json.snap
new file mode 100644
index 0000000..f5a7976
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__wildcard__wildcard_delta_audit_locked.json.snap
@@ -0,0 +1,23 @@
+---
+source: src/tests/wildcard.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0"
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__wildcard__wildcard_delta_audit_locked.snap b/src/tests/snapshots/cargo_vet__tests__wildcard__wildcard_delta_audit_locked.snap
new file mode 100644
index 0000000..79656dc
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__wildcard__wildcard_delta_audit_locked.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/wildcard.rs
+expression: human
+---
+Vetting Succeeded (3 fully audited)
+
diff --git a/src/tests/snapshots/cargo_vet__tests__wildcard__wildcard_delta_audit_wrong_user_id_locked.json.snap b/src/tests/snapshots/cargo_vet__tests__wildcard__wildcard_delta_audit_wrong_user_id_locked.json.snap
new file mode 100644
index 0000000..6ae3b08
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__wildcard__wildcard_delta_audit_wrong_user_id_locked.json.snap
@@ -0,0 +1,57 @@
+---
+source: src/tests/wildcard.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "transitive-third-party1",
+        "notable_parents": "third-party1",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "5.0.0",
+          "diffstat": {
+            "insertions": 25,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "safe-to-deploy": [
+        {
+          "name": "transitive-third-party1",
+          "notable_parents": "third-party1",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "5.0.0",
+            "diffstat": {
+              "insertions": 25,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 25
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__wildcard__wildcard_delta_audit_wrong_user_id_locked.snap b/src/tests/snapshots/cargo_vet__tests__wildcard__wildcard_delta_audit_wrong_user_id_locked.snap
new file mode 100644
index 0000000..4ca8687
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__wildcard__wildcard_delta_audit_wrong_user_id_locked.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/wildcard.rs
+expression: human
+---
+Vetting Failed!
+
+1 unvetted dependencies:
+  transitive-third-party1:10.0.0 missing ["safe-to-deploy"]
+
+recommended audits for safe-to-deploy:
+    Command                                          Publisher  Used By       Audit Size
+    cargo vet inspect transitive-third-party1 5.0.0  UNKNOWN    third-party1  25 lines
+
+estimated audit backlog: 25 lines
+
+Use |cargo vet certify| to record the audits.
+
diff --git a/src/tests/snapshots/cargo_vet__tests__wildcard__wildcard_full_audit_locked.json.snap b/src/tests/snapshots/cargo_vet__tests__wildcard__wildcard_full_audit_locked.json.snap
new file mode 100644
index 0000000..f5a7976
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__wildcard__wildcard_full_audit_locked.json.snap
@@ -0,0 +1,23 @@
+---
+source: src/tests/wildcard.rs
+expression: json
+---
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "third-party1",
+      "version": "10.0.0"
+    },
+    {
+      "name": "third-party2",
+      "version": "10.0.0"
+    },
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0"
+    }
+  ],
+  "vetted_partially": [],
+  "vetted_with_exemptions": []
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__wildcard__wildcard_full_audit_locked.snap b/src/tests/snapshots/cargo_vet__tests__wildcard__wildcard_full_audit_locked.snap
new file mode 100644
index 0000000..79656dc
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__wildcard__wildcard_full_audit_locked.snap
@@ -0,0 +1,6 @@
+---
+source: src/tests/wildcard.rs
+expression: human
+---
+Vetting Succeeded (3 fully audited)
+
diff --git a/src/tests/snapshots/cargo_vet__tests__wildcard__wildcard_full_audit_wrong_user_id_locked.json.snap b/src/tests/snapshots/cargo_vet__tests__wildcard__wildcard_full_audit_wrong_user_id_locked.json.snap
new file mode 100644
index 0000000..a03129e
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__wildcard__wildcard_full_audit_wrong_user_id_locked.json.snap
@@ -0,0 +1,57 @@
+---
+source: src/tests/wildcard.rs
+expression: json
+---
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "transitive-third-party1",
+      "version": "10.0.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "transitive-third-party1",
+        "notable_parents": "third-party1",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "10.0.0",
+          "diffstat": {
+            "insertions": 100,
+            "deletions": 0,
+            "files_changed": 1
+          }
+        }
+      }
+    ],
+    "suggest_by_criteria": {
+      "safe-to-deploy": [
+        {
+          "name": "transitive-third-party1",
+          "notable_parents": "third-party1",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "10.0.0",
+            "diffstat": {
+              "insertions": 100,
+              "deletions": 0,
+              "files_changed": 1
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 100
+  }
+}
diff --git a/src/tests/snapshots/cargo_vet__tests__wildcard__wildcard_full_audit_wrong_user_id_locked.snap b/src/tests/snapshots/cargo_vet__tests__wildcard__wildcard_full_audit_wrong_user_id_locked.snap
new file mode 100644
index 0000000..40dfe15
--- /dev/null
+++ b/src/tests/snapshots/cargo_vet__tests__wildcard__wildcard_full_audit_wrong_user_id_locked.snap
@@ -0,0 +1,17 @@
+---
+source: src/tests/wildcard.rs
+expression: human
+---
+Vetting Failed!
+
+1 unvetted dependencies:
+  transitive-third-party1:10.0.0 missing ["safe-to-deploy"]
+
+recommended audits for safe-to-deploy:
+    Command                                           Publisher  Used By       Audit Size
+    cargo vet inspect transitive-third-party1 10.0.0  UNKNOWN    third-party1  100 lines
+
+estimated audit backlog: 100 lines
+
+Use |cargo vet certify| to record the audits.
+
diff --git a/src/tests/store_parsing.rs b/src/tests/store_parsing.rs
new file mode 100644
index 0000000..d2a2b28
--- /dev/null
+++ b/src/tests/store_parsing.rs
@@ -0,0 +1,441 @@
+const EMPTY_CONFIG: &str = r##"
+# cargo-vet config file
+
+[cargo-vet]
+version = "1.0"
+"##;
+const EMPTY_AUDITS: &str = r##"
+# cargo-vet audits file
+
+[audits]
+"##;
+const EMPTY_IMPORTS: &str = r##"
+# cargo-vet imports lock
+"##;
+
+fn get_valid_store(config: &str, audits: &str, imports: &str) -> String {
+    let today = chrono::NaiveDate::from_ymd_opt(2023, 1, 1).unwrap();
+    let res = crate::Store::mock_acquire(config, audits, imports, today, true);
+    match res {
+        Ok(_) => String::new(),
+        Err(e) => format!("{:?}", miette::Report::new(e)),
+    }
+}
+
+#[test]
+fn test_all_empty() {
+    let acquire_errors = get_valid_store("\n", "\n", "\n");
+    insta::assert_snapshot!(acquire_errors);
+}
+
+#[test]
+fn test_all_min() {
+    let acquire_errors = get_valid_store(EMPTY_CONFIG, EMPTY_AUDITS, EMPTY_IMPORTS);
+    insta::assert_snapshot!(acquire_errors);
+}
+
+#[test]
+fn test_simple_bad_audit() {
+    let audits = r##"
+# cargo-vet audits file
+
+[[audits.serde]]
+criteria = "bad"
+version = "1.0.0"
+"##;
+
+    let acquire_errors = get_valid_store(EMPTY_CONFIG, audits, EMPTY_IMPORTS);
+    insta::assert_snapshot!(acquire_errors);
+}
+
+#[test]
+fn test_many_bad_audits() {
+    let audits = r##"
+# cargo-vet audits file
+
+[criteria.good]
+description = "great"
+implies = ["safe-to-deploy", "bad-imply"]
+
+[[audits.serde]]
+criteria = "bad"
+version = "1.0.0"
+
+[[audits.serde]]
+criteria = "safe-to-jog"
+version = "2.0.0"
+
+[[audits.serde]]
+criteria = "oops"
+delta = "1.0.0 -> 1.1.0"
+
+[[audits.serde]]
+criteria = ["safe-to-run", "dang"]
+delta = "1.0.0 -> 1.1.0"
+
+[[audits.serde]]
+criteria = "no-good-bad-bad"
+violation = "^5.0.0"
+"##;
+
+    let acquire_errors = get_valid_store(EMPTY_CONFIG, audits, EMPTY_IMPORTS);
+    insta::assert_snapshot!(acquire_errors);
+}
+
+#[test]
+fn test_many_bad_config() {
+    let config = r##"
+# cargo-vet config file
+
+[cargo-vet]
+version = "1.0"
+
+[policy.boring]
+audit-as-crates-io = true
+
+[policy.clap]
+criteria = "safe-to-deploy"
+dev-criteria = "safe-to-run"
+dependency-criteria = { clap_derive = "good" }
+
+[policy.serde]
+criteria = "bad"
+dev-criteria = "nope"
+dependency-criteria = { clap = ["safe-to-run", "unsafe-for-all", "good"], serde_derive = "nada" }
+
+[[exemptions.clap]]
+version = "1.0.0"
+criteria = "oops"
+
+[[exemptions.clap_derive]]
+version = "1.0.0"
+criteria = "safe-to-run"
+"##;
+
+    let audits = r##"
+# cargo-vet audits file
+
+[criteria.good]
+description = "great"
+implies = "safe-to-deploy"
+
+[audits]
+"##;
+
+    let acquire_errors = get_valid_store(config, audits, EMPTY_IMPORTS);
+    insta::assert_snapshot!(acquire_errors);
+}
+
+#[test]
+fn test_outdated_imports_lock_extra_peer() {
+    let config = r##"
+# cargo-vet config file
+
+[cargo-vet]
+version = "1.0"
+
+[imports.peer1]
+url = "https://peer1.com"
+"##;
+
+    let imports = r##"
+# cargo-vet imports lock
+
+[[audits.peer1.audits.third-party1]]
+criteria = "safe-to-deploy"
+version = "10.0.0"
+
+[[audits.peer2.audits.third-party2]]
+criteria = "safe-to-deploy"
+version = "10.0.0"
+"##;
+
+    let acquire_errors = get_valid_store(config, EMPTY_AUDITS, imports);
+    insta::assert_snapshot!(acquire_errors);
+}
+
+#[test]
+fn test_outdated_imports_lock_missing_peer() {
+    let config = r##"
+# cargo-vet config file
+
+[cargo-vet]
+version = "1.0"
+
+[imports.peer1]
+url = "https://peer1.com"
+
+[imports.peer2]
+url = "https://peer2.com"
+"##;
+
+    let imports = r##"
+# cargo-vet imports lock
+
+[[audits.peer1.audits.third-party1]]
+criteria = "safe-to-deploy"
+version = "10.0.0"
+"##;
+
+    let acquire_errors = get_valid_store(config, EMPTY_AUDITS, imports);
+    insta::assert_snapshot!(acquire_errors);
+}
+
+#[test]
+fn test_outdated_imports_lock_excluded_crate() {
+    let config = r##"
+# cargo-vet config file
+
+[cargo-vet]
+version = "1.0"
+
+[imports.peer1]
+url = "https://peer1.com"
+exclude = ["third-party1"]
+"##;
+
+    let imports = r##"
+# cargo-vet imports lock
+
+[[audits.peer1.audits.third-party1]]
+criteria = "safe-to-deploy"
+version = "10.0.0"
+
+[[audits.peer1.audits.third-party2]]
+criteria = "safe-to-deploy"
+version = "10.0.0"
+"##;
+
+    let acquire_errors = get_valid_store(config, EMPTY_AUDITS, imports);
+    insta::assert_snapshot!(acquire_errors);
+}
+
+#[test]
+fn test_outdated_imports_lock_ok() {
+    let config = r##"
+# cargo-vet config file
+
+[cargo-vet]
+version = "1.0"
+
+[imports.peer1]
+url = "https://peer1.com"
+exclude = ["third-party2"]
+
+[imports.peer2]
+url = "https://peer1.com"
+"##;
+
+    let imports = r##"
+# cargo-vet imports lock
+
+[[audits.peer1.audits.third-party1]]
+criteria = "safe-to-deploy"
+version = "10.0.0"
+
+[[audits.peer2.audits.third-party2]]
+criteria = "safe-to-deploy"
+version = "10.0.0"
+"##;
+
+    let acquire_errors = get_valid_store(config, EMPTY_AUDITS, imports);
+    insta::assert_snapshot!(acquire_errors);
+}
+
+#[test]
+fn test_unknown_field_config() {
+    let config = r##"
+# cargo-vet config file
+
+[cargo-vet]
+version = "1.0"
+
+[imports.peer1]
+url = "https://peer1.com"
+exclude = ["zzz", "aaa"]
+unknown-field = "hi"
+
+[[exemptions.zzz]]
+version = "1.0.0"
+criteria = "safe-to-deploy"
+unknown-field = "hi"
+"##;
+
+    let imports = r##"
+# cargo-vet imports lock
+
+[[audits.peer1.audits.third-party1]]
+criteria = "safe-to-deploy"
+version = "10.0.0"
+"##;
+
+    let acquire_errors = get_valid_store(config, EMPTY_AUDITS, imports);
+    insta::assert_snapshot!(acquire_errors);
+}
+
+#[test]
+fn test_unknown_field_criteria() {
+    let audits = r##"
+# cargo-vet audits file
+
+[criteria.good]
+description = "great"
+implies = "safe-to-deploy"
+unknown-field = "invalid"
+
+[audits]
+"##;
+
+    let acquire_errors = get_valid_store(EMPTY_CONFIG, audits, EMPTY_IMPORTS);
+    insta::assert_snapshot!(acquire_errors);
+}
+
+#[test]
+fn test_unknown_field_audit() {
+    let audits = r##"
+# cargo-vet audits file
+
+[[audits.zzz]]
+criteria = "safe-to-deploy"
+version = "2.0.0"
+unknown-field = "invalid"
+"##;
+
+    let acquire_errors = get_valid_store(EMPTY_CONFIG, audits, EMPTY_IMPORTS);
+    insta::assert_snapshot!(acquire_errors);
+}
+
+#[test]
+fn test_distant_future_end_date() {
+    // NOTE: `get_valid_store` pretends that "today" is 2023-01-01, so this will
+    // always be over a year in the future.
+    let audits = r##"
+# cargo-vet audits file
+
+[[wildcard-audits.zzz]]
+criteria = "safe-to-deploy"
+user-id = 1
+start = "2015-05-15"
+end = "2024-05-15"
+
+[audits]
+"##;
+
+    let acquire_errors = get_valid_store(EMPTY_CONFIG, audits, EMPTY_IMPORTS);
+    insta::assert_snapshot!(acquire_errors);
+}
+
+#[test]
+fn test_distant_future_end_date_leap_year() {
+    // Make sure that we handle the day being on Feb. 29th. We'll treat 1 year
+    // in the future as Feb. 28th, as it won't be a leap-year.
+    let audits = r##"
+# cargo-vet audits file
+
+[[wildcard-audits.zzz]]
+criteria = "safe-to-deploy"
+user-id = 1
+start = "2015-05-15"
+end = "2021-03-01"
+
+[audits]
+"##;
+
+    let today = chrono::NaiveDate::from_ymd_opt(2020, 2, 29).unwrap();
+    let acquire_errors =
+        match crate::Store::mock_acquire(EMPTY_CONFIG, audits, EMPTY_IMPORTS, today, true) {
+            Ok(_) => String::new(),
+            Err(e) => format!("{:?}", miette::Report::new(e)),
+        };
+    insta::assert_snapshot!(acquire_errors);
+}
+
+#[test]
+fn test_invalid_formatting() {
+    let config = r##"
+# cargo-vet config file
+
+[cargo-vet]
+version = "1.0"
+
+[imports.peer1]
+url = "https://peer1.com"
+exclude = ["zzz", "aaa"]
+
+[imports.peer2]
+url = "https://peer1.com"
+
+[[exemptions.zzz]]
+version = "1.0.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.bbb]]
+criteria = "safe-to-deploy"
+version = "1.0.0"
+
+[[exemptions.aaa]]
+version = "1.0.0"
+criteria = "safe-to-deploy"
+"##;
+
+    let audits = r##"
+# cargo-vet audits file
+
+[criteria.good]
+description = "great"
+implies = "safe-to-deploy"
+
+[[audits.serde]]
+criteria = ["safe-to-deploy", "good"]
+version = "2.0.0"
+
+[[audits.serde]]
+criteria = ["safe-to-deploy", "good"]
+version = "1.0.0"
+notes = "valid field"
+"##;
+
+    let imports = r##"
+# cargo-vet imports lock
+
+[[audits.peer1.audits.third-party1]]
+criteria = "safe-to-deploy"
+version = "10.0.0"
+
+[[audits.peer2.audits.third-party2]]
+criteria = "safe-to-deploy"
+version = "10.0.0"
+"##;
+
+    let acquire_errors = get_valid_store(config, audits, imports);
+    insta::assert_snapshot!(acquire_errors);
+}
+
+#[test]
+fn parse_criteria_map() {
+    let config = r##"
+# cargo-vet config file
+
+[cargo-vet]
+version = "1.0"
+
+[imports.peer1]
+url = "https://peer1.com"
+
+[imports.peer1.criteria-map]
+fuzzed = "safe-to-run"
+safe-to-deploy = "safe-to-run"
+"##;
+
+    let imports = r##"
+# cargo-vet imports lock
+
+[audits.peer1.criteria.fuzzed]
+description = "fuzzed"
+
+[audits.peer1.audits]
+"##;
+
+    let acquire_errors = get_valid_store(config, EMPTY_AUDITS, imports);
+    insta::assert_snapshot!(acquire_errors);
+}
diff --git a/src/tests/trusted.rs b/src/tests/trusted.rs
new file mode 100644
index 0000000..c9c687a
--- /dev/null
+++ b/src/tests/trusted.rs
@@ -0,0 +1,280 @@
+use super::*;
+
+#[test]
+fn trusted_full_audit_locked() {
+    // (Pass) A a trusted entry for a crate when locked
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, mut imports) = builtin_files_full_audited(&metadata);
+    audits.audits.remove("transitive-third-party1");
+    audits.trusted.insert(
+        "transitive-third-party1".to_owned(),
+        vec![trusted_entry(1, SAFE_TO_DEPLOY)],
+    );
+
+    imports.publisher.insert(
+        "transitive-third-party1".to_owned(),
+        vec![publisher_entry(ver(DEFAULT_VER), 1)],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("trusted_full_audit_locked", metadata, store);
+}
+
+#[test]
+fn trusted_wrong_user_id_locked() {
+    // (Fail) A trusted entry for a crate with the wrong user when locked
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, mut imports) = builtin_files_full_audited(&metadata);
+    audits.audits.remove("transitive-third-party1");
+    audits.trusted.insert(
+        "transitive-third-party1".to_owned(),
+        vec![trusted_entry(2, SAFE_TO_DEPLOY)],
+    );
+
+    imports.publisher.insert(
+        "transitive-third-party1".to_owned(),
+        vec![publisher_entry(ver(DEFAULT_VER), 1)],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("trusted_wrong_user_id_locked", metadata, store);
+}
+
+#[test]
+fn trusted_delta_audit_locked() {
+    // (Pass) A trusted entry plus delta-audit for a crate when locked
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, mut imports) = builtin_files_full_audited(&metadata);
+    audits.audits.insert(
+        "transitive-third-party1".to_owned(),
+        vec![delta_audit(ver(5), ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+    );
+    audits.trusted.insert(
+        "transitive-third-party1".to_owned(),
+        vec![trusted_entry(1, SAFE_TO_DEPLOY)],
+    );
+
+    imports.publisher.insert(
+        "transitive-third-party1".to_owned(),
+        vec![publisher_entry(ver(5), 1)],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("trusted_delta_audit_locked", metadata, store);
+}
+
+#[test]
+fn trusted_delta_audit_wrong_user_id_locked() {
+    // (Fail) A trusted entry plus delta-audit for a crate with the wrong user when locked
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, mut imports) = builtin_files_full_audited(&metadata);
+    audits.audits.insert(
+        "transitive-third-party1".to_owned(),
+        vec![delta_audit(ver(5), ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+    );
+    audits.trusted.insert(
+        "transitive-third-party1".to_owned(),
+        vec![trusted_entry(2, SAFE_TO_DEPLOY)],
+    );
+
+    imports.publisher.insert(
+        "transitive-third-party1".to_owned(),
+        vec![publisher_entry(ver(5), 1)],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("trusted_delta_audit_wrong_user_id_locked", metadata, store);
+}
+
+#[test]
+fn trusted_suggest_local() {
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, imports) = builtin_files_full_audited(&metadata);
+    audits.audits.remove("transitive-third-party1");
+    audits.trusted.insert(
+        "other-crate".to_owned(),
+        vec![trusted_entry(1, SAFE_TO_DEPLOY)],
+    );
+
+    let mut network = Network::new_mock();
+    MockRegistryBuilder::new()
+        .user(1, "testuser", "Test user")
+        .package(
+            "transitive-third-party1",
+            &[reg_published_by(ver(DEFAULT_VER), Some(1), "2022-12-12")],
+        )
+        .serve(&mut network);
+
+    let cfg = mock_cfg(&metadata);
+
+    let store = Store::mock_online(&cfg, config, audits, imports, &network, true).unwrap();
+
+    assert_report_snapshot!("trusted_suggest_local", metadata, store, Some(&network));
+}
+
+#[test]
+fn trusted_suggest_import() {
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = builtin_files_full_audited(&metadata);
+    audits.audits.remove("transitive-third-party1");
+
+    config.imports.insert(
+        FOREIGN.to_owned(),
+        crate::format::RemoteImport {
+            url: vec![FOREIGN_URL.to_owned()],
+            ..Default::default()
+        },
+    );
+
+    let new_foreign_audits = AuditsFile {
+        criteria: SortedMap::new(),
+        audits: SortedMap::new(),
+        wildcard_audits: SortedMap::new(),
+        trusted: [(
+            "other-crate".to_owned(),
+            vec![trusted_entry(1, SAFE_TO_DEPLOY)],
+        )]
+        .into_iter()
+        .collect(),
+    };
+
+    let mut network = Network::new_mock();
+    network.mock_serve_toml(FOREIGN_URL, &new_foreign_audits);
+    MockRegistryBuilder::new()
+        .user(1, "testuser", "Test user")
+        .package(
+            "transitive-third-party1",
+            &[reg_published_by(ver(DEFAULT_VER), Some(1), "2022-12-12")],
+        )
+        .serve(&mut network);
+
+    let cfg = mock_cfg(&metadata);
+
+    let store = Store::mock_online(&cfg, config, audits, imports, &network, true).unwrap();
+
+    assert_report_snapshot!("trusted_suggest_import", metadata, store, Some(&network));
+}
+
+#[test]
+fn trusted_suggest_import_multiple() {
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = builtin_files_full_audited(&metadata);
+    audits.audits.remove("transitive-third-party1");
+
+    config.imports.insert(
+        FOREIGN.to_owned(),
+        crate::format::RemoteImport {
+            url: vec![FOREIGN_URL.to_owned()],
+            ..Default::default()
+        },
+    );
+
+    config.imports.insert(
+        OTHER_FOREIGN.to_owned(),
+        crate::format::RemoteImport {
+            url: vec![OTHER_FOREIGN_URL.to_owned()],
+            ..Default::default()
+        },
+    );
+
+    let new_foreign_audits = AuditsFile {
+        criteria: SortedMap::new(),
+        audits: SortedMap::new(),
+        wildcard_audits: SortedMap::new(),
+        trusted: [(
+            "other-crate".to_owned(),
+            vec![trusted_entry(1, SAFE_TO_DEPLOY)],
+        )]
+        .into_iter()
+        .collect(),
+    };
+
+    let mut network = Network::new_mock();
+    network.mock_serve_toml(FOREIGN_URL, &new_foreign_audits);
+    network.mock_serve_toml(OTHER_FOREIGN_URL, &new_foreign_audits);
+    MockRegistryBuilder::new()
+        .user(1, "testuser", "Test user")
+        .package(
+            "transitive-third-party1",
+            &[reg_published_by(ver(DEFAULT_VER), Some(1), "2022-12-12")],
+        )
+        .serve(&mut network);
+
+    let cfg = mock_cfg(&metadata);
+
+    let store = Store::mock_online(&cfg, config, audits, imports, &network, true).unwrap();
+
+    assert_report_snapshot!(
+        "trusted_suggest_import_multiple",
+        metadata,
+        store,
+        Some(&network)
+    );
+}
+
+#[test]
+fn trusted_suggest_local_ambiguous() {
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, imports) = builtin_files_full_audited(&metadata);
+    audits.audits.remove("transitive-third-party1");
+    audits.trusted.insert(
+        "other-crate".to_owned(),
+        vec![trusted_entry(1, SAFE_TO_DEPLOY)],
+    );
+
+    let mut network = Network::new_mock();
+    MockRegistryBuilder::new()
+        .user(1, "testuser", "Test user")
+        .user(2, "otheruser", "Other user")
+        .package(
+            "transitive-third-party1",
+            &[
+                reg_published_by(ver(9), Some(2), "2022-12-12"),
+                reg_published_by(ver(DEFAULT_VER), Some(1), "2022-12-12"),
+            ],
+        )
+        .serve(&mut network);
+
+    let cfg = mock_cfg(&metadata);
+
+    let store = Store::mock_online(&cfg, config, audits, imports, &network, true).unwrap();
+
+    assert_report_snapshot!(
+        "trusted_suggest_local_ambiguous",
+        metadata,
+        store,
+        Some(&network)
+    );
+}
diff --git a/src/tests/unpublished.rs b/src/tests/unpublished.rs
new file mode 100644
index 0000000..733eb73
--- /dev/null
+++ b/src/tests/unpublished.rs
@@ -0,0 +1,218 @@
+use super::*;
+
+#[derive(Copy, Clone, Eq, PartialEq)]
+enum UnpublishedInitialState {
+    // wildcard audit applies to 8 and 9, unpublished from 8 to 10
+    WildcardAudit,
+    // full audit applying to 8. unpublished from 8 to 10
+    FullAudit,
+    // wildcard audit with cached publisher applies to 8. Live publisher applies
+    // to 9. No unpublished entry.
+    WildcardNoUnpublished,
+    // no audits/unpublished entries
+    Nothing,
+}
+
+/// Helper used by a couple of tests. Runs `update_store` against a common store
+/// state with the given update mode.
+fn unpublished_basic_regenerate(
+    initial: UnpublishedInitialState,
+    mode: crate::resolver::UpdateMode,
+) -> String {
+    let _enter = TEST_RUNTIME.enter();
+
+    let mock = MockMetadata::descriptive();
+    let metadata = mock.metadata();
+    let (mut config, mut audits, mut imports) = builtin_files_inited(&metadata);
+
+    config.exemptions.remove("descriptive");
+    config
+        .policy
+        .insert("descriptive".to_owned(), audit_as_policy(Some(true)));
+    imports.unpublished.insert(
+        "descriptive".to_owned(),
+        vec![crate::format::UnpublishedEntry {
+            version: ver(DEFAULT_VER),
+            audited_as: ver(8),
+            still_unpublished: false,
+            is_fresh_import: false,
+        }],
+    );
+
+    match initial {
+        UnpublishedInitialState::FullAudit => {
+            audits.audits.insert(
+                "descriptive".to_owned(),
+                vec![full_audit(ver(8), SAFE_TO_DEPLOY)],
+            );
+        }
+        UnpublishedInitialState::WildcardAudit | UnpublishedInitialState::WildcardNoUnpublished => {
+            audits.wildcard_audits.insert(
+                "descriptive".to_owned(),
+                vec![wildcard_audit(1, SAFE_TO_DEPLOY)],
+            );
+            imports.publisher.insert(
+                "descriptive".to_owned(),
+                vec![publisher_entry_named(ver(8), 1, "user1", "User One")],
+            );
+            if initial == UnpublishedInitialState::WildcardNoUnpublished {
+                imports.unpublished.remove("descriptive");
+            }
+        }
+        UnpublishedInitialState::Nothing => {
+            imports.unpublished.remove("descriptive");
+        }
+    }
+
+    let mut network = Network::new_mock();
+    MockRegistryBuilder::new()
+        .user(1, "user1", "User One")
+        .package(
+            "descriptive",
+            &[
+                reg_published_by(ver(8), Some(1), "2022-12-15"),
+                reg_published_by(ver(9), Some(1), "2022-12-15"),
+            ],
+        )
+        .serve(&mut network);
+
+    let cfg = mock_cfg(&metadata);
+
+    let mut store = Store::mock_online(&cfg, config, audits, imports, &network, false).unwrap();
+
+    let old = store.mock_commit();
+    crate::resolver::update_store(&mock_cfg(&metadata), &mut store, |_| mode);
+    let new = store.mock_commit();
+
+    diff_store_commits(&old, &new)
+}
+
+#[test]
+fn audit_as_crates_io_unpublished_blank_regenerate_exemptions() {
+    let output = unpublished_basic_regenerate(
+        UnpublishedInitialState::Nothing,
+        crate::resolver::UpdateMode {
+            search_mode: crate::resolver::SearchMode::RegenerateExemptions,
+            prune_exemptions: true,
+            prune_imports: true,
+        },
+    );
+    insta::assert_snapshot!(output);
+}
+
+#[test]
+fn audit_as_crates_io_unpublished_full_regenerate_exemptions() {
+    let output = unpublished_basic_regenerate(
+        UnpublishedInitialState::FullAudit,
+        crate::resolver::UpdateMode {
+            search_mode: crate::resolver::SearchMode::RegenerateExemptions,
+            prune_exemptions: true,
+            prune_imports: true,
+        },
+    );
+    insta::assert_snapshot!(output);
+}
+
+#[test]
+fn audit_as_crates_io_unpublished_full_prune() {
+    let output = unpublished_basic_regenerate(
+        UnpublishedInitialState::FullAudit,
+        crate::resolver::UpdateMode {
+            search_mode: crate::resolver::SearchMode::PreferFreshImports,
+            prune_exemptions: true,
+            prune_imports: true,
+        },
+    );
+    insta::assert_snapshot!(output);
+}
+
+#[test]
+fn audit_as_crates_io_unpublished_full_prefer_exemptions() {
+    let output = unpublished_basic_regenerate(
+        UnpublishedInitialState::FullAudit,
+        crate::resolver::UpdateMode {
+            search_mode: crate::resolver::SearchMode::PreferExemptions,
+            prune_exemptions: false,
+            prune_imports: false,
+        },
+    );
+    insta::assert_snapshot!(output);
+}
+
+#[test]
+fn audit_as_crates_io_unpublished_wildcard_regenerate_exemptions() {
+    let output = unpublished_basic_regenerate(
+        UnpublishedInitialState::WildcardAudit,
+        crate::resolver::UpdateMode {
+            search_mode: crate::resolver::SearchMode::RegenerateExemptions,
+            prune_exemptions: true,
+            prune_imports: true,
+        },
+    );
+    insta::assert_snapshot!(output);
+}
+
+#[test]
+fn audit_as_crates_io_unpublished_wildcard_prune() {
+    let output = unpublished_basic_regenerate(
+        UnpublishedInitialState::WildcardAudit,
+        crate::resolver::UpdateMode {
+            search_mode: crate::resolver::SearchMode::PreferFreshImports,
+            prune_exemptions: true,
+            prune_imports: true,
+        },
+    );
+    insta::assert_snapshot!(output);
+}
+
+#[test]
+fn audit_as_crates_io_unpublished_wildcard_prefer_exemptions() {
+    let output = unpublished_basic_regenerate(
+        UnpublishedInitialState::WildcardAudit,
+        crate::resolver::UpdateMode {
+            search_mode: crate::resolver::SearchMode::PreferExemptions,
+            prune_exemptions: false,
+            prune_imports: false,
+        },
+    );
+    insta::assert_snapshot!(output);
+}
+
+#[test]
+fn audit_as_crates_io_unpublished_wildcard_nounpublished_regenerate_exemptions() {
+    let output = unpublished_basic_regenerate(
+        UnpublishedInitialState::WildcardNoUnpublished,
+        crate::resolver::UpdateMode {
+            search_mode: crate::resolver::SearchMode::RegenerateExemptions,
+            prune_exemptions: true,
+            prune_imports: true,
+        },
+    );
+    insta::assert_snapshot!(output);
+}
+
+#[test]
+fn audit_as_crates_io_unpublished_wildcard_nounpublished_prune() {
+    let output = unpublished_basic_regenerate(
+        UnpublishedInitialState::WildcardNoUnpublished,
+        crate::resolver::UpdateMode {
+            search_mode: crate::resolver::SearchMode::PreferFreshImports,
+            prune_exemptions: true,
+            prune_imports: true,
+        },
+    );
+    insta::assert_snapshot!(output);
+}
+
+#[test]
+fn audit_as_crates_io_unpublished_wildcard_nounpublished_prefer_exemptions() {
+    let output = unpublished_basic_regenerate(
+        UnpublishedInitialState::WildcardNoUnpublished,
+        crate::resolver::UpdateMode {
+            search_mode: crate::resolver::SearchMode::PreferExemptions,
+            prune_exemptions: false,
+            prune_imports: false,
+        },
+    );
+    insta::assert_snapshot!(output);
+}
diff --git a/src/tests/vet.rs b/src/tests/vet.rs
new file mode 100644
index 0000000..5e215a9
--- /dev/null
+++ b/src/tests/vet.rs
@@ -0,0 +1,2636 @@
+use crate::format::{RegistryEntry, RegistryFile};
+
+use super::*;
+
+#[test]
+fn mock_simple_init() {
+    // (Pass) Should look the same as a fresh 'vet init'.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, audits, imports) = files_inited(&metadata);
+
+    let store = Store::mock(config, audits, imports);
+    assert_report_snapshot!("mock-simple-init", metadata, store);
+}
+
+#[test]
+fn mock_simple_no_exemptions() {
+    // (Fail) Should look the same as a fresh 'vet init' but with all 'exemptions' entries deleted.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, audits, imports) = files_no_exemptions(&metadata);
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("mock-simple-no-unaudited", metadata, store);
+}
+
+#[test]
+fn mock_simple_full_audited() {
+    // (Pass) All entries have direct full audits.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, audits, imports) = files_full_audited(&metadata);
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("mock-simple-full-audited", metadata, store);
+}
+
+#[test]
+fn builtin_simple_init() {
+    // (Pass) Should look the same as a fresh 'vet init'.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, audits, imports) = builtin_files_inited(&metadata);
+
+    let store = Store::mock(config, audits, imports);
+    assert_report_snapshot!("builtin-simple-init", metadata, store);
+}
+
+#[test]
+fn builtin_simple_no_exemptions() {
+    // (Fail) Should look the same as a fresh 'vet init' but with all 'exemptions' entries deleted.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, audits, imports) = builtin_files_no_exemptions(&metadata);
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin-simple-no-unaudited", metadata, store);
+}
+
+#[test]
+fn builtin_simple_full_audited() {
+    // (Pass) All entries have direct full audits.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, audits, imports) = builtin_files_full_audited(&metadata);
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin-simple-full-audited", metadata, store);
+}
+
+#[test]
+fn mock_simple_missing_transitive() {
+    // (Fail) Missing an audit for a transitive dep
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, imports) = files_full_audited(&metadata);
+
+    audits
+        .audits
+        .get_mut("transitive-third-party1")
+        .unwrap()
+        .clear();
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("mock-simple-missing-transitive", metadata, store);
+}
+
+#[test]
+fn mock_simple_missing_direct_internal() {
+    // (Fail) Missing an audit for a direct dep that has children
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, imports) = files_full_audited(&metadata);
+
+    audits.audits.get_mut("third-party1").unwrap().clear();
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("mock-simple-missing-direct-internal", metadata, store);
+}
+
+#[test]
+fn mock_simple_missing_direct_leaf() {
+    // (Fail) Missing an entry for direct dep that has no children
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, imports) = files_full_audited(&metadata);
+
+    audits.audits.get_mut("third-party2").unwrap().clear();
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("mock-simple-missing-direct-leaf", metadata, store);
+}
+
+#[test]
+fn mock_simple_missing_leaves() {
+    // (Fail) Missing all leaf audits (but not the internal)
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, imports) = files_full_audited(&metadata);
+
+    audits.audits.get_mut("third-party2").unwrap().clear();
+    audits
+        .audits
+        .get_mut("transitive-third-party1")
+        .unwrap()
+        .clear();
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("mock-simple-missing-leaves", metadata, store);
+}
+
+#[test]
+fn mock_simple_weaker_transitive_req() {
+    // (Pass) A third-party dep with weaker requirements on a child dep
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = files_full_audited(&metadata);
+
+    let trans_audits = &mut audits.audits.get_mut("transitive-third-party1").unwrap();
+    trans_audits.clear();
+    trans_audits.push(full_audit(ver(DEFAULT_VER), "weak-reviewed"));
+
+    let direct_audits = &mut audits.audits.get_mut("third-party1").unwrap();
+    direct_audits.clear();
+    direct_audits.push(full_audit(ver(DEFAULT_VER), "reviewed"));
+
+    config.policy.insert(
+        "third-party1".to_string(),
+        dep_policy([("transitive-third-party1", ["weak-reviewed"])]),
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("mock-simple-weaker-transitive-req", metadata, store);
+}
+
+#[test]
+fn mock_simple_weaker_transitive_req_using_implies() {
+    // (Pass) A third-party dep with weaker requirements on a child dep
+    // but the child dep actually has *super* reqs, to check that implies works
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = files_full_audited(&metadata);
+
+    let trans_audits = &mut audits.audits.get_mut("transitive-third-party1").unwrap();
+    trans_audits.clear();
+    trans_audits.push(full_audit(ver(DEFAULT_VER), "strong-reviewed"));
+
+    let direct_audits = &mut audits.audits.get_mut("third-party1").unwrap();
+    direct_audits.clear();
+    direct_audits.push(full_audit(ver(DEFAULT_VER), "reviewed"));
+
+    config.policy.insert(
+        "third-party1".to_string(),
+        dep_policy([("transitive-third-party1", ["weak-reviewed"])]),
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!(
+        "mock-simple-weaker-transitive-req-using-implies",
+        metadata,
+        store
+    );
+}
+
+#[test]
+fn mock_simple_lower_version_review() {
+    // (Fail) A dep that has a review but for a lower version.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, imports) = files_full_audited(&metadata);
+
+    let direct_audits = &mut audits.audits.get_mut("third-party1").unwrap();
+    direct_audits.clear();
+    direct_audits.push(full_audit(ver(DEFAULT_VER - 1), DEFAULT_CRIT));
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("mock-simple-lower-version-review", metadata, store);
+}
+
+#[test]
+fn mock_simple_higher_version_review() {
+    // (Fail) A dep that has a review but for a higher version.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, imports) = files_full_audited(&metadata);
+
+    let direct_audits = &mut audits.audits.get_mut("third-party1").unwrap();
+    direct_audits.clear();
+    direct_audits.push(full_audit(ver(DEFAULT_VER + 1), DEFAULT_CRIT));
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("mock-simple-higher-version-review", metadata, store);
+}
+
+#[test]
+fn mock_simple_higher_and_lower_version_review() {
+    // (Fail) A dep that has a review but for both a higher and lower version.
+    // Once I mock out fake diffs it should prefer the lower one because the
+    // system will make application size grow quadratically.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, imports) = files_full_audited(&metadata);
+
+    let direct_audits = &mut audits.audits.get_mut("third-party1").unwrap();
+    direct_audits.clear();
+    direct_audits.push(full_audit(ver(DEFAULT_VER - 1), DEFAULT_CRIT));
+    direct_audits.push(full_audit(ver(DEFAULT_VER + 1), DEFAULT_CRIT));
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!(
+        "mock-simple-higher-and-lower-version-review",
+        metadata,
+        store
+    );
+}
+
+#[test]
+fn mock_simple_reviewed_too_weakly() {
+    // (Fail) A dep has a review but the criteria is too weak
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, imports) = files_full_audited(&metadata);
+
+    let trans_audits = &mut audits.audits.get_mut("transitive-third-party1").unwrap();
+    trans_audits.clear();
+    trans_audits.push(full_audit(ver(DEFAULT_VER), "weak-reviewed"));
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("mock-simple-reviewed-too-weakly", metadata, store);
+}
+
+#[test]
+fn mock_simple_delta_to_exemptions() {
+    // (Pass) A dep has a delta to an exemptions entry
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = files_full_audited(&metadata);
+
+    let direct_audits = &mut audits.audits.get_mut("third-party1").unwrap();
+    direct_audits.clear();
+    direct_audits.push(delta_audit(
+        ver(DEFAULT_VER - 5),
+        ver(DEFAULT_VER),
+        DEFAULT_CRIT,
+    ));
+
+    let direct_exemptions = &mut config.exemptions;
+    direct_exemptions.insert(
+        "third-party1".to_string(),
+        vec![exemptions(ver(DEFAULT_VER - 5), DEFAULT_CRIT)],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("mock-simple-delta-to-unaudited", metadata, store);
+}
+
+#[test]
+fn mock_simple_delta_to_exemptions_overshoot() {
+    // (Fail) A dep has a delta but it overshoots the exemptions entry.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = files_full_audited(&metadata);
+
+    let direct_audits = &mut audits.audits.get_mut("third-party1").unwrap();
+    direct_audits.clear();
+    direct_audits.push(delta_audit(
+        ver(DEFAULT_VER - 6),
+        ver(DEFAULT_VER),
+        DEFAULT_CRIT,
+    ));
+
+    let direct_exemptions = &mut config.exemptions;
+    direct_exemptions.insert(
+        "third-party1".to_string(),
+        vec![exemptions(ver(DEFAULT_VER - 5), DEFAULT_CRIT)],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("mock-simple-delta-to-unaudited-overshoot", metadata, store);
+}
+
+#[test]
+fn mock_simple_delta_to_exemptions_undershoot() {
+    // (Fail) A dep has a delta but it undershoots the exemptions entry.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = files_full_audited(&metadata);
+
+    let direct_audits = &mut audits.audits.get_mut("third-party1").unwrap();
+    direct_audits.clear();
+    direct_audits.push(delta_audit(
+        ver(DEFAULT_VER - 3),
+        ver(DEFAULT_VER),
+        DEFAULT_CRIT,
+    ));
+
+    let direct_exemptions = &mut config.exemptions;
+    direct_exemptions.insert(
+        "third-party1".to_string(),
+        vec![exemptions(ver(DEFAULT_VER - 5), DEFAULT_CRIT)],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("mock-simple-delta-to-unaudited-undershoot", metadata, store);
+}
+
+#[test]
+fn mock_simple_delta_to_full_audit() {
+    // (Pass) A dep has a delta to a fully audited entry
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, imports) = files_full_audited(&metadata);
+
+    let direct_audits = &mut audits.audits.get_mut("third-party1").unwrap();
+    direct_audits.clear();
+    direct_audits.push(delta_audit(
+        ver(DEFAULT_VER - 5),
+        ver(DEFAULT_VER),
+        DEFAULT_CRIT,
+    ));
+    direct_audits.push(full_audit(ver(DEFAULT_VER - 5), DEFAULT_CRIT));
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("mock-simple-delta-to-full-audit", metadata, store);
+}
+
+#[test]
+fn mock_simple_delta_to_full_audit_overshoot() {
+    // (Fail) A dep has a delta to a fully audited entry but overshoots
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, imports) = files_full_audited(&metadata);
+
+    let direct_audits = &mut audits.audits.get_mut("third-party1").unwrap();
+    direct_audits.clear();
+    direct_audits.push(delta_audit(
+        ver(DEFAULT_VER - 6),
+        ver(DEFAULT_VER),
+        DEFAULT_CRIT,
+    ));
+    direct_audits.push(full_audit(ver(DEFAULT_VER - 5), DEFAULT_CRIT));
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("mock-simple-delta-to-full-audit-overshoot", metadata, store);
+}
+
+#[test]
+fn mock_simple_delta_to_full_audit_undershoot() {
+    // (Fail) A dep has a delta to a fully audited entry but undershoots
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, imports) = files_full_audited(&metadata);
+
+    let direct_audits = &mut audits.audits.get_mut("third-party1").unwrap();
+    direct_audits.clear();
+    direct_audits.push(delta_audit(
+        ver(DEFAULT_VER - 3),
+        ver(DEFAULT_VER),
+        DEFAULT_CRIT,
+    ));
+    direct_audits.push(full_audit(ver(DEFAULT_VER - 5), DEFAULT_CRIT));
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!(
+        "mock-simple-delta-to-full-audit-undershoot",
+        metadata,
+        store
+    );
+}
+
+#[test]
+fn mock_simple_reverse_delta_to_full_audit() {
+    // (Pass) A dep has a *reverse* delta to a fully audited entry
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, imports) = files_full_audited(&metadata);
+
+    let direct_audits = &mut audits.audits.get_mut("third-party1").unwrap();
+    direct_audits.clear();
+    direct_audits.push(delta_audit(
+        ver(DEFAULT_VER + 5),
+        ver(DEFAULT_VER),
+        DEFAULT_CRIT,
+    ));
+    direct_audits.push(full_audit(ver(DEFAULT_VER + 5), DEFAULT_CRIT));
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("mock-simple-reverse-delta-to-full-audit", metadata, store);
+}
+
+#[test]
+fn mock_simple_reverse_delta_to_exemptions() {
+    // (Pass) A dep has a *reverse* delta to an exemptions entry
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = files_full_audited(&metadata);
+
+    let direct_audits = &mut audits.audits.get_mut("third-party1").unwrap();
+    direct_audits.clear();
+    direct_audits.push(delta_audit(
+        ver(DEFAULT_VER + 5),
+        ver(DEFAULT_VER),
+        DEFAULT_CRIT,
+    ));
+
+    let direct_exemptions = &mut config.exemptions;
+    direct_exemptions.insert(
+        "third-party1".to_string(),
+        vec![exemptions(ver(DEFAULT_VER + 5), DEFAULT_CRIT)],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("mock-simple-reverse-delta-to-unaudited", metadata, store);
+}
+
+#[test]
+fn mock_simple_wrongly_reversed_delta_to_exemptions() {
+    // (Fail) A dep has a *reverse* delta to an exemptions entry but they needed a normal one
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = files_full_audited(&metadata);
+
+    let direct_audits = &mut audits.audits.get_mut("third-party1").unwrap();
+    direct_audits.clear();
+    direct_audits.push(delta_audit(
+        ver(DEFAULT_VER),
+        ver(DEFAULT_VER - 5),
+        DEFAULT_CRIT,
+    ));
+
+    let direct_exemptions = &mut config.exemptions;
+    direct_exemptions.insert(
+        "third-party1".to_string(),
+        vec![exemptions(ver(DEFAULT_VER - 5), DEFAULT_CRIT)],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!(
+        "mock-simple-wrongly-reversed-delta-to-unaudited",
+        metadata,
+        store
+    );
+}
+
+#[test]
+fn mock_simple_wrongly_reversed_delta_to_full_audit() {
+    // (Fail) A dep has a *reverse* delta to a fully audited entry but they needed a normal one
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, imports) = files_full_audited(&metadata);
+
+    let direct_audits = &mut audits.audits.get_mut("third-party1").unwrap();
+    direct_audits.clear();
+    direct_audits.push(delta_audit(
+        ver(DEFAULT_VER),
+        ver(DEFAULT_VER - 5),
+        DEFAULT_CRIT,
+    ));
+    direct_audits.push(full_audit(ver(DEFAULT_VER - 5), DEFAULT_CRIT));
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!(
+        "mock-simple-wrongly-reversed-delta-to-full-audit",
+        metadata,
+        store
+    );
+}
+
+#[test]
+fn mock_simple_needed_reversed_delta_to_exemptions() {
+    // (Fail) A dep has a delta to an exemptions entry but they needed a reversed one
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = files_full_audited(&metadata);
+
+    let direct_audits = &mut audits.audits.get_mut("third-party1").unwrap();
+    direct_audits.clear();
+    direct_audits.push(delta_audit(
+        ver(DEFAULT_VER),
+        ver(DEFAULT_VER + 5),
+        DEFAULT_CRIT,
+    ));
+
+    let direct_exemptions = &mut config.exemptions;
+    direct_exemptions.insert(
+        "third-party1".to_string(),
+        vec![exemptions(ver(DEFAULT_VER + 5), DEFAULT_CRIT)],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!(
+        "mock-simple-needed-reversed-delta-to-unaudited",
+        metadata,
+        store
+    );
+}
+
+#[test]
+fn mock_simple_delta_to_exemptions_too_weak() {
+    // (Fail) A dep has a delta to an exemptions entry but it's too weak
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = files_full_audited(&metadata);
+
+    let direct_audits = &mut audits.audits.get_mut("third-party1").unwrap();
+    direct_audits.clear();
+    direct_audits.push(delta_audit(
+        ver(DEFAULT_VER - 5),
+        ver(DEFAULT_VER),
+        "weak-reviewed",
+    ));
+
+    let direct_exemptions = &mut config.exemptions;
+    direct_exemptions.insert(
+        "third-party1".to_string(),
+        vec![exemptions(ver(DEFAULT_VER - 5), DEFAULT_CRIT)],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("mock-simple-delta-to-unaudited-too-weak", metadata, store);
+}
+
+#[test]
+fn mock_simple_delta_to_full_audit_too_weak() {
+    // (Fail) A dep has a delta to a fully audited entry but it's too weak
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, imports) = files_full_audited(&metadata);
+
+    let direct_audits = &mut audits.audits.get_mut("third-party1").unwrap();
+    direct_audits.clear();
+    direct_audits.push(delta_audit(
+        ver(DEFAULT_VER - 5),
+        ver(DEFAULT_VER),
+        "weak-reviewed",
+    ));
+    direct_audits.push(full_audit(ver(DEFAULT_VER - 5), DEFAULT_CRIT));
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("mock-simple-delta-to-full-audit-too-weak", metadata, store);
+}
+
+#[test]
+fn mock_simple_delta_to_too_weak_full_audit() {
+    // (Fail) A dep has a delta to a fully audited entry but it's too weak
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, imports) = files_full_audited(&metadata);
+
+    let direct_audits = &mut audits.audits.get_mut("third-party1").unwrap();
+    direct_audits.clear();
+    direct_audits.push(delta_audit(
+        ver(DEFAULT_VER - 5),
+        ver(DEFAULT_VER),
+        DEFAULT_CRIT,
+    ));
+    direct_audits.push(full_audit(ver(DEFAULT_VER - 5), "weak-reviewed"));
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("mock-simple-delta-to-too-weak-full-audit", metadata, store);
+}
+
+#[test]
+fn mock_complex_inited() {
+    // (Fail) Should look the same as a fresh 'vet init' but with all 'exemptions' entries deleted.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::complex();
+    let metadata = mock.metadata();
+    let (config, audits, imports) = files_inited(&metadata);
+
+    let store = Store::mock(config, audits, imports);
+    assert_report_snapshot!("mock-complex-inited", metadata, store);
+}
+
+#[test]
+fn mock_complex_no_exemptions() {
+    // (Fail) Should look the same as a fresh 'vet init' but with all 'exemptions' entries deleted.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::complex();
+    let metadata = mock.metadata();
+    let (config, audits, imports) = files_no_exemptions(&metadata);
+
+    let store = Store::mock(config, audits, imports);
+    assert_report_snapshot!("mock-complex-no-unaudited", metadata, store);
+}
+
+#[test]
+fn mock_complex_full_audited() {
+    // (Pass) All entries have direct full audits.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::complex();
+    let metadata = mock.metadata();
+    let (config, audits, imports) = files_full_audited(&metadata);
+
+    let store = Store::mock(config, audits, imports);
+    assert_report_snapshot!("mock-complex-full-audited", metadata, store);
+}
+
+#[test]
+fn builtin_complex_inited() {
+    // (Fail) Should look the same as a fresh 'vet init' but with all 'exemptions' entries deleted.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::complex();
+    let metadata = mock.metadata();
+    let (config, audits, imports) = builtin_files_inited(&metadata);
+
+    let store = Store::mock(config, audits, imports);
+    assert_report_snapshot!("builtin-complex-inited", metadata, store);
+}
+
+#[test]
+fn builtin_complex_no_exemptions() {
+    // (Fail) Should look the same as a fresh 'vet init' but with all 'exemptions' entries deleted.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::complex();
+    let metadata = mock.metadata();
+    let (config, audits, imports) = builtin_files_no_exemptions(&metadata);
+
+    let store = Store::mock(config, audits, imports);
+    assert_report_snapshot!("builtin-complex-no-unaudited", metadata, store);
+}
+
+#[test]
+fn builtin_complex_full_audited() {
+    // (Pass) All entries have direct full audits.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::complex();
+    let metadata = mock.metadata();
+    let (config, audits, imports) = builtin_files_full_audited(&metadata);
+
+    let store = Store::mock(config, audits, imports);
+    assert_report_snapshot!("builtin-complex-full-audited", metadata, store);
+}
+
+#[test]
+fn builtin_complex_minimal_audited() {
+    // (Pass) All entries have direct full audits.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::complex();
+    let metadata = mock.metadata();
+    let (config, audits, imports) = builtin_files_minimal_audited(&metadata);
+
+    let store = Store::mock(config, audits, imports);
+    assert_report_snapshot!("builtin-complex-minimal-audited", metadata, store);
+}
+
+#[test]
+fn mock_complex_missing_core5() {
+    // (Fail) Missing an audit for the v5 version of third-core
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::complex();
+    let metadata = mock.metadata();
+    let (config, mut audits, imports) = files_full_audited(&metadata);
+
+    audits.audits.insert(
+        "third-core".to_string(),
+        vec![full_audit(ver(DEFAULT_VER), "reviewed")],
+    );
+
+    let store = Store::mock(config, audits, imports);
+    assert_report_snapshot!("mock-complex-missing-core5", metadata, store);
+}
+
+#[test]
+fn mock_complex_missing_core10() {
+    // (Fail) Missing an audit for the v10 version of third-core
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::complex();
+    let metadata = mock.metadata();
+    let (config, mut audits, imports) = files_full_audited(&metadata);
+
+    audits.audits.insert(
+        "third-core".to_string(),
+        vec![full_audit(ver(5), "reviewed")],
+    );
+
+    let store = Store::mock(config, audits, imports);
+    assert_report_snapshot!("mock-complex-missing-core10", metadata, store);
+}
+
+#[test]
+fn mock_complex_core10_too_weak() {
+    // (Fail) Criteria for core10 is too weak
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::complex();
+    let metadata = mock.metadata();
+    let (config, mut audits, imports) = files_full_audited(&metadata);
+
+    audits.audits.insert(
+        "third-core".to_string(),
+        vec![
+            full_audit(ver(DEFAULT_VER), "weak-reviewed"),
+            full_audit(ver(5), "reviewed"),
+        ],
+    );
+
+    let store = Store::mock(config, audits, imports);
+    assert_report_snapshot!("mock-complex-core10-too-weak", metadata, store);
+}
+
+#[test]
+fn mock_complex_core10_partially_too_weak() {
+    // (Fail) Criteria for core10 is too weak for thirdA but not thirdA and thirdAB (full)
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::complex();
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = files_full_audited(&metadata);
+
+    audits.audits.insert(
+        "third-core".to_string(),
+        vec![
+            full_audit(ver(DEFAULT_VER), "weak-reviewed"),
+            full_audit(ver(5), "reviewed"),
+        ],
+    );
+
+    let audit_with_weaker_req = full_audit(ver(DEFAULT_VER), "reviewed");
+    audits
+        .audits
+        .insert("thirdA".to_string(), vec![audit_with_weaker_req.clone()]);
+    audits
+        .audits
+        .insert("thirdAB".to_string(), vec![audit_with_weaker_req]);
+
+    config.policy.insert(
+        "thirdA".to_string(),
+        dep_policy([("third-core", ["weak-reviewed"])]),
+    );
+    config.policy.insert(
+        "thirdAB".to_string(),
+        dep_policy([("third-core", ["weak-reviewed"])]),
+    );
+
+    let store = Store::mock(config, audits, imports);
+    assert_report_snapshot!("mock-complex-core10-partially-too-weak", metadata, store);
+}
+
+#[test]
+fn mock_complex_core10_partially_too_weak_via_weak_delta() {
+    // (Fail) Criteria for core10 is too weak for thirdA but not thirdA and thirdAB (weak delta)
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::complex();
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = files_full_audited(&metadata);
+
+    audits.audits.insert(
+        "third-core".to_string(),
+        vec![
+            delta_audit(ver(5), ver(DEFAULT_VER), "weak-reviewed"),
+            full_audit(ver(5), "reviewed"),
+        ],
+    );
+
+    let audit_with_weaker_req = full_audit(ver(DEFAULT_VER), "reviewed");
+    audits
+        .audits
+        .insert("thirdA".to_string(), vec![audit_with_weaker_req.clone()]);
+    audits
+        .audits
+        .insert("thirdAB".to_string(), vec![audit_with_weaker_req]);
+
+    config.policy.insert(
+        "thirdA".to_string(),
+        dep_policy([("third-core", ["weak-reviewed"])]),
+    );
+    config.policy.insert(
+        "thirdAB".to_string(),
+        dep_policy([("third-core", ["weak-reviewed"])]),
+    );
+
+    let store = Store::mock(config, audits, imports);
+    assert_report_snapshot!(
+        "mock-complex-core10-partially-too-weak-via-weak-delta",
+        metadata,
+        store
+    );
+}
+
+#[test]
+fn mock_complex_core10_partially_too_weak_via_strong_delta() {
+    // (Fail) Criteria for core10 is too weak for thirdA but not thirdA and thirdAB
+    // because there's a strong delta from 5->10 but 0->5 is still weak!
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::complex();
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = files_full_audited(&metadata);
+
+    audits.audits.insert(
+        "third-core".to_string(),
+        vec![
+            delta_audit(ver(5), ver(DEFAULT_VER), "reviewed"),
+            full_audit(ver(5), "weak-reviewed"),
+        ],
+    );
+
+    let audit_with_weaker_req = full_audit(ver(DEFAULT_VER), "reviewed");
+    audits
+        .audits
+        .insert("thirdA".to_string(), vec![audit_with_weaker_req.clone()]);
+    audits
+        .audits
+        .insert("thirdAB".to_string(), vec![audit_with_weaker_req]);
+
+    config.policy.insert(
+        "firstA".to_string(),
+        dep_policy([("third-core", ["weak-reviewed"])]),
+    );
+    config.policy.insert(
+        "thirdA".to_string(),
+        dep_policy([("third-core", ["weak-reviewed"])]),
+    );
+    config.policy.insert(
+        "thirdAB".to_string(),
+        dep_policy([("third-core", ["weak-reviewed"])]),
+    );
+
+    let store = Store::mock(config, audits, imports);
+    assert_report_snapshot!(
+        "mock-complex-core10-partially-too-weak-via-strong-delta",
+        metadata,
+        store
+    );
+}
+
+#[test]
+fn mock_simple_policy_root_too_strong() {
+    // (Fail) Root policy is too strong
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+    let metadata = mock.metadata();
+    let (mut config, audits, imports) = files_full_audited(&metadata);
+
+    config
+        .policy
+        .insert("root-package".to_string(), self_policy(["strong-reviewed"]));
+
+    let store = Store::mock(config, audits, imports);
+    assert_report_snapshot!("simple-policy-root-too-strong", metadata, store);
+}
+
+#[test]
+fn mock_simple_policy_root_weaker() {
+    // (Pass) Root policy weaker than necessary
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+    let metadata = mock.metadata();
+    let (mut config, audits, imports) = files_full_audited(&metadata);
+
+    config
+        .policy
+        .insert("root-package".to_string(), self_policy(["weak-reviewed"]));
+
+    let store = Store::mock(config, audits, imports);
+    assert_report_snapshot!("simple-policy-root-weaker", metadata, store);
+}
+
+#[test]
+fn mock_simple_policy_first_too_strong() {
+    // (Fail) First-party policy is too strong
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+    let metadata = mock.metadata();
+    let (mut config, audits, imports) = files_full_audited(&metadata);
+
+    config
+        .policy
+        .insert("first-party".to_string(), self_policy(["strong-reviewed"]));
+
+    let store = Store::mock(config, audits, imports);
+    assert_report_snapshot!("simple-policy-first-too-strong", metadata, store);
+}
+
+#[test]
+fn mock_simple_policy_first_weaker() {
+    // (Pass) First-party policy weaker than necessary
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+    let metadata = mock.metadata();
+    let (mut config, audits, imports) = files_full_audited(&metadata);
+
+    config
+        .policy
+        .insert("first-party".to_string(), self_policy(["weak-reviewed"]));
+
+    let store = Store::mock(config, audits, imports);
+    assert_report_snapshot!("simple-policy-first-weaker", metadata, store);
+}
+
+#[test]
+fn mock_simple_policy_root_dep_weaker() {
+    // (Pass) root->first-party policy weaker than necessary
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+    let metadata = mock.metadata();
+    let (mut config, audits, imports) = files_full_audited(&metadata);
+
+    config.policy.insert(
+        "root-package".to_string(),
+        dep_policy([("first-party", ["weak-reviewed"])]),
+    );
+
+    let store = Store::mock(config, audits, imports);
+    assert_report_snapshot!("simple-policy-root-dep-weaker", metadata, store);
+}
+
+#[test]
+fn mock_simple_policy_root_dep_too_strong() {
+    // (Pass) root->first-party policy stronger than necessary
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+    let metadata = mock.metadata();
+    let (mut config, audits, imports) = files_full_audited(&metadata);
+
+    config.policy.insert(
+        "root-package".to_string(),
+        dep_policy([("first-party", ["strong-reviewed"])]),
+    );
+
+    let store = Store::mock(config, audits, imports);
+    assert_report_snapshot!("simple-policy-root-dep-too-strong", metadata, store);
+}
+
+#[test]
+fn mock_simple_policy_first_dep_weaker() {
+    // (Pass) first-party->third-party policy weaker than necessary
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+    let metadata = mock.metadata();
+    let (mut config, audits, imports) = files_full_audited(&metadata);
+
+    config.policy.insert(
+        "first-party".to_string(),
+        dep_policy([("third-party1", ["weak-reviewed"])]),
+    );
+
+    let store = Store::mock(config, audits, imports);
+    assert_report_snapshot!("simple-policy-first-dep-weaker", metadata, store);
+}
+
+#[test]
+fn mock_simple_policy_first_dep_too_strong() {
+    // (Pass) first-party->third-party policy too strong
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+    let metadata = mock.metadata();
+    let (mut config, audits, imports) = files_full_audited(&metadata);
+
+    config.policy.insert(
+        "first-party".to_string(),
+        dep_policy([("third-party1", ["strong-reviewed"])]),
+    );
+
+    let store = Store::mock(config, audits, imports);
+    assert_report_snapshot!("simple-policy-first-dep-too-strong", metadata, store);
+}
+
+#[test]
+fn mock_simple_policy_first_dep_stronger() {
+    // (Pass) first-party->third-party policy stronger but satisfied
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = files_full_audited(&metadata);
+
+    config.policy.insert(
+        "first-party".to_string(),
+        dep_policy([("third-party2", ["strong-reviewed"])]),
+    );
+
+    audits.audits.insert(
+        "third-party2".to_string(),
+        vec![full_audit(ver(DEFAULT_VER), "strong-reviewed")],
+    );
+
+    let store = Store::mock(config, audits, imports);
+    assert_report_snapshot!("simple-policy-first-dep-stronger", metadata, store);
+}
+
+#[test]
+fn mock_simple_policy_first_dep_weaker_needed() {
+    // (Pass) first-party->third-party policy weaker out of necessity
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = files_full_audited(&metadata);
+
+    config.policy.insert(
+        "first-party".to_string(),
+        dep_policy([("third-party1", ["weak-reviewed"])]),
+    );
+
+    audits.audits.insert(
+        "third-party1".to_string(),
+        vec![full_audit(ver(DEFAULT_VER), "weak-reviewed")],
+    );
+
+    let store = Store::mock(config, audits, imports);
+    assert_report_snapshot!("simple-policy-first-dep-weaker-needed", metadata, store);
+}
+
+#[test]
+fn mock_simple_policy_first_dep_extra() {
+    // (Pass) first-party->third-party policy has extra satisfied criteria
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = files_full_audited(&metadata);
+
+    config.policy.insert(
+        "first-party".to_string(),
+        dep_policy([("third-party2", ["reviewed", "fuzzed"])]),
+    );
+
+    audits.audits.insert(
+        "third-party2".to_string(),
+        vec![
+            full_audit(ver(DEFAULT_VER), "reviewed"),
+            full_audit(ver(DEFAULT_VER), "fuzzed"),
+        ],
+    );
+
+    let store = Store::mock(config, audits, imports);
+    assert_report_snapshot!("simple-policy-first-dep-extra", metadata, store);
+}
+
+#[test]
+fn mock_simple_policy_first_dep_extra_missing() {
+    // (Fail) first-party->third-party policy has extra unsatisfied criteria
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = files_full_audited(&metadata);
+
+    config.policy.insert(
+        "first-party".to_string(),
+        dep_policy([("third-party2", ["reviewed", "fuzzed"])]),
+    );
+
+    audits.audits.insert(
+        "third-party2".to_string(),
+        vec![full_audit(ver(DEFAULT_VER), "reviewed")],
+    );
+
+    let store = Store::mock(config, audits, imports);
+    assert_report_snapshot!("simple-policy-first-dep-extra-missing", metadata, store);
+}
+
+#[test]
+fn mock_simple_policy_first_extra_partially_missing() {
+    // (Fail) first-party policy has extra unsatisfied criteria
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = files_full_audited(&metadata);
+
+    config.policy.insert(
+        "first-party".to_string(),
+        self_policy(["reviewed", "fuzzed"]),
+    );
+
+    audits.audits.insert(
+        "third-party2".to_string(),
+        vec![
+            full_audit(ver(DEFAULT_VER), "reviewed"),
+            full_audit(ver(DEFAULT_VER), "fuzzed"),
+        ],
+    );
+
+    let store = Store::mock(config, audits, imports);
+    assert_report_snapshot!(
+        "simple-policy-first-extra-partially-missing",
+        metadata,
+        store
+    );
+}
+
+#[test]
+fn mock_simple_first_policy_redundant() {
+    // (Pass) first-party policy has redundant implied things
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+    let metadata = mock.metadata();
+    let (mut config, audits, imports) = files_full_audited(&metadata);
+
+    config.policy.insert(
+        "first-party".to_string(),
+        self_policy(["reviewed", "weak-reviewed"]),
+    );
+
+    let store = Store::mock(config, audits, imports);
+    assert_report_snapshot!("simple-policy-first-policy-redundant", metadata, store);
+}
+
+#[test]
+fn builtin_simple_deps_inited() {
+    // (Pass) Should look the same as a fresh 'vet init'.
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple_deps();
+
+    let metadata = mock.metadata();
+    let (config, audits, imports) = builtin_files_inited(&metadata);
+
+    let store = Store::mock(config, audits, imports);
+    assert_report_snapshot!("builtin-simple-deps-init", metadata, store);
+}
+
+#[test]
+fn builtin_simple_deps_no_exemptions() {
+    // (Fail) Should look the same as a fresh 'vet init' but with all 'exemptions' entries deleted.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple_deps();
+
+    let metadata = mock.metadata();
+    let (config, audits, imports) = builtin_files_no_exemptions(&metadata);
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin-simple-deps-no-unaudited", metadata, store);
+}
+
+#[test]
+fn builtin_simple_deps_full_audited() {
+    // (Pass) All entries have direct full audits.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple_deps();
+
+    let metadata = mock.metadata();
+    let (config, audits, imports) = builtin_files_full_audited(&metadata);
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin-simple-deps-full-audited", metadata, store);
+}
+
+#[test]
+fn builtin_simple_deps_minimal_audited() {
+    // (Pass) All entries have direct full audits.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple_deps();
+
+    let metadata = mock.metadata();
+    let (config, audits, imports) = builtin_files_minimal_audited(&metadata);
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin-simple-deps-minimal-audited", metadata, store);
+}
+
+#[test]
+fn builtin_no_deps() {
+    // (Pass) No actual deps
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::new(vec![MockPackage {
+        name: "root-package",
+        is_workspace: true,
+        is_first_party: true,
+        deps: vec![],
+        ..Default::default()
+    }]);
+
+    let metadata = mock.metadata();
+    let (config, audits, imports) = builtin_files_full_audited(&metadata);
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin-no-deps", metadata, store);
+}
+
+#[test]
+fn builtin_only_first_deps() {
+    // (Pass) No actual deps
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::new(vec![
+        MockPackage {
+            name: "root-package",
+            is_workspace: true,
+            is_first_party: true,
+            deps: vec![dep("first-party")],
+            ..Default::default()
+        },
+        MockPackage {
+            name: "first-party",
+            is_first_party: true,
+            deps: vec![],
+            ..Default::default()
+        },
+    ]);
+
+    let metadata = mock.metadata();
+    let (config, audits, imports) = builtin_files_full_audited(&metadata);
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin-only-first-deps", metadata, store);
+}
+
+#[test]
+fn builtin_cycle_inited() {
+    // (Pass) Should look the same as a fresh 'vet init'.
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::cycle();
+
+    let metadata = mock.metadata();
+    let (config, audits, imports) = builtin_files_inited(&metadata);
+
+    let store = Store::mock(config, audits, imports);
+    assert_report_snapshot!("builtin-cycle-inited", metadata, store);
+}
+
+#[test]
+fn builtin_cycle_exemptions() {
+    // (Fail) Should look the same as a fresh 'vet init' but with all 'exemptions' entries deleted.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::cycle();
+
+    let metadata = mock.metadata();
+    let (config, audits, imports) = builtin_files_no_exemptions(&metadata);
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin-cycle-unaudited", metadata, store);
+}
+
+#[test]
+fn builtin_cycle_full_audited() {
+    // (Pass) All entries have direct full audits.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::cycle();
+
+    let metadata = mock.metadata();
+    let (config, audits, imports) = builtin_files_full_audited(&metadata);
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin-cycle-full-audited", metadata, store);
+}
+
+#[test]
+fn builtin_cycle_minimal_audited() {
+    // (Pass) All entries have direct full audits.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::cycle();
+
+    let metadata = mock.metadata();
+    let (config, audits, imports) = builtin_files_minimal_audited(&metadata);
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin-cycle-minimal-audited", metadata, store);
+}
+
+#[test]
+fn builtin_dev_detection() {
+    // (Pass) Check that we properly identify things that are or aren't only dev-deps,
+    // even when they're indirect or used in both contexts.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::dev_detection();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, imports) = builtin_files_no_exemptions(&metadata);
+    audits.audits.insert(
+        "normal".to_string(),
+        vec![full_audit(ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+    );
+    audits.audits.insert(
+        "both".to_string(),
+        vec![full_audit(ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+    );
+    audits.audits.insert(
+        "simple-dev".to_string(),
+        vec![full_audit(ver(DEFAULT_VER), SAFE_TO_RUN)],
+    );
+    audits.audits.insert(
+        "simple-dev-indirect".to_string(),
+        vec![full_audit(ver(DEFAULT_VER), SAFE_TO_RUN)],
+    );
+    audits.audits.insert(
+        "dev-cycle-direct".to_string(),
+        vec![full_audit(ver(DEFAULT_VER), SAFE_TO_RUN)],
+    );
+    audits.audits.insert(
+        "dev-cycle-indirect".to_string(),
+        vec![full_audit(ver(DEFAULT_VER), SAFE_TO_RUN)],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin-dev-detection", metadata, store);
+}
+
+#[test]
+fn builtin_dev_detection_empty() {
+    // (Fail) same as above but without any audits to confirm expectations
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::dev_detection();
+
+    let metadata = mock.metadata();
+    let (config, audits, imports) = builtin_files_no_exemptions(&metadata);
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin-dev-detection-empty", metadata, store);
+}
+
+#[test]
+fn builtin_dev_detection_empty_deeper() {
+    // (Fail) same as above but deeper
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::dev_detection();
+
+    let metadata = mock.metadata();
+    let (config, audits, imports) = builtin_files_no_exemptions(&metadata);
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin-dev-detection-empty-deeper", metadata, store);
+}
+
+#[test]
+fn builtin_simple_exemptions_extra() {
+    // (Pass) there's an extra unused exemptions entry, but the other is needed
+    // (This test could warn if we try to detect "useless exemptions" eagerly)
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = builtin_files_full_audited(&metadata);
+
+    audits.audits.insert("third-party1".to_string(), vec![]);
+
+    config.exemptions.insert(
+        "third-party1".to_string(),
+        vec![
+            exemptions(ver(5), SAFE_TO_DEPLOY),
+            exemptions(ver(DEFAULT_VER), SAFE_TO_DEPLOY),
+        ],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin-simple-unaudited-extra", metadata, store);
+}
+
+#[test]
+fn builtin_simple_exemptions_not_a_real_dep() {
+    // (Pass) there's an exemptions entry for a package that isn't in our tree at all.
+    // (This test could warn if we try to detect "useless exemptions" eagerly)
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, audits, imports) = builtin_files_full_audited(&metadata);
+
+    config.exemptions.insert(
+        "fake-dep".to_string(),
+        vec![exemptions(ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin-simple-not-a-real-dep", metadata, store);
+}
+
+#[test]
+fn builtin_simple_deps_exemptions_overbroad() {
+    // (Pass) the exemptions entry is needed but it's overbroad
+    // (This test could warn if we try to detect "useless exemptions" eagerly)
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple_deps();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = builtin_files_full_audited(&metadata);
+
+    audits.audits.insert("dev".to_string(), vec![]);
+
+    config.exemptions.insert(
+        "dev".to_string(),
+        vec![exemptions(ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin-simple-unaudited-overbroad", metadata, store);
+}
+
+#[test]
+fn builtin_complex_exemptions_twins() {
+    // (Pass) two versions of a crate exist and both are exemptions and they're needed
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::complex();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = builtin_files_full_audited(&metadata);
+
+    audits.audits.insert("third-core".to_string(), vec![]);
+
+    config.exemptions.insert(
+        "third-core".to_string(),
+        vec![
+            exemptions(ver(DEFAULT_VER), SAFE_TO_DEPLOY),
+            exemptions(ver(5), SAFE_TO_DEPLOY),
+        ],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin-simple-unaudited-twins", metadata, store);
+}
+
+#[test]
+fn builtin_complex_exemptions_partial_twins() {
+    // (Pass) two versions of a crate exist and one is exemptions and one is audited
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::complex();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = builtin_files_full_audited(&metadata);
+
+    audits.audits.insert(
+        "third-core".to_string(),
+        vec![full_audit(ver(5), SAFE_TO_DEPLOY)],
+    );
+
+    config.exemptions.insert(
+        "third-core".to_string(),
+        vec![exemptions(ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin-simple-unaudited-partial-twins", metadata, store);
+}
+
+#[test]
+fn builtin_simple_exemptions_in_delta() {
+    // (Pass) An audited entry overlaps a delta and isn't needed
+    // (This test could warn if we try to detect "useless exemptions" eagerly)
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = builtin_files_full_audited(&metadata);
+
+    audits.audits.insert(
+        "third-party1".to_string(),
+        vec![
+            full_audit(ver(3), SAFE_TO_DEPLOY),
+            delta_audit(ver(3), ver(5), SAFE_TO_DEPLOY),
+            delta_audit(ver(5), ver(DEFAULT_VER), SAFE_TO_DEPLOY),
+        ],
+    );
+
+    config.exemptions.insert(
+        "third-party1".to_string(),
+        vec![exemptions(ver(5), SAFE_TO_DEPLOY)],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin-simple-unaudited-in-delta", metadata, store);
+}
+
+#[test]
+fn builtin_simple_exemptions_in_full() {
+    // (Pass) An audited entry overlaps a full audit and isn't needed
+    // (This test could warn if we try to detect "useless exemptions" eagerly)
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = builtin_files_full_audited(&metadata);
+
+    audits.audits.insert(
+        "third-party1".to_string(),
+        vec![
+            full_audit(ver(3), SAFE_TO_DEPLOY),
+            delta_audit(ver(3), ver(5), SAFE_TO_DEPLOY),
+            delta_audit(ver(5), ver(DEFAULT_VER), SAFE_TO_DEPLOY),
+        ],
+    );
+
+    config.exemptions.insert(
+        "third-party1".to_string(),
+        vec![exemptions(ver(3), SAFE_TO_DEPLOY)],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin-simple-unaudited-in-full", metadata, store);
+}
+
+#[test]
+fn builtin_simple_exemptions_in_direct_full() {
+    // (Pass) An audited entry overlaps a full audit which is the cur version and isn't needed
+    // (This test used to warn when we tried to detect "useless exemptions" eagerly)
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = builtin_files_full_audited(&metadata);
+
+    audits.audits.insert(
+        "third-party1".to_string(),
+        vec![full_audit(ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+    );
+
+    config.exemptions.insert(
+        "third-party1".to_string(),
+        vec![exemptions(ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin-simple-unaudited-in-direct-full", metadata, store);
+}
+
+#[test]
+fn builtin_simple_exemptions_nested_weaker_req() {
+    // (Pass) A dep that has weaker requirements on its dep
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = builtin_files_full_audited(&metadata);
+
+    audits.audits.insert(
+        "third-party1".to_string(),
+        vec![
+            delta_audit(ver(3), ver(6), SAFE_TO_DEPLOY),
+            delta_audit(ver(6), ver(DEFAULT_VER), SAFE_TO_DEPLOY),
+        ],
+    );
+    audits.audits.insert(
+        "transitive-third-party1".to_string(),
+        vec![
+            delta_audit(ver(4), ver(8), SAFE_TO_RUN),
+            delta_audit(ver(8), ver(DEFAULT_VER), SAFE_TO_RUN),
+        ],
+    );
+
+    config.policy.insert(
+        "third-party1".to_string(),
+        dep_policy([("transitive-third-party1", [SAFE_TO_RUN])]),
+    );
+
+    config.exemptions.insert(
+        "third-party1".to_string(),
+        vec![exemptions(ver(3), SAFE_TO_DEPLOY)],
+    );
+
+    config.exemptions.insert(
+        "transitive-third-party1".to_string(),
+        vec![exemptions(ver(4), SAFE_TO_RUN)],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!(
+        "builtin-simple-unaudited-nested-weaker-req",
+        metadata,
+        store
+    );
+}
+
+#[test]
+fn builtin_simple_exemptions_nested_stronger_req() {
+    // (Pass) A dep that has stronger requirements on its dep
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = builtin_files_full_audited(&metadata);
+
+    config.policy.insert(
+        "first-party".to_string(),
+        dep_policy([("third-party1", [SAFE_TO_RUN])]),
+    );
+    config.policy.insert(
+        "third-party1".to_string(),
+        dep_policy([("transitive-third-party1", [SAFE_TO_DEPLOY])]),
+    );
+
+    audits.audits.insert(
+        "third-party1".to_string(),
+        vec![
+            delta_audit(ver(3), ver(6), SAFE_TO_RUN),
+            delta_audit(ver(6), ver(DEFAULT_VER), SAFE_TO_RUN),
+        ],
+    );
+    audits.audits.insert(
+        "transitive-third-party1".to_string(),
+        vec![
+            delta_audit(ver(4), ver(8), SAFE_TO_DEPLOY),
+            delta_audit(ver(8), ver(DEFAULT_VER), SAFE_TO_DEPLOY),
+        ],
+    );
+
+    config.exemptions.insert(
+        "third-party1".to_string(),
+        vec![exemptions(ver(3), SAFE_TO_RUN)],
+    );
+
+    config.exemptions.insert(
+        "transitive-third-party1".to_string(),
+        vec![exemptions(ver(4), SAFE_TO_DEPLOY)],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!(
+        "builtin-simple-unaudited-nested-stronger-req",
+        metadata,
+        store
+    );
+}
+
+#[test]
+fn builtin_simple_deps_exemptions_adds_uneeded_criteria() {
+    // (Pass) An audited entry overlaps a full audit which is the cur version and isn't needed
+    // (This test could warn if we try to detect "useless exemptions" eagerly)
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple_deps();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = builtin_files_full_audited(&metadata);
+
+    audits.audits.insert(
+        "dev".to_string(),
+        vec![
+            full_audit(ver(5), SAFE_TO_RUN),
+            delta_audit(ver(5), ver(DEFAULT_VER), SAFE_TO_DEPLOY),
+        ],
+    );
+
+    config
+        .exemptions
+        .insert("dev".to_string(), vec![exemptions(ver(5), SAFE_TO_DEPLOY)]);
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!(
+        "builtin-simple-deps-unaudited-adds-uneeded-criteria",
+        metadata,
+        store
+    );
+}
+
+#[test]
+fn builtin_dev_detection_exemptions_adds_uneeded_criteria_indirect() {
+    // (Pass) An audited entry overlaps a full audit which is the cur version and isn't needed
+    // (This test could warn if we try to detect "useless exemptions" eagerly)
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::dev_detection();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = builtin_files_minimal_audited(&metadata);
+
+    audits.audits.insert(
+        "simple-dev-indirect".to_string(),
+        vec![
+            full_audit(ver(5), SAFE_TO_RUN),
+            delta_audit(ver(5), ver(DEFAULT_VER), SAFE_TO_DEPLOY),
+        ],
+    );
+
+    config.exemptions.insert(
+        "simple-dev-indirect".to_string(),
+        vec![exemptions(ver(5), SAFE_TO_DEPLOY)],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!(
+        "builtin-dev-detection-unaudited-adds-uneeded-criteria-indirect",
+        metadata,
+        store
+    );
+}
+
+#[test]
+fn builtin_dev_detection_cursed_full() {
+    // (Pass): dev-indirect has safe-to-run and by policy we only need safe-to-run
+    // but dev (its parent) is audited for safe-to-deploy which naively requires the child
+    // be safe-to-deploy. However criteria "decomposition" makes this ok, and we do succesfully
+    // validate for safe-to-run.
+    //
+    // This test is "cursed" because it caused some crashes in glitched out the blame system.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::dev_detection();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, imports) = builtin_files_full_audited(&metadata);
+
+    audits.audits.insert(
+        "simple-dev-indirect".to_string(),
+        vec![
+            full_audit(ver(5), SAFE_TO_RUN),
+            delta_audit(ver(5), ver(DEFAULT_VER), SAFE_TO_RUN),
+            delta_audit(ver(5), ver(DEFAULT_VER), SAFE_TO_DEPLOY),
+        ],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin-dev-detection-cursed-full", metadata, store);
+}
+
+#[test]
+fn builtin_dev_detection_cursed_minimal() {
+    // (Pass): the same as the full cursed one, but without the cursed part.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::dev_detection();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, imports) = builtin_files_minimal_audited(&metadata);
+
+    audits.audits.insert(
+        "simple-dev-indirect".to_string(),
+        vec![
+            full_audit(ver(5), SAFE_TO_RUN),
+            delta_audit(ver(5), ver(DEFAULT_VER), SAFE_TO_RUN),
+            delta_audit(ver(5), ver(DEFAULT_VER), SAFE_TO_DEPLOY),
+        ],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin-dev-detection-cursed-minimal", metadata, store);
+}
+
+#[test]
+fn builtin_simple_delta_cycle() {
+    // (Pass) simple delta cycle
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, imports) = builtin_files_full_audited(&metadata);
+
+    audits.audits.insert(
+        "third-party1".to_string(),
+        vec![
+            full_audit(ver(3), SAFE_TO_DEPLOY),
+            delta_audit(ver(3), ver(5), SAFE_TO_DEPLOY),
+            delta_audit(ver(5), ver(7), SAFE_TO_DEPLOY),
+            delta_audit(ver(7), ver(5), SAFE_TO_DEPLOY),
+            delta_audit(ver(7), ver(DEFAULT_VER), SAFE_TO_DEPLOY),
+        ],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin-simple-delta-cycle", metadata, store);
+}
+
+#[test]
+fn builtin_simple_noop_delta() {
+    // (Pass) completely pointless noop delta
+    // (This test could warn if we try to detect "useless deltas")
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, imports) = builtin_files_full_audited(&metadata);
+
+    audits.audits.insert(
+        "third-party1".to_string(),
+        vec![
+            full_audit(ver(3), SAFE_TO_DEPLOY),
+            delta_audit(ver(3), ver(5), SAFE_TO_DEPLOY),
+            delta_audit(ver(5), ver(5), SAFE_TO_DEPLOY),
+            delta_audit(ver(5), ver(7), SAFE_TO_DEPLOY),
+            delta_audit(ver(7), ver(DEFAULT_VER), SAFE_TO_DEPLOY),
+        ],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin-simple-noop-delta", metadata, store);
+}
+
+#[test]
+fn builtin_simple_delta_double_cycle() {
+    // (Pass) double delta cycle
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, imports) = builtin_files_full_audited(&metadata);
+
+    audits.audits.insert(
+        "third-party1".to_string(),
+        vec![
+            full_audit(ver(2), SAFE_TO_DEPLOY),
+            delta_audit(ver(2), ver(3), SAFE_TO_DEPLOY),
+            delta_audit(ver(3), ver(4), SAFE_TO_DEPLOY),
+            delta_audit(ver(4), ver(3), SAFE_TO_DEPLOY),
+            delta_audit(ver(4), ver(5), SAFE_TO_DEPLOY),
+            delta_audit(ver(5), ver(6), SAFE_TO_DEPLOY),
+            delta_audit(ver(6), ver(5), SAFE_TO_DEPLOY),
+            delta_audit(ver(6), ver(7), SAFE_TO_DEPLOY),
+            delta_audit(ver(7), ver(DEFAULT_VER), SAFE_TO_DEPLOY),
+        ],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin-simple-delta-double-cycle", metadata, store);
+}
+
+#[test]
+fn builtin_simple_delta_broken_double_cycle() {
+    // (Fail) double delta cycle that's broken
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, imports) = builtin_files_full_audited(&metadata);
+
+    audits.audits.insert(
+        "third-party1".to_string(),
+        vec![
+            full_audit(ver(2), SAFE_TO_DEPLOY),
+            delta_audit(ver(2), ver(3), SAFE_TO_DEPLOY),
+            delta_audit(ver(3), ver(4), SAFE_TO_DEPLOY),
+            delta_audit(ver(4), ver(3), SAFE_TO_DEPLOY),
+            // broken: delta_audit(ver(4), ver(5), SAFE_TO_DEPLOY),
+            delta_audit(ver(5), ver(6), SAFE_TO_DEPLOY),
+            delta_audit(ver(6), ver(5), SAFE_TO_DEPLOY),
+            delta_audit(ver(6), ver(7), SAFE_TO_DEPLOY),
+            delta_audit(ver(7), ver(DEFAULT_VER), SAFE_TO_DEPLOY),
+        ],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin-simple-delta-broken-double-cycle", metadata, store);
+}
+
+#[test]
+fn builtin_simple_delta_broken_cycle() {
+    // (Fail) simple delta cycle that's broken
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, imports) = builtin_files_full_audited(&metadata);
+
+    audits.audits.insert(
+        "third-party1".to_string(),
+        vec![
+            full_audit(ver(3), SAFE_TO_DEPLOY),
+            delta_audit(ver(3), ver(5), SAFE_TO_DEPLOY),
+            delta_audit(ver(5), ver(7), SAFE_TO_DEPLOY),
+            delta_audit(ver(7), ver(5), SAFE_TO_DEPLOY),
+            // broken: delta_audit(ver(7), ver(8), SAFE_TO_DEPLOY),
+            delta_audit(ver(8), ver(DEFAULT_VER), SAFE_TO_DEPLOY),
+        ],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin-simple-delta-broken-cycle", metadata, store);
+}
+
+#[test]
+fn builtin_simple_long_cycle() {
+    // (Pass) long delta cycle
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, imports) = builtin_files_full_audited(&metadata);
+
+    audits.audits.insert(
+        "third-party1".to_string(),
+        vec![
+            full_audit(ver(2), SAFE_TO_DEPLOY),
+            delta_audit(ver(2), ver(7), SAFE_TO_DEPLOY),
+            delta_audit(ver(7), ver(6), SAFE_TO_DEPLOY),
+            delta_audit(ver(6), ver(8), SAFE_TO_DEPLOY),
+            delta_audit(ver(8), ver(7), SAFE_TO_DEPLOY),
+            delta_audit(ver(8), ver(DEFAULT_VER), SAFE_TO_DEPLOY),
+        ],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin-simple-long-cycle", metadata, store);
+}
+
+#[test]
+fn builtin_simple_useless_long_cycle() {
+    // (Pass) useless long delta cycle
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, imports) = builtin_files_full_audited(&metadata);
+
+    audits.audits.insert(
+        "third-party1".to_string(),
+        vec![
+            full_audit(ver(2), SAFE_TO_DEPLOY),
+            delta_audit(ver(2), ver(7), SAFE_TO_DEPLOY),
+            delta_audit(ver(7), ver(6), SAFE_TO_DEPLOY),
+            delta_audit(ver(6), ver(8), SAFE_TO_DEPLOY),
+            delta_audit(ver(8), ver(7), SAFE_TO_DEPLOY),
+            delta_audit(ver(7), ver(DEFAULT_VER), SAFE_TO_DEPLOY),
+        ],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin-simple-useless-long-cycle", metadata, store);
+}
+
+#[test]
+fn builtin_haunted_init() {
+    // (Pass) Should look the same as a fresh 'vet init'.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::haunted_tree();
+
+    let metadata = mock.metadata();
+    let (config, audits, imports) = builtin_files_inited(&metadata);
+
+    let store = Store::mock(config, audits, imports);
+    assert_report_snapshot!("builtin-haunted-init", metadata, store);
+}
+
+#[test]
+fn builtin_haunted_no_exemptions() {
+    // (Fail) Should look the same as a fresh 'vet init' but with all 'exemptions' entries deleted.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::haunted_tree();
+
+    let metadata = mock.metadata();
+    let (config, audits, imports) = builtin_files_no_exemptions(&metadata);
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin-haunted-no-unaudited", metadata, store);
+}
+
+#[test]
+fn builtin_haunted_no_exemptions_deeper() {
+    // (Fail) Should look the same as a fresh 'vet init' but with all 'exemptions' entries deleted.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::haunted_tree();
+
+    let metadata = mock.metadata();
+    let (config, audits, imports) = builtin_files_no_exemptions(&metadata);
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin-haunted-no-unaudited-deeper", metadata, store);
+}
+
+#[test]
+fn builtin_haunted_full_audited() {
+    // (Pass) All entries have direct full audits.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::haunted_tree();
+
+    let metadata = mock.metadata();
+    let (config, audits, imports) = builtin_files_full_audited(&metadata);
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin-haunted-full-audited", metadata, store);
+}
+
+#[test]
+fn builtin_haunted_minimal_audited() {
+    // (Pass) All entries have direct minimal audits.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::haunted_tree();
+
+    let metadata = mock.metadata();
+    let (config, audits, imports) = builtin_files_minimal_audited(&metadata);
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin-haunted-minimal-audited", metadata, store);
+}
+
+#[test]
+fn builtin_simple_audit_as_default_root_no_audit() {
+    // (Fail) the root is audit-as-crates-io but has no audits
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, audits, imports) = builtin_files_inited(&metadata);
+
+    config
+        .policy
+        .insert("root-package".to_string(), audit_as_policy(Some(true)));
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!(
+        "builtin-simple-audit-as-default-root-no-audit",
+        metadata,
+        store
+    );
+}
+
+#[test]
+fn builtin_simple_audit_as_default_root() {
+    // (Pass) the root is audit-as-crates-io and only needs to be safe-to-run
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, audits, imports) = builtin_files_inited(&metadata);
+
+    config
+        .policy
+        .insert("root-package".to_string(), audit_as_policy(Some(true)));
+    config.exemptions.insert(
+        "root-package".to_string(),
+        vec![exemptions(ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin-simple-audit-as-default-root", metadata, store);
+}
+
+#[test]
+fn builtin_simple_audit_as_default_root_too_weak() {
+    // (Fail) the root is audit-as-crates-io but is only safe-to-run
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, audits, imports) = builtin_files_inited(&metadata);
+
+    config
+        .policy
+        .insert("root-package".to_string(), audit_as_policy(Some(true)));
+    config.exemptions.insert(
+        "root-package".to_string(),
+        vec![exemptions(ver(DEFAULT_VER), SAFE_TO_RUN)],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!(
+        "builtin-simple-audit-as-default-root-too-weak",
+        metadata,
+        store
+    );
+}
+
+#[test]
+fn builtin_simple_audit_as_weaker_root() {
+    // (Pass) the root is audit-as-crates-io and only needs to be safe-to-run
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, audits, imports) = builtin_files_inited(&metadata);
+
+    config.policy.insert(
+        "root-package".to_string(),
+        audit_as_policy_with(Some(true), |policy| {
+            policy.criteria = Some(vec![SAFE_TO_RUN.to_string().into()]);
+        }),
+    );
+    config.exemptions.insert(
+        "root-package".to_string(),
+        vec![exemptions(ver(DEFAULT_VER), SAFE_TO_RUN)],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin-simple-audit-as-weaker-root", metadata, store);
+}
+
+#[test]
+fn builtin_simple_foreign_audited() {
+    // (Pass) All entries have direct full audits.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, mut imports) = builtin_files_full_audited(&metadata);
+    imports.audits.insert(FOREIGN.to_owned(), audits.clone());
+    config.imports.insert(
+        FOREIGN.to_owned(),
+        crate::format::RemoteImport {
+            url: vec![FOREIGN_URL.to_owned()],
+            ..Default::default()
+        },
+    );
+    audits.audits.clear();
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin_simple_foreign_audited", metadata, store);
+}
+
+#[test]
+fn mock_simple_foreign_audited_pun_no_mapping() {
+    // (Fail) All entries have direct full audits, but there isn't a mapping so the names don't match
+    // NOTE: it's possible this situation merits a "help" message"?
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = files_full_audited(&metadata);
+
+    let mut network = Network::new_mock();
+    network.mock_serve_toml(FOREIGN_URL, &audits);
+
+    config.imports.insert(
+        FOREIGN.to_owned(),
+        crate::format::RemoteImport {
+            url: vec![FOREIGN_URL.to_owned()],
+            ..Default::default()
+        },
+    );
+    audits.audits.clear();
+
+    let store = Store::mock_online(
+        &mock_cfg(&metadata),
+        config,
+        audits,
+        imports,
+        &network,
+        true,
+    )
+    .unwrap();
+
+    assert_report_snapshot!(
+        "mock_simple_foreign_audited_pun_no_mapping",
+        metadata,
+        store
+    );
+}
+
+#[test]
+fn mock_simple_foreign_audited_pun_mapped() {
+    // (Pass) All entries have direct full audits, and are mapped in
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = files_full_audited(&metadata);
+
+    let mut network = Network::new_mock();
+    network.mock_serve_toml(FOREIGN_URL, &audits);
+
+    config.imports.insert(
+        FOREIGN.to_owned(),
+        crate::format::RemoteImport {
+            url: vec![FOREIGN_URL.to_owned()],
+            criteria_map: [(
+                DEFAULT_CRIT.to_owned().into(),
+                vec![DEFAULT_CRIT.to_owned().into()],
+            )]
+            .into_iter()
+            .collect(),
+            ..Default::default()
+        },
+    );
+    audits.audits.clear();
+
+    let store = Store::mock_online(
+        &mock_cfg(&metadata),
+        config,
+        audits,
+        imports,
+        &network,
+        true,
+    )
+    .unwrap();
+
+    assert_report_snapshot!("mock_simple_foreign_audited_pun_mapped", metadata, store);
+}
+
+#[test]
+fn mock_simple_foreign_audited_pun_wrong_mapped() {
+    // (Fail) All entries have direct full audits, and are mapped in... but from the wrong import
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, imports) = files_full_audited(&metadata);
+
+    // FOREIGN has the audits, but OTHER_FOREIGN has the mapping
+    let mut network = Network::new_mock();
+    network.mock_serve_toml(FOREIGN_URL, &audits);
+    audits.audits.clear();
+    network.mock_serve_toml(OTHER_FOREIGN_URL, &audits);
+
+    config.imports.insert(
+        OTHER_FOREIGN.to_owned(),
+        crate::format::RemoteImport {
+            url: vec![OTHER_FOREIGN_URL.to_owned()],
+            criteria_map: [(
+                DEFAULT_CRIT.to_owned().into(),
+                vec![DEFAULT_CRIT.to_owned().into()],
+            )]
+            .into_iter()
+            .collect(),
+            ..Default::default()
+        },
+    );
+    config.imports.insert(
+        FOREIGN.to_owned(),
+        crate::format::RemoteImport {
+            url: vec![FOREIGN_URL.to_owned()],
+            ..Default::default()
+        },
+    );
+
+    let store = Store::mock_online(
+        &mock_cfg(&metadata),
+        config,
+        audits,
+        imports,
+        &network,
+        true,
+    )
+    .unwrap();
+
+    assert_report_snapshot!(
+        "mock_simple_foreign_audited_pun_wrong_mapped",
+        metadata,
+        store
+    );
+}
+
+#[test]
+fn builtin_simple_foreign_tag_team() {
+    // (Pass) An audit is achieved by connecting a foreign builtin audit to a local one
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, mut imports) = builtin_files_full_audited(&metadata);
+    audits.audits.insert(
+        "transitive-third-party1".to_owned(),
+        vec![full_audit(ver(5), SAFE_TO_DEPLOY)],
+    );
+    imports.audits.insert(
+        FOREIGN.to_owned(),
+        AuditsFile {
+            criteria: SortedMap::new(),
+            wildcard_audits: SortedMap::new(),
+            audits: [(
+                "transitive-third-party1".to_owned(),
+                vec![delta_audit(ver(5), ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+            )]
+            .into_iter()
+            .collect(),
+            trusted: SortedMap::new(),
+        },
+    );
+    config.imports.insert(
+        FOREIGN.to_owned(),
+        crate::format::RemoteImport {
+            url: vec![FOREIGN_URL.to_owned()],
+            ..Default::default()
+        },
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin_simple_foreign_tag_team", metadata, store);
+}
+
+#[test]
+fn builtin_simple_mega_foreign_tag_team() {
+    // (Pass) An audit is achieved by connecting a two foreign imports together
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, mut imports) = builtin_files_full_audited(&metadata);
+    audits.audits.insert(
+        "transitive-third-party1".to_owned(),
+        vec![full_audit(ver(3), SAFE_TO_DEPLOY)],
+    );
+    imports.audits.insert(
+        FOREIGN.to_owned(),
+        AuditsFile {
+            criteria: SortedMap::new(),
+            wildcard_audits: SortedMap::new(),
+            audits: [(
+                "transitive-third-party1".to_owned(),
+                vec![delta_audit(ver(3), ver(6), SAFE_TO_DEPLOY)],
+            )]
+            .into_iter()
+            .collect(),
+            trusted: SortedMap::new(),
+        },
+    );
+    config.imports.insert(
+        FOREIGN.to_owned(),
+        crate::format::RemoteImport {
+            url: vec![FOREIGN_URL.to_owned()],
+            ..Default::default()
+        },
+    );
+    imports.audits.insert(
+        OTHER_FOREIGN.to_owned(),
+        AuditsFile {
+            criteria: SortedMap::new(),
+            wildcard_audits: SortedMap::new(),
+            audits: [(
+                "transitive-third-party1".to_owned(),
+                vec![delta_audit(ver(6), ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+            )]
+            .into_iter()
+            .collect(),
+            trusted: SortedMap::new(),
+        },
+    );
+    config.imports.insert(
+        OTHER_FOREIGN.to_owned(),
+        crate::format::RemoteImport {
+            url: vec![OTHER_FOREIGN_URL.to_owned()],
+            ..Default::default()
+        },
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin_simple_mega_foreign_tag_team", metadata, store);
+}
+
+#[test]
+fn builtin_simple_registry_suggestions() {
+    // (Pass) After a failing audit, registry suggestions may be proposed if run
+    // with network access.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, audits, imports) = builtin_files_no_exemptions(&metadata);
+
+    let mut network = Network::new_mock();
+    network.mock_serve_toml(
+        crate::storage::REGISTRY_URL,
+        &RegistryFile {
+            registry: [
+                (
+                    FOREIGN.to_owned(),
+                    RegistryEntry {
+                        url: vec![FOREIGN_URL.to_owned()],
+                    },
+                ),
+                (
+                    OTHER_FOREIGN.to_owned(),
+                    RegistryEntry {
+                        url: vec![OTHER_FOREIGN_URL.to_owned()],
+                    },
+                ),
+            ]
+            .into_iter()
+            .collect(),
+        },
+    );
+    network.mock_serve_toml(
+        FOREIGN_URL,
+        &AuditsFile {
+            criteria: SortedMap::new(),
+            wildcard_audits: SortedMap::new(),
+            audits: [
+                (
+                    "third-party1".to_owned(),
+                    vec![full_audit(ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+                ),
+                (
+                    "third-party2".to_owned(),
+                    vec![delta_audit(ver(1), ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+                ),
+            ]
+            .into_iter()
+            .collect(),
+            trusted: SortedMap::new(),
+        },
+    );
+    network.mock_serve_toml(
+        OTHER_FOREIGN_URL,
+        &AuditsFile {
+            criteria: SortedMap::new(),
+            wildcard_audits: SortedMap::new(),
+            audits: [
+                (
+                    "transitive-third-party1".to_owned(),
+                    vec![full_audit(ver(8), SAFE_TO_DEPLOY)],
+                ),
+                (
+                    "third-party2".to_owned(),
+                    vec![full_audit(ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+                ),
+            ]
+            .into_iter()
+            .collect(),
+            trusted: SortedMap::new(),
+        },
+    );
+
+    let store = Store::mock_online(
+        &mock_cfg(&metadata),
+        config,
+        audits,
+        imports,
+        &network,
+        true,
+    )
+    .unwrap();
+
+    assert_report_snapshot!(
+        "builtin_simple_registry_suggestions",
+        metadata,
+        store,
+        Some(&network)
+    );
+}
diff --git a/src/tests/violations.rs b/src/tests/violations.rs
new file mode 100644
index 0000000..ce95f9d
--- /dev/null
+++ b/src/tests/violations.rs
@@ -0,0 +1,265 @@
+use super::*;
+
+#[test]
+fn mock_simple_violation_cur_exemptions() {
+    // (Fail) All marked 'exemptions' but a 'violation' entry matches a current version.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, imports) = files_inited(&metadata);
+
+    let violation_ver = VersionReq::parse(&format!("={DEFAULT_VER}")).unwrap();
+    audits
+        .audits
+        .entry("third-party1".to_string())
+        .or_default()
+        .push(violation(violation_ver, "weak-reviewed"));
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("mock-simple-violation-cur-unaudited", metadata, store);
+}
+
+#[test]
+fn mock_simple_violation_cur_full_audit() {
+    // (Fail) All full audited but a 'violation' entry matches a current version.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, imports) = files_full_audited(&metadata);
+
+    let violation = VersionReq::parse(&format!("={DEFAULT_VER}")).unwrap();
+    audits.audits.insert(
+        "third-party1".to_string(),
+        vec![
+            violation_hard(violation),
+            full_audit(ver(DEFAULT_VER), SAFE_TO_DEPLOY),
+        ],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("mock-simple-violation-cur-full-audit", metadata, store);
+}
+
+#[test]
+fn mock_simple_violation_delta() {
+    // (Fail) A 'violation' matches a delta but not the cur version
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, imports) = files_full_audited(&metadata);
+
+    let violation = VersionReq::parse("=5.0.0").unwrap();
+    audits.audits.insert(
+        "third-party1".to_string(),
+        vec![
+            violation_hard(violation),
+            full_audit(ver(3), SAFE_TO_DEPLOY),
+            delta_audit(ver(3), ver(5), SAFE_TO_DEPLOY),
+            delta_audit(ver(5), ver(DEFAULT_VER), SAFE_TO_DEPLOY),
+        ],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("mock-simple-violation-delta", metadata, store);
+}
+
+#[test]
+fn mock_simple_violation_full_audit() {
+    // (Fail) A 'violation' matches a full audit but not the cur version
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, imports) = files_full_audited(&metadata);
+
+    let violation = VersionReq::parse("=3.0.0").unwrap();
+    audits.audits.insert(
+        "third-party1".to_string(),
+        vec![
+            violation_hard(violation),
+            full_audit(ver(3), SAFE_TO_DEPLOY),
+            delta_audit(ver(3), ver(5), SAFE_TO_DEPLOY),
+            delta_audit(ver(5), ver(DEFAULT_VER), SAFE_TO_DEPLOY),
+        ],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("mock-simple-violation-full-audit", metadata, store);
+}
+
+#[test]
+fn mock_simple_violation_wildcard() {
+    // (Fail) A 'violation' matches a full audit but not the cur version
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, imports) = files_full_audited(&metadata);
+
+    let violation = VersionReq::parse("*").unwrap();
+    audits.audits.insert(
+        "third-party1".to_string(),
+        vec![
+            violation_hard(violation),
+            full_audit(ver(DEFAULT_VER), SAFE_TO_DEPLOY),
+        ],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("mock-simple-violation-wildcard", metadata, store);
+}
+
+#[test]
+fn builtin_simple_deps_violation_dodged() {
+    // (Pass) A 'violation' matches a full audit but we only audit for weaker so it's fine
+
+    let mock = MockMetadata::simple_deps();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, imports) = builtin_files_full_audited(&metadata);
+
+    let violation_ver = VersionReq::parse("*").unwrap();
+    audits.audits.insert(
+        "dev".to_string(),
+        vec![
+            violation(violation_ver, SAFE_TO_DEPLOY),
+            full_audit(ver(DEFAULT_VER), SAFE_TO_RUN),
+        ],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin-simple-deps-violation-dodged", metadata, store);
+}
+
+#[test]
+fn builtin_simple_deps_violation_low_hit() {
+    // (Fail) A 'violation' matches a full audit and both are safe-to-run
+
+    let mock = MockMetadata::simple_deps();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, imports) = builtin_files_full_audited(&metadata);
+
+    let violation_ver = VersionReq::parse("*").unwrap();
+    audits.audits.insert(
+        "dev".to_string(),
+        vec![
+            violation(violation_ver, SAFE_TO_RUN),
+            full_audit(ver(DEFAULT_VER), SAFE_TO_RUN),
+        ],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin-simple-deps-violation-low-hit", metadata, store);
+}
+
+#[test]
+fn builtin_simple_deps_violation_high_hit() {
+    // (Fail) A 'violation' matches a full audit and both are safe-to-deploy
+
+    let mock = MockMetadata::simple_deps();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, imports) = builtin_files_full_audited(&metadata);
+
+    let violation_ver = VersionReq::parse("*").unwrap();
+    audits.audits.insert(
+        "dev".to_string(),
+        vec![
+            violation(violation_ver, SAFE_TO_DEPLOY),
+            full_audit(ver(DEFAULT_VER), SAFE_TO_DEPLOY),
+        ],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin-simple-deps-violation-high-hit", metadata, store);
+}
+
+#[test]
+fn builtin_simple_deps_violation_imply_hit() {
+    // (Fail) A safe-to-run 'violation' matches a safe-to-deploy full audit
+
+    let mock = MockMetadata::simple_deps();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, imports) = builtin_files_full_audited(&metadata);
+
+    let violation_ver = VersionReq::parse("*").unwrap();
+    audits.audits.insert(
+        "dev".to_string(),
+        vec![
+            violation(violation_ver, SAFE_TO_RUN),
+            full_audit(ver(DEFAULT_VER), SAFE_TO_DEPLOY),
+        ],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("builtin-simple-deps-violation-imply-hit", metadata, store);
+}
+
+#[test]
+fn builtin_simple_deps_violation_redundant_low_hit() {
+    // (Fail) A [safe-to-run, safe-to-deploy] 'violation' matches a safe-to-run full audit
+
+    let mock = MockMetadata::simple_deps();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, imports) = builtin_files_full_audited(&metadata);
+
+    let violation_ver = VersionReq::parse("*").unwrap();
+    audits.audits.insert(
+        "dev".to_string(),
+        vec![
+            violation_m(violation_ver, [SAFE_TO_RUN, SAFE_TO_DEPLOY]),
+            full_audit(ver(DEFAULT_VER), SAFE_TO_RUN),
+        ],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!(
+        "builtin-simple-deps-violation-redundant-low-hit",
+        metadata,
+        store
+    );
+}
+
+#[test]
+fn mock_simple_violation_hit_with_extra_junk() {
+    // (Fail) A [safe-to-run, fuzzed] 'violation' matches a safe-to-run full audit
+
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, imports) = files_full_audited(&metadata);
+
+    let violation_ver = VersionReq::parse("*").unwrap();
+    audits.audits.insert(
+        "third-party1".to_string(),
+        vec![
+            violation_m(violation_ver, [SAFE_TO_RUN, "fuzzed"]),
+            full_audit(ver(DEFAULT_VER), SAFE_TO_RUN),
+        ],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("mock-simple-violation-hit-with-extra-junk", metadata, store);
+}
diff --git a/src/tests/wildcard.rs b/src/tests/wildcard.rs
new file mode 100644
index 0000000..2917433
--- /dev/null
+++ b/src/tests/wildcard.rs
@@ -0,0 +1,184 @@
+use super::*;
+
+#[test]
+fn wildcard_full_audit_locked() {
+    // (Pass) A wildcard full-audit for a crate when locked
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, mut imports) = builtin_files_full_audited(&metadata);
+    audits.audits.remove("transitive-third-party1");
+    audits.wildcard_audits.insert(
+        "transitive-third-party1".to_owned(),
+        vec![wildcard_audit(1, SAFE_TO_DEPLOY)],
+    );
+
+    imports.publisher.insert(
+        "transitive-third-party1".to_owned(),
+        vec![publisher_entry(ver(DEFAULT_VER), 1)],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("wildcard_full_audit_locked", metadata, store);
+}
+
+#[test]
+fn wildcard_full_audit_wrong_user_id_locked() {
+    // (Fail) A wildcard full-audit for a crate with the wrong user when locked
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, mut imports) = builtin_files_full_audited(&metadata);
+    audits.audits.remove("transitive-third-party1");
+    audits.wildcard_audits.insert(
+        "transitive-third-party1".to_owned(),
+        vec![wildcard_audit(2, SAFE_TO_DEPLOY)],
+    );
+
+    imports.publisher.insert(
+        "transitive-third-party1".to_owned(),
+        vec![publisher_entry(ver(DEFAULT_VER), 1)],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("wildcard_full_audit_wrong_user_id_locked", metadata, store);
+}
+
+#[test]
+fn wildcard_delta_audit_locked() {
+    // (Pass) A wildcard plus delta-audit for a crate when locked
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, mut imports) = builtin_files_full_audited(&metadata);
+    audits.audits.insert(
+        "transitive-third-party1".to_owned(),
+        vec![delta_audit(ver(5), ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+    );
+    audits.wildcard_audits.insert(
+        "transitive-third-party1".to_owned(),
+        vec![wildcard_audit(1, SAFE_TO_DEPLOY)],
+    );
+
+    imports.publisher.insert(
+        "transitive-third-party1".to_owned(),
+        vec![publisher_entry(ver(5), 1)],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("wildcard_delta_audit_locked", metadata, store);
+}
+
+#[test]
+fn wildcard_delta_audit_wrong_user_id_locked() {
+    // (Fail) A wildcard plus delta-audit for a crate with the wrong user when locked
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (config, mut audits, mut imports) = builtin_files_full_audited(&metadata);
+    audits.audits.insert(
+        "transitive-third-party1".to_owned(),
+        vec![delta_audit(ver(5), ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+    );
+    audits.wildcard_audits.insert(
+        "transitive-third-party1".to_owned(),
+        vec![wildcard_audit(2, SAFE_TO_DEPLOY)],
+    );
+
+    imports.publisher.insert(
+        "transitive-third-party1".to_owned(),
+        vec![publisher_entry(ver(5), 1)],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("wildcard_delta_audit_wrong_user_id_locked", metadata, store);
+}
+
+#[test]
+fn imported_wildcard_audit() {
+    // (Pass) Delta audit based on an imported wildcard audit.
+
+    let _enter = TEST_RUNTIME.enter();
+    let mock = MockMetadata::simple();
+
+    let metadata = mock.metadata();
+    let (mut config, mut audits, mut imports) = builtin_files_full_audited(&metadata);
+    audits
+        .criteria
+        .insert("example".to_string(), criteria("example criteria"));
+    audits.audits.remove("third-party1");
+    audits.audits.insert(
+        "transitive-third-party1".to_owned(),
+        vec![delta_audit(ver(5), ver(DEFAULT_VER), SAFE_TO_DEPLOY)],
+    );
+
+    imports.audits.insert(
+        FOREIGN.to_owned(),
+        AuditsFile {
+            criteria: [(
+                "example".to_string(),
+                criteria("Example criteria description"),
+            )]
+            .into_iter()
+            .collect(),
+            wildcard_audits: [
+                (
+                    "third-party1".to_owned(),
+                    vec![wildcard_audit(3, SAFE_TO_DEPLOY)],
+                ),
+                (
+                    "transitive-third-party1".to_owned(),
+                    vec![wildcard_audit_m(1, [SAFE_TO_DEPLOY, "example"])],
+                ),
+            ]
+            .into_iter()
+            .collect(),
+            audits: SortedMap::new(),
+            trusted: SortedMap::new(),
+        },
+    );
+    config.imports.insert(
+        FOREIGN.to_owned(),
+        crate::format::RemoteImport {
+            url: vec![FOREIGN_URL.to_owned()],
+            criteria_map: [(
+                "example".to_owned().into(),
+                vec!["example".to_owned().into()],
+            )]
+            .into_iter()
+            .collect(),
+            ..Default::default()
+        },
+    );
+
+    imports.publisher.insert(
+        "transitive-third-party1".to_owned(),
+        vec![
+            publisher_entry(ver(5), 1),
+            publisher_entry(ver(DEFAULT_VER), 2),
+        ],
+    );
+    imports.publisher.insert(
+        "third-party1".to_owned(),
+        vec![
+            publisher_entry(ver(5), 3),
+            publisher_entry(ver(DEFAULT_VER), 3),
+        ],
+    );
+
+    let store = Store::mock(config, audits, imports);
+
+    assert_report_snapshot!("imported_wildcard_audit", metadata, store);
+}
diff --git a/supply-chain/audits.toml b/supply-chain/audits.toml
new file mode 100644
index 0000000..d9be1f6
--- /dev/null
+++ b/supply-chain/audits.toml
@@ -0,0 +1,74 @@
+
+# cargo-vet audits file
+
+[[audits.backtrace]]
+who = "Nika Layzell <nika@thelayzells.com>"
+criteria = "safe-to-deploy"
+delta = "0.3.66 -> 0.3.65"
+notes = "Only changes were to the miri backend, which will be checked"
+
+[[audits.base64-stream]]
+who = "Alex Franchuk <afranchuk@mozilla.com>"
+criteria = "safe-to-deploy"
+version = "1.2.7"
+notes = """
+The crate is fairly straightforward. There are a few unsafe blocks to elide
+bounds-checking when copying data, but I have manually verified that the unsafe
+blocks will always have lengths within bounds of source and destination
+pointers. Some `debug_assert!`s document and check these invariants as well
+(though there could be more).
+"""
+
+[[audits.cargo_metadata]]
+who = "Nika Layzell <nika@thelayzells.com>"
+criteria = "safe-to-deploy"
+delta = "0.14.2 -> 0.15.2"
+
+[[audits.doc-comment]]
+who = "Nika Layzell <nika@thelayzells.com>"
+criteria = "safe-to-deploy"
+version = "0.3.3"
+notes = """
+Trivial macro crate implementing a trick for expanding macros within doc
+comments on older versions of rustc.
+"""
+
+[[audits.either]]
+who = "Nika Layzell <nika@thelayzells.com>"
+criteria = "safe-to-deploy"
+version = "1.6.1"
+notes = """
+Straightforward crate providing the Either enum and trait implementations with
+no unsafe code.
+"""
+
+[[audits.home]]
+who = "Nika Layzell <nika@thelayzells.com>"
+criteria = "safe-to-deploy"
+version = "0.5.3"
+notes = """
+Crate with straightforward code for determining the user's HOME directory. Only
+unsafe code is used to invoke the Windows SHGetFolderPathW API to get the
+profile directory when the USERPROFILE environment variable is unavailable.
+"""
+
+[[audits.is_ci]]
+who = "Nika Layzell <nika@thelayzells.com>"
+criteria = "safe-to-deploy"
+version = "1.1.1"
+notes = "Trivial crate which checks the environment for specific environment variables"
+
+[[audits.lazy_static]]
+who = "Nika Layzell <nika@thelayzells.com>"
+criteria = "safe-to-deploy"
+version = "1.4.0"
+notes = "I have read over the macros, and audited the unsafe code."
+
+[[audits.similar]]
+who = "Nika Layzell <nika@thelayzells.com>"
+criteria = "safe-to-deploy"
+version = "2.2.0"
+notes = """
+Algorithm crate implemented entirely in safe rust. Does no platform-specific
+logic, only implementing diffing and string manipulation algorithms.
+"""
diff --git a/supply-chain/config.toml b/supply-chain/config.toml
new file mode 100644
index 0000000..56bf263
--- /dev/null
+++ b/supply-chain/config.toml
@@ -0,0 +1,675 @@
+
+# cargo-vet config file
+
+[cargo-vet]
+version = "0.8"
+
+[imports.bytecodealliance]
+url = "https://raw.githubusercontent.com/bytecodealliance/wasmtime/main/supply-chain/audits.toml"
+
+[imports.embark]
+url = "https://raw.githubusercontent.com/EmbarkStudios/rust-ecosystem/main/audits.toml"
+
+[imports.google]
+url = "https://raw.githubusercontent.com/google/supply-chain/main/audits.toml"
+
+[imports.isrg]
+url = "https://raw.githubusercontent.com/divviup/libprio-rs/main/supply-chain/audits.toml"
+
+[imports.mozilla]
+url = "https://raw.githubusercontent.com/mozilla/supply-chain/main/audits.toml"
+
+[policy.cargo-vet]
+audit-as-crates-io = false
+
+[[exemptions.addr2line]]
+version = "0.17.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.adler]]
+version = "1.0.2"
+criteria = "safe-to-deploy"
+
+[[exemptions.aho-corasick]]
+version = "0.7.18"
+criteria = "safe-to-deploy"
+
+[[exemptions.ansi_term]]
+version = "0.12.1"
+criteria = "safe-to-deploy"
+
+[[exemptions.backtrace-ext]]
+version = "0.2.1"
+criteria = "safe-to-deploy"
+
+[[exemptions.base64]]
+version = "0.13.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.bitflags]]
+version = "1.3.2"
+criteria = "safe-to-deploy"
+
+[[exemptions.bytes]]
+version = "1.1.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.camino]]
+version = "1.0.9"
+criteria = "safe-to-deploy"
+
+[[exemptions.chrono]]
+version = "0.4.23"
+criteria = "safe-to-deploy"
+
+[[exemptions.clap]]
+version = "3.2.6"
+criteria = "safe-to-deploy"
+
+[[exemptions.clap-cargo]]
+version = "0.9.1"
+criteria = "safe-to-deploy"
+
+[[exemptions.clap_derive]]
+version = "3.2.6"
+criteria = "safe-to-deploy"
+
+[[exemptions.clap_lex]]
+version = "0.2.2"
+criteria = "safe-to-deploy"
+
+[[exemptions.combine]]
+version = "4.6.4"
+criteria = "safe-to-deploy"
+
+[[exemptions.console]]
+version = "0.15.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.crates-index]]
+version = "0.18.8"
+criteria = "safe-to-deploy"
+
+[[exemptions.crc32fast]]
+version = "1.3.2"
+criteria = "safe-to-deploy"
+
+[[exemptions.dirs]]
+version = "4.0.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.dirs-sys]]
+version = "0.3.7"
+criteria = "safe-to-deploy"
+
+[[exemptions.educe]]
+version = "0.4.20"
+criteria = "safe-to-deploy"
+
+[[exemptions.encode_unicode]]
+version = "0.3.6"
+criteria = "safe-to-deploy"
+
+[[exemptions.enum-ordinalize]]
+version = "3.1.12"
+criteria = "safe-to-deploy"
+
+[[exemptions.fastrand]]
+version = "1.7.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.filetime]]
+version = "0.2.16"
+criteria = "safe-to-deploy"
+
+[[exemptions.flate2]]
+version = "1.0.24"
+criteria = "safe-to-deploy"
+
+[[exemptions.form_urlencoded]]
+version = "1.0.1"
+criteria = "safe-to-deploy"
+
+[[exemptions.futures-channel]]
+version = "0.3.21"
+criteria = "safe-to-deploy"
+
+[[exemptions.futures-core]]
+version = "0.3.21"
+criteria = "safe-to-deploy"
+
+[[exemptions.futures-sink]]
+version = "0.3.21"
+criteria = "safe-to-deploy"
+
+[[exemptions.futures-task]]
+version = "0.3.21"
+criteria = "safe-to-deploy"
+
+[[exemptions.futures-util]]
+version = "0.3.21"
+criteria = "safe-to-deploy"
+
+[[exemptions.generic-array]]
+version = "0.14.6"
+criteria = "safe-to-deploy"
+
+[[exemptions.getrandom]]
+version = "0.2.7"
+criteria = "safe-to-deploy"
+
+[[exemptions.gimli]]
+version = "0.26.1"
+criteria = "safe-to-deploy"
+
+[[exemptions.git2]]
+version = "0.14.4"
+criteria = "safe-to-deploy"
+
+[[exemptions.h2]]
+version = "0.3.13"
+criteria = "safe-to-deploy"
+
+[[exemptions.hashbrown]]
+version = "0.11.2"
+criteria = "safe-to-deploy"
+
+[[exemptions.hermit-abi]]
+version = "0.1.19"
+criteria = "safe-to-deploy"
+
+[[exemptions.hermit-abi]]
+version = "0.3.1"
+criteria = "safe-to-deploy"
+
+[[exemptions.http]]
+version = "0.2.8"
+criteria = "safe-to-deploy"
+
+[[exemptions.http-body]]
+version = "0.4.5"
+criteria = "safe-to-deploy"
+
+[[exemptions.httparse]]
+version = "1.7.1"
+criteria = "safe-to-deploy"
+
+[[exemptions.hyper]]
+version = "0.14.19"
+criteria = "safe-to-deploy"
+
+[[exemptions.hyper-rustls]]
+version = "0.23.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.indexmap]]
+version = "1.8.2"
+criteria = "safe-to-deploy"
+
+[[exemptions.indicatif]]
+version = "0.17.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.insta]]
+version = "1.16.0"
+criteria = "safe-to-run"
+
+[[exemptions.instant]]
+version = "0.1.12"
+criteria = "safe-to-deploy"
+
+[[exemptions.io-lifetimes]]
+version = "1.0.11"
+criteria = "safe-to-deploy"
+
+[[exemptions.ipnet]]
+version = "2.5.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.itertools]]
+version = "0.10.3"
+criteria = "safe-to-deploy"
+
+[[exemptions.itoa]]
+version = "1.0.2"
+criteria = "safe-to-deploy"
+
+[[exemptions.jobserver]]
+version = "0.1.24"
+criteria = "safe-to-deploy"
+
+[[exemptions.js-sys]]
+version = "0.3.57"
+criteria = "safe-to-deploy"
+
+[[exemptions.libc]]
+version = "0.2.146"
+criteria = "safe-to-deploy"
+
+[[exemptions.libgit2-sys]]
+version = "0.13.4+1.4.2"
+criteria = "safe-to-deploy"
+
+[[exemptions.libz-sys]]
+version = "1.1.8"
+criteria = "safe-to-deploy"
+
+[[exemptions.linux-raw-sys]]
+version = "0.3.8"
+criteria = "safe-to-deploy"
+
+[[exemptions.memchr]]
+version = "2.5.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.miette]]
+version = "5.9.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.miette-derive]]
+version = "5.9.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.mime]]
+version = "0.3.16"
+criteria = "safe-to-deploy"
+
+[[exemptions.minimal-lexical]]
+version = "0.2.1"
+criteria = "safe-to-deploy"
+
+[[exemptions.miniz_oxide]]
+version = "0.5.3"
+criteria = "safe-to-deploy"
+
+[[exemptions.mio]]
+version = "0.8.3"
+criteria = "safe-to-deploy"
+
+[[exemptions.nom]]
+version = "7.1.1"
+criteria = "safe-to-deploy"
+
+[[exemptions.num_cpus]]
+version = "1.13.1"
+criteria = "safe-to-deploy"
+
+[[exemptions.number_prefix]]
+version = "0.4.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.object]]
+version = "0.28.4"
+criteria = "safe-to-deploy"
+
+[[exemptions.once_cell]]
+version = "1.12.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.open]]
+version = "3.0.2"
+criteria = "safe-to-deploy"
+
+[[exemptions.os_str_bytes]]
+version = "6.1.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.owo-colors]]
+version = "3.4.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.pathdiff]]
+version = "0.2.1"
+criteria = "safe-to-deploy"
+
+[[exemptions.percent-encoding]]
+version = "2.1.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.pin-project-lite]]
+version = "0.2.9"
+criteria = "safe-to-deploy"
+
+[[exemptions.proc-macro-error]]
+version = "1.0.4"
+criteria = "safe-to-deploy"
+
+[[exemptions.proc-macro2]]
+version = "1.0.60"
+criteria = "safe-to-deploy"
+
+[[exemptions.redox_syscall]]
+version = "0.2.13"
+criteria = "safe-to-deploy"
+
+[[exemptions.redox_users]]
+version = "0.4.3"
+criteria = "safe-to-deploy"
+
+[[exemptions.regex]]
+version = "1.5.6"
+criteria = "safe-to-deploy"
+
+[[exemptions.regex-syntax]]
+version = "0.6.26"
+criteria = "safe-to-deploy"
+
+[[exemptions.remove_dir_all]]
+version = "0.5.3"
+criteria = "safe-to-deploy"
+
+[[exemptions.reqwest]]
+version = "0.11.11"
+criteria = "safe-to-deploy"
+
+[[exemptions.ring]]
+version = "0.16.20"
+criteria = "safe-to-deploy"
+
+[[exemptions.rustc_version]]
+version = "0.4.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.rustix]]
+version = "0.37.20"
+criteria = "safe-to-deploy"
+
+[[exemptions.rustls]]
+version = "0.20.6"
+criteria = "safe-to-deploy"
+
+[[exemptions.rustls-pemfile]]
+version = "1.0.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.ryu]]
+version = "1.0.10"
+criteria = "safe-to-deploy"
+
+[[exemptions.semver]]
+version = "1.0.10"
+criteria = "safe-to-deploy"
+
+[[exemptions.serde]]
+version = "1.0.137"
+criteria = "safe-to-deploy"
+
+[[exemptions.serde_derive]]
+version = "1.0.137"
+criteria = "safe-to-deploy"
+
+[[exemptions.serde_json]]
+version = "1.0.82"
+criteria = "safe-to-deploy"
+
+[[exemptions.serde_urlencoded]]
+version = "0.7.1"
+criteria = "safe-to-deploy"
+
+[[exemptions.serde_yaml]]
+version = "0.8.24"
+criteria = "safe-to-run"
+
+[[exemptions.sharded-slab]]
+version = "0.1.4"
+criteria = "safe-to-deploy"
+
+[[exemptions.signal-hook-registry]]
+version = "1.4.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.smallvec]]
+version = "1.8.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.smartstring]]
+version = "1.0.1"
+criteria = "safe-to-deploy"
+
+[[exemptions.smawk]]
+version = "0.3.1"
+criteria = "safe-to-deploy"
+
+[[exemptions.socket2]]
+version = "0.4.4"
+criteria = "safe-to-deploy"
+
+[[exemptions.spin]]
+version = "0.5.2"
+criteria = "safe-to-deploy"
+
+[[exemptions.static_assertions]]
+version = "1.1.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.strsim]]
+version = "0.10.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.supports-color]]
+version = "2.0.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.supports-hyperlinks]]
+version = "2.1.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.supports-unicode]]
+version = "2.0.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.syn]]
+version = "1.0.96"
+criteria = "safe-to-deploy"
+
+[[exemptions.syn]]
+version = "2.0.18"
+criteria = "safe-to-deploy"
+
+[[exemptions.tar]]
+version = "0.4.38"
+criteria = "safe-to-deploy"
+
+[[exemptions.tempfile]]
+version = "3.3.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.termcolor]]
+version = "1.1.3"
+criteria = "safe-to-deploy"
+
+[[exemptions.terminal_size]]
+version = "0.1.17"
+criteria = "safe-to-deploy"
+
+[[exemptions.textwrap]]
+version = "0.15.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.thread_local]]
+version = "1.1.4"
+criteria = "safe-to-deploy"
+
+[[exemptions.tokio]]
+version = "1.20.1"
+criteria = "safe-to-deploy"
+
+[[exemptions.tokio-macros]]
+version = "1.8.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.tokio-rustls]]
+version = "0.23.4"
+criteria = "safe-to-deploy"
+
+[[exemptions.tokio-util]]
+version = "0.7.3"
+criteria = "safe-to-deploy"
+
+[[exemptions.toml]]
+version = "0.5.9"
+criteria = "safe-to-deploy"
+
+[[exemptions.toml_edit]]
+version = "0.14.4"
+criteria = "safe-to-deploy"
+
+[[exemptions.tower-service]]
+version = "0.3.1"
+criteria = "safe-to-deploy"
+
+[[exemptions.tracing]]
+version = "0.1.35"
+criteria = "safe-to-deploy"
+
+[[exemptions.tracing-attributes]]
+version = "0.1.21"
+criteria = "safe-to-deploy"
+
+[[exemptions.tracing-core]]
+version = "0.1.27"
+criteria = "safe-to-deploy"
+
+[[exemptions.tracing-log]]
+version = "0.1.3"
+criteria = "safe-to-deploy"
+
+[[exemptions.tracing-subscriber]]
+version = "0.3.11"
+criteria = "safe-to-deploy"
+
+[[exemptions.try-lock]]
+version = "0.2.3"
+criteria = "safe-to-deploy"
+
+[[exemptions.typenum]]
+version = "1.15.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.unicode-ident]]
+version = "1.0.1"
+criteria = "safe-to-deploy"
+
+[[exemptions.unicode-linebreak]]
+version = "0.1.2"
+criteria = "safe-to-deploy"
+
+[[exemptions.url]]
+version = "2.2.2"
+criteria = "safe-to-deploy"
+
+[[exemptions.wasi]]
+version = "0.11.0+wasi-snapshot-preview1"
+criteria = "safe-to-deploy"
+
+[[exemptions.wasm-bindgen]]
+version = "0.2.80"
+criteria = "safe-to-deploy"
+
+[[exemptions.wasm-bindgen-backend]]
+version = "0.2.80"
+criteria = "safe-to-deploy"
+
+[[exemptions.wasm-bindgen-futures]]
+version = "0.4.30"
+criteria = "safe-to-deploy"
+
+[[exemptions.wasm-bindgen-macro]]
+version = "0.2.80"
+criteria = "safe-to-deploy"
+
+[[exemptions.wasm-bindgen-macro-support]]
+version = "0.2.80"
+criteria = "safe-to-deploy"
+
+[[exemptions.web-sys]]
+version = "0.3.57"
+criteria = "safe-to-deploy"
+
+[[exemptions.webpki]]
+version = "0.22.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.webpki-roots]]
+version = "0.22.3"
+criteria = "safe-to-deploy"
+
+[[exemptions.winapi]]
+version = "0.3.9"
+criteria = "safe-to-deploy"
+
+[[exemptions.winapi-i686-pc-windows-gnu]]
+version = "0.4.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.winapi-util]]
+version = "0.1.5"
+criteria = "safe-to-deploy"
+
+[[exemptions.winapi-x86_64-pc-windows-gnu]]
+version = "0.4.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.windows-sys]]
+version = "0.36.1"
+criteria = "safe-to-deploy"
+
+[[exemptions.windows-sys]]
+version = "0.48.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.windows-targets]]
+version = "0.48.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.windows_aarch64_gnullvm]]
+version = "0.48.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.windows_aarch64_msvc]]
+version = "0.36.1"
+criteria = "safe-to-deploy"
+
+[[exemptions.windows_aarch64_msvc]]
+version = "0.48.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.windows_i686_gnu]]
+version = "0.36.1"
+criteria = "safe-to-deploy"
+
+[[exemptions.windows_i686_gnu]]
+version = "0.48.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.windows_i686_msvc]]
+version = "0.36.1"
+criteria = "safe-to-deploy"
+
+[[exemptions.windows_i686_msvc]]
+version = "0.48.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.windows_x86_64_gnu]]
+version = "0.36.1"
+criteria = "safe-to-deploy"
+
+[[exemptions.windows_x86_64_gnu]]
+version = "0.48.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.windows_x86_64_gnullvm]]
+version = "0.48.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.windows_x86_64_msvc]]
+version = "0.36.1"
+criteria = "safe-to-deploy"
+
+[[exemptions.windows_x86_64_msvc]]
+version = "0.48.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.winreg]]
+version = "0.10.1"
+criteria = "safe-to-deploy"
diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock
new file mode 100644
index 0000000..218c859
--- /dev/null
+++ b/supply-chain/imports.lock
@@ -0,0 +1,385 @@
+
+# cargo-vet imports lock
+
+[[publisher.bumpalo]]
+version = "3.10.0"
+when = "2022-06-01"
+user-id = 696
+user-login = "fitzgen"
+user-name = "Nick Fitzgerald"
+
+[[publisher.unicode-width]]
+version = "0.1.9"
+when = "2021-09-16"
+user-id = 1139
+user-login = "Manishearth"
+user-name = "Manish Goregaokar"
+
+[[audits.bytecodealliance.wildcard-audits.bumpalo]]
+who = "Nick Fitzgerald <fitzgen@gmail.com>"
+criteria = "safe-to-deploy"
+user-id = 696 # Nick Fitzgerald (fitzgen)
+start = "2019-03-16"
+end = "2024-03-10"
+
+[[audits.bytecodealliance.audits.atty]]
+who = "Alex Crichton <alex@alexcrichton.com>"
+criteria = "safe-to-deploy"
+version = "0.2.14"
+notes = """
+Contains only unsafe code for what this crate's purpose is and only accesses
+the environment's terminal information when asked. Does its stated purpose and
+no more.
+"""
+
+[[audits.bytecodealliance.audits.backtrace]]
+who = "Alex Crichton <alex@alexcrichton.com>"
+criteria = "safe-to-deploy"
+version = "0.3.66"
+notes = "I am the author of this crate."
+
+[[audits.bytecodealliance.audits.cargo-platform]]
+who = "Pat Hickey <phickey@fastly.com>"
+criteria = "safe-to-deploy"
+version = "0.1.2"
+notes = "no build, no ambient capabilities, no unsafe"
+
+[[audits.bytecodealliance.audits.cc]]
+who = "Alex Crichton <alex@alexcrichton.com>"
+criteria = "safe-to-deploy"
+version = "1.0.73"
+notes = "I am the author of this crate."
+
+[[audits.bytecodealliance.audits.cfg-if]]
+who = "Alex Crichton <alex@alexcrichton.com>"
+criteria = "safe-to-deploy"
+version = "1.0.0"
+notes = "I am the author of this crate."
+
+[[audits.bytecodealliance.audits.errno]]
+who = "Dan Gohman <dev@sunfishcode.online>"
+criteria = "safe-to-deploy"
+version = "0.3.0"
+notes = "This crate uses libc and windows-sys APIs to get and set the raw OS error value."
+
+[[audits.bytecodealliance.audits.errno]]
+who = "Dan Gohman <dev@sunfishcode.online>"
+criteria = "safe-to-deploy"
+delta = "0.3.0 -> 0.3.1"
+notes = "Just a dependency version bump and a bug fix for redox"
+
+[[audits.bytecodealliance.audits.errno-dragonfly]]
+who = "Jamey Sharp <jsharp@fastly.com>"
+criteria = "safe-to-deploy"
+version = "0.1.2"
+notes = "This should be portable to any POSIX system and seems like it should be part of the libc crate, but at any rate it's safe as is."
+
+[[audits.bytecodealliance.audits.heck]]
+who = "Alex Crichton <alex@alexcrichton.com>"
+criteria = "safe-to-deploy"
+version = "0.4.0"
+notes = "Contains `forbid_unsafe` and only uses `std::fmt` from the standard library. Otherwise only contains string manipulation."
+
+[[audits.bytecodealliance.audits.httpdate]]
+who = "Pat Hickey <phickey@fastly.com>"
+criteria = "safe-to-deploy"
+version = "1.0.2"
+notes = "No unsafety, no io"
+
+[[audits.bytecodealliance.audits.idna]]
+who = "Alex Crichton <alex@alexcrichton.com>"
+criteria = "safe-to-deploy"
+version = "0.3.0"
+notes = """
+This is a crate without unsafe code or usage of the standard library. The large
+size of this crate comes from the large generated unicode tables file. This
+crate is broadly used throughout the ecosystem and does not contain anything
+suspicious.
+"""
+
+[[audits.bytecodealliance.audits.is-terminal]]
+who = "Dan Gohman <dev@sunfishcode.online>"
+criteria = "safe-to-deploy"
+version = "0.4.7"
+notes = """
+The is-terminal implementation code is now sync'd up with the prototype
+implementation in the Rust standard library.
+"""
+
+[[audits.bytecodealliance.audits.pin-utils]]
+who = "Pat Hickey <phickey@fastly.com>"
+criteria = "safe-to-deploy"
+version = "0.1.0"
+
+[[audits.bytecodealliance.audits.pkg-config]]
+who = "Pat Hickey <phickey@fastly.com>"
+criteria = "safe-to-deploy"
+version = "0.3.25"
+notes = "This crate shells out to the pkg-config executable, but it appears to sanitize inputs reasonably."
+
+[[audits.bytecodealliance.audits.quote]]
+who = "Pat Hickey <phickey@fastly.com>"
+criteria = "safe-to-deploy"
+delta = "1.0.23 -> 1.0.27"
+
+[[audits.bytecodealliance.audits.rustc-demangle]]
+who = "Alex Crichton <alex@alexcrichton.com>"
+criteria = "safe-to-deploy"
+version = "0.1.21"
+notes = "I am the author of this crate."
+
+[[audits.bytecodealliance.audits.sct]]
+who = "Pat Hickey <phickey@fastly.com>"
+criteria = "safe-to-deploy"
+version = "0.7.0"
+notes = "no unsafe, no build, no ambient capabilities"
+
+[[audits.bytecodealliance.audits.slab]]
+who = "Pat Hickey <phickey@fastly.com>"
+criteria = "safe-to-deploy"
+version = "0.4.6"
+notes = "provides a datastructure implemented using std's Vec. all uses of unsafe are just delegating to the underlying unsafe Vec methods."
+
+[[audits.bytecodealliance.audits.tinyvec]]
+who = "Alex Crichton <alex@alexcrichton.com>"
+criteria = "safe-to-deploy"
+version = "1.6.0"
+notes = """
+This crate, while it implements collections, does so without `std::*` APIs and
+without `unsafe`. Skimming the crate everything looks reasonable and what one
+would expect from idiomatic safe collections in Rust.
+"""
+
+[[audits.bytecodealliance.audits.unicode-bidi]]
+who = "Alex Crichton <alex@alexcrichton.com>"
+criteria = "safe-to-deploy"
+version = "0.3.8"
+notes = """
+This crate has no unsafe code and does not use `std::*`. Skimming the crate it
+does not attempt to out of the bounds of what it's already supposed to be doing.
+"""
+
+[[audits.bytecodealliance.audits.unicode-normalization]]
+who = "Alex Crichton <alex@alexcrichton.com>"
+criteria = "safe-to-deploy"
+version = "0.1.19"
+notes = """
+This crate contains one usage of `unsafe` which I have manually checked to see
+it as correct. This crate's size comes in large part due to the generated
+unicode tables that it contains. This crate is additionally widely used
+throughout the ecosystem and skimming the crate shows no usage of `std::*` APIs
+and nothing suspicious.
+"""
+
+[[audits.bytecodealliance.audits.vcpkg]]
+who = "Pat Hickey <phickey@fastly.com>"
+criteria = "safe-to-deploy"
+version = "0.2.15"
+notes = "no build.rs, no macros, no unsafe. It reads the filesystem and makes copies of DLLs into OUT_DIR."
+
+[[audits.bytecodealliance.audits.want]]
+who = "Pat Hickey <phickey@fastly.com>"
+criteria = "safe-to-deploy"
+version = "0.3.0"
+
+[[audits.bytecodealliance.audits.wasm-bindgen-shared]]
+who = "Pat Hickey <phickey@fastly.com>"
+criteria = "safe-to-deploy"
+delta = "0.2.83 -> 0.2.80"
+
+[[audits.embark.audits.epaint]]
+who = "Johan Andersson <opensource@embark-studios.com>"
+criteria = "safe-to-deploy"
+violation = "<0.20.0"
+notes = "Specified crate license does not include licenses of embedded fonts if using default features or the `default_fonts` feature. Tracked in: https://github.com/emilk/egui/issues/2321"
+
+[[audits.embark.audits.thiserror]]
+who = "Johan Andersson <opensource@embark-studios.com>"
+criteria = "safe-to-deploy"
+version = "1.0.40"
+notes = "Wrapper over implementation crate, found no unsafe or ambient capabilities used"
+
+[[audits.embark.audits.thiserror-impl]]
+who = "Johan Andersson <opensource@embark-studios.com>"
+criteria = "safe-to-deploy"
+version = "1.0.40"
+notes = "Found no unsafe or ambient capabilities used"
+
+[[audits.embark.audits.tinyvec_macros]]
+who = "Johan Andersson <opensource@embark-studios.com>"
+criteria = "safe-to-deploy"
+version = "0.1.0"
+notes = "Inspected it and is a tiny crate with single safe macro"
+
+[[audits.embark.audits.valuable]]
+who = "Johan Andersson <opensource@embark-studios.com>"
+criteria = "safe-to-deploy"
+version = "0.1.0"
+notes = "No unsafe usage or ambient capabilities, sane build script"
+
+[[audits.embark.audits.yaml-rust]]
+who = "Johan Andersson <opensource@embark-studios.com>"
+criteria = "safe-to-deploy"
+version = "0.4.5"
+notes = "No unsafe usage or ambient capabilities"
+
+[[audits.google.audits.proc-macro-error-attr]]
+who = "George Burgess IV <gbiv@google.com>"
+criteria = "safe-to-deploy"
+version = "1.0.4"
+aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT"
+
+[[audits.google.audits.version_check]]
+who = "George Burgess IV <gbiv@google.com>"
+criteria = "safe-to-deploy"
+version = "0.9.4"
+aggregated-from = "https://chromium.googlesource.com/chromiumos/third_party/rust_crates/+/refs/heads/main/cargo-vet/audits.toml?format=TEXT"
+
+[[audits.isrg.audits.untrusted]]
+who = "David Cook <dcook@divviup.org>"
+criteria = "safe-to-deploy"
+version = "0.7.1"
+
+[[audits.isrg.audits.wasm-bindgen-shared]]
+who = "David Cook <dcook@divviup.org>"
+criteria = "safe-to-deploy"
+version = "0.2.83"
+
+[[audits.mozilla.wildcard-audits.unicode-width]]
+who = "Manish Goregaokar <manishsmail@gmail.com>"
+criteria = "safe-to-deploy"
+user-id = 1139 # Manish Goregaokar (Manishearth)
+start = "2019-12-05"
+end = "2024-05-03"
+notes = "All code written or reviewed by Manish"
+aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml"
+
+[[audits.mozilla.audits.autocfg]]
+who = "Josh Stone <jistone@redhat.com>"
+criteria = "safe-to-deploy"
+version = "1.1.0"
+notes = "All code written or reviewed by Josh Stone."
+aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml"
+
+[[audits.mozilla.audits.cargo_metadata]]
+who = "Jan-Erik Rediger <jrediger@mozilla.com>"
+criteria = "safe-to-deploy"
+version = "0.15.2"
+notes = "I reviewed the whole code base. Parser for the output of cargo-metadata, relying mostly on serde. No unsafe code used."
+aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml"
+
+[[audits.mozilla.audits.encoding_rs]]
+who = "Henri Sivonen <hsivonen@hsivonen.fi>"
+criteria = "safe-to-deploy"
+version = "0.8.31"
+notes = "I, Henri Sivonen, wrote encoding_rs for Gecko and have reviewed contributions by others. There are two caveats to the certification: 1) The crate does things that are documented to be UB but that do not appear to actually be UB due to integer types differing from the general rule; https://github.com/hsivonen/encoding_rs/issues/79 . 2) It would be prudent to re-review the code that reinterprets buffers of integers as SIMD vectors; see https://github.com/hsivonen/encoding_rs/issues/87 ."
+aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml"
+
+[[audits.mozilla.audits.fnv]]
+who = "Bobby Holley <bobbyholley@gmail.com>"
+criteria = "safe-to-deploy"
+version = "1.0.7"
+notes = "Simple hasher implementation with no unsafe code."
+aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml"
+
+[[audits.mozilla.audits.hex]]
+who = "Simon Friedberger <simon@mozilla.com>"
+criteria = "safe-to-deploy"
+version = "0.4.3"
+aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml"
+
+[[audits.mozilla.audits.idna]]
+who = "Bobby Holley <bobbyholley@gmail.com>"
+criteria = "safe-to-deploy"
+delta = "0.3.0 -> 0.2.3"
+notes = "Backwards diff with some algorithm changes, no unsafe code."
+aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml"
+
+[[audits.mozilla.audits.linked-hash-map]]
+who = "Aria Beingessner <a.beingessner@gmail.com>"
+criteria = "safe-to-deploy"
+version = "0.5.4"
+notes = "I own this crate (I am contain-rs) and 0.5.4 passes miri. This code is very old and used by lots of people, so I'm pretty confident in it, even though it's in maintenance-mode and missing some nice-to-have APIs."
+aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml"
+
+[[audits.mozilla.audits.log]]
+who = "Mike Hommey <mh+mozilla@glandium.org>"
+criteria = "safe-to-deploy"
+version = "0.4.17"
+aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml"
+
+[[audits.mozilla.audits.matches]]
+who = "Bobby Holley <bobbyholley@gmail.com>"
+criteria = "safe-to-deploy"
+version = "0.1.9"
+notes = "This is a trivial crate."
+aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml"
+
+[[audits.mozilla.audits.num-bigint]]
+who = "Josh Stone <jistone@redhat.com>"
+criteria = "safe-to-deploy"
+version = "0.4.3"
+notes = "All code written or reviewed by Josh Stone."
+aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml"
+
+[[audits.mozilla.audits.num-integer]]
+who = "Josh Stone <jistone@redhat.com>"
+criteria = "safe-to-deploy"
+version = "0.1.45"
+notes = "All code written or reviewed by Josh Stone."
+aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml"
+
+[[audits.mozilla.audits.num-traits]]
+who = "Josh Stone <jistone@redhat.com>"
+criteria = "safe-to-deploy"
+version = "0.2.15"
+notes = "All code written or reviewed by Josh Stone."
+aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml"
+
+[[audits.mozilla.audits.quote]]
+who = "Nika Layzell <nika@thelayzells.com>"
+criteria = "safe-to-deploy"
+version = "1.0.18"
+notes = """
+`quote` is a utility crate used by proc-macros to generate TokenStreams
+conveniently from source code. The bulk of the logic is some complex
+interlocking `macro_rules!` macros which are used to parse and build the
+`TokenStream` within the proc-macro.
+
+This crate contains no unsafe code, and the internal logic, while difficult to
+read, is generally straightforward. I have audited the the quote macros, ident
+formatter, and runtime logic.
+"""
+aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml"
+
+[[audits.mozilla.audits.quote]]
+who = "Mike Hommey <mh+mozilla@glandium.org>"
+criteria = "safe-to-deploy"
+delta = "1.0.18 -> 1.0.21"
+aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml"
+
+[[audits.mozilla.audits.quote]]
+who = "Mike Hommey <mh+mozilla@glandium.org>"
+criteria = "safe-to-deploy"
+delta = "1.0.21 -> 1.0.23"
+aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml"
+
+[[audits.mozilla.audits.quote]]
+who = "Jan-Erik Rediger <jrediger@mozilla.com>"
+criteria = "safe-to-deploy"
+delta = "1.0.27 -> 1.0.28"
+notes = "Enabled on wasm targets"
+aggregated-from = "https://raw.githubusercontent.com/mozilla/glean/main/supply-chain/audits.toml"
+
+[[audits.mozilla.audits.rustc-hash]]
+who = "Bobby Holley <bobbyholley@gmail.com>"
+criteria = "safe-to-deploy"
+version = "1.1.0"
+notes = "Straightforward crate with no unsafe code, does what it says on the tin."
+aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml"
+
+[[audits.mozilla.audits.typenum]]
+who = "Mike Hommey <mh+mozilla@glandium.org>"
+criteria = "safe-to-deploy"
+delta = "1.15.0 -> 1.16.0"
+aggregated-from = "https://hg.mozilla.org/mozilla-central/raw-file/tip/supply-chain/audits.toml"
diff --git a/tests/.gitignore b/tests/.gitignore
new file mode 100644
index 0000000..fc3d3a2
--- /dev/null
+++ b/tests/.gitignore
@@ -0,0 +1,5 @@
+# Don't check in anything in the testing cache directory other than
+# diff-cache.toml and publisher-cache.json
+cache/*
+!cache/diff-cache.toml
+!cache/crates-io-cache.json
diff --git a/tests/cache/crates-io-cache.json b/tests/cache/crates-io-cache.json
new file mode 100644
index 0000000..3b7bd15
--- /dev/null
+++ b/tests/cache/crates-io-cache.json
@@ -0,0 +1 @@
+{"users":{"1":{"login":"alexcrichton","name":"Alex Crichton"},"5":{"login":"sfackler","name":"Steven Fackler"},"7":{"login":"SimonSapin","name":"Simon Sapin"},"10":{"login":"carllerche","name":"Carl Lerche"},"63":{"login":"retep998","name":"Peter Atashian"},"86":{"login":"sebcrozet","name":"Sébastien Crozet"},"163":{"login":"alex","name":"Alex Gaynor"},"189":{"login":"BurntSushi","name":"Andrew Gallant"},"280":{"login":"Stebalien","name":"Steven Allen"},"356":{"login":"bluss","name":"bluss"},"359":{"login":"seanmonstar","name":"Sean McArthur"},"530":{"login":"gentoo90","name":null},"539":{"login":"cuviper","name":"Josh Stone"},"696":{"login":"fitzgen","name":"Nick Fitzgerald"},"915":{"login":"LukasKalbertodt","name":"Lukas Kalbertodt"},"982":{"login":"pyfisch","name":null},"988":{"login":"kornelski","name":"Kornel"},"1139":{"login":"Manishearth","name":"Manish Goregaokar"},"1249":{"login":"hawkw","name":"Eliza Weisman"},"1459":{"login":"faern","name":"Linus Färnstrand"},"2017":{"login":"mbrubeck","name":"Matt Brubeck"},"2396":{"login":"jdm","name":"Josh Matthews"},"2668":{"login":"jackpot51","name":"Jeremy Soller"},"2678":{"login":"XAMPPRocky","name":null},"2699":{"login":"matklad","name":"Alex Kladov"},"2728":{"login":"steffengy","name":"Steffen Butzer"},"2915":{"login":"Amanieu","name":"Amanieu d'Antras"},"3100":{"login":"nox","name":"Anthony Ramine"},"3204":{"login":"KodrAus","name":"Ashley Mannix"},"3618":{"login":"dtolnay","name":"David Tolnay"},"3623":{"login":"sdroege","name":"Sebastian Dröge"},"3824":{"login":"yoshuawuyts","name":"Yosh"},"3959":{"login":"LucioFranco","name":"Lucio Franco"},"4333":{"login":"joshtriplett","name":"Josh Triplett"},"4484":{"login":"hsivonen","name":"Henri Sivonen"},"4798":{"login":"Pauan","name":null},"5149":{"login":"cramertj","name":"Taylor Cramer"},"5153":{"login":"stjepang","name":"Stjepan Glavina"},"5484":{"login":"mgeisler","name":"Martin Geisler"},"5725":{"login":"gnzlbg","name":"gnzlbg"},"5946":{"login":"jrmuizel","name":"Jeff Muizelaar"},"6025":{"login":"Thomasdezeeuw","name":"Thomas de Zeeuw"},"6457":{"login":"mcgoo","name":null},"6741":{"login":"Darksonn","name":"Alice Ryhl"},"6825":{"login":"sunfishcode","name":"Dan Gohman"},"7211":{"login":"Lokathor","name":"Lokathor"},"8197":{"login":"krisprice","name":null},"8585":{"login":"rbtcollins","name":"Robert Collins"},"12432":{"login":"davidpdrsn","name":"David Pedersen"},"13388":{"login":"richardanaya","name":"RICHΛRD ΛNΛYΛ"},"27926":{"login":"MSxDOS","name":null},"29348":{"login":"stlankes","name":"Stefan Lankes"},"33035":{"login":"taiki-e","name":"Taiki Endo"},"42742":{"login":"4lDO2","name":"4lDO2"},"51017":{"login":"JohnTitor","name":"Yuki Okushi"},"55123":{"login":"rust-lang-owner","name":null},"65916":{"login":"Noah-Kennedy","name":"Noah Kennedy"},"68756":{"login":"dylni","name":null},"72883":{"login":"valenting","name":"Valentin Gosu"},"83082":{"login":"waych","name":"Mike Waychison"},"91569":{"login":"mkroening","name":"Martin Kröning"},"94492":{"login":"Soveu","name":null},"107629":{"login":"bdonlan","name":null}},"crates":{"bumpalo":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"1.0.0":{"created_at":"2018-11-24T06:21:56.420457Z","published_by":null},"1.0.1":{"created_at":"2018-11-26T21:18:50.756080Z","published_by":null},"1.0.2":{"created_at":"2018-11-26T21:30:39.502869Z","published_by":null},"1.1.0":{"created_at":"2018-11-28T23:54:20.869269Z","published_by":null},"1.2.0":{"created_at":"2019-01-16T00:31:34.159729Z","published_by":null},"2.0.0":{"created_at":"2019-02-11T18:51:32.754173Z","published_by":null},"2.1.0":{"created_at":"2019-02-12T18:07:40.780728Z","published_by":null},"2.2.0":{"created_at":"2019-03-16T03:23:31.087090Z","published_by":696},"2.2.1":{"created_at":"2019-03-18T16:25:48.138419Z","published_by":696},"2.2.2":{"created_at":"2019-03-18T18:21:47.553208Z","published_by":696},"2.3.0":{"created_at":"2019-03-26T16:18:00.235045Z","published_by":696},"2.4.0":{"created_at":"2019-04-19T17:32:22.774157Z","published_by":696},"2.4.1":{"created_at":"2019-04-19T17:36:58.924318Z","published_by":696},"2.4.2":{"created_at":"2019-05-17T22:38:40.788235Z","published_by":696},"2.4.3":{"created_at":"2019-05-20T18:04:08.490633Z","published_by":696},"2.5.0":{"created_at":"2019-07-01T16:53:07.692348Z","published_by":696},"2.6.0":{"created_at":"2019-08-19T19:43:39.650073Z","published_by":696},"3.0.0":{"created_at":"2019-12-20T23:33:09.635147Z","published_by":696},"3.1.0":{"created_at":"2019-12-27T19:54:40.233006Z","published_by":696},"3.1.1":{"created_at":"2020-01-03T18:29:27.064169Z","published_by":696},"3.1.2":{"created_at":"2020-01-07T17:33:19.003494Z","published_by":696},"3.2.0":{"created_at":"2020-02-07T20:39:15.395727Z","published_by":696},"3.2.1":{"created_at":"2020-03-24T21:12:40.961820Z","published_by":696},"3.3.0":{"created_at":"2020-05-13T21:51:21.500776Z","published_by":696},"3.4.0":{"created_at":"2020-06-01T18:11:53.024614Z","published_by":696},"3.5.0":{"created_at":"2021-01-23T01:24:05.266357Z","published_by":696},"3.6.0":{"created_at":"2021-01-29T22:35:48.282714Z","published_by":696},"3.6.1":{"created_at":"2021-02-18T17:43:51.079762Z","published_by":696},"3.7.0":{"created_at":"2021-05-28T17:18:14.347740Z","published_by":696},"3.7.1":{"created_at":"2021-09-17T16:37:11.655156Z","published_by":696},"3.8.0":{"created_at":"2021-10-19T18:06:40.464928Z","published_by":696},"3.9.0":{"created_at":"2022-01-06T00:16:36.443747Z","published_by":696},"3.9.1":{"created_at":"2022-01-06T18:08:38.790928Z","published_by":696},"3.10.0":{"created_at":"2022-06-01T17:50:21.807422Z","published_by":696},"3.11.0":{"created_at":"2022-08-17T16:33:31.204194Z","published_by":696},"3.11.1":{"created_at":"2022-10-18T21:15:46.723220Z","published_by":696},"3.12.0":{"created_at":"2023-01-17T17:41:29.993882Z","published_by":696},"3.12.1":{"created_at":"2023-04-21T21:28:02.334771Z","published_by":696},"3.12.2":{"created_at":"2023-05-09T18:05:11.576598Z","published_by":696}},"metadata":{"description":"A fast bump allocation arena for Rust.","repository":"https://github.com/fitzgen/bumpalo"}},"bytes":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.0.1":{"created_at":"2015-01-30T08:04:59.465676Z","published_by":null},"0.1.0":{"created_at":"2015-02-10T07:37:03.441894Z","published_by":null},"0.1.1":{"created_at":"2015-02-11T03:36:11.550758Z","published_by":null},"0.1.2":{"created_at":"2015-02-15T22:54:55.112509Z","published_by":null},"0.2.0":{"created_at":"2015-03-25T05:28:26.992376Z","published_by":null},"0.2.1":{"created_at":"2015-03-25T19:58:31.119983Z","published_by":null},"0.2.2":{"created_at":"2015-03-29T00:01:19.797054Z","published_by":null},"0.2.3":{"created_at":"2015-03-30T22:16:06.841583Z","published_by":null},"0.2.4":{"created_at":"2015-04-02T03:12:34.691723Z","published_by":null},"0.2.5":{"created_at":"2015-04-07T21:53:33.713910Z","published_by":null},"0.2.6":{"created_at":"2015-04-07T23:16:33.439617Z","published_by":null},"0.2.7":{"created_at":"2015-04-12T22:30:17.234637Z","published_by":null},"0.2.8":{"created_at":"2015-04-22T16:33:40.714066Z","published_by":null},"0.2.9":{"created_at":"2015-05-13T05:27:24.518471Z","published_by":null},"0.2.10":{"created_at":"2015-07-08T20:20:45.770464Z","published_by":null},"0.2.11":{"created_at":"2015-08-10T17:21:19.828857Z","published_by":null},"0.3.0":{"created_at":"2015-12-04T04:45:51.403842Z","published_by":null},"0.4.0":{"created_at":"2017-02-24T18:40:29.623858Z","published_by":null},"0.4.1":{"created_at":"2017-03-15T16:37:07.470919Z","published_by":null},"0.4.2":{"created_at":"2017-04-05T19:19:10.803647Z","published_by":null},"0.4.3":{"created_at":"2017-04-30T23:26:46.071622Z","published_by":null},"0.4.4":{"created_at":"2017-05-26T17:09:56.629483Z","published_by":null},"0.4.5":{"created_at":"2017-08-12T17:59:40.397123Z","published_by":null},"0.4.6":{"created_at":"2018-01-08T17:11:59.661243Z","published_by":null},"0.4.7":{"created_at":"2018-04-27T19:57:17.704488Z","published_by":null},"0.4.8":{"created_at":"2018-05-25T23:51:05.925659Z","published_by":null},"0.4.9":{"created_at":"2018-07-23T02:35:38.673738Z","published_by":null},"0.4.10":{"created_at":"2018-09-04T20:31:48.449001Z","published_by":null},"0.4.11":{"created_at":"2018-11-17T22:34:06.150664Z","published_by":null},"0.4.12":{"created_at":"2019-03-06T20:42:37.646139Z","published_by":10},"0.5.0":{"created_at":"2019-11-25T19:19:45.328700Z","published_by":10},"0.5.1":{"created_at":"2019-11-26T22:19:21.979936Z","published_by":10},"0.5.2":{"created_at":"2019-11-27T21:02:02.469775Z","published_by":359},"0.5.3":{"created_at":"2019-12-12T20:00:49.805256Z","published_by":359},"0.5.4":{"created_at":"2020-01-23T18:42:17.671357Z","published_by":359},"0.5.5":{"created_at":"2020-06-18T21:30:41.621413Z","published_by":359},"0.5.6":{"created_at":"2020-07-14T00:58:51.945006Z","published_by":359},"0.6.0":{"created_at":"2020-10-20T23:08:43.595071Z","published_by":10},"1.0.0":{"created_at":"2020-12-22T23:30:37.563187Z","published_by":10},"1.0.1":{"created_at":"2021-01-11T17:08:40.393490Z","published_by":6741},"1.1.0":{"created_at":"2021-08-25T15:50:02.014922Z","published_by":6741},"1.2.0":{"created_at":"2022-07-19T11:40:30.843556Z","published_by":6741},"1.2.1":{"created_at":"2022-07-30T09:35:56.148102Z","published_by":6741},"1.3.0":{"created_at":"2022-11-21T07:07:27.614741Z","published_by":6741},"1.4.0":{"created_at":"2023-01-31T19:39:04.980716Z","published_by":6741}},"metadata":{"description":"Types and traits for working with bytes","repository":"https://github.com/tokio-rs/bytes"}},"cc":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.0.1":{"created_at":"2014-12-16T05:26:51.314316Z","published_by":null},"1.0.0":{"created_at":"2017-09-19T17:52:18.895879Z","published_by":null},"1.0.1":{"created_at":"2017-10-12T00:16:37.585632Z","published_by":null},"1.0.2":{"created_at":"2017-10-23T15:47:14.210596Z","published_by":null},"1.0.3":{"created_at":"2017-10-27T15:43:48.068679Z","published_by":null},"1.0.4":{"created_at":"2018-01-08T15:23:24.047050Z","published_by":null},"1.0.5":{"created_at":"2018-03-04T03:35:32.879225Z","published_by":null},"1.0.6":{"created_at":"2018-03-09T17:11:48.858785Z","published_by":null},"1.0.7":{"created_at":"2018-03-13T22:14:43.411688Z","published_by":null},"1.0.8":{"created_at":"2018-03-16T16:05:01.318490Z","published_by":null},"1.0.9":{"created_at":"2018-03-21T22:07:39.998093Z","published_by":null},"1.0.10":{"created_at":"2018-04-13T16:25:45.132754Z","published_by":null},"1.0.11":{"created_at":"2018-04-26T14:45:18.755541Z","published_by":null},"1.0.12":{"created_at":"2018-04-26T14:54:40.142515Z","published_by":null},"1.0.13":{"created_at":"2018-04-27T15:59:53.964465Z","published_by":null},"1.0.14":{"created_at":"2018-04-29T15:02:02.570940Z","published_by":null},"1.0.15":{"created_at":"2018-05-01T14:05:42.560546Z","published_by":null},"1.0.16":{"created_at":"2018-05-29T16:22:37.900077Z","published_by":null},"1.0.17":{"created_at":"2018-05-29T16:23:13.272386Z","published_by":null},"1.0.18":{"created_at":"2018-07-11T14:16:53.717840Z","published_by":null},"1.0.19":{"created_at":"2018-08-21T17:22:35.535212Z","published_by":null},"1.0.20":{"created_at":"2018-08-22T16:16:23.828125Z","published_by":null},"1.0.21":{"created_at":"2018-08-24T16:46:30.757293Z","published_by":null},"1.0.22":{"created_at":"2018-08-24T18:43:19.391300Z","published_by":null},"1.0.23":{"created_at":"2018-08-30T00:00:23.353946Z","published_by":null},"1.0.24":{"created_at":"2018-09-07T15:20:08.133456Z","published_by":null},"1.0.25":{"created_at":"2018-09-12T00:02:31.765147Z","published_by":null},"1.0.26":{"created_at":"2018-12-12T16:12:37.093052Z","published_by":null},"1.0.27":{"created_at":"2018-12-18T17:17:59.711996Z","published_by":null},"1.0.28":{"created_at":"2018-12-21T15:59:23.686485Z","published_by":null},"1.0.29":{"created_at":"2019-02-05T07:54:39.416610Z","published_by":null},"1.0.30":{"created_at":"2019-03-01T19:35:46.731401Z","published_by":1},"1.0.31":{"created_at":"2019-03-12T14:35:23.047777Z","published_by":1},"1.0.32":{"created_at":"2019-03-26T14:17:12.884187Z","published_by":1},"1.0.33":{"created_at":"2019-04-02T20:14:42.363786Z","published_by":1},"1.0.34":{"created_at":"2019-04-02T20:30:06.822253Z","published_by":1},"1.0.35":{"created_at":"2019-04-08T14:11:41.698021Z","published_by":1},"1.0.36":{"created_at":"2019-04-30T14:16:46.501215Z","published_by":1},"1.0.37":{"created_at":"2019-05-14T14:05:56.215575Z","published_by":1},"1.0.38":{"created_at":"2019-07-23T14:31:39.862452Z","published_by":1},"1.0.39":{"created_at":"2019-08-12T20:31:43.736990Z","published_by":1},"1.0.40":{"created_at":"2019-08-12T21:04:00.835764Z","published_by":1},"1.0.41":{"created_at":"2019-08-27T19:37:39.180991Z","published_by":1},"1.0.42":{"created_at":"2019-09-05T16:07:04.726050Z","published_by":1},"1.0.43":{"created_at":"2019-09-07T16:02:33.680110Z","published_by":1},"1.0.44":{"created_at":"2019-09-07T16:21:40.119195Z","published_by":1},"1.0.45":{"created_at":"2019-09-07T16:46:24.689530Z","published_by":1},"1.0.46":{"created_at":"2019-10-16T22:42:42.430639Z","published_by":1},"1.0.47":{"created_at":"2019-11-05T19:40:37.409912Z","published_by":1},"1.0.48":{"created_at":"2019-12-04T16:24:38.078820Z","published_by":1},"1.0.49":{"created_at":"2020-01-06T15:38:13.298245Z","published_by":1},"1.0.50":{"created_at":"2020-01-08T16:24:14.272491Z","published_by":1},"1.0.51":{"created_at":"2020-04-22T14:19:06.836585Z","published_by":1},"1.0.52":{"created_at":"2020-04-22T19:51:25.338710Z","published_by":1},"1.0.53":{"created_at":"2020-05-14T15:47:11.224981Z","published_by":1},"1.0.54":{"created_at":"2020-05-19T22:08:08.098238Z","published_by":1},"1.0.55":{"created_at":"2020-06-25T21:00:08.557549Z","published_by":1},"1.0.56":{"created_at":"2020-06-29T13:37:56.678586Z","published_by":1},"1.0.57":{"created_at":"2020-07-02T14:11:42.120778Z","published_by":1},"1.0.58":{"created_at":"2020-07-08T14:05:13.352192Z","published_by":1},"1.0.59":{"created_at":"2020-08-18T22:58:06.408633Z","published_by":1},"1.0.60":{"created_at":"2020-09-16T15:45:16.628468Z","published_by":1},"1.0.61":{"created_at":"2020-10-08T21:08:52.672925Z","published_by":1},"1.0.62":{"created_at":"2020-11-06T20:34:22.671008Z","published_by":1},"1.0.63":{"created_at":"2020-11-18T22:53:48.366292Z","published_by":1},"1.0.64":{"created_at":"2020-11-20T16:13:48.307293Z","published_by":1},"1.0.65":{"created_at":"2020-11-20T20:38:35.157673Z","published_by":1},"1.0.66":{"created_at":"2020-12-03T19:10:55.447311Z","published_by":1},"1.0.67":{"created_at":"2021-02-19T20:07:37.068690Z","published_by":1},"1.0.68":{"created_at":"2021-05-24T20:07:17.679758Z","published_by":1},"1.0.69":{"created_at":"2021-07-12T14:10:38.689359Z","published_by":1},"1.0.70":{"created_at":"2021-08-31T14:05:14.196531Z","published_by":1},"1.0.71":{"created_at":"2021-10-06T20:45:49.202183Z","published_by":1},"1.0.72":{"created_at":"2021-11-10T15:53:24.344736Z","published_by":1},"1.0.73":{"created_at":"2022-02-16T18:53:19.812635Z","published_by":1},"1.0.74":{"created_at":"2022-10-29T14:03:21.556444Z","published_by":55123},"1.0.75":{"created_at":"2022-11-08T12:58:03.635771Z","published_by":55123},"1.0.76":{"created_at":"2022-11-08T23:34:18.078556Z","published_by":55123},"1.0.77":{"created_at":"2022-11-20T22:53:57.876281Z","published_by":55123},"1.0.78":{"created_at":"2022-12-15T02:25:32.857907Z","published_by":55123},"1.0.79":{"created_at":"2023-01-28T20:05:46.728869Z","published_by":55123}},"metadata":{"description":"A build-time dependency for Cargo build scripts to assist in invoking the native\nC compiler to compile native C code into a static archive to be linked into Rust\ncode.\n","repository":"https://github.com/rust-lang/cc-rs"}},"cfg-if":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2015-07-08T01:11:24.506905Z","published_by":null},"0.1.1":{"created_at":"2017-06-08T16:18:35.478651Z","published_by":null},"0.1.2":{"created_at":"2017-07-05T14:50:25.744161Z","published_by":null},"0.1.3":{"created_at":"2018-05-03T13:50:58.459164Z","published_by":null},"0.1.4":{"created_at":"2018-06-27T15:54:57.694896Z","published_by":null},"0.1.5":{"created_at":"2018-08-13T02:04:53.478202Z","published_by":null},"0.1.6":{"created_at":"2018-10-20T02:28:41.963911Z","published_by":null},"0.1.7":{"created_at":"2019-03-04T21:18:17.180300Z","published_by":1},"0.1.8":{"created_at":"2019-05-15T00:43:20.286961Z","published_by":1},"0.1.9":{"created_at":"2019-05-15T00:43:43.387945Z","published_by":1},"0.1.10":{"created_at":"2019-09-24T15:27:21.754908Z","published_by":1},"1.0.0":{"created_at":"2020-10-06T18:44:12.678620Z","published_by":1}},"metadata":{"description":"A macro to ergonomically define an item depending on a large number of #[cfg]\nparameters. Structured like an if-else chain, the first matching branch is the\nitem that gets emitted.\n","repository":"https://github.com/alexcrichton/cfg-if"}},"core-foundation":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.0.1":{"created_at":"2015-02-12T10:50:45.604923Z","published_by":null},"0.0.2":{"created_at":"2015-02-12T11:55:32.332672Z","published_by":null},"0.1.0":{"created_at":"2015-06-04T03:50:01.359241Z","published_by":null},"0.2.0":{"created_at":"2015-11-05T20:03:26.030236Z","published_by":null},"0.2.1":{"created_at":"2016-04-20T22:38:46.309074Z","published_by":null},"0.2.2":{"created_at":"2016-05-16T18:20:12.646152Z","published_by":null},"0.2.3":{"created_at":"2017-01-09T18:55:00.920793Z","published_by":null},"0.3.0":{"created_at":"2017-01-26T16:12:31.210676Z","published_by":null},"0.4.0":{"created_at":"2017-06-02T03:30:24.101717Z","published_by":null},"0.4.1":{"created_at":"2017-06-16T18:56:05.401534Z","published_by":null},"0.4.2":{"created_at":"2017-07-27T21:54:03.740757Z","published_by":null},"0.4.3":{"created_at":"2017-08-09T02:17:57.534222Z","published_by":null},"0.4.4":{"created_at":"2017-08-09T18:18:21.832515Z","published_by":null},"0.4.5":{"created_at":"2017-11-27T13:58:54.261043Z","published_by":null},"0.4.6":{"created_at":"2017-11-28T21:06:37.548955Z","published_by":null},"0.5.0":{"created_at":"2018-01-25T21:33:31.525859Z","published_by":null},"0.5.1":{"created_at":"2018-01-26T19:21:56.408137Z","published_by":null},"0.6.0":{"created_at":"2018-05-02T22:18:42.949065Z","published_by":null},"0.6.1":{"created_at":"2018-07-12T14:54:01.348608Z","published_by":null},"0.6.2":{"created_at":"2018-09-28T17:46:48.446168Z","published_by":null},"0.6.3":{"created_at":"2018-11-08T23:07:20.230223Z","published_by":null},"0.6.4":{"created_at":"2019-03-29T19:25:36.088648Z","published_by":5946},"0.7.0":{"created_at":"2019-11-12T23:14:09.725812Z","published_by":2396},"0.7.1":{"created_at":"2020-06-28T17:40:14.838776Z","published_by":2396},"0.8.0":{"created_at":"2020-06-29T15:23:41.215405Z","published_by":2396},"0.9.0":{"created_at":"2020-06-29T15:43:15.079994Z","published_by":2396},"0.9.1":{"created_at":"2020-09-15T16:04:54.935910Z","published_by":2396},"0.9.2":{"created_at":"2021-10-12T23:53:15.631231Z","published_by":2396},"0.9.3":{"created_at":"2022-02-07T20:15:57.580279Z","published_by":5946}},"metadata":{"description":"Bindings to Core Foundation for macOS","repository":"https://github.com/servo/core-foundation-rs"}},"core-foundation-sys":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2015-10-17T23:55:36.661219Z","published_by":null},"0.2.0":{"created_at":"2015-11-05T20:03:11.272264Z","published_by":null},"0.2.1":{"created_at":"2016-04-20T22:38:35.553791Z","published_by":null},"0.2.2":{"created_at":"2016-05-16T18:19:50.813629Z","published_by":null},"0.2.3":{"created_at":"2017-01-09T18:54:21.413939Z","published_by":null},"0.3.0":{"created_at":"2017-01-26T15:52:58.515401Z","published_by":null},"0.3.1":{"created_at":"2017-02-02T12:23:36.453520Z","published_by":null},"0.4.0":{"created_at":"2017-06-02T02:56:49.595243Z","published_by":null},"0.4.1":{"created_at":"2017-06-16T18:54:31.228238Z","published_by":null},"0.4.2":{"created_at":"2017-07-27T21:53:50.673502Z","published_by":null},"0.4.3":{"created_at":"2017-08-09T02:17:42.281315Z","published_by":null},"0.4.4":{"created_at":"2017-08-09T18:17:58.792179Z","published_by":null},"0.4.5":{"created_at":"2017-11-27T13:58:35.050893Z","published_by":null},"0.4.6":{"created_at":"2017-11-28T21:06:25.056164Z","published_by":null},"0.5.0":{"created_at":"2018-01-25T21:33:20.861735Z","published_by":null},"0.5.1":{"created_at":"2018-01-26T19:21:45.579931Z","published_by":null},"0.6.0":{"created_at":"2018-05-02T22:18:28.451677Z","published_by":null},"0.6.1":{"created_at":"2018-07-12T14:53:47.528085Z","published_by":null},"0.6.2":{"created_at":"2018-09-28T17:46:19.778984Z","published_by":null},"0.7.0":{"created_at":"2019-11-12T23:13:42.903054Z","published_by":2396},"0.7.1":{"created_at":"2020-06-28T17:39:46.431104Z","published_by":2396},"0.7.2":{"created_at":"2020-06-29T15:23:00.242030Z","published_by":2396},"0.8.0":{"created_at":"2020-06-29T15:42:54.387136Z","published_by":2396},"0.8.1":{"created_at":"2020-09-15T16:02:45.140604Z","published_by":2396},"0.8.2":{"created_at":"2020-10-14T15:26:16.613685Z","published_by":5946},"0.8.3":{"created_at":"2021-10-12T23:52:36.846692Z","published_by":2396},"0.8.4":{"created_at":"2023-04-03T00:54:44.158097Z","published_by":5946}},"metadata":{"description":"Bindings to Core Foundation for macOS","repository":"https://github.com/servo/core-foundation-rs"}},"encoding_rs":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.2.0":{"created_at":"2016-07-09T12:27:23.194308Z","published_by":null},"0.2.1":{"created_at":"2016-07-14T12:09:50.287208Z","published_by":null},"0.2.2":{"created_at":"2016-08-09T08:10:23.675990Z","published_by":null},"0.2.3":{"created_at":"2016-09-06T10:55:20.363236Z","published_by":null},"0.2.4":{"created_at":"2016-09-29T11:12:15.137652Z","published_by":null},"0.3.0":{"created_at":"2016-12-13T09:50:27.230768Z","published_by":null},"0.3.1":{"created_at":"2016-12-20T09:28:52.625203Z","published_by":null},"0.3.2":{"created_at":"2016-12-21T13:44:16.029243Z","published_by":null},"0.4.0":{"created_at":"2017-02-15T06:58:57.965089Z","published_by":null},"0.5.0":{"created_at":"2017-02-23T14:38:37.072982Z","published_by":null},"0.5.1":{"created_at":"2017-04-24T06:59:48.682547Z","published_by":null},"0.6.0":{"created_at":"2017-04-24T11:20:59.079588Z","published_by":null},"0.6.1":{"created_at":"2017-04-25T07:51:35.027180Z","published_by":null},"0.6.2":{"created_at":"2017-04-28T07:04:58.990360Z","published_by":null},"0.6.3":{"created_at":"2017-04-29T18:25:23.601557Z","published_by":null},"0.6.4":{"created_at":"2017-05-02T08:19:55.622416Z","published_by":null},"0.6.5":{"created_at":"2017-05-03T14:49:18.028195Z","published_by":null},"0.6.6":{"created_at":"2017-05-04T12:07:35.786775Z","published_by":null},"0.6.7":{"created_at":"2017-05-08T08:56:41.963183Z","published_by":null},"0.6.8":{"created_at":"2017-05-10T10:47:41.074611Z","published_by":null},"0.6.9":{"created_at":"2017-05-11T12:23:50.960095Z","published_by":null},"0.6.10":{"created_at":"2017-05-17T11:19:48.728465Z","published_by":null},"0.6.11":{"created_at":"2017-06-02T09:02:21.643509Z","published_by":null},"0.7.0":{"created_at":"2017-08-25T09:09:55.872295Z","published_by":null},"0.7.1":{"created_at":"2017-10-04T10:03:56.623108Z","published_by":null},"0.7.2":{"created_at":"2018-01-18T10:13:08.844602Z","published_by":null},"0.8.0":{"created_at":"2018-06-04T12:45:25.088559Z","published_by":null},"0.8.1":{"created_at":"2018-06-19T09:42:07.285893Z","published_by":null},"0.8.2":{"created_at":"2018-06-21T12:27:33.630773Z","published_by":null},"0.8.3":{"created_at":"2018-06-21T12:41:58.404695Z","published_by":null},"0.8.4":{"created_at":"2018-06-27T09:16:10.089654Z","published_by":null},"0.8.5":{"created_at":"2018-08-09T12:13:56.111743Z","published_by":null},"0.8.6":{"created_at":"2018-08-13T08:19:24.052951Z","published_by":null},"0.8.7":{"created_at":"2018-09-26T15:47:13.094016Z","published_by":null},"0.8.8":{"created_at":"2018-10-01T13:48:12.722197Z","published_by":null},"0.8.9":{"created_at":"2018-10-04T09:01:34.583814Z","published_by":null},"0.8.10":{"created_at":"2018-10-07T09:32:21.714137Z","published_by":null},"0.8.11":{"created_at":"2018-11-16T10:28:00.899220Z","published_by":null},"0.8.12":{"created_at":"2018-11-17T18:16:38.680497Z","published_by":null},"0.8.13":{"created_at":"2018-11-28T14:54:33.588769Z","published_by":null},"0.8.14":{"created_at":"2019-01-07T14:59:05.891519Z","published_by":null},"0.8.15":{"created_at":"2019-01-29T19:32:40.215473Z","published_by":null},"0.8.16":{"created_at":"2019-02-07T19:33:52.422473Z","published_by":null},"0.8.17":{"created_at":"2019-02-26T13:16:01.400759Z","published_by":4484},"0.8.18":{"created_at":"2019-09-05T14:48:59.481638Z","published_by":4484},"0.8.19":{"created_at":"2019-09-06T10:08:29.690895Z","published_by":4484},"0.8.20":{"created_at":"2019-09-16T11:27:27.198555Z","published_by":4484},"0.8.21":{"created_at":"2019-12-18T11:35:24.262713Z","published_by":4484},"0.8.22":{"created_at":"2019-12-19T12:33:26.650240Z","published_by":4484},"0.8.23":{"created_at":"2020-05-13T07:08:51.752848Z","published_by":4484},"0.8.24":{"created_at":"2020-08-24T08:18:08.607485Z","published_by":4484},"0.8.25":{"created_at":"2020-11-02T07:56:56.414948Z","published_by":4484},"0.8.26":{"created_at":"2020-11-02T14:25:39.913558Z","published_by":4484},"0.8.27":{"created_at":"2021-02-04T11:33:44.360358Z","published_by":4484},"0.8.28":{"created_at":"2021-02-04T14:04:58.119777Z","published_by":4484},"0.8.29":{"created_at":"2021-10-17T16:52:49.651992Z","published_by":4484},"0.8.30":{"created_at":"2021-12-08T09:59:02.226016Z","published_by":4484},"0.8.31":{"created_at":"2022-04-05T11:25:17.954856Z","published_by":4484},"0.8.32":{"created_at":"2023-02-01T16:22:32.645458Z","published_by":4484}},"metadata":{"description":"A Gecko-oriented implementation of the Encoding Standard","repository":"https://github.com/hsivonen/encoding_rs"}},"fastrand":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"1.0.0":{"created_at":"2020-05-27T16:21:53.648642Z","published_by":5153},"1.1.0":{"created_at":"2020-05-28T20:33:49.907610Z","published_by":5153},"1.2.0":{"created_at":"2020-06-17T19:06:00.306863Z","published_by":5153},"1.2.1":{"created_at":"2020-06-17T19:21:58.384865Z","published_by":5153},"1.2.2":{"created_at":"2020-06-17T19:29:58.103222Z","published_by":5153},"1.2.3":{"created_at":"2020-06-17T20:37:59.817341Z","published_by":5153},"1.2.4":{"created_at":"2020-06-18T11:11:13.787966Z","published_by":5153},"1.3.0":{"created_at":"2020-06-26T14:28:40.510402Z","published_by":5153},"1.3.1":{"created_at":"2020-06-26T14:29:21.648555Z","published_by":5153},"1.3.2":{"created_at":"2020-06-26T17:54:54.702076Z","published_by":5153},"1.3.3":{"created_at":"2020-07-07T07:53:42.142259Z","published_by":5153},"1.3.4":{"created_at":"2020-08-08T17:15:13.290494Z","published_by":5153},"1.3.5":{"created_at":"2020-09-01T05:27:37.743887Z","published_by":5153},"1.4.0":{"created_at":"2020-10-03T15:58:35.125079Z","published_by":5153},"1.4.1":{"created_at":"2021-04-24T09:11:31.088962Z","published_by":33035},"1.5.0":{"created_at":"2021-07-19T15:18:13.763152Z","published_by":33035},"1.6.0":{"created_at":"2021-12-19T06:01:52.884391Z","published_by":33035},"1.7.0":{"created_at":"2022-01-22T07:03:10.239333Z","published_by":33035},"1.8.0":{"created_at":"2022-07-23T17:16:38.234624Z","published_by":33035},"1.9.0":{"created_at":"2023-02-14T03:05:13.515607Z","published_by":33035}},"metadata":{"description":"A simple and fast random number generator","repository":"https://github.com/smol-rs/fastrand"}},"fnv":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"1.0.0":{"created_at":"2015-04-06T18:59:35.796343Z","published_by":null},"1.0.1":{"created_at":"2016-01-24T14:27:11.726176Z","published_by":null},"1.0.2":{"created_at":"2016-01-24T14:33:08.471748Z","published_by":null},"1.0.3":{"created_at":"2016-07-13T11:12:20.931135Z","published_by":null},"1.0.4":{"created_at":"2016-08-30T07:36:12.240356Z","published_by":null},"1.0.5":{"created_at":"2016-09-19T11:01:26.359390Z","published_by":null},"1.0.6":{"created_at":"2017-11-09T23:15:54.192466Z","published_by":null},"1.0.7":{"created_at":"2020-05-14T16:06:29.223002Z","published_by":1139}},"metadata":{"description":"Fowler–Noll–Vo hash function","repository":"https://github.com/servo/rust-fnv"}},"foreign-types":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2017-01-31T04:36:40.076916Z","published_by":null},"0.2.0":{"created_at":"2017-02-11T18:15:23.957926Z","published_by":null},"0.2.1":{"created_at":"2017-11-27T00:01:56.663735Z","published_by":null},"0.3.0":{"created_at":"2017-09-05T00:44:42.808399Z","published_by":null},"0.3.1":{"created_at":"2017-11-26T23:58:01.086851Z","published_by":null},"0.3.2":{"created_at":"2017-11-28T23:17:39.115409Z","published_by":null},"0.4.0":{"created_at":"2019-03-04T17:48:47.314192Z","published_by":5},"0.5.0":{"created_at":"2019-10-13T19:24:15.900127Z","published_by":5}},"metadata":{"description":"A framework for Rust wrappers over C APIs","repository":"https://github.com/sfackler/foreign-types"}},"foreign-types-shared":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2017-11-26T23:56:57.865334Z","published_by":null},"0.1.1":{"created_at":"2017-11-28T23:16:45.760735Z","published_by":null},"0.2.0":{"created_at":"2019-03-04T17:47:56.508619Z","published_by":5},"0.3.0":{"created_at":"2019-10-13T19:22:43.857631Z","published_by":5},"0.3.1":{"created_at":"2022-03-21T22:06:37.413498Z","published_by":5}},"metadata":{"description":"An internal crate used by foreign-types","repository":"https://github.com/sfackler/foreign-types"}},"form_urlencoded":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"1.0.0":{"created_at":"2020-06-19T22:23:03.447945Z","published_by":7},"1.0.1":{"created_at":"2021-02-19T08:57:10.500473Z","published_by":1139},"1.1.0":{"created_at":"2022-09-08T07:53:18.485220Z","published_by":72883}},"metadata":{"description":"Parser and serializer for the application/x-www-form-urlencoded syntax, as used by HTML forms.","repository":"https://github.com/servo/rust-url"}},"futures-channel":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.2.0-alpha":{"created_at":"2018-03-05T22:36:02.312563Z","published_by":null},"0.2.0-beta":{"created_at":"2018-03-20T22:52:35.687603Z","published_by":null},"0.2.0":{"created_at":"2018-04-06T19:03:24.951781Z","published_by":null},"0.2.1":{"created_at":"2018-04-19T18:56:32.913081Z","published_by":null},"0.3.0":{"created_at":"2019-11-06T18:19:59.623511Z","published_by":5149},"0.3.1":{"created_at":"2019-11-07T18:29:00.470079Z","published_by":5149},"0.3.2":{"created_at":"2020-02-04T00:17:12.232488Z","published_by":5149},"0.3.3":{"created_at":"2020-02-05T00:55:36.631985Z","published_by":5149},"0.3.4":{"created_at":"2020-02-07T02:02:33.782272Z","published_by":5149},"0.3.5":{"created_at":"2020-05-08T23:16:11.971290Z","published_by":5149},"0.3.6":{"created_at":"2020-10-05T18:41:12.238734Z","published_by":33035},"0.3.7":{"created_at":"2020-10-23T18:03:52.686614Z","published_by":33035},"0.3.8":{"created_at":"2020-11-09T18:15:56.184274Z","published_by":33035},"0.3.9":{"created_at":"2021-01-08T04:32:51.196168Z","published_by":33035},"0.3.10":{"created_at":"2021-01-13T11:37:24.150190Z","published_by":33035},"0.3.11":{"created_at":"2021-01-14T18:36:41.731314Z","published_by":33035},"0.3.12":{"created_at":"2021-01-15T12:00:02.958832Z","published_by":33035},"0.3.13":{"created_at":"2021-02-23T03:54:59.197932Z","published_by":33035},"0.3.14":{"created_at":"2021-04-10T07:01:25.803959Z","published_by":33035},"0.3.15":{"created_at":"2021-05-11T12:11:35.246959Z","published_by":33035},"0.3.16":{"created_at":"2021-07-23T19:04:37.790466Z","published_by":33035},"0.3.17":{"created_at":"2021-08-30T10:32:58.894073Z","published_by":33035},"0.3.18":{"created_at":"2021-11-23T02:27:35.459858Z","published_by":33035},"0.3.19":{"created_at":"2021-12-18T16:15:00.255131Z","published_by":33035},"0.3.20":{"created_at":"2022-02-06T08:36:05.371300Z","published_by":33035},"0.3.21":{"created_at":"2022-02-06T12:22:18.790056Z","published_by":33035},"0.3.22":{"created_at":"2022-08-14T12:22:43.354078Z","published_by":33035},"0.3.23":{"created_at":"2022-08-14T12:34:17.956567Z","published_by":33035},"0.3.24":{"created_at":"2022-08-29T13:38:18.985949Z","published_by":33035},"0.3.25":{"created_at":"2022-10-20T03:15:30.130701Z","published_by":33035},"0.3.26":{"created_at":"2023-01-30T16:08:43.585501Z","published_by":33035},"0.3.27":{"created_at":"2023-03-11T16:07:27.662166Z","published_by":33035},"0.3.28":{"created_at":"2023-03-30T17:15:22.399065Z","published_by":33035}},"metadata":{"description":"Channels for asynchronous communication using futures-rs.\n","repository":"https://github.com/rust-lang/futures-rs"}},"futures-core":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.2.0-alpha":{"created_at":"2018-03-05T22:32:59.053938Z","published_by":null},"0.2.0-beta":{"created_at":"2018-03-20T22:51:29.677242Z","published_by":null},"0.2.0":{"created_at":"2018-04-06T19:02:11.236635Z","published_by":null},"0.2.1":{"created_at":"2018-04-19T18:55:55.964143Z","published_by":null},"0.3.0":{"created_at":"2019-11-06T18:19:06.666056Z","published_by":5149},"0.3.1":{"created_at":"2019-11-07T18:28:07.637483Z","published_by":5149},"0.3.2":{"created_at":"2020-02-04T00:16:15.598808Z","published_by":5149},"0.3.3":{"created_at":"2020-02-05T00:54:50.242471Z","published_by":5149},"0.3.4":{"created_at":"2020-02-07T02:00:48.636849Z","published_by":5149},"0.3.5":{"created_at":"2020-05-08T23:14:17.325774Z","published_by":5149},"0.3.6":{"created_at":"2020-10-05T18:39:59.479334Z","published_by":33035},"0.3.7":{"created_at":"2020-10-23T18:02:13.548270Z","published_by":33035},"0.3.8":{"created_at":"2020-11-09T18:05:35.912488Z","published_by":33035},"0.3.9":{"created_at":"2021-01-08T04:30:32.178545Z","published_by":33035},"0.3.10":{"created_at":"2021-01-13T11:35:02.817757Z","published_by":33035},"0.3.11":{"created_at":"2021-01-14T18:34:20.982770Z","published_by":33035},"0.3.12":{"created_at":"2021-01-15T11:57:39.075046Z","published_by":33035},"0.3.13":{"created_at":"2021-02-23T03:52:39.988982Z","published_by":33035},"0.3.14":{"created_at":"2021-04-10T06:58:10.420510Z","published_by":33035},"0.3.15":{"created_at":"2021-05-11T12:08:18.162917Z","published_by":33035},"0.3.16":{"created_at":"2021-07-23T19:01:20.895936Z","published_by":33035},"0.3.17":{"created_at":"2021-08-30T10:29:42.738643Z","published_by":33035},"0.3.18":{"created_at":"2021-11-23T02:24:16.889999Z","published_by":33035},"0.3.19":{"created_at":"2021-12-18T16:11:42.318660Z","published_by":33035},"0.3.20":{"created_at":"2022-02-06T08:32:49.481338Z","published_by":33035},"0.3.21":{"created_at":"2022-02-06T12:19:02.629467Z","published_by":33035},"0.3.22":{"created_at":"2022-08-14T12:19:28.553655Z","published_by":33035},"0.3.23":{"created_at":"2022-08-14T12:31:02.674868Z","published_by":33035},"0.3.24":{"created_at":"2022-08-29T13:35:06.371021Z","published_by":33035},"0.3.25":{"created_at":"2022-10-20T03:12:17.224873Z","published_by":33035},"0.3.26":{"created_at":"2023-01-30T16:03:16.320794Z","published_by":33035},"0.3.27":{"created_at":"2023-03-11T16:03:42.526008Z","published_by":33035},"0.3.28":{"created_at":"2023-03-30T17:11:32.263730Z","published_by":33035}},"metadata":{"description":"The core traits and types in for the `futures` library.\n","repository":"https://github.com/rust-lang/futures-rs"}},"futures-sink":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.2.0-alpha":{"created_at":"2018-03-05T22:41:47.740444Z","published_by":null},"0.2.0-beta":{"created_at":"2018-03-20T22:53:22.416628Z","published_by":null},"0.2.0":{"created_at":"2018-04-06T19:03:48.664183Z","published_by":null},"0.2.1":{"created_at":"2018-04-19T18:58:09.952200Z","published_by":null},"0.3.0":{"created_at":"2019-11-06T18:19:49.969076Z","published_by":5149},"0.3.1":{"created_at":"2019-11-07T18:28:40.529421Z","published_by":5149},"0.3.2":{"created_at":"2020-02-04T00:16:37.301347Z","published_by":5149},"0.3.3":{"created_at":"2020-02-05T00:55:21.965137Z","published_by":5149},"0.3.4":{"created_at":"2020-02-07T02:02:15.300665Z","published_by":5149},"0.3.5":{"created_at":"2020-05-08T23:15:41.560509Z","published_by":5149},"0.3.6":{"created_at":"2020-10-05T18:40:31.858153Z","published_by":33035},"0.3.7":{"created_at":"2020-10-23T18:03:12.338832Z","published_by":33035},"0.3.8":{"created_at":"2020-11-09T18:06:01.446015Z","published_by":33035},"0.3.9":{"created_at":"2021-01-08T04:31:41.152021Z","published_by":33035},"0.3.10":{"created_at":"2021-01-13T11:36:12.053804Z","published_by":33035},"0.3.11":{"created_at":"2021-01-14T18:35:30.190082Z","published_by":33035},"0.3.12":{"created_at":"2021-01-15T11:58:51.331815Z","published_by":33035},"0.3.13":{"created_at":"2021-02-23T03:53:49.141548Z","published_by":33035},"0.3.14":{"created_at":"2021-04-10T06:59:47.566984Z","published_by":33035},"0.3.15":{"created_at":"2021-05-11T12:09:56.098242Z","published_by":33035},"0.3.16":{"created_at":"2021-07-23T19:02:58.018648Z","published_by":33035},"0.3.17":{"created_at":"2021-08-30T10:31:19.571094Z","published_by":33035},"0.3.18":{"created_at":"2021-11-23T02:25:55.180998Z","published_by":33035},"0.3.19":{"created_at":"2021-12-18T16:13:19.180097Z","published_by":33035},"0.3.20":{"created_at":"2022-02-06T08:34:26.039269Z","published_by":33035},"0.3.21":{"created_at":"2022-02-06T12:20:39.348520Z","published_by":33035},"0.3.22":{"created_at":"2022-08-14T12:21:04.742540Z","published_by":33035},"0.3.23":{"created_at":"2022-08-14T12:32:39.579735Z","published_by":33035},"0.3.24":{"created_at":"2022-08-29T13:36:41.945691Z","published_by":33035},"0.3.25":{"created_at":"2022-10-20T03:13:52.706910Z","published_by":33035},"0.3.26":{"created_at":"2023-01-30T16:05:22.203664Z","published_by":33035},"0.3.27":{"created_at":"2023-03-11T16:05:36.910813Z","published_by":33035},"0.3.28":{"created_at":"2023-03-30T17:13:24.474344Z","published_by":33035}},"metadata":{"description":"The asynchronous `Sink` trait for the futures-rs library.\n","repository":"https://github.com/rust-lang/futures-rs"}},"futures-task":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.0.0":{"created_at":"2019-07-29T02:21:47.224744Z","published_by":33035},"0.3.0":{"created_at":"2019-11-06T18:20:20.102203Z","published_by":5149},"0.3.1":{"created_at":"2019-11-07T18:27:29.983140Z","published_by":5149},"0.3.2":{"created_at":"2020-02-04T00:18:21.424649Z","published_by":5149},"0.3.3":{"created_at":"2020-02-05T00:56:39.775701Z","published_by":5149},"0.3.4":{"created_at":"2020-02-07T02:01:41.008580Z","published_by":5149},"0.3.5":{"created_at":"2020-05-08T23:14:39.892837Z","published_by":5149},"0.3.6":{"created_at":"2020-10-05T18:41:41.056220Z","published_by":33035},"0.3.7":{"created_at":"2020-10-23T18:03:43.144282Z","published_by":33035},"0.3.8":{"created_at":"2020-11-09T18:15:20.414929Z","published_by":33035},"0.3.9":{"created_at":"2021-01-08T04:32:15.822002Z","published_by":33035},"0.3.10":{"created_at":"2021-01-13T11:36:47.041491Z","published_by":33035},"0.3.11":{"created_at":"2021-01-14T18:36:05.648591Z","published_by":33035},"0.3.12":{"created_at":"2021-01-15T11:59:26.282231Z","published_by":33035},"0.3.13":{"created_at":"2021-02-23T03:54:23.609626Z","published_by":33035},"0.3.14":{"created_at":"2021-04-10T07:00:36.063304Z","published_by":33035},"0.3.15":{"created_at":"2021-05-11T12:10:44.978698Z","published_by":33035},"0.3.16":{"created_at":"2021-07-23T19:03:46.793419Z","published_by":33035},"0.3.17":{"created_at":"2021-08-30T10:32:08.353343Z","published_by":33035},"0.3.18":{"created_at":"2021-11-23T02:26:44.583544Z","published_by":33035},"0.3.19":{"created_at":"2021-12-18T16:14:07.825633Z","published_by":33035},"0.3.20":{"created_at":"2022-02-06T08:35:14.968952Z","published_by":33035},"0.3.21":{"created_at":"2022-02-06T12:21:28.040028Z","published_by":33035},"0.3.22":{"created_at":"2022-08-14T12:21:53.311445Z","published_by":33035},"0.3.23":{"created_at":"2022-08-14T12:33:27.832950Z","published_by":33035},"0.3.24":{"created_at":"2022-08-29T13:37:30.247291Z","published_by":33035},"0.3.25":{"created_at":"2022-10-20T03:14:41.107757Z","published_by":33035},"0.3.26":{"created_at":"2023-01-30T16:06:51.530877Z","published_by":33035},"0.3.27":{"created_at":"2023-03-11T16:06:28.618665Z","published_by":33035},"0.3.28":{"created_at":"2023-03-30T17:14:23.681288Z","published_by":33035}},"metadata":{"description":"Tools for working with tasks.\n","repository":"https://github.com/rust-lang/futures-rs"}},"futures-util":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.2.0-alpha":{"created_at":"2018-03-05T22:42:24.332661Z","published_by":null},"0.2.0-beta":{"created_at":"2018-03-20T22:54:56.609491Z","published_by":null},"0.2.0":{"created_at":"2018-04-06T19:04:44.655927Z","published_by":null},"0.2.1":{"created_at":"2018-04-19T18:58:36.349063Z","published_by":null},"0.3.0":{"created_at":"2019-11-06T18:21:36.278225Z","published_by":5149},"0.3.1":{"created_at":"2019-11-07T18:30:03.284614Z","published_by":5149},"0.3.2":{"created_at":"2020-02-04T00:18:58.967225Z","published_by":5149},"0.3.3":{"created_at":"2020-02-05T00:57:05.557265Z","published_by":5149},"0.3.4":{"created_at":"2020-02-07T02:03:11.651378Z","published_by":5149},"0.3.5":{"created_at":"2020-05-08T23:17:29.946517Z","published_by":5149},"0.3.6":{"created_at":"2020-10-05T18:47:33.531233Z","published_by":33035},"0.3.7":{"created_at":"2020-10-23T18:04:48.706192Z","published_by":33035},"0.3.8":{"created_at":"2020-11-09T18:17:24.446928Z","published_by":33035},"0.3.9":{"created_at":"2021-01-08T04:34:25.236094Z","published_by":33035},"0.3.10":{"created_at":"2021-01-13T11:38:58.748458Z","published_by":33035},"0.3.11":{"created_at":"2021-01-14T18:38:15.767225Z","published_by":33035},"0.3.12":{"created_at":"2021-01-15T12:01:39.302229Z","published_by":33035},"0.3.13":{"created_at":"2021-02-23T03:56:32.375310Z","published_by":33035},"0.3.14":{"created_at":"2021-04-10T07:03:25.623240Z","published_by":33035},"0.3.15":{"created_at":"2021-05-11T12:13:32.138031Z","published_by":33035},"0.3.16":{"created_at":"2021-07-23T19:06:33.647440Z","published_by":33035},"0.3.17":{"created_at":"2021-08-30T10:34:54.314174Z","published_by":33035},"0.3.18":{"created_at":"2021-11-23T02:29:31.998303Z","published_by":33035},"0.3.19":{"created_at":"2021-12-18T16:16:55.732648Z","published_by":33035},"0.3.20":{"created_at":"2022-02-06T08:38:00.884840Z","published_by":33035},"0.3.21":{"created_at":"2022-02-06T12:24:14.620328Z","published_by":33035},"0.3.22":{"created_at":"2022-08-14T12:24:30.204211Z","published_by":33035},"0.3.23":{"created_at":"2022-08-14T12:36:04.864073Z","published_by":33035},"0.3.24":{"created_at":"2022-08-29T13:40:04.978883Z","published_by":33035},"0.3.25":{"created_at":"2022-10-20T03:17:15.989237Z","published_by":33035},"0.3.26":{"created_at":"2023-01-30T16:11:23.147529Z","published_by":33035},"0.3.27":{"created_at":"2023-03-11T16:09:24.189152Z","published_by":33035},"0.3.28":{"created_at":"2023-03-30T17:17:27.139438Z","published_by":33035}},"metadata":{"description":"Common utilities and extension traits for the futures-rs library.\n","repository":"https://github.com/rust-lang/futures-rs"}},"h2":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.0.0":{"created_at":"2017-03-09T22:02:00.510015Z","published_by":null},"0.1.0":{"created_at":"2018-01-12T17:17:19.571697Z","published_by":null},"0.1.1":{"created_at":"2018-03-08T18:07:17.527359Z","published_by":null},"0.1.2":{"created_at":"2018-03-14T01:12:35.835270Z","published_by":null},"0.1.3":{"created_at":"2018-03-28T19:51:31.941648Z","published_by":null},"0.1.4":{"created_at":"2018-04-05T20:17:06.599901Z","published_by":null},"0.1.5":{"created_at":"2018-04-07T01:42:39.800633Z","published_by":null},"0.1.6":{"created_at":"2018-04-25T03:48:35.329829Z","published_by":null},"0.1.7":{"created_at":"2018-05-14T17:36:06.183985Z","published_by":null},"0.1.8":{"created_at":"2018-05-23T18:35:28.997146Z","published_by":null},"0.1.9":{"created_at":"2018-05-31T18:44:06.434684Z","published_by":null},"0.1.10":{"created_at":"2018-06-18T19:02:40.555092Z","published_by":null},"0.1.11":{"created_at":"2018-07-31T18:48:13.621573Z","published_by":null},"0.1.12":{"created_at":"2018-08-08T23:07:10.269354Z","published_by":null},"0.1.13":{"created_at":"2018-10-16T21:41:53.321599Z","published_by":null},"0.1.14":{"created_at":"2018-12-05T18:06:48.002485Z","published_by":null},"0.1.15":{"created_at":"2019-01-13T17:53:58.423476Z","published_by":null},"0.1.16":{"created_at":"2019-01-24T18:39:42.696816Z","published_by":null},"0.1.17":{"created_at":"2019-03-13T02:01:01.873247Z","published_by":359},"0.1.18":{"created_at":"2019-04-09T19:30:32.270488Z","published_by":359},"0.1.19":{"created_at":"2019-05-15T21:10:14.077364Z","published_by":359},"0.1.20":{"created_at":"2019-05-16T21:15:53.747297Z","published_by":359},"0.1.21":{"created_at":"2019-05-30T17:50:52.990101Z","published_by":359},"0.1.22":{"created_at":"2019-06-03T18:27:59.530414Z","published_by":359},"0.1.23":{"created_at":"2019-06-05T02:54:23.905918Z","published_by":359},"0.1.24":{"created_at":"2019-06-17T21:38:10.845763Z","published_by":359},"0.1.25":{"created_at":"2019-06-28T20:22:26.446970Z","published_by":359},"0.1.26":{"created_at":"2019-07-26T02:05:00.362308Z","published_by":359},"0.2.0-alpha.1":{"created_at":"2019-09-04T18:11:30.922817Z","published_by":359},"0.2.0-alpha.2":{"created_at":"2019-09-20T21:07:00.527073Z","published_by":359},"0.2.0-alpha.3":{"created_at":"2019-10-01T15:39:56.075216Z","published_by":359},"0.2.0":{"created_at":"2019-12-03T20:07:59.232497Z","published_by":359},"0.2.1":{"created_at":"2019-12-06T20:25:40.953742Z","published_by":359},"0.2.2":{"created_at":"2020-03-03T20:28:54.272385Z","published_by":359},"0.2.3":{"created_at":"2020-03-25T17:24:43.254080Z","published_by":359},"0.2.4":{"created_at":"2020-03-30T22:28:02.897719Z","published_by":359},"0.2.5":{"created_at":"2020-05-06T20:21:15.150824Z","published_by":359},"0.2.6":{"created_at":"2020-07-13T23:58:09.491316Z","published_by":359},"0.2.7":{"created_at":"2020-10-23T15:16:33.968362Z","published_by":359},"0.3.0":{"created_at":"2020-12-23T18:21:04.033946Z","published_by":359},"0.3.1":{"created_at":"2021-02-26T20:38:14.977122Z","published_by":359},"0.3.2":{"created_at":"2021-03-25T00:35:57.363149Z","published_by":359},"0.3.3":{"created_at":"2021-04-29T16:23:51.654927Z","published_by":359},"0.3.4":{"created_at":"2021-08-20T22:48:45.273259Z","published_by":359},"0.3.5":{"created_at":"2021-09-29T20:41:10.689676Z","published_by":359},"0.3.6":{"created_at":"2021-09-30T19:20:11.708971Z","published_by":359},"0.3.7":{"created_at":"2021-10-22T17:50:39.221924Z","published_by":359},"0.3.8":{"created_at":"2021-12-08T19:21:46.728968Z","published_by":359},"0.3.9":{"created_at":"2021-12-09T17:30:16.626924Z","published_by":359},"0.3.10":{"created_at":"2022-01-07T01:10:44.941963Z","published_by":359},"0.3.11":{"created_at":"2022-01-26T18:15:58.566663Z","published_by":359},"0.3.12":{"created_at":"2022-03-09T19:01:45.616730Z","published_by":359},"0.3.13":{"created_at":"2022-03-31T23:31:46.761880Z","published_by":359},"0.3.14":{"created_at":"2022-08-16T22:30:45.383145Z","published_by":359},"0.3.15":{"created_at":"2022-10-24T13:04:26.256635Z","published_by":359},"0.3.16":{"created_at":"2023-02-27T18:03:14.662720Z","published_by":359},"0.3.17":{"created_at":"2023-04-13T14:59:25.899122Z","published_by":359},"0.3.18":{"created_at":"2023-04-17T18:52:32.373107Z","published_by":359},"0.3.19":{"created_at":"2023-05-12T19:38:50.176225Z","published_by":359}},"metadata":{"description":"An HTTP/2 client and server","repository":"https://github.com/hyperium/h2"}},"hashbrown":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2018-10-29T14:28:15.767024Z","published_by":null},"0.1.1":{"created_at":"2018-10-30T20:07:38.295593Z","published_by":null},"0.1.2":{"created_at":"2018-10-31T03:06:32.191558Z","published_by":null},"0.1.3":{"created_at":"2018-11-01T22:08:44.039068Z","published_by":null},"0.1.4":{"created_at":"2018-11-04T00:33:34.769544Z","published_by":null},"0.1.5":{"created_at":"2018-11-08T20:43:18.760480Z","published_by":null},"0.1.6":{"created_at":"2018-11-16T23:45:47.927183Z","published_by":null},"0.1.7":{"created_at":"2018-12-05T03:10:10.795248Z","published_by":null},"0.1.8":{"created_at":"2019-01-14T03:09:31.993405Z","published_by":null},"0.2.0":{"created_at":"2019-04-02T17:59:55.856661Z","published_by":2915},"0.2.1":{"created_at":"2019-04-14T10:41:13.987568Z","published_by":2915},"0.2.2":{"created_at":"2019-04-16T12:44:58.526019Z","published_by":2915},"0.3.0":{"created_at":"2019-04-22T22:31:12.404350Z","published_by":2915},"0.3.1":{"created_at":"2019-05-30T10:49:36.177120Z","published_by":2915},"0.4.0":{"created_at":"2019-05-30T22:20:15.135839Z","published_by":2915},"0.5.0":{"created_at":"2019-06-12T16:44:56.883916Z","published_by":2915},"0.5.1":{"created_at":"2019-08-04T18:59:23.909985Z","published_by":2915},"0.6.0":{"created_at":"2019-08-13T11:08:31.011656Z","published_by":2915},"0.6.1":{"created_at":"2019-10-04T11:30:44.464505Z","published_by":2915},"0.6.2":{"created_at":"2019-10-23T19:46:37.063198Z","published_by":2915},"0.6.3":{"created_at":"2019-10-31T13:37:43.410089Z","published_by":2915},"0.7.0":{"created_at":"2020-01-31T01:48:33.392903Z","published_by":2915},"0.7.1":{"created_at":"2020-03-16T18:31:04.726535Z","published_by":2915},"0.7.2":{"created_at":"2020-04-27T12:55:15.809936Z","published_by":2915},"0.8.0":{"created_at":"2020-06-18T15:22:54.646086Z","published_by":2915},"0.8.1":{"created_at":"2020-07-16T21:42:05.386851Z","published_by":2915},"0.8.2":{"created_at":"2020-08-08T13:25:48.664422Z","published_by":2915},"0.9.0":{"created_at":"2020-09-03T18:43:51.307693Z","published_by":2915},"0.9.1":{"created_at":"2020-09-28T19:15:42.863104Z","published_by":2915},"0.10.0":{"created_at":"2021-01-16T21:03:36.484692Z","published_by":2915},"0.11.0":{"created_at":"2021-03-14T18:56:18.747669Z","published_by":2915},"0.11.1":{"created_at":"2021-03-20T04:17:29.104373Z","published_by":2915},"0.11.2":{"created_at":"2021-03-25T05:35:27.460640Z","published_by":2915},"0.12.0":{"created_at":"2022-01-17T01:23:25.647567Z","published_by":2915},"0.12.1":{"created_at":"2022-05-02T22:34:37.253539Z","published_by":2915},"0.12.2":{"created_at":"2022-07-08T23:59:25.124909Z","published_by":2915},"0.12.3":{"created_at":"2022-07-17T11:20:38.243573Z","published_by":2915},"0.13.0":{"created_at":"2022-11-10T01:28:29.463063Z","published_by":2915},"0.13.1":{"created_at":"2022-11-10T01:30:36.821250Z","published_by":2915},"0.13.2":{"created_at":"2023-01-12T00:19:12.957746Z","published_by":2915}},"metadata":{"description":"A Rust port of Google's SwissTable hash map","repository":"https://github.com/rust-lang/hashbrown"}},"hermit-abi":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2019-10-20T07:34:22.550962Z","published_by":29348},"0.1.1":{"created_at":"2019-10-20T07:40:46.858491Z","published_by":29348},"0.1.2":{"created_at":"2019-10-25T22:35:55.612858Z","published_by":29348},"0.1.3":{"created_at":"2019-10-26T15:35:56.023651Z","published_by":29348},"0.1.4":{"created_at":"2019-12-16T14:01:40.108623Z","published_by":29348},"0.1.5":{"created_at":"2019-12-16T14:12:00.325594Z","published_by":29348},"0.1.6":{"created_at":"2020-01-04T07:48:37.344118Z","published_by":29348},"0.1.7":{"created_at":"2020-02-17T10:14:09.569904Z","published_by":29348},"0.1.8":{"created_at":"2020-02-23T17:56:31.588220Z","published_by":29348},"0.1.9":{"created_at":"2020-03-29T21:36:07.577Z","published_by":29348},"0.1.10":{"created_at":"2020-03-30T05:26:02.266059Z","published_by":29348},"0.1.11":{"created_at":"2020-04-15T15:25:54.286675Z","published_by":29348},"0.1.12":{"created_at":"2020-04-26T09:55:21.350100Z","published_by":29348},"0.1.13":{"created_at":"2020-05-16T12:12:26.309430Z","published_by":29348},"0.1.14":{"created_at":"2020-06-13T20:44:32.357155Z","published_by":29348},"0.1.15":{"created_at":"2020-07-05T22:49:10.519980Z","published_by":29348},"0.1.16":{"created_at":"2020-09-21T20:52:16.691900Z","published_by":29348},"0.1.17":{"created_at":"2020-10-06T08:53:09.043883Z","published_by":29348},"0.1.18":{"created_at":"2021-01-17T08:00:44.882978Z","published_by":29348},"0.1.19":{"created_at":"2021-06-24T15:17:54.720028Z","published_by":29348},"0.1.20":{"created_at":"2021-12-09T22:14:01.464713Z","published_by":29348},"0.2.0":{"created_at":"2021-12-10T15:38:34.232869Z","published_by":29348},"0.2.1":{"created_at":"2022-06-11T21:24:21.553768Z","published_by":29348},"0.2.2":{"created_at":"2022-06-24T13:46:01.043011Z","published_by":29348},"0.2.3":{"created_at":"2022-06-25T19:42:29.495067Z","published_by":29348},"0.2.4":{"created_at":"2022-07-24T19:03:02.471106Z","published_by":29348},"0.2.5":{"created_at":"2022-07-31T17:01:28.655250Z","published_by":29348},"0.2.6":{"created_at":"2022-09-06T08:17:48.219058Z","published_by":91569},"0.3.0":{"created_at":"2023-01-26T19:36:25.935568Z","published_by":29348},"0.3.1":{"created_at":"2023-02-08T15:45:41.037455Z","published_by":91569}},"metadata":{"description":"Hermit system calls definitions.","repository":"https://github.com/hermitcore/rusty-hermit"}},"http":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.0.0-prealpha":{"created_at":"2014-11-20T23:30:38.837436Z","published_by":null},"0.1.0":{"created_at":"2017-09-08T16:22:45.221428Z","published_by":null},"0.1.1":{"created_at":"2017-10-09T17:12:12.521728Z","published_by":null},"0.1.2":{"created_at":"2017-11-30T04:45:57.621532Z","published_by":null},"0.1.3":{"created_at":"2017-12-11T19:24:18.698381Z","published_by":null},"0.1.4":{"created_at":"2018-01-05T06:22:55.730529Z","published_by":null},"0.1.5":{"created_at":"2018-02-28T20:40:38.751499Z","published_by":null},"0.1.6":{"created_at":"2018-06-13T22:39:40.787600Z","published_by":null},"0.1.7":{"created_at":"2018-06-23T01:22:47.838337Z","published_by":null},"0.1.8":{"created_at":"2018-07-23T18:45:29.552512Z","published_by":null},"0.1.9":{"created_at":"2018-08-07T05:28:34.472144Z","published_by":null},"0.1.10":{"created_at":"2018-08-08T18:42:46.089981Z","published_by":null},"0.1.11":{"created_at":"2018-09-05T20:14:01.843164Z","published_by":null},"0.1.12":{"created_at":"2018-09-08T00:47:33.307842Z","published_by":null},"0.1.13":{"created_at":"2018-09-14T17:45:40.246189Z","published_by":null},"0.1.14":{"created_at":"2018-11-21T21:37:27.595984Z","published_by":null},"0.1.15":{"created_at":"2019-01-22T19:27:25.997694Z","published_by":null},"0.1.16":{"created_at":"2019-02-19T20:36:36.611410Z","published_by":null},"0.1.17":{"created_at":"2019-04-05T17:13:14.823183Z","published_by":359},"0.1.18":{"created_at":"2019-07-26T17:38:54.864425Z","published_by":359},"0.1.19":{"created_at":"2019-10-15T18:46:25.074769Z","published_by":359},"0.1.20":{"created_at":"2019-11-26T18:27:09.067986Z","published_by":359},"0.1.21":{"created_at":"2019-12-02T19:19:27.440232Z","published_by":359},"0.2.0":{"created_at":"2019-12-02T22:04:52.914334Z","published_by":359},"0.2.1":{"created_at":"2020-03-25T19:07:37.219560Z","published_by":359},"0.2.2":{"created_at":"2020-12-15T00:11:26.570420Z","published_by":359},"0.2.3":{"created_at":"2021-01-08T18:43:49.345045Z","published_by":359},"0.2.4":{"created_at":"2021-04-07T20:58:42.071209Z","published_by":359},"0.2.5":{"created_at":"2021-09-21T17:37:42.508895Z","published_by":359},"0.2.6":{"created_at":"2021-12-30T23:06:33.563829Z","published_by":359},"0.2.7":{"created_at":"2022-04-28T19:18:09.701085Z","published_by":359},"0.2.8":{"created_at":"2022-06-06T22:07:00.715863Z","published_by":359},"0.2.9":{"created_at":"2023-02-17T17:11:01.408584Z","published_by":359}},"metadata":{"description":"A set of types for representing HTTP requests and responses.\n","repository":"https://github.com/hyperium/http"}},"http-body":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.0.0":{"created_at":"2019-04-04T18:06:00.061574Z","published_by":10},"0.1.0":{"created_at":"2019-05-07T17:34:28.515247Z","published_by":10},"0.2.0-alpha.1":{"created_at":"2019-08-27T16:50:55.424439Z","published_by":3959},"0.2.0-alpha.2":{"created_at":"2019-10-01T15:56:00.877360Z","published_by":359},"0.2.0-alpha.3":{"created_at":"2019-10-01T18:38:34.832097Z","published_by":359},"0.2.0":{"created_at":"2019-12-03T19:10:57.871658Z","published_by":359},"0.3.0":{"created_at":"2019-12-04T23:23:05.301976Z","published_by":359},"0.3.1":{"created_at":"2019-12-13T18:09:48.505280Z","published_by":359},"0.4.0":{"created_at":"2020-12-23T15:35:52.370274Z","published_by":359},"0.4.1":{"created_at":"2021-03-19T16:15:07.850006Z","published_by":12432},"0.4.2":{"created_at":"2021-05-08T14:07:18.435218Z","published_by":12432},"0.4.3":{"created_at":"2021-08-08T15:15:27.714359Z","published_by":12432},"0.4.4":{"created_at":"2021-10-22T15:43:44.317831Z","published_by":3959},"0.4.5":{"created_at":"2022-05-20T20:41:02.284648Z","published_by":3959},"1.0.0-rc.2":{"created_at":"2022-12-29T15:23:42.697238Z","published_by":359},"1.0.0-rc1":{"created_at":"2022-10-25T18:08:20.577034Z","published_by":3959}},"metadata":{"description":"Trait representing an asynchronous, streaming, HTTP request or response body.\n","repository":"https://github.com/hyperium/http-body"}},"httparse":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.0.1":{"created_at":"2015-02-20T18:34:43.468130Z","published_by":null},"0.0.2":{"created_at":"2015-03-11T19:13:55.834531Z","published_by":null},"0.0.3":{"created_at":"2015-03-13T17:41:24.601590Z","published_by":null},"0.0.4":{"created_at":"2015-03-20T01:40:40.362169Z","published_by":null},"0.0.5":{"created_at":"2015-03-21T15:29:49.810899Z","published_by":null},"0.0.6":{"created_at":"2015-04-01T04:11:13.620294Z","published_by":null},"0.1.0":{"created_at":"2015-04-03T04:43:35.734289Z","published_by":null},"0.1.1":{"created_at":"2015-04-19T03:30:09.179593Z","published_by":null},"0.1.2":{"created_at":"2015-05-13T21:15:50.054702Z","published_by":null},"0.1.3":{"created_at":"2015-05-27T23:07:26.576602Z","published_by":null},"0.1.4":{"created_at":"2015-06-08T19:52:26.040366Z","published_by":null},"0.1.5":{"created_at":"2015-07-06T20:09:47.409607Z","published_by":null},"0.1.6":{"created_at":"2015-08-17T18:23:39.236682Z","published_by":null},"1.0.0":{"created_at":"2015-09-16T21:34:50.300719Z","published_by":null},"1.1.0":{"created_at":"2015-12-08T17:14:52.780484Z","published_by":null},"1.1.1":{"created_at":"2016-01-04T23:18:36.695441Z","published_by":null},"1.1.2":{"created_at":"2016-03-31T16:49:42.537884Z","published_by":null},"1.2.0":{"created_at":"2016-11-07T22:27:41.846180Z","published_by":null},"1.2.1":{"created_at":"2016-12-04T19:02:13.924596Z","published_by":null},"1.2.2":{"created_at":"2017-04-19T00:24:18.444104Z","published_by":null},"1.2.3":{"created_at":"2017-05-29T02:34:20.058431Z","published_by":null},"1.2.4":{"created_at":"2018-01-17T02:12:39.286604Z","published_by":null},"1.2.5":{"created_at":"2018-06-22T18:25:54.589319Z","published_by":null},"1.3.0":{"created_at":"2018-06-22T18:36:18.343528Z","published_by":null},"1.3.1":{"created_at":"2018-06-22T21:11:02.050606Z","published_by":null},"1.3.2":{"created_at":"2018-06-28T19:18:11.632499Z","published_by":null},"1.3.3":{"created_at":"2018-09-27T19:44:27.985603Z","published_by":null},"1.3.4":{"created_at":"2019-07-03T21:52:54.662579Z","published_by":359},"1.3.5":{"created_at":"2021-02-01T19:33:04.883181Z","published_by":359},"1.3.6":{"created_at":"2021-04-07T20:41:39.738831Z","published_by":359},"1.4.0":{"created_at":"2021-04-16T18:21:41.952770Z","published_by":359},"1.4.1":{"created_at":"2021-05-10T20:25:29.745390Z","published_by":359},"1.5.0":{"created_at":"2021-08-18T23:22:44.090055Z","published_by":359},"1.5.1":{"created_at":"2021-08-19T17:45:18.845929Z","published_by":359},"1.6.0":{"created_at":"2022-02-08T18:13:03.992570Z","published_by":359},"1.7.0":{"created_at":"2022-04-11T17:13:22.464338Z","published_by":359},"1.7.1":{"created_at":"2022-04-26T22:40:57.311667Z","published_by":359},"1.8.0":{"created_at":"2022-08-30T21:49:42.923020Z","published_by":359}},"metadata":{"description":"A tiny, safe, speedy, zero-copy HTTP/1.x parser.","repository":"https://github.com/seanmonstar/httparse"}},"httpdate":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2016-10-15T10:48:40.752919Z","published_by":null},"0.2.0":{"created_at":"2016-12-24T14:45:32.649633Z","published_by":null},"0.2.1":{"created_at":"2017-03-04T21:01:08.633094Z","published_by":null},"0.3.0":{"created_at":"2017-09-06T13:44:45.048990Z","published_by":null},"0.3.1":{"created_at":"2018-03-04T18:58:31.205587Z","published_by":null},"0.3.2":{"created_at":"2018-05-02T16:11:36.654618Z","published_by":null},"1.0.0-alpha":{"created_at":"2021-03-29T19:32:43.301162Z","published_by":982},"1.0.0":{"created_at":"2021-03-31T10:38:33.106922Z","published_by":982},"1.0.1":{"created_at":"2021-05-24T16:43:54.882155Z","published_by":982},"1.0.2":{"created_at":"2021-11-17T11:33:29.000083Z","published_by":982}},"metadata":{"description":"HTTP date parsing and formatting","repository":"https://github.com/pyfisch/httpdate"}},"hyper":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.0.1":{"created_at":"2014-11-22T04:05:30.476341Z","published_by":null},"0.0.2":{"created_at":"2014-11-24T02:36:33.930314Z","published_by":null},"0.0.3":{"created_at":"2014-11-24T12:37:44.239764Z","published_by":null},"0.0.4":{"created_at":"2014-11-25T05:02:25.409594Z","published_by":null},"0.0.5":{"created_at":"2014-11-25T06:09:22.897745Z","published_by":null},"0.0.6":{"created_at":"2014-11-28T03:19:08.169618Z","published_by":null},"0.0.7":{"created_at":"2014-12-03T07:09:35.093121Z","published_by":null},"0.0.8":{"created_at":"2014-12-06T01:02:38.973871Z","published_by":null},"0.0.9":{"created_at":"2014-12-07T07:25:38.774119Z","published_by":null},"0.0.10":{"created_at":"2014-12-08T01:24:11.285099Z","published_by":null},"0.0.11":{"created_at":"2014-12-11T03:06:28.884651Z","published_by":null},"0.0.12":{"created_at":"2014-12-14T08:55:36.926095Z","published_by":null},"0.0.13":{"created_at":"2014-12-16T12:03:12.967203Z","published_by":null},"0.0.14":{"created_at":"2014-12-20T11:08:31.676181Z","published_by":null},"0.0.15":{"created_at":"2014-12-21T19:10:07.535134Z","published_by":null},"0.0.16":{"created_at":"2014-12-22T04:33:32.250549Z","published_by":null},"0.0.17":{"created_at":"2014-12-24T00:42:41.128486Z","published_by":null},"0.0.18":{"created_at":"2014-12-30T01:45:08.123145Z","published_by":null},"0.0.19":{"created_at":"2014-12-31T02:04:26.097840Z","published_by":null},"0.0.20":{"created_at":"2015-01-02T21:29:49.759406Z","published_by":null},"0.0.21":{"created_at":"2015-01-06T03:28:31.145581Z","published_by":null},"0.1.0":{"created_at":"2015-01-11T05:39:58.571700Z","published_by":null},"0.1.1":{"created_at":"2015-01-13T20:02:06.467386Z","published_by":null},"0.1.2":{"created_at":"2015-01-19T19:25:56.050058Z","published_by":null},"0.1.3":{"created_at":"2015-01-21T00:23:51.569689Z","published_by":null},"0.1.4":{"created_at":"2015-01-21T19:58:27.663524Z","published_by":null},"0.1.5":{"created_at":"2015-01-21T22:02:58.499346Z","published_by":null},"0.1.6":{"created_at":"2015-01-22T07:31:12.982175Z","published_by":null},"0.1.7":{"created_at":"2015-01-23T23:43:40.608415Z","published_by":null},"0.1.8":{"created_at":"2015-01-27T18:06:26.151467Z","published_by":null},"0.1.9":{"created_at":"2015-01-28T20:19:05.433528Z","published_by":null},"0.1.10":{"created_at":"2015-02-04T03:07:37.751754Z","published_by":null},"0.1.11":{"created_at":"2015-02-07T06:11:16.728611Z","published_by":null},"0.1.12":{"created_at":"2015-02-13T20:29:49.467245Z","published_by":null},"0.1.13":{"created_at":"2015-02-17T23:32:29.220614Z","published_by":null},"0.2.0":{"created_at":"2015-02-21T23:16:23.125798Z","published_by":null},"0.2.1":{"created_at":"2015-02-27T13:58:50.847172Z","published_by":null},"0.3.0":{"created_at":"2015-03-04T05:10:59.669258Z","published_by":null},"0.3.1":{"created_at":"2015-03-18T18:06:34.099771Z","published_by":null},"0.3.2":{"created_at":"2015-03-21T00:34:45.919284Z","published_by":null},"0.3.3":{"created_at":"2015-03-25T20:05:08.723547Z","published_by":null},"0.3.4":{"created_at":"2015-03-26T21:07:06.857824Z","published_by":null},"0.3.5":{"created_at":"2015-03-28T18:40:00.304522Z","published_by":null},"0.3.6":{"created_at":"2015-03-30T17:03:15.233688Z","published_by":null},"0.3.7":{"created_at":"2015-04-01T00:15:28.052389Z","published_by":null},"0.3.8":{"created_at":"2015-04-02T19:59:14.389172Z","published_by":null},"0.3.9":{"created_at":"2015-04-03T16:59:30.409213Z","published_by":null},"0.3.10":{"created_at":"2015-04-06T18:16:19.833141Z","published_by":null},"0.3.11":{"created_at":"2015-04-09T00:23:34.945758Z","published_by":null},"0.3.12":{"created_at":"2015-04-16T04:44:48.938645Z","published_by":null},"0.3.13":{"created_at":"2015-04-17T21:44:17.247516Z","published_by":null},"0.3.14":{"created_at":"2015-04-19T03:48:10.723958Z","published_by":null},"0.3.15":{"created_at":"2015-04-29T21:11:19.827945Z","published_by":null},"0.3.16":{"created_at":"2015-05-02T00:39:57.295285Z","published_by":null},"0.4.0":{"created_at":"2015-05-07T20:49:08.348573Z","published_by":null},"0.5.0":{"created_at":"2015-05-13T01:26:39.400032Z","published_by":null},"0.5.1":{"created_at":"2015-05-25T15:59:50.601391Z","published_by":null},"0.5.2":{"created_at":"2015-06-01T18:44:04.142581Z","published_by":null},"0.6.0":{"created_at":"2015-06-25T17:23:11.479879Z","published_by":null},"0.6.1":{"created_at":"2015-06-27T04:21:56.278051Z","published_by":null},"0.6.2":{"created_at":"2015-07-06T22:24:43.804229Z","published_by":null},"0.6.3":{"created_at":"2015-07-08T17:36:39.254398Z","published_by":null},"0.6.4":{"created_at":"2015-07-11T03:44:34.120500Z","published_by":null},"0.6.5":{"created_at":"2015-07-23T20:45:17.679394Z","published_by":null},"0.6.6":{"created_at":"2015-07-25T18:10:39.881337Z","published_by":null},"0.6.7":{"created_at":"2015-08-03T19:03:01.500874Z","published_by":null},"0.6.8":{"created_at":"2015-08-03T19:10:36.267378Z","published_by":null},"0.6.9":{"created_at":"2015-08-13T22:00:42.648932Z","published_by":null},"0.6.10":{"created_at":"2015-08-19T21:58:14.845105Z","published_by":null},"0.6.11":{"created_at":"2015-08-27T22:52:03.482276Z","published_by":null},"0.6.12":{"created_at":"2015-09-02T01:13:39.656725Z","published_by":null},"0.6.13":{"created_at":"2015-09-02T16:28:44.442509Z","published_by":null},"0.6.14":{"created_at":"2015-09-21T21:23:32.103777Z","published_by":null},"0.6.15":{"created_at":"2015-10-09T22:48:18.175916Z","published_by":null},"0.6.16":{"created_at":"2015-11-16T18:45:20.967037Z","published_by":null},"0.7.0":{"created_at":"2015-11-24T20:13:42.706792Z","published_by":null},"0.7.1":{"created_at":"2015-12-19T22:02:21.223049Z","published_by":null},"0.7.2":{"created_at":"2016-01-04T23:26:21.457637Z","published_by":null},"0.8.0":{"created_at":"2016-03-14T16:58:41.081524Z","published_by":null},"0.8.1":{"created_at":"2016-04-13T22:14:15.545325Z","published_by":null},"0.9.0":{"created_at":"2016-04-21T23:22:19.778499Z","published_by":null},"0.9.1":{"created_at":"2016-04-22T00:12:40.087439Z","published_by":null},"0.9.2":{"created_at":"2016-05-04T19:18:52.045186Z","published_by":null},"0.9.3":{"created_at":"2016-05-05T23:06:05.357205Z","published_by":null},"0.9.4":{"created_at":"2016-05-09T22:32:27.496119Z","published_by":null},"0.9.5":{"created_at":"2016-05-18T17:01:09.179031Z","published_by":null},"0.9.6":{"created_at":"2016-05-23T17:50:34.167888Z","published_by":null},"0.9.7":{"created_at":"2016-06-09T09:11:57.510360Z","published_by":null},"0.9.8":{"created_at":"2016-06-14T13:43:21.765878Z","published_by":null},"0.9.9":{"created_at":"2016-06-21T22:15:23.342662Z","published_by":null},"0.9.10":{"created_at":"2016-07-12T00:26:44.241219Z","published_by":null},"0.9.11":{"created_at":"2016-10-31T20:52:01.831609Z","published_by":null},"0.9.12":{"created_at":"2016-11-09T19:07:19.134245Z","published_by":null},"0.9.13":{"created_at":"2016-11-28T01:55:26.055680Z","published_by":null},"0.9.14":{"created_at":"2016-12-12T18:24:21.063691Z","published_by":null},"0.9.15":{"created_at":"2017-01-23T19:30:33.247948Z","published_by":null},"0.9.17":{"created_at":"2017-01-25T18:14:48.945990Z","published_by":null},"0.9.18":{"created_at":"2017-01-30T18:12:30.568041Z","published_by":null},"0.10.0":{"created_at":"2017-01-11T19:49:26.019218Z","published_by":null},"0.10.1":{"created_at":"2017-01-23T19:31:40.964485Z","published_by":null},"0.10.2":{"created_at":"2017-01-23T19:47:02.584729Z","published_by":null},"0.10.3":{"created_at":"2017-01-30T19:28:16.108166Z","published_by":null},"0.10.4":{"created_at":"2017-01-31T22:39:46.507534Z","published_by":null},"0.10.5":{"created_at":"2017-03-01T19:15:26.649546Z","published_by":null},"0.10.6":{"created_at":"2017-04-05T17:13:18.905389Z","published_by":null},"0.10.7":{"created_at":"2017-04-08T21:24:29.796701Z","published_by":null},"0.10.8":{"created_at":"2017-04-11T16:47:25.220546Z","published_by":null},"0.10.9":{"created_at":"2017-04-19T19:28:01.851584Z","published_by":null},"0.10.10":{"created_at":"2017-05-09T06:04:44.054053Z","published_by":null},"0.10.11":{"created_at":"2017-05-29T18:05:39.051263Z","published_by":null},"0.10.12":{"created_at":"2017-06-06T20:21:22.885816Z","published_by":null},"0.10.13":{"created_at":"2017-08-24T00:19:47.247117Z","published_by":null},"0.10.14":{"created_at":"2018-10-24T19:17:59.173056Z","published_by":null},"0.10.15":{"created_at":"2018-10-25T22:22:15.251141Z","published_by":null},"0.10.16":{"created_at":"2019-04-26T18:35:31.890250Z","published_by":359},"0.11.0":{"created_at":"2017-06-13T20:28:20.538484Z","published_by":null},"0.11.1":{"created_at":"2017-07-03T22:22:06.212909Z","published_by":null},"0.11.2":{"created_at":"2017-07-27T21:52:55.002501Z","published_by":null},"0.11.3":{"created_at":"2017-09-29T00:10:54.164429Z","published_by":null},"0.11.4":{"created_at":"2017-09-29T05:03:32.542669Z","published_by":null},"0.11.5":{"created_at":"2017-10-02T22:47:29.812618Z","published_by":null},"0.11.6":{"created_at":"2017-10-03T01:23:09.005503Z","published_by":null},"0.11.7":{"created_at":"2017-11-14T21:59:09.550862Z","published_by":null},"0.11.8":{"created_at":"2017-12-07T01:17:14.397671Z","published_by":null},"0.11.9":{"created_at":"2017-12-10T03:05:12.164964Z","published_by":null},"0.11.10":{"created_at":"2017-12-26T23:26:43.392677Z","published_by":null},"0.11.11":{"created_at":"2018-01-05T20:57:42.831794Z","published_by":null},"0.11.12":{"created_at":"2018-01-08T18:59:43.213567Z","published_by":null},"0.11.13":{"created_at":"2018-01-12T19:49:01.691581Z","published_by":null},"0.11.14":{"created_at":"2018-01-16T22:21:47.971396Z","published_by":null},"0.11.15":{"created_at":"2018-01-22T20:19:37.136221Z","published_by":null},"0.11.16":{"created_at":"2018-01-30T21:49:19.883858Z","published_by":null},"0.11.17":{"created_at":"2018-02-06T00:51:15.988679Z","published_by":null},"0.11.18":{"created_at":"2018-02-07T22:03:07.459759Z","published_by":null},"0.11.19":{"created_at":"2018-02-21T21:44:56.212388Z","published_by":null},"0.11.20":{"created_at":"2018-02-26T23:09:06.401180Z","published_by":null},"0.11.21":{"created_at":"2018-02-28T23:18:21.449505Z","published_by":null},"0.11.22":{"created_at":"2018-03-07T20:58:03.048572Z","published_by":null},"0.11.23":{"created_at":"2018-03-22T21:33:58.294511Z","published_by":null},"0.11.24":{"created_at":"2018-03-23T00:09:15.187163Z","published_by":null},"0.11.25":{"created_at":"2018-04-04T19:34:14.155827Z","published_by":null},"0.11.26":{"created_at":"2018-05-05T21:29:07.294398Z","published_by":null},"0.11.27":{"created_at":"2018-05-16T19:03:23.143009Z","published_by":null},"0.12.0":{"created_at":"2018-06-01T22:49:07.267079Z","published_by":null},"0.12.1":{"created_at":"2018-06-05T00:12:27.230016Z","published_by":null},"0.12.2":{"created_at":"2018-06-19T19:57:14.862991Z","published_by":null},"0.12.3":{"created_at":"2018-06-25T20:31:04.855526Z","published_by":null},"0.12.4":{"created_at":"2018-06-28T23:23:10.953858Z","published_by":null},"0.12.5":{"created_at":"2018-06-29T03:55:24.327440Z","published_by":null},"0.12.6":{"created_at":"2018-07-12T00:21:52.996628Z","published_by":null},"0.12.7":{"created_at":"2018-07-23T16:53:05.304119Z","published_by":null},"0.12.8":{"created_at":"2018-08-10T21:44:12.788494Z","published_by":null},"0.12.9":{"created_at":"2018-08-29T00:33:02.905108Z","published_by":null},"0.12.10":{"created_at":"2018-09-15T00:45:11.034662Z","published_by":null},"0.12.11":{"created_at":"2018-09-28T20:06:36.584738Z","published_by":null},"0.12.12":{"created_at":"2018-10-16T17:23:41.855799Z","published_by":null},"0.12.13":{"created_at":"2018-10-26T20:05:48.100952Z","published_by":null},"0.12.14":{"created_at":"2018-11-07T19:03:30.573086Z","published_by":null},"0.12.15":{"created_at":"2018-11-20T20:56:52.027044Z","published_by":null},"0.12.16":{"created_at":"2018-11-22T00:48:25.902198Z","published_by":null},"0.12.17":{"created_at":"2018-12-06T01:03:08.627345Z","published_by":null},"0.12.18":{"created_at":"2018-12-12T00:16:09.851478Z","published_by":null},"0.12.19":{"created_at":"2018-12-18T20:49:10.841792Z","published_by":null},"0.12.20":{"created_at":"2019-01-07T23:17:51.720788Z","published_by":null},"0.12.21":{"created_at":"2019-01-15T18:15:15.834116Z","published_by":null},"0.12.22":{"created_at":"2019-01-23T19:40:04.816759Z","published_by":null},"0.12.23":{"created_at":"2019-01-24T19:23:19.593590Z","published_by":null},"0.12.24":{"created_at":"2019-02-11T20:12:12.010181Z","published_by":null},"0.12.25":{"created_at":"2019-03-01T23:07:00.039354Z","published_by":359},"0.12.26":{"created_at":"2019-04-09T19:55:28.625495Z","published_by":359},"0.12.27":{"created_at":"2019-04-10T16:46:02.770284Z","published_by":359},"0.12.28":{"created_at":"2019-04-29T23:22:27.012547Z","published_by":359},"0.12.29":{"created_at":"2019-05-16T22:22:24.350003Z","published_by":359},"0.12.30":{"created_at":"2019-06-14T20:44:37.056384Z","published_by":359},"0.12.31":{"created_at":"2019-06-25T23:10:54.489389Z","published_by":359},"0.12.32":{"created_at":"2019-07-08T23:19:24.819295Z","published_by":359},"0.12.33":{"created_at":"2019-07-15T18:19:13.302541Z","published_by":359},"0.12.34":{"created_at":"2019-09-04T21:46:58.773153Z","published_by":359},"0.12.35":{"created_at":"2019-09-13T18:14:54.917747Z","published_by":359},"0.12.36":{"created_at":"2021-02-18T03:02:44.417073Z","published_by":359},"0.13.0-alpha.1":{"created_at":"2019-09-04T18:51:23.212349Z","published_by":359},"0.13.0-alpha.2":{"created_at":"2019-09-24T19:25:25.368076Z","published_by":359},"0.13.0-alpha.3":{"created_at":"2019-10-01T17:31:03.723266Z","published_by":359},"0.13.0-alpha.4":{"created_at":"2019-10-01T18:45:51.763305Z","published_by":359},"0.13.0":{"created_at":"2019-12-10T17:47:39.177466Z","published_by":359},"0.13.1":{"created_at":"2019-12-16T19:45:49.406299Z","published_by":359},"0.13.2":{"created_at":"2020-01-29T20:44:32.090299Z","published_by":359},"0.13.3":{"created_at":"2020-03-03T23:36:29.868576Z","published_by":359},"0.13.4":{"created_at":"2020-03-20T23:15:13.806033Z","published_by":359},"0.13.5":{"created_at":"2020-04-17T19:31:42.577549Z","published_by":359},"0.13.6":{"created_at":"2020-05-29T18:56:44.805609Z","published_by":359},"0.13.7":{"created_at":"2020-07-14T00:05:45.703827Z","published_by":359},"0.13.8":{"created_at":"2020-09-18T17:46:43.222540Z","published_by":359},"0.13.9":{"created_at":"2020-11-02T23:22:54.974700Z","published_by":359},"0.13.10":{"created_at":"2021-02-05T22:48:09.365753Z","published_by":359},"0.14.0":{"created_at":"2020-12-23T18:59:39.569975Z","published_by":359},"0.14.1":{"created_at":"2020-12-23T21:02:38.522609Z","published_by":359},"0.14.2":{"created_at":"2020-12-29T18:53:37.679473Z","published_by":359},"0.14.3":{"created_at":"2021-02-05T22:49:46.143545Z","published_by":359},"0.14.4":{"created_at":"2021-02-05T23:56:50.676803Z","published_by":359},"0.14.5":{"created_at":"2021-03-26T19:04:38.993191Z","published_by":359},"0.14.6":{"created_at":"2021-04-21T23:20:26.579756Z","published_by":359},"0.14.7":{"created_at":"2021-04-22T16:53:35.098211Z","published_by":359},"0.14.8":{"created_at":"2021-05-25T15:13:54.020681Z","published_by":359},"0.14.9":{"created_at":"2021-06-07T19:53:39.378190Z","published_by":359},"0.14.10":{"created_at":"2021-07-07T18:34:07.364319Z","published_by":359},"0.14.11":{"created_at":"2021-07-21T22:07:04.258255Z","published_by":359},"0.14.12":{"created_at":"2021-08-24T23:25:44.040063Z","published_by":359},"0.14.13":{"created_at":"2021-09-16T17:26:21.771917Z","published_by":359},"0.14.14":{"created_at":"2021-10-22T16:54:06.087705Z","published_by":359},"0.14.15":{"created_at":"2021-11-16T19:57:40.610880Z","published_by":359},"0.14.16":{"created_at":"2021-12-09T18:16:13.150236Z","published_by":359},"0.14.17":{"created_at":"2022-02-10T20:50:36.015499Z","published_by":359},"0.14.18":{"created_at":"2022-03-22T21:13:03.602124Z","published_by":359},"0.14.19":{"created_at":"2022-05-27T19:07:13.028047Z","published_by":359},"0.14.20":{"created_at":"2022-07-07T21:33:09.265432Z","published_by":359},"0.14.21":{"created_at":"2022-10-31T13:38:50.795729Z","published_by":359},"0.14.22":{"created_at":"2022-10-31T17:38:08.446594Z","published_by":359},"0.14.23":{"created_at":"2022-11-07T19:18:41.366924Z","published_by":359},"0.14.24":{"created_at":"2023-02-02T14:40:26.245712Z","published_by":359},"0.14.25":{"created_at":"2023-03-10T18:36:16.030688Z","published_by":359},"0.14.26":{"created_at":"2023-04-13T20:10:42.655568Z","published_by":359},"1.0.0-rc.1":{"created_at":"2022-10-26T14:17:25.061783Z","published_by":359},"1.0.0-rc.2":{"created_at":"2022-12-29T20:01:49.622019Z","published_by":359},"1.0.0-rc.3":{"created_at":"2023-02-23T18:47:11.500827Z","published_by":359}},"metadata":{"description":"A fast and correct HTTP library.","repository":"https://github.com/hyperium/hyper"}},"hyper-tls":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.0.0":{"created_at":"2017-01-04T22:41:13.366514Z","published_by":null},"0.1.0":{"created_at":"2017-06-14T18:11:59.599222Z","published_by":null},"0.1.1":{"created_at":"2017-06-21T22:14:41.728039Z","published_by":null},"0.1.2":{"created_at":"2017-06-28T17:51:44.485881Z","published_by":null},"0.1.3":{"created_at":"2018-03-18T16:15:55.780732Z","published_by":null},"0.1.4":{"created_at":"2018-07-31T20:27:21.946231Z","published_by":null},"0.2.0":{"created_at":"2018-06-01T22:58:04.018681Z","published_by":null},"0.2.1":{"created_at":"2018-06-11T22:47:04.084976Z","published_by":null},"0.3.0":{"created_at":"2018-06-26T18:32:00.851761Z","published_by":null},"0.3.1":{"created_at":"2018-10-02T18:51:58.953118Z","published_by":null},"0.3.2":{"created_at":"2019-03-19T19:57:26.223290Z","published_by":359},"0.4.0-alpha.1":{"created_at":"2019-09-04T22:22:54.621944Z","published_by":359},"0.4.0-alpha.2":{"created_at":"2019-09-24T19:43:26.740364Z","published_by":359},"0.4.0-alpha.3":{"created_at":"2019-10-01T18:17:24.665042Z","published_by":359},"0.4.0-alpha.4":{"created_at":"2019-10-01T18:52:17.435484Z","published_by":359},"0.4.0":{"created_at":"2019-12-10T18:01:31.900541Z","published_by":359},"0.4.1":{"created_at":"2020-01-09T20:37:17.261316Z","published_by":359},"0.4.2":{"created_at":"2020-07-06T17:21:56.444179Z","published_by":359},"0.4.3":{"created_at":"2020-07-06T21:21:39.155712Z","published_by":359},"0.5.0":{"created_at":"2020-12-29T20:36:06.738971Z","published_by":359}},"metadata":{"description":"Default TLS implementation for use with hyper","repository":"https://github.com/hyperium/hyper-tls"}},"idna":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.0.0":{"created_at":"2016-03-27T08:55:20.657552Z","published_by":null},"0.1.0":{"created_at":"2016-03-30T17:06:12.360066Z","published_by":null},"0.1.1":{"created_at":"2017-04-03T13:00:49.534250Z","published_by":null},"0.1.2":{"created_at":"2017-05-22T17:16:21.883514Z","published_by":null},"0.1.4":{"created_at":"2017-07-13T16:22:02.314938Z","published_by":null},"0.1.5":{"created_at":"2018-07-06T12:39:51.583851Z","published_by":null},"0.2.0":{"created_at":"2019-07-23T15:37:40.786100Z","published_by":7},"0.2.1":{"created_at":"2021-02-05T10:31:26.746278Z","published_by":72883},"0.2.2":{"created_at":"2021-02-18T08:44:37.521040Z","published_by":72883},"0.2.3":{"created_at":"2021-04-16T09:17:24.371295Z","published_by":7},"0.3.0":{"created_at":"2022-09-08T07:54:22.951729Z","published_by":72883}},"metadata":{"description":"IDNA (Internationalizing Domain Names in Applications) and Punycode.","repository":"https://github.com/servo/rust-url/"}},"indexmap":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.0.1":{"created_at":"2018-01-30T17:59:48.053105Z","published_by":null},"0.4.1":{"created_at":"2018-02-14T08:50:11.172473Z","published_by":null},"1.0.0":{"created_at":"2018-03-11T11:27:37.643955Z","published_by":null},"1.0.1":{"created_at":"2018-03-24T20:13:26.315449Z","published_by":null},"1.0.2":{"created_at":"2018-10-22T18:18:24.889373Z","published_by":null},"1.1.0":{"created_at":"2019-08-20T18:29:26.656475Z","published_by":356},"1.2.0":{"created_at":"2019-09-08T12:10:57.910984Z","published_by":356},"1.3.0":{"created_at":"2019-10-18T19:44:57.006109Z","published_by":356},"1.3.1":{"created_at":"2020-01-15T19:46:44.988096Z","published_by":539},"1.3.2":{"created_at":"2020-02-05T18:47:39.602197Z","published_by":539},"1.4.0":{"created_at":"2020-06-01T18:08:09.650523Z","published_by":356},"1.5.0":{"created_at":"2020-07-18T00:14:59.912481Z","published_by":539},"1.5.1":{"created_at":"2020-08-08T00:01:53.175065Z","published_by":539},"1.5.2":{"created_at":"2020-09-01T20:04:48.520835Z","published_by":539},"1.6.0":{"created_at":"2020-09-05T21:30:08.495419Z","published_by":539},"1.6.1":{"created_at":"2020-12-15T01:05:12.617816Z","published_by":539},"1.6.2":{"created_at":"2021-03-05T21:33:20.709682Z","published_by":539},"1.7.0":{"created_at":"2021-06-29T18:17:22.066045Z","published_by":539},"1.8.0":{"created_at":"2022-01-07T20:01:55.230289Z","published_by":539},"1.8.1":{"created_at":"2022-03-29T22:52:06.362595Z","published_by":539},"1.8.2":{"created_at":"2022-05-28T00:00:09.501594Z","published_by":539},"1.9.0":{"created_at":"2022-06-17T00:31:01.072834Z","published_by":539},"1.9.1":{"created_at":"2022-06-21T18:03:56.663563Z","published_by":539},"1.9.2":{"created_at":"2022-11-17T21:24:58.069387Z","published_by":539},"1.9.3":{"created_at":"2023-03-24T23:54:22.153441Z","published_by":539}},"metadata":{"description":"A hash table with consistent order and fast iteration.","repository":"https://github.com/bluss/indexmap"}},"instant":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2019-04-09T08:42:23.290642Z","published_by":86},"0.1.1":{"created_at":"2019-04-09T10:51:28.620283Z","published_by":86},"0.1.2":{"created_at":"2019-10-01T19:34:27.681701Z","published_by":86},"0.1.3":{"created_at":"2020-04-24T19:59:12.242336Z","published_by":86},"0.1.4":{"created_at":"2020-05-18T21:31:43.280893Z","published_by":86},"0.1.5":{"created_at":"2020-06-28T14:42:21.516403Z","published_by":86},"0.1.6":{"created_at":"2020-07-05T19:38:18.822721Z","published_by":86},"0.1.7":{"created_at":"2020-09-19T18:00:38.705113Z","published_by":86},"0.1.8":{"created_at":"2020-10-24T15:19:22.966936Z","published_by":86},"0.1.9":{"created_at":"2020-11-19T14:23:20.609017Z","published_by":86},"0.1.10":{"created_at":"2021-07-11T15:26:29.582846Z","published_by":86},"0.1.11":{"created_at":"2021-09-23T07:53:18.224347Z","published_by":86},"0.1.12":{"created_at":"2021-10-18T08:31:56.199350Z","published_by":86}},"metadata":{"description":"A partial replacement for std::time::Instant that works on WASM too.","repository":"https://github.com/sebcrozet/instant"}},"ipnet":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2017-08-14T20:09:09.629621Z","published_by":null},"0.11.0":{"created_at":"2017-08-15T05:33:40.661063Z","published_by":null},"0.11.1":{"created_at":"2017-08-15T05:42:43.906125Z","published_by":null},"0.12.0":{"created_at":"2017-08-15T22:14:05.783265Z","published_by":null},"0.13.0":{"created_at":"2017-08-17T01:21:29.791296Z","published_by":null},"0.13.1":{"created_at":"2017-08-17T02:11:58.377330Z","published_by":null},"0.13.2":{"created_at":"2017-08-17T06:35:03.355767Z","published_by":null},"0.13.3":{"created_at":"2017-08-17T20:01:20.715270Z","published_by":null},"0.14.0":{"created_at":"2017-08-17T22:50:24.757033Z","published_by":null},"0.15.0":{"created_at":"2017-08-20T01:01:43.499967Z","published_by":null},"0.15.1":{"created_at":"2017-08-20T02:15:45.531042Z","published_by":null},"0.15.2":{"created_at":"2017-08-20T02:38:08.560389Z","published_by":null},"0.15.3":{"created_at":"2017-08-20T05:25:21.793723Z","published_by":null},"0.16.0":{"created_at":"2017-08-20T05:43:50.709728Z","published_by":null},"0.16.1":{"created_at":"2017-08-22T02:11:52.092632Z","published_by":null},"0.16.2":{"created_at":"2017-08-22T03:47:23.968093Z","published_by":null},"0.17.0":{"created_at":"2017-08-22T06:32:39.166744Z","published_by":null},"0.17.1":{"created_at":"2017-08-22T06:36:22.931658Z","published_by":null},"0.18.0":{"created_at":"2017-08-23T01:10:16.915177Z","published_by":null},"0.18.1":{"created_at":"2017-08-23T02:14:51.296503Z","published_by":null},"0.18.2":{"created_at":"2017-08-23T04:52:03.060537Z","published_by":null},"0.18.3":{"created_at":"2017-08-23T05:00:36.307761Z","published_by":null},"0.19.0":{"created_at":"2017-08-23T05:51:23.644039Z","published_by":null},"0.20.0":{"created_at":"2017-08-28T01:11:36.730627Z","published_by":null},"0.20.1":{"created_at":"2017-08-28T01:14:12.461450Z","published_by":null},"0.21.0":{"created_at":"2017-08-28T02:00:41.915624Z","published_by":null},"0.21.1":{"created_at":"2017-08-28T05:05:56.120842Z","published_by":null},"0.22.0":{"created_at":"2017-08-28T22:58:48.172133Z","published_by":null},"0.23.0":{"created_at":"2017-08-28T23:14:03.794423Z","published_by":null},"0.23.1":{"created_at":"2017-08-31T19:51:08.847170Z","published_by":null},"0.24.0":{"created_at":"2017-08-31T22:45:55.054882Z","published_by":null},"0.24.1":{"created_at":"2017-08-31T22:53:43.777327Z","published_by":null},"0.24.2":{"created_at":"2017-08-31T22:59:51.666855Z","published_by":null},"0.24.3":{"created_at":"2017-09-01T02:20:12.023137Z","published_by":null},"0.25.0":{"created_at":"2017-09-01T06:04:55.062564Z","published_by":null},"0.25.1":{"created_at":"2017-09-01T06:10:18.679245Z","published_by":null},"0.26.0":{"created_at":"2017-09-02T05:35:04.791440Z","published_by":null},"0.26.1":{"created_at":"2017-09-02T05:46:28.303670Z","published_by":null},"0.26.2":{"created_at":"2017-09-03T01:16:24.131778Z","published_by":null},"0.26.3":{"created_at":"2017-09-04T03:19:28.701100Z","published_by":null},"0.26.4":{"created_at":"2017-09-04T03:32:25.770843Z","published_by":null},"0.26.5":{"created_at":"2017-09-12T21:56:11.284490Z","published_by":null},"0.27.0":{"created_at":"2017-09-14T02:27:21.166178Z","published_by":null},"0.28.0":{"created_at":"2017-09-21T04:19:38.153844Z","published_by":null},"0.28.1":{"created_at":"2017-09-21T04:45:25.197158Z","published_by":null},"1.0.0-rc1":{"created_at":"2017-10-02T22:03:20.391702Z","published_by":null},"1.0.0":{"created_at":"2017-10-03T18:50:15.058977Z","published_by":null},"1.1.0":{"created_at":"2018-04-13T23:57:46.520122Z","published_by":null},"1.2.0":{"created_at":"2018-04-17T19:36:44.200064Z","published_by":null},"1.2.1":{"created_at":"2018-06-07T02:17:23.584046Z","published_by":null},"2.0.0-rc1":{"created_at":"2018-07-27T00:15:25.205237Z","published_by":null},"2.0.0":{"created_at":"2018-08-22T01:56:34.991708Z","published_by":null},"2.0.1":{"created_at":"2019-10-12T22:16:23.793455Z","published_by":8197},"2.1.0":{"created_at":"2019-11-08T20:56:26.698541Z","published_by":8197},"2.2.0":{"created_at":"2020-02-02T22:41:48.370500Z","published_by":8197},"2.3.0":{"created_at":"2020-03-16T01:14:18.038457Z","published_by":8197},"2.3.1":{"created_at":"2021-06-15T01:48:53.038603Z","published_by":8197},"2.4.0":{"created_at":"2022-03-06T19:57:18.564464Z","published_by":8197},"2.5.0":{"created_at":"2022-04-16T23:21:27.952464Z","published_by":8197},"2.5.1":{"created_at":"2022-11-06T19:48:30.737276Z","published_by":8197},"2.6.0":{"created_at":"2022-12-07T22:23:08.678065Z","published_by":8197},"2.7.0":{"created_at":"2022-12-11T18:33:36.072246Z","published_by":8197},"2.7.1":{"created_at":"2023-01-08T16:48:18.952973Z","published_by":8197},"2.7.2":{"created_at":"2023-03-27T19:47:48.855353Z","published_by":8197}},"metadata":{"description":"Provides types and useful methods for working with IPv4 and IPv6 network addresses, commonly called IP prefixes. The new `IpNet`, `Ipv4Net`, and `Ipv6Net` types build on the existing `IpAddr`, `Ipv4Addr`, and `Ipv6Addr` types already provided in Rust's standard library and align to their design to stay consistent. The module also provides useful traits that extend `Ipv4Addr` and `Ipv6Addr` with methods for `Add`, `Sub`, `BitAnd`, and `BitOr` operations. The module only uses stable feature so it is guaranteed to compile using the stable toolchain.","repository":"https://github.com/krisprice/ipnet"}},"itoa":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2016-06-25T21:50:33.832Z","published_by":null},"0.1.1":{"created_at":"2016-06-25T22:11:59.936596Z","published_by":null},"0.2.0":{"created_at":"2017-01-24T00:46:50.544524Z","published_by":null},"0.2.1":{"created_at":"2017-01-26T05:02:29.236880Z","published_by":null},"0.3.0":{"created_at":"2017-01-28T21:51:45.197898Z","published_by":null},"0.3.1":{"created_at":"2017-02-05T13:48:13.898443Z","published_by":null},"0.3.2":{"created_at":"2017-08-23T02:50:57.356249Z","published_by":null},"0.3.3":{"created_at":"2017-08-29T16:53:53.028297Z","published_by":null},"0.3.4":{"created_at":"2017-09-16T21:30:58.093298Z","published_by":null},"0.4.0":{"created_at":"2018-03-18T07:26:46.287117Z","published_by":null},"0.4.1":{"created_at":"2018-03-22T16:57:07.690030Z","published_by":null},"0.4.2":{"created_at":"2018-07-05T20:40:17.796632Z","published_by":null},"0.4.3":{"created_at":"2018-09-08T19:14:06.426719Z","published_by":null},"0.4.4":{"created_at":"2019-05-02T02:28:39.369152Z","published_by":3618},"0.4.5":{"created_at":"2020-01-24T21:12:17.999384Z","published_by":3618},"0.4.6":{"created_at":"2020-06-16T18:31:04.040704Z","published_by":3618},"0.4.7":{"created_at":"2020-12-27T20:59:16.512139Z","published_by":3618},"0.4.8":{"created_at":"2021-08-22T05:23:32.920419Z","published_by":3618},"1.0.0":{"created_at":"2021-12-12T18:42:10.171508Z","published_by":3618},"1.0.1":{"created_at":"2021-12-12T20:07:27.905826Z","published_by":3618},"1.0.2":{"created_at":"2022-05-15T21:10:07.198532Z","published_by":3618},"1.0.3":{"created_at":"2022-08-03T13:52:23.198494Z","published_by":3618},"1.0.4":{"created_at":"2022-10-06T23:06:09.692683Z","published_by":3618},"1.0.5":{"created_at":"2022-12-17T19:20:20.925676Z","published_by":3618},"1.0.6":{"created_at":"2023-03-03T23:18:06.755251Z","published_by":3618}},"metadata":{"description":"Fast integer primitive to string conversion","repository":"https://github.com/dtolnay/itoa"}},"js-sys":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2018-07-19T19:42:36.874339Z","published_by":null},"0.2.0":{"created_at":"2018-07-26T22:12:41.330010Z","published_by":null},"0.2.1":{"created_at":"2018-08-13T21:36:27.164442Z","published_by":null},"0.2.2":{"created_at":"2018-08-17T06:37:42.130553Z","published_by":null},"0.2.3":{"created_at":"2018-08-27T20:36:53.402853Z","published_by":null},"0.2.4":{"created_at":"2018-08-27T20:40:17.003045Z","published_by":null},"0.2.5":{"created_at":"2018-09-06T21:51:16.078845Z","published_by":null},"0.2.6":{"created_at":"2018-09-07T05:11:15.771015Z","published_by":null},"0.2.7":{"created_at":"2018-09-21T22:47:42.216577Z","published_by":null},"0.3.0":{"created_at":"2018-09-26T14:56:10.793442Z","published_by":null},"0.3.1":{"created_at":"2018-10-05T18:21:51.476873Z","published_by":null},"0.3.2":{"created_at":"2018-10-10T20:20:46.675265Z","published_by":null},"0.3.3":{"created_at":"2018-10-29T19:57:38.118681Z","published_by":null},"0.3.4":{"created_at":"2018-10-29T21:32:32.805007Z","published_by":null},"0.3.5":{"created_at":"2018-11-12T18:33:36.162837Z","published_by":null},"0.3.6":{"created_at":"2018-12-04T14:26:09.707865Z","published_by":null},"0.3.7":{"created_at":"2019-01-07T15:48:37.650854Z","published_by":null},"0.3.8":{"created_at":"2019-01-09T17:19:38.888780Z","published_by":null},"0.3.9":{"created_at":"2019-01-16T21:26:16.451720Z","published_by":null},"0.3.10":{"created_at":"2019-01-18T23:34:43.887695Z","published_by":null},"0.3.11":{"created_at":"2019-02-12T03:56:10.091073Z","published_by":null},"0.3.12":{"created_at":"2019-02-12T20:39:20.650870Z","published_by":null},"0.3.13":{"created_at":"2019-02-12T23:00:26.070097Z","published_by":null},"0.3.14":{"created_at":"2019-02-15T16:18:23.736985Z","published_by":null},"0.3.15":{"created_at":"2019-03-04T17:56:01.868571Z","published_by":1},"0.3.16":{"created_at":"2019-03-13T19:48:57.408055Z","published_by":1},"0.3.17":{"created_at":"2019-03-22T01:29:23.060915Z","published_by":1},"0.3.18":{"created_at":"2019-04-10T21:13:49.107637Z","published_by":1},"0.3.19":{"created_at":"2019-04-11T14:40:55.643494Z","published_by":1},"0.3.20":{"created_at":"2019-04-29T16:22:03.331476Z","published_by":1},"0.3.21":{"created_at":"2019-05-16T16:30:46.791892Z","published_by":1},"0.3.22":{"created_at":"2019-05-20T17:51:38.395732Z","published_by":1},"0.3.23":{"created_at":"2019-06-14T18:45:58.743016Z","published_by":1},"0.3.24":{"created_at":"2019-06-19T21:47:42.938547Z","published_by":1},"0.3.25":{"created_at":"2019-07-11T22:17:40.413594Z","published_by":1},"0.3.26":{"created_at":"2019-08-14T18:47:29.015855Z","published_by":1},"0.3.27":{"created_at":"2019-08-19T11:24:15.372029Z","published_by":1},"0.3.28":{"created_at":"2019-09-26T19:08:50.204047Z","published_by":1},"0.3.29":{"created_at":"2019-10-24T21:09:45.188080Z","published_by":1},"0.3.30":{"created_at":"2019-10-29T14:38:57.305243Z","published_by":1},"0.3.31":{"created_at":"2019-11-07T19:00:41.527498Z","published_by":1},"0.3.32":{"created_at":"2019-11-19T17:06:30.138002Z","published_by":1},"0.3.33":{"created_at":"2019-12-20T17:00:56.960100Z","published_by":4798},"0.3.34":{"created_at":"2020-01-06T19:18:18.385925Z","published_by":1},"0.3.35":{"created_at":"2020-01-07T19:49:09.101414Z","published_by":1},"0.3.36":{"created_at":"2020-03-03T17:34:57.512157Z","published_by":1},"0.3.37":{"created_at":"2020-03-26T00:36:34.531026Z","published_by":1},"0.3.38":{"created_at":"2020-04-29T16:23:18.240789Z","published_by":1},"0.3.39":{"created_at":"2020-05-01T15:35:05.464176Z","published_by":1},"0.3.40":{"created_at":"2020-05-28T13:58:13.577739Z","published_by":1},"0.3.41":{"created_at":"2020-06-29T14:49:03.438883Z","published_by":1},"0.3.42":{"created_at":"2020-07-15T14:59:25.428254Z","published_by":1},"0.3.43":{"created_at":"2020-07-28T18:10:04.262323Z","published_by":1},"0.3.44":{"created_at":"2020-07-28T21:27:33.628278Z","published_by":1},"0.3.45":{"created_at":"2020-09-09T00:58:18.721446Z","published_by":1},"0.3.46":{"created_at":"2020-11-30T18:36:53.020741Z","published_by":1},"0.3.47":{"created_at":"2021-01-25T16:56:20.954473Z","published_by":1},"0.3.48":{"created_at":"2021-02-26T16:38:35.034915Z","published_by":1},"0.3.49":{"created_at":"2021-03-18T16:06:01.012898Z","published_by":1},"0.3.50":{"created_at":"2021-03-29T14:57:31.521685Z","published_by":1},"0.3.51":{"created_at":"2021-05-10T14:08:46.634071Z","published_by":1},"0.3.52":{"created_at":"2021-08-02T15:40:32.081287Z","published_by":1},"0.3.53":{"created_at":"2021-08-19T15:07:42.876609Z","published_by":1},"0.3.54":{"created_at":"2021-09-08T16:02:44.012295Z","published_by":1},"0.3.55":{"created_at":"2021-09-15T16:18:34.771398Z","published_by":1},"0.3.56":{"created_at":"2022-01-19T21:00:34.902807Z","published_by":1},"0.3.57":{"created_at":"2022-04-07T20:16:45.285878Z","published_by":1},"0.3.58":{"created_at":"2022-06-14T15:07:24.439992Z","published_by":1},"0.3.59":{"created_at":"2022-07-25T15:07:49.763741Z","published_by":1},"0.3.60":{"created_at":"2022-09-12T14:27:57.166267Z","published_by":1},"0.3.61":{"created_at":"2023-02-01T17:15:42.179242Z","published_by":1},"0.3.62":{"created_at":"2023-05-09T14:17:34.969532Z","published_by":1},"0.3.63":{"created_at":"2023-05-15T22:27:10.745894Z","published_by":1}},"metadata":{"description":"Bindings for all JS global objects and functions in all JS environments like\nNode.js and browsers, built on `#[wasm_bindgen]` using the `wasm-bindgen` crate.\n","repository":"https://github.com/rustwasm/wasm-bindgen/tree/master/crates/js-sys"}},"lazy_static":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2014-11-23T01:18:11.000765Z","published_by":null},"0.1.1":{"created_at":"2014-11-23T01:30:03.772414Z","published_by":null},"0.1.2":{"created_at":"2014-12-21T17:01:51.387708Z","published_by":null},"0.1.3":{"created_at":"2014-12-26T00:46:33.214207Z","published_by":null},"0.1.4":{"created_at":"2015-01-03T23:53:46.036800Z","published_by":null},"0.1.5":{"created_at":"2015-01-08T12:19:26.529269Z","published_by":null},"0.1.6":{"created_at":"2015-01-10T16:31:07.079413Z","published_by":null},"0.1.7":{"created_at":"2015-01-16T15:52:43.255570Z","published_by":null},"0.1.8":{"created_at":"2015-03-07T08:14:33.189119Z","published_by":null},"0.1.9":{"created_at":"2015-04-06T12:40:08.218485Z","published_by":null},"0.1.10":{"created_at":"2015-04-06T13:36:07.926207Z","published_by":null},"0.1.11":{"created_at":"2015-05-30T17:41:11.474385Z","published_by":null},"0.1.12":{"created_at":"2015-07-21T09:00:21.530373Z","published_by":null},"0.1.13":{"created_at":"2015-08-03T21:26:23.541464Z","published_by":null},"0.1.14":{"created_at":"2015-08-03T22:15:37.846682Z","published_by":null},"0.1.15":{"created_at":"2015-10-02T17:19:14.053093Z","published_by":null},"0.1.16":{"created_at":"2016-04-11T21:31:49.831094Z","published_by":null},"0.2.0":{"created_at":"2016-04-13T19:07:32.292586Z","published_by":null},"0.2.1":{"created_at":"2016-04-27T14:32:49.775275Z","published_by":null},"0.2.2":{"created_at":"2016-11-11T15:38:33.019791Z","published_by":null},"0.2.3":{"created_at":"2017-03-04T13:27:29.294655Z","published_by":null},"0.2.4":{"created_at":"2017-03-04T13:57:27.441424Z","published_by":null},"0.2.5":{"created_at":"2017-03-25T20:10:52.436327Z","published_by":null},"0.2.6":{"created_at":"2017-04-01T23:55:14.765869Z","published_by":null},"0.2.7":{"created_at":"2017-04-12T09:55:43.548959Z","published_by":null},"0.2.8":{"created_at":"2017-04-12T10:05:06.103731Z","published_by":null},"0.2.9":{"created_at":"2017-09-28T16:21:33.813209Z","published_by":null},"0.2.10":{"created_at":"2017-11-13T15:41:41.000986Z","published_by":null},"0.2.11":{"created_at":"2017-11-20T09:59:41.453585Z","published_by":null},"1.0.0":{"created_at":"2017-11-29T13:58:33.521087Z","published_by":null},"1.0.1":{"created_at":"2018-05-29T00:53:47.893568Z","published_by":null},"1.0.2":{"created_at":"2018-07-15T03:55:00.997120Z","published_by":null},"1.1.0":{"created_at":"2018-08-05T01:56:55.738886Z","published_by":null},"1.1.1":{"created_at":"2020-01-13T15:41:59.710811Z","published_by":915},"1.2.0":{"created_at":"2018-11-04T22:43:54.106581Z","published_by":null},"1.3.0":{"created_at":"2019-02-27T00:02:09.802332Z","published_by":3204},"1.4.0":{"created_at":"2019-08-26T00:55:06.276184Z","published_by":3204}},"metadata":{"description":"A macro for declaring lazily evaluated statics in Rust.","repository":"https://github.com/rust-lang-nursery/lazy-static.rs"}},"libc":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2015-01-15T20:22:13.116297Z","published_by":null},"0.1.1":{"created_at":"2015-01-29T16:47:33.701342Z","published_by":null},"0.1.2":{"created_at":"2015-02-08T14:03:43.901010Z","published_by":null},"0.1.3":{"created_at":"2015-03-12T21:09:28.261382Z","published_by":null},"0.1.4":{"created_at":"2015-03-28T17:51:19.880974Z","published_by":null},"0.1.5":{"created_at":"2015-04-03T01:01:05.479603Z","published_by":null},"0.1.6":{"created_at":"2015-04-07T17:10:55.631663Z","published_by":null},"0.1.7":{"created_at":"2015-05-07T07:53:09.958093Z","published_by":null},"0.1.8":{"created_at":"2015-05-17T19:46:43.744066Z","published_by":null},"0.1.9":{"created_at":"2015-07-11T20:51:59.562389Z","published_by":null},"0.1.10":{"created_at":"2015-08-15T20:29:52.723609Z","published_by":null},"0.1.11":{"created_at":"2015-10-19T20:51:19.038697Z","published_by":null},"0.1.12":{"created_at":"2015-10-28T21:22:48.024978Z","published_by":null},"0.2.0":{"created_at":"2015-11-03T21:32:22.041055Z","published_by":null},"0.2.1":{"created_at":"2015-11-05T01:47:03.827924Z","published_by":null},"0.2.2":{"created_at":"2015-11-10T18:10:05.382010Z","published_by":null},"0.2.3":{"created_at":"2015-12-16T21:01:18.534146Z","published_by":null},"0.2.4":{"created_at":"2015-12-17T23:20:44.957419Z","published_by":null},"0.2.5":{"created_at":"2016-01-21T21:32:48.948442Z","published_by":null},"0.2.6":{"created_at":"2016-01-27T01:00:27.842917Z","published_by":null},"0.2.7":{"created_at":"2016-02-09T23:27:41.755788Z","published_by":null},"0.2.8":{"created_at":"2016-03-07T22:42:56.442340Z","published_by":null},"0.2.9":{"created_at":"2016-03-31T04:45:51.223181Z","published_by":null},"0.2.10":{"created_at":"2016-04-12T22:56:49.175130Z","published_by":null},"0.2.11":{"created_at":"2016-05-03T20:19:50.829787Z","published_by":null},"0.2.12":{"created_at":"2016-06-10T15:11:59.859394Z","published_by":null},"0.2.13":{"created_at":"2016-06-28T19:40:27.264246Z","published_by":null},"0.2.14":{"created_at":"2016-07-11T18:54:03.962781Z","published_by":null},"0.2.15":{"created_at":"2016-08-05T01:54:20.962345Z","published_by":null},"0.2.16":{"created_at":"2016-09-09T06:56:18.886090Z","published_by":null},"0.2.17":{"created_at":"2016-10-15T07:47:24.133563Z","published_by":null},"0.2.18":{"created_at":"2016-12-02T21:36:53.789601Z","published_by":null},"0.2.19":{"created_at":"2017-01-04T22:37:52.728926Z","published_by":null},"0.2.20":{"created_at":"2017-01-17T17:24:02.660081Z","published_by":null},"0.2.21":{"created_at":"2017-03-02T02:47:02.806228Z","published_by":null},"0.2.22":{"created_at":"2017-04-26T22:38:24.471289Z","published_by":null},"0.2.23":{"created_at":"2017-05-19T03:34:16.804139Z","published_by":null},"0.2.24":{"created_at":"2017-06-15T19:39:48.812470Z","published_by":null},"0.2.25":{"created_at":"2017-07-07T17:20:26.072226Z","published_by":null},"0.2.26":{"created_at":"2017-07-07T23:15:50.860753Z","published_by":null},"0.2.27":{"created_at":"2017-07-22T17:15:02.416831Z","published_by":null},"0.2.28":{"created_at":"2017-07-24T17:09:13.948619Z","published_by":null},"0.2.29":{"created_at":"2017-08-01T01:19:13.341558Z","published_by":null},"0.2.30":{"created_at":"2017-08-27T18:10:50.883879Z","published_by":null},"0.2.31":{"created_at":"2017-09-20T03:45:25.228566Z","published_by":null},"0.2.32":{"created_at":"2017-10-06T14:16:36.183976Z","published_by":null},"0.2.33":{"created_at":"2017-10-28T20:20:15.081891Z","published_by":null},"0.2.34":{"created_at":"2017-11-30T15:47:00.179016Z","published_by":null},"0.2.35":{"created_at":"2018-01-04T03:32:05.574245Z","published_by":null},"0.2.36":{"created_at":"2018-01-12T15:57:05.925907Z","published_by":null},"0.2.37":{"created_at":"2018-02-27T12:01:41.193552Z","published_by":null},"0.2.38":{"created_at":"2018-03-05T02:37:48.373061Z","published_by":null},"0.2.39":{"created_at":"2018-03-05T16:41:58.165278Z","published_by":null},"0.2.40":{"created_at":"2018-03-26T06:55:01.045990Z","published_by":null},"0.2.41":{"created_at":"2018-05-21T15:06:52.283653Z","published_by":null},"0.2.42":{"created_at":"2018-06-01T21:41:57.990052Z","published_by":null},"0.2.43":{"created_at":"2018-08-06T13:58:01.547975Z","published_by":null},"0.2.44":{"created_at":"2018-11-22T06:00:01.887655Z","published_by":null},"0.2.45":{"created_at":"2018-12-10T16:35:24.539294Z","published_by":null},"0.2.46":{"created_at":"2019-01-03T15:25:59.610479Z","published_by":null},"0.2.47":{"created_at":"2019-01-14T20:33:01.729887Z","published_by":null},"0.2.48":{"created_at":"2019-01-23T15:55:42.577086Z","published_by":null},"0.2.49":{"created_at":"2019-02-22T20:46:56.611254Z","published_by":5725},"0.2.50":{"created_at":"2019-03-05T11:06:03.435987Z","published_by":5725},"0.2.51":{"created_at":"2019-03-29T02:31:51.111106Z","published_by":1},"0.2.53":{"created_at":"2019-04-26T14:30:38.962189Z","published_by":1},"0.2.54":{"created_at":"2019-05-02T18:25:18.335275Z","published_by":5725},"0.2.55":{"created_at":"2019-05-17T06:42:13.034356Z","published_by":5725},"0.2.56":{"created_at":"2019-05-31T10:38:25.863324Z","published_by":5725},"0.2.57":{"created_at":"2019-05-31T14:44:24.875772Z","published_by":5725},"0.2.58":{"created_at":"2019-06-02T11:48:10.298304Z","published_by":5725},"0.2.59":{"created_at":"2019-07-08T10:13:27.883487Z","published_by":5725},"0.2.60":{"created_at":"2019-07-15T08:53:50.369368Z","published_by":5725},"0.2.61":{"created_at":"2019-08-12T12:08:21.939841Z","published_by":5725},"0.2.62":{"created_at":"2019-08-15T08:45:37.666151Z","published_by":5725},"0.2.63":{"created_at":"2019-08-20T11:13:40.910876Z","published_by":5725},"0.2.64":{"created_at":"2019-10-15T17:30:37.828035Z","published_by":5725},"0.2.65":{"created_at":"2019-10-18T08:17:25.326162Z","published_by":5725},"0.2.66":{"created_at":"2019-11-29T12:21:47.053991Z","published_by":5725},"0.2.67":{"created_at":"2020-02-20T23:23:31.192298Z","published_by":1},"0.2.68":{"created_at":"2020-03-17T20:36:42.630322Z","published_by":51017},"0.2.69":{"created_at":"2020-04-13T23:25:06.331888Z","published_by":51017},"0.2.70":{"created_at":"2020-05-12T16:22:44.420976Z","published_by":51017},"0.2.71":{"created_at":"2020-05-26T15:42:25.610518Z","published_by":51017},"0.2.72":{"created_at":"2020-07-07T23:23:14.200609Z","published_by":51017},"0.2.73":{"created_at":"2020-07-19T20:40:52.896687Z","published_by":51017},"0.2.74":{"created_at":"2020-07-28T19:39:00.322775Z","published_by":51017},"0.2.75":{"created_at":"2020-08-20T04:12:12.653219Z","published_by":51017},"0.2.76":{"created_at":"2020-08-20T07:06:38.704864Z","published_by":51017},"0.2.77":{"created_at":"2020-09-10T05:13:43.360156Z","published_by":51017},"0.2.78":{"created_at":"2020-10-01T04:02:08.640863Z","published_by":4333},"0.2.79":{"created_at":"2020-10-05T05:09:40.185687Z","published_by":4333},"0.2.80":{"created_at":"2020-10-25T20:52:26.931032Z","published_by":51017},"0.2.81":{"created_at":"2020-12-06T18:54:53.493905Z","published_by":51017},"0.2.82":{"created_at":"2021-01-07T20:52:24.135640Z","published_by":51017},"0.2.83":{"created_at":"2021-01-27T22:45:59.596615Z","published_by":2915},"0.2.84":{"created_at":"2021-01-29T00:46:40.915083Z","published_by":4333},"0.2.85":{"created_at":"2021-02-02T05:46:43.092194Z","published_by":2915},"0.2.86":{"created_at":"2021-02-08T15:28:57.902155Z","published_by":51017},"0.2.87":{"created_at":"2021-03-02T10:16:23.718119Z","published_by":51017},"0.2.88":{"created_at":"2021-03-05T17:35:06.558104Z","published_by":51017},"0.2.89":{"created_at":"2021-03-15T20:03:44.894344Z","published_by":4333},"0.2.90":{"created_at":"2021-03-18T19:42:10.386928Z","published_by":51017},"0.2.91":{"created_at":"2021-03-23T02:44:55.604468Z","published_by":51017},"0.2.92":{"created_at":"2021-03-30T11:22:06.045250Z","published_by":51017},"0.2.93":{"created_at":"2021-04-06T16:16:39.313854Z","published_by":51017},"0.2.94":{"created_at":"2021-04-26T14:22:11.638687Z","published_by":51017},"0.2.95":{"created_at":"2021-05-25T18:34:23.635446Z","published_by":51017},"0.2.96":{"created_at":"2021-06-09T04:22:01.589626Z","published_by":51017},"0.2.97":{"created_at":"2021-06-11T21:51:35.874791Z","published_by":51017},"0.2.98":{"created_at":"2021-07-07T15:29:01.482505Z","published_by":51017},"0.2.99":{"created_at":"2021-08-10T03:58:34.990889Z","published_by":51017},"0.2.100":{"created_at":"2021-08-21T02:04:38.874285Z","published_by":51017},"0.2.101":{"created_at":"2021-08-25T20:22:11.454103Z","published_by":2915},"0.2.102":{"created_at":"2021-09-15T09:55:17.312071Z","published_by":51017},"0.2.103":{"created_at":"2021-09-27T12:52:29.220758Z","published_by":2915},"0.2.104":{"created_at":"2021-10-16T07:01:53.734979Z","published_by":51017},"0.2.105":{"created_at":"2021-10-23T13:24:17.236063Z","published_by":51017},"0.2.106":{"created_at":"2021-10-30T23:35:43.114175Z","published_by":4333},"0.2.107":{"created_at":"2021-11-07T03:02:53.463257Z","published_by":51017},"0.2.108":{"created_at":"2021-11-20T13:47:03.044586Z","published_by":51017},"0.2.109":{"created_at":"2021-12-04T15:25:44.501406Z","published_by":2915},"0.2.110":{"created_at":"2021-12-10T00:21:45.930232Z","published_by":2915},"0.2.111":{"created_at":"2021-12-11T20:42:42.594204Z","published_by":2915},"0.2.112":{"created_at":"2021-12-13T19:32:34.112911Z","published_by":2915},"0.2.113":{"created_at":"2022-01-20T13:05:16.415762Z","published_by":2915},"0.2.114":{"created_at":"2022-01-25T11:27:33.487554Z","published_by":2915},"0.2.115":{"created_at":"2022-01-27T12:07:59.337237Z","published_by":2915},"0.2.116":{"created_at":"2022-01-28T19:42:18.320291Z","published_by":2915},"0.2.117":{"created_at":"2022-02-02T20:48:59.592278Z","published_by":2915},"0.2.118":{"created_at":"2022-02-15T11:37:03.079931Z","published_by":2915},"0.2.119":{"created_at":"2022-02-19T15:29:15.020557Z","published_by":2915},"0.2.120":{"created_at":"2022-03-14T13:55:31.618353Z","published_by":2915},"0.2.121":{"created_at":"2022-03-18T20:40:18.383430Z","published_by":2915},"0.2.122":{"created_at":"2022-04-06T23:39:03.310145Z","published_by":2915},"0.2.123":{"created_at":"2022-04-12T15:46:54.561456Z","published_by":2915},"0.2.124":{"created_at":"2022-04-18T23:40:23.878438Z","published_by":2915},"0.2.125":{"created_at":"2022-04-29T12:09:45.626233Z","published_by":2915},"0.2.126":{"created_at":"2022-05-17T06:50:22.341885Z","published_by":2915},"0.2.127":{"created_at":"2022-08-03T21:57:33.480837Z","published_by":4333},"0.2.128":{"created_at":"2022-08-09T19:26:04.315015Z","published_by":2915},"0.2.129":{"created_at":"2022-08-09T23:18:11.972651Z","published_by":51017},"0.2.130":{"created_at":"2022-08-11T14:03:26.080129Z","published_by":2915},"0.2.131":{"created_at":"2022-08-11T23:20:24.191394Z","published_by":2915},"0.2.132":{"created_at":"2022-08-16T15:54:54.193202Z","published_by":2915},"0.2.133":{"created_at":"2022-09-19T13:11:59.928487Z","published_by":51017},"0.2.134":{"created_at":"2022-09-29T12:24:31.333528Z","published_by":51017},"0.2.135":{"created_at":"2022-10-10T10:58:39.987802Z","published_by":51017},"0.2.136":{"created_at":"2022-10-24T11:01:16.866869Z","published_by":51017},"0.2.137":{"created_at":"2022-10-25T23:09:06.123869Z","published_by":51017},"0.2.138":{"created_at":"2022-12-03T03:41:03.369278Z","published_by":51017},"0.2.139":{"created_at":"2022-12-22T09:08:17.035512Z","published_by":51017},"0.2.140":{"created_at":"2023-03-09T13:13:53.139256Z","published_by":51017},"0.2.141":{"created_at":"2023-04-04T08:36:23.796347Z","published_by":51017},"0.2.142":{"created_at":"2023-04-20T13:54:28.289757Z","published_by":51017},"0.2.143":{"created_at":"2023-05-06T16:30:54.305919Z","published_by":51017},"0.2.144":{"created_at":"2023-05-08T12:19:35.462023Z","published_by":51017}},"metadata":{"description":"Raw FFI bindings to platform libraries like libc.\n","repository":"https://github.com/rust-lang/libc"}},"log":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2014-12-13T22:10:19.971330Z","published_by":null},"0.1.1":{"created_at":"2014-12-15T20:36:32.495101Z","published_by":null},"0.1.2":{"created_at":"2014-12-19T16:21:29.470721Z","published_by":null},"0.1.3":{"created_at":"2014-12-23T16:09:02.359217Z","published_by":null},"0.1.4":{"created_at":"2014-12-24T19:48:51.655770Z","published_by":null},"0.1.5":{"created_at":"2014-12-29T16:24:11.027522Z","published_by":null},"0.1.6":{"created_at":"2015-01-02T16:03:48.832282Z","published_by":null},"0.1.7":{"created_at":"2015-01-03T21:58:14.998346Z","published_by":null},"0.1.8":{"created_at":"2015-01-07T16:10:58.541597Z","published_by":null},"0.1.9":{"created_at":"2015-01-09T10:51:18.051784Z","published_by":null},"0.1.10":{"created_at":"2015-01-23T13:59:47.584667Z","published_by":null},"0.2.0":{"created_at":"2015-01-27T15:32:59.679953Z","published_by":null},"0.2.1":{"created_at":"2015-01-28T16:29:22.362781Z","published_by":null},"0.2.2":{"created_at":"2015-02-03T16:19:33.455987Z","published_by":null},"0.2.3":{"created_at":"2015-02-09T20:22:53.903178Z","published_by":null},"0.2.4":{"created_at":"2015-02-19T08:08:23.536262Z","published_by":null},"0.2.5":{"created_at":"2015-02-26T17:35:33.661691Z","published_by":null},"0.2.6":{"created_at":"2015-03-23T01:01:55.428091Z","published_by":null},"0.3.0":{"created_at":"2015-03-24T06:32:03.704267Z","published_by":null},"0.3.1":{"created_at":"2015-03-28T18:22:21.308029Z","published_by":null},"0.3.2":{"created_at":"2015-08-27T16:45:33.113161Z","published_by":null},"0.3.3":{"created_at":"2015-10-27T20:03:54.930749Z","published_by":null},"0.3.4":{"created_at":"2015-11-26T17:03:05.080221Z","published_by":null},"0.3.5":{"created_at":"2016-01-16T18:06:49.737961Z","published_by":null},"0.3.6":{"created_at":"2016-04-01T22:53:50.193788Z","published_by":null},"0.3.7":{"created_at":"2017-03-08T04:41:08.888487Z","published_by":null},"0.3.8":{"created_at":"2017-05-24T02:47:22.267122Z","published_by":null},"0.3.9":{"created_at":"2017-12-24T21:50:36.778771Z","published_by":null},"0.4.0-rc.1":{"created_at":"2017-12-06T07:04:16.824518Z","published_by":null},"0.4.0":{"created_at":"2017-12-24T21:48:00.741034Z","published_by":null},"0.4.1":{"created_at":"2017-12-30T23:33:32.282260Z","published_by":null},"0.4.2":{"created_at":"2018-06-06T02:51:53.620806Z","published_by":null},"0.4.3":{"created_at":"2018-06-29T20:00:07.114985Z","published_by":null},"0.4.4":{"created_at":"2018-08-17T14:52:10.226844Z","published_by":null},"0.4.5":{"created_at":"2018-09-03T15:59:42.032823Z","published_by":null},"0.4.6":{"created_at":"2018-10-27T19:06:10.563576Z","published_by":null},"0.4.7":{"created_at":"2019-07-10T23:50:39.016113Z","published_by":3204},"0.4.8":{"created_at":"2019-07-28T21:04:08.549810Z","published_by":3204},"0.4.9":{"created_at":"2019-12-15T22:31:49.836352Z","published_by":3204},"0.4.10":{"created_at":"2019-12-16T05:32:44.535384Z","published_by":3204},"0.4.11":{"created_at":"2020-07-16T01:16:23.089580Z","published_by":3204},"0.4.12":{"created_at":"2021-01-08T00:49:23.128409Z","published_by":3204},"0.4.13":{"created_at":"2021-01-11T10:17:42.908063Z","published_by":3204},"0.4.14":{"created_at":"2021-01-27T03:07:43.295598Z","published_by":3204},"0.4.15":{"created_at":"2022-03-22T10:00:33.955006Z","published_by":3204},"0.4.16":{"created_at":"2022-03-22T14:05:01.760878Z","published_by":3204},"0.4.17":{"created_at":"2022-05-02T22:41:41.257746Z","published_by":3204}},"metadata":{"description":"A lightweight logging facade for Rust\n","repository":"https://github.com/rust-lang/log"}},"matches":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.0.1":{"created_at":"2014-12-04T18:37:50.992067Z","published_by":null},"0.1.0":{"created_at":"2014-12-05T02:31:23.103582Z","published_by":null},"0.1.1":{"created_at":"2015-01-05T17:08:37.155502Z","published_by":null},"0.1.2":{"created_at":"2015-01-10T00:48:49.946186Z","published_by":null},"0.1.3":{"created_at":"2016-10-09T18:13:59.815018Z","published_by":null},"0.1.4":{"created_at":"2016-11-04T13:38:05.109131Z","published_by":null},"0.1.5":{"created_at":"2017-06-06T12:12:53.926551Z","published_by":null},"0.1.6":{"created_at":"2017-06-06T12:49:14.555717Z","published_by":null},"0.1.7":{"created_at":"2018-07-19T08:04:03.065094Z","published_by":null},"0.1.8":{"created_at":"2018-08-22T17:24:34.680174Z","published_by":null},"0.1.9":{"created_at":"2021-08-12T18:08:14.338081Z","published_by":7},"0.1.10":{"created_at":"2023-01-21T21:21:50.529117Z","published_by":7}},"metadata":{"description":"A macro to evaluate, as a boolean, whether an expression matches a pattern.","repository":"https://github.com/SimonSapin/rust-std-candidates"}},"memchr":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.1":{"created_at":"2015-06-11T23:00:13.087980Z","published_by":null},"0.1.2":{"created_at":"2015-06-11T23:14:20.471360Z","published_by":null},"0.1.3":{"created_at":"2015-06-14T20:15:14.777803Z","published_by":null},"0.1.4":{"created_at":"2015-08-15T16:45:36.529581Z","published_by":null},"0.1.5":{"created_at":"2015-08-15T16:53:12.745803Z","published_by":null},"0.1.6":{"created_at":"2015-08-30T15:29:48.815359Z","published_by":null},"0.1.7":{"created_at":"2015-11-05T23:08:07.136698Z","published_by":null},"0.1.8":{"created_at":"2016-02-14T20:19:36.540432Z","published_by":null},"0.1.9":{"created_at":"2016-02-14T21:36:18.625525Z","published_by":null},"0.1.10":{"created_at":"2016-02-14T22:15:02.099168Z","published_by":null},"0.1.11":{"created_at":"2016-04-02T22:40:12.853649Z","published_by":null},"1.0.0":{"created_at":"2016-12-30T07:00:49.553724Z","published_by":null},"1.0.1":{"created_at":"2017-01-04T01:43:18.364364Z","published_by":null},"1.0.2":{"created_at":"2017-10-21T11:48:11.339241Z","published_by":null},"2.0.0":{"created_at":"2017-10-21T12:04:54.755658Z","published_by":null},"2.0.1":{"created_at":"2017-11-30T14:17:29.911926Z","published_by":null},"2.0.2":{"created_at":"2018-08-25T14:22:13.186333Z","published_by":null},"2.1.0":{"created_at":"2018-09-17T23:54:03.186524Z","published_by":null},"2.1.1":{"created_at":"2018-10-29T11:02:24.371682Z","published_by":null},"2.1.2":{"created_at":"2018-12-11T12:24:31.673252Z","published_by":null},"2.1.3":{"created_at":"2019-01-19T13:12:46.038305Z","published_by":null},"2.2.0":{"created_at":"2019-02-12T01:41:34.021756Z","published_by":null},"2.2.1":{"created_at":"2019-07-07T20:17:10.047139Z","published_by":189},"2.3.0":{"created_at":"2020-01-10T23:59:40.598322Z","published_by":189},"2.3.1":{"created_at":"2020-02-12T14:48:46.723884Z","published_by":189},"2.3.2":{"created_at":"2020-02-13T02:03:29.389721Z","published_by":189},"2.3.3":{"created_at":"2020-02-21T19:47:38.702704Z","published_by":189},"2.3.4":{"created_at":"2020-10-28T21:16:27.230962Z","published_by":189},"2.4.0":{"created_at":"2021-04-30T23:24:14.968995Z","published_by":189},"2.4.1":{"created_at":"2021-08-18T13:33:58.977784Z","published_by":189},"2.5.0":{"created_at":"2022-04-30T17:29:35.225543Z","published_by":189}},"metadata":{"description":"Safe interface to memchr.","repository":"https://github.com/BurntSushi/memchr"}},"mime":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.0.1":{"created_at":"2014-11-20T08:33:46.410233Z","published_by":null},"0.0.2":{"created_at":"2014-12-20T02:55:54.565203Z","published_by":null},"0.0.3":{"created_at":"2014-12-24T08:48:27.303021Z","published_by":null},"0.0.4":{"created_at":"2014-12-29T20:18:49.472741Z","published_by":null},"0.0.5":{"created_at":"2015-01-06T20:31:03.076584Z","published_by":null},"0.0.6":{"created_at":"2015-01-10T01:40:49.500384Z","published_by":null},"0.0.7":{"created_at":"2015-01-23T21:50:25.170784Z","published_by":null},"0.0.8":{"created_at":"2015-02-03T19:12:31.481871Z","published_by":null},"0.0.9":{"created_at":"2015-03-04T04:53:46.787229Z","published_by":null},"0.0.10":{"created_at":"2015-03-04T04:54:57.299964Z","published_by":null},"0.0.11":{"created_at":"2015-05-04T23:18:50.785682Z","published_by":null},"0.0.12":{"created_at":"2015-06-09T01:28:00.958869Z","published_by":null},"0.1.0":{"created_at":"2015-08-03T17:19:47.121963Z","published_by":null},"0.1.1":{"created_at":"2015-11-24T06:28:53.584194Z","published_by":null},"0.1.2":{"created_at":"2016-02-09T19:12:04.696308Z","published_by":null},"0.1.3":{"created_at":"2016-02-10T17:52:22.630894Z","published_by":null},"0.2.0":{"created_at":"2016-03-09T18:56:46.244759Z","published_by":null},"0.2.1":{"created_at":"2016-06-14T15:18:52.834565Z","published_by":null},"0.2.2":{"created_at":"2016-08-01T16:49:21.375536Z","published_by":null},"0.2.3":{"created_at":"2017-03-16T02:11:14.329690Z","published_by":null},"0.2.4":{"created_at":"2017-05-14T03:49:09.016635Z","published_by":null},"0.2.5":{"created_at":"2017-05-28T18:01:05.980586Z","published_by":null},"0.2.6":{"created_at":"2017-05-29T17:50:20.601829Z","published_by":null},"0.3.0":{"created_at":"2017-06-08T19:52:41.768175Z","published_by":null},"0.3.1":{"created_at":"2017-06-09T21:34:15.763755Z","published_by":null},"0.3.2":{"created_at":"2017-06-13T03:12:54.083213Z","published_by":null},"0.3.3":{"created_at":"2017-08-09T22:00:03.014898Z","published_by":null},"0.3.4":{"created_at":"2017-09-19T18:15:12.617165Z","published_by":null},"0.3.5":{"created_at":"2017-10-11T20:16:15.014373Z","published_by":null},"0.3.6":{"created_at":"2018-04-26T19:09:51.811619Z","published_by":null},"0.3.7":{"created_at":"2018-05-04T19:50:37.069209Z","published_by":null},"0.3.8":{"created_at":"2018-07-02T17:04:48.337981Z","published_by":null},"0.3.9":{"created_at":"2018-08-10T18:27:25.998853Z","published_by":null},"0.3.10":{"created_at":"2018-10-15T18:31:32.729496Z","published_by":null},"0.3.11":{"created_at":"2018-10-15T19:59:58.565052Z","published_by":null},"0.3.12":{"created_at":"2018-10-15T20:12:20.401189Z","published_by":null},"0.3.13":{"created_at":"2019-01-11T20:24:58.393359Z","published_by":null},"0.3.14":{"created_at":"2019-09-09T18:23:46.493317Z","published_by":359},"0.3.15":{"created_at":"2020-01-06T18:27:36.958951Z","published_by":359},"0.3.16":{"created_at":"2020-01-07T20:11:07.848448Z","published_by":359},"0.3.17":{"created_at":"2023-03-20T15:05:04.272177Z","published_by":359}},"metadata":{"description":"Strongly Typed Mimes","repository":"https://github.com/hyperium/mime"}},"mio":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2014-11-11T23:52:39.682594Z","published_by":null},"0.2.1":{"created_at":"2015-02-18T16:50:54.083354Z","published_by":null},"0.3.0":{"created_at":"2015-03-30T00:05:56.605140Z","published_by":null},"0.3.1":{"created_at":"2015-04-02T03:57:56.667111Z","published_by":null},"0.3.2":{"created_at":"2015-04-07T23:46:06.323065Z","published_by":null},"0.3.3":{"created_at":"2015-04-12T22:34:47.616196Z","published_by":null},"0.3.4":{"created_at":"2015-04-22T13:20:55.076804Z","published_by":null},"0.3.5":{"created_at":"2015-05-13T17:16:02.677577Z","published_by":null},"0.3.6":{"created_at":"2015-06-12T06:34:05.348478Z","published_by":null},"0.3.7":{"created_at":"2015-07-09T20:32:02.949138Z","published_by":null},"0.4.0":{"created_at":"2015-07-16T22:27:41.110445Z","published_by":null},"0.4.1":{"created_at":"2015-07-21T22:40:12.133603Z","published_by":null},"0.4.2":{"created_at":"2015-08-17T19:12:16.615974Z","published_by":null},"0.4.3":{"created_at":"2015-08-31T03:47:29.265126Z","published_by":null},"0.4.4":{"created_at":"2015-11-05T20:23:41.258440Z","published_by":null},"0.5.0":{"created_at":"2015-12-04T05:35:14.306067Z","published_by":null},"0.5.1":{"created_at":"2016-04-27T22:11:08.838760Z","published_by":null},"0.6.0":{"created_at":"2016-09-02T04:34:07.833211Z","published_by":null},"0.6.1":{"created_at":"2016-10-31T03:13:11.682229Z","published_by":null},"0.6.2":{"created_at":"2016-12-18T19:04:57.013351Z","published_by":null},"0.6.3":{"created_at":"2017-01-22T18:45:01.214706Z","published_by":null},"0.6.4":{"created_at":"2017-01-24T22:31:29.512505Z","published_by":null},"0.6.5":{"created_at":"2017-03-15T06:27:55.426415Z","published_by":null},"0.6.6":{"created_at":"2017-03-22T19:54:10.861496Z","published_by":null},"0.6.7":{"created_at":"2017-04-27T17:18:59.597746Z","published_by":null},"0.6.8":{"created_at":"2017-05-26T18:53:59.096537Z","published_by":null},"0.6.9":{"created_at":"2017-06-07T20:28:39.119714Z","published_by":null},"0.6.10":{"created_at":"2017-07-31T21:05:31.737289Z","published_by":null},"0.6.11":{"created_at":"2017-10-25T18:18:10.549219Z","published_by":null},"0.6.12":{"created_at":"2018-01-06T00:27:15.535598Z","published_by":null},"0.6.13":{"created_at":"2018-02-06T06:07:57.676326Z","published_by":null},"0.6.14":{"created_at":"2018-03-08T21:05:57.546973Z","published_by":null},"0.6.15":{"created_at":"2018-07-03T22:39:23.584850Z","published_by":null},"0.6.16":{"created_at":"2018-09-05T22:37:55.040251Z","published_by":null},"0.6.17":{"created_at":"2019-05-15T23:35:43.189763Z","published_by":10},"0.6.18":{"created_at":"2019-05-24T19:00:12.879369Z","published_by":10},"0.6.19":{"created_at":"2019-05-28T18:05:42.999789Z","published_by":10},"0.6.20":{"created_at":"2019-11-22T06:34:24.988137Z","published_by":10},"0.6.21":{"created_at":"2019-11-27T20:50:13.464358Z","published_by":10},"0.6.22":{"created_at":"2020-05-01T19:28:12.337105Z","published_by":6025},"0.6.23":{"created_at":"2020-12-01T18:45:17.200865Z","published_by":6025},"0.7.0-alpha.1":{"created_at":"2019-12-17T09:31:22.418831Z","published_by":6025},"0.7.0":{"created_at":"2020-03-02T18:00:07.808304Z","published_by":6025},"0.7.1":{"created_at":"2020-09-29T17:56:15.144298Z","published_by":6025},"0.7.2":{"created_at":"2020-10-02T08:07:06.912503Z","published_by":6025},"0.7.3":{"created_at":"2020-10-07T17:06:23.330978Z","published_by":10},"0.7.4":{"created_at":"2020-10-20T15:25:02.485017Z","published_by":10},"0.7.5":{"created_at":"2020-10-31T18:22:32.576794Z","published_by":6025},"0.7.6":{"created_at":"2020-11-16T14:39:21.860126Z","published_by":6025},"0.7.7":{"created_at":"2020-12-30T16:31:26.143066Z","published_by":6025},"0.7.8":{"created_at":"2021-02-17T21:28:25.766620Z","published_by":6025},"0.7.9":{"created_at":"2021-02-22T21:31:57.483421Z","published_by":6025},"0.7.10":{"created_at":"2021-03-15T18:46:24.493254Z","published_by":6025},"0.7.11":{"created_at":"2021-03-22T21:53:59.768794Z","published_by":6025},"0.7.12":{"created_at":"2021-06-12T18:45:43.584283Z","published_by":6025},"0.7.13":{"created_at":"2021-06-14T19:19:50.178644Z","published_by":6025},"0.7.14":{"created_at":"2021-10-16T16:18:24.653683Z","published_by":6025},"0.8.0":{"created_at":"2021-11-13T12:18:38.808889Z","published_by":6025},"0.8.1":{"created_at":"2022-03-11T13:40:19.643338Z","published_by":6025},"0.8.2":{"created_at":"2022-03-17T19:08:46.679648Z","published_by":6025},"0.8.3":{"created_at":"2022-05-08T15:49:27.018511Z","published_by":6025},"0.8.4":{"created_at":"2022-06-19T17:11:02.364454Z","published_by":6025},"0.8.5":{"created_at":"2022-10-24T17:17:07.672597Z","published_by":6025},"0.8.6":{"created_at":"2023-02-14T11:20:15.518978Z","published_by":6025}},"metadata":{"description":"Lightweight non-blocking I/O.","repository":"https://github.com/tokio-rs/mio"}},"miow":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2015-10-28T16:48:07.671788Z","published_by":null},"0.1.1":{"created_at":"2015-11-10T17:16:23.293045Z","published_by":null},"0.1.2":{"created_at":"2016-02-11T17:44:29.617531Z","published_by":null},"0.1.3":{"created_at":"2016-07-19T06:51:34.508210Z","published_by":null},"0.1.4":{"created_at":"2016-12-14T22:56:52.269410Z","published_by":null},"0.1.5":{"created_at":"2016-12-19T07:16:11.930783Z","published_by":null},"0.2.0":{"created_at":"2017-01-20T18:23:02.183161Z","published_by":null},"0.2.1":{"created_at":"2017-03-10T05:32:24.142580Z","published_by":null},"0.2.2":{"created_at":"2020-11-27T22:40:22.201982Z","published_by":1459},"0.3.0":{"created_at":"2018-01-03T17:24:31.415848Z","published_by":null},"0.3.1":{"created_at":"2018-01-03T17:37:27.970915Z","published_by":null},"0.3.2":{"created_at":"2018-08-22T21:10:51.634935Z","published_by":null},"0.3.3":{"created_at":"2018-08-25T04:18:21.748523Z","published_by":null},"0.3.4":{"created_at":"2020-05-20T13:53:35.478973Z","published_by":3824},"0.3.5":{"created_at":"2020-06-09T08:52:09.943010Z","published_by":3824},"0.3.6":{"created_at":"2020-11-15T19:41:29.945759Z","published_by":1459},"0.3.7":{"created_at":"2021-03-22T20:12:30.332260Z","published_by":1459},"0.3.8":{"created_at":"2021-11-22T09:53:56.607088Z","published_by":3824},"0.4.0":{"created_at":"2021-11-29T14:10:07.266103Z","published_by":3824},"0.5.0":{"created_at":"2022-11-10T16:38:19.699672Z","published_by":1459}},"metadata":{"description":"A zero overhead I/O library for Windows, focusing on IOCP and async I/O\nabstractions.\n","repository":"https://github.com/yoshuawuyts/miow"}},"native-tls":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2016-11-09T21:07:37.761548Z","published_by":null},"0.1.1":{"created_at":"2017-01-12T00:41:26.958484Z","published_by":null},"0.1.2":{"created_at":"2017-04-02T00:31:07.513614Z","published_by":null},"0.1.3":{"created_at":"2017-06-23T20:35:25.677458Z","published_by":null},"0.1.4":{"created_at":"2017-06-23T20:45:12.385348Z","published_by":null},"0.1.5":{"created_at":"2018-01-11T17:28:12.141639Z","published_by":null},"0.2.0":{"created_at":"2018-06-26T03:39:55.693469Z","published_by":null},"0.2.1":{"created_at":"2018-08-05T01:31:03.865528Z","published_by":null},"0.2.2":{"created_at":"2018-10-22T16:14:15.722563Z","published_by":null},"0.2.3":{"created_at":"2019-04-27T04:28:14.565472Z","published_by":5},"0.2.4":{"created_at":"2020-03-06T01:16:44.724055Z","published_by":5},"0.2.5":{"created_at":"2020-11-06T02:37:55.931792Z","published_by":5},"0.2.6":{"created_at":"2020-11-10T13:45:39.370928Z","published_by":5},"0.2.7":{"created_at":"2020-12-29T22:08:04.037122Z","published_by":5},"0.2.8":{"created_at":"2021-08-11T00:38:29.712257Z","published_by":5},"0.2.9":{"created_at":"2022-03-27T17:16:45.299407Z","published_by":5},"0.2.10":{"created_at":"2022-03-28T21:55:00.192984Z","published_by":5},"0.2.11":{"created_at":"2022-11-01T17:06:38.766830Z","published_by":5}},"metadata":{"description":"A wrapper over a platform's native TLS implementation","repository":"https://github.com/sfackler/rust-native-tls"}},"ntapi":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2018-09-11T16:14:17.942528Z","published_by":null},"0.1.1":{"created_at":"2018-09-11T16:36:06.207897Z","published_by":null},"0.2.0":{"created_at":"2018-10-01T21:33:25.946699Z","published_by":null},"0.3.0":{"created_at":"2019-03-02T18:08:14.358574Z","published_by":27926},"0.3.1":{"created_at":"2019-04-27T15:52:55.587286Z","published_by":27926},"0.3.2":{"created_at":"2019-07-16T18:51:47.714850Z","published_by":27926},"0.3.3":{"created_at":"2019-07-17T17:24:27.530749Z","published_by":27926},"0.3.4":{"created_at":"2020-05-09T11:06:27.120854Z","published_by":27926},"0.3.5":{"created_at":"2020-10-23T11:40:23.579576Z","published_by":27926},"0.3.6":{"created_at":"2020-10-23T14:18:19.183775Z","published_by":27926},"0.3.7":{"created_at":"2022-02-11T02:21:41.315258Z","published_by":27926},"0.4.0":{"created_at":"2022-09-23T22:02:12.049486Z","published_by":27926},"0.4.1":{"created_at":"2023-04-20T23:14:01.046654Z","published_by":27926}},"metadata":{"description":"FFI bindings for Native API","repository":"https://github.com/MSxDOS/ntapi"}},"once_cell":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.0.1":{"created_at":"2018-08-02T00:12:48.969947Z","published_by":null},"0.0.2":{"created_at":"2018-08-02T00:54:16.709376Z","published_by":null},"0.0.3":{"created_at":"2018-08-02T09:53:36.912140Z","published_by":null},"0.1.0":{"created_at":"2018-08-02T14:47:34.078426Z","published_by":null},"0.1.1":{"created_at":"2018-08-02T14:50:16.120272Z","published_by":null},"0.1.2":{"created_at":"2018-08-02T15:02:49.850977Z","published_by":null},"0.1.3":{"created_at":"2018-08-04T05:17:24.679322Z","published_by":null},"0.1.4":{"created_at":"2018-08-06T14:45:45.814938Z","published_by":null},"0.1.5":{"created_at":"2018-09-02T20:04:53.494204Z","published_by":null},"0.1.6":{"created_at":"2018-09-21T20:07:34.904250Z","published_by":null},"0.1.7":{"created_at":"2019-01-14T10:17:48.036863Z","published_by":null},"0.1.8":{"created_at":"2019-02-07T15:00:43.565942Z","published_by":null},"0.2.0":{"created_at":"2019-05-07T11:12:17.020609Z","published_by":2699},"0.2.1":{"created_at":"2019-06-05T19:49:07.365168Z","published_by":2699},"0.2.2":{"created_at":"2019-07-06T05:21:50.756048Z","published_by":2699},"0.2.3":{"created_at":"2019-07-15T16:51:45.577058Z","published_by":2699},"0.2.4":{"created_at":"2019-07-21T09:28:12.206082Z","published_by":2699},"0.2.5":{"created_at":"2019-08-07T18:41:09.641543Z","published_by":2699},"0.2.6":{"created_at":"2019-08-07T19:45:34.991472Z","published_by":2699},"0.2.7":{"created_at":"2019-08-23T08:12:43.954151Z","published_by":2699},"1.0.0-pre.1":{"created_at":"2019-08-27T07:11:31.281603Z","published_by":2699},"1.0.0-pre.2":{"created_at":"2019-08-27T21:24:43.119098Z","published_by":2699},"1.0.0":{"created_at":"2019-09-01T11:42:56.930614Z","published_by":2699},"1.0.1":{"created_at":"2019-09-01T19:32:32.364001Z","published_by":2699},"1.0.2":{"created_at":"2019-09-02T08:33:26.611598Z","published_by":2699},"1.1.0":{"created_at":"2019-09-06T17:43:45.213240Z","published_by":2699},"1.2.0":{"created_at":"2019-09-21T16:21:41.300542Z","published_by":2699},"1.3.0":{"created_at":"2020-01-14T17:46:39.933033Z","published_by":2699},"1.3.1":{"created_at":"2020-01-17T14:54:14.543173Z","published_by":2699},"1.4.0":{"created_at":"2020-05-13T23:43:32.335751Z","published_by":2699},"1.4.1":{"created_at":"2020-08-17T21:02:00.824094Z","published_by":2699},"1.5.0":{"created_at":"2020-11-11T02:00:08.377660Z","published_by":2699},"1.5.1":{"created_at":"2020-11-11T10:14:24.443010Z","published_by":2699},"1.5.2":{"created_at":"2020-11-12T12:55:17.677525Z","published_by":2699},"1.6.0":{"created_at":"2021-02-22T10:41:21.875436Z","published_by":2699},"1.7.0":{"created_at":"2021-02-24T16:57:21.929727Z","published_by":2699},"1.7.1":{"created_at":"2021-03-02T08:51:21.891080Z","published_by":2699},"1.7.2":{"created_at":"2021-03-02T15:22:34.860755Z","published_by":2699},"1.8.0-pre.1":{"created_at":"2021-05-29T13:43:13.050979Z","published_by":2699},"1.8.0":{"created_at":"2021-06-13T09:45:35.608186Z","published_by":2699},"1.9.0":{"created_at":"2021-12-14T13:25:10.511344Z","published_by":2699},"1.10.0":{"created_at":"2022-03-03T18:58:47.832846Z","published_by":2699},"1.11.0":{"created_at":"2022-05-19T15:43:03.908215Z","published_by":2699},"1.12.0-pre.1":{"created_at":"2022-05-20T15:26:00.724776Z","published_by":2699},"1.12.0":{"created_at":"2022-05-23T13:36:37.213015Z","published_by":2699},"1.12.1":{"created_at":"2022-07-04T10:44:06.334060Z","published_by":2699},"1.13.0":{"created_at":"2022-07-04T23:57:12.516881Z","published_by":2699},"1.13.1":{"created_at":"2022-08-16T13:13:11.672144Z","published_by":2699},"1.14.0":{"created_at":"2022-09-02T12:01:44.868039Z","published_by":2699},"1.15.0-pre.1":{"created_at":"2022-09-15T23:41:35.499374Z","published_by":2699},"1.15.0":{"created_at":"2022-09-20T20:20:31.017135Z","published_by":2699},"1.16.0-pre.1":{"created_at":"2022-10-22T18:37:41.950864Z","published_by":2699},"1.16.0":{"created_at":"2022-10-29T09:07:19.543668Z","published_by":2699},"1.17.0":{"created_at":"2022-12-29T17:34:46.600758Z","published_by":2699},"1.17.1":{"created_at":"2023-02-14T15:31:58.530357Z","published_by":2699}},"metadata":{"description":"Single assignment cells and lazy values.","repository":"https://github.com/matklad/once_cell"}},"openssl":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.0.1":{"created_at":"2014-11-21T06:54:00.713138Z","published_by":null},"0.0.2":{"created_at":"2014-11-24T02:30:36.227158Z","published_by":null},"0.1.0":{"created_at":"2014-11-26T00:47:22.347499Z","published_by":null},"0.1.1":{"created_at":"2014-11-26T16:50:23.567453Z","published_by":null},"0.2.0":{"created_at":"2014-11-28T05:37:38.580760Z","published_by":null},"0.2.1":{"created_at":"2014-11-28T23:49:55.089830Z","published_by":null},"0.2.2":{"created_at":"2014-12-01T21:24:12.367221Z","published_by":null},"0.2.3":{"created_at":"2014-12-12T04:11:20.715265Z","published_by":null},"0.2.4":{"created_at":"2014-12-15T17:26:38.562669Z","published_by":null},"0.2.5":{"created_at":"2014-12-16T16:57:07.805835Z","published_by":null},"0.2.6":{"created_at":"2014-12-19T16:22:09.458924Z","published_by":null},"0.2.7":{"created_at":"2014-12-21T18:36:01.773912Z","published_by":null},"0.2.8":{"created_at":"2014-12-23T16:58:00.268597Z","published_by":null},"0.2.9":{"created_at":"2014-12-29T22:44:42.015689Z","published_by":null},"0.2.10":{"created_at":"2015-01-01T05:16:43.628329Z","published_by":null},"0.2.11":{"created_at":"2015-01-03T16:32:53.899811Z","published_by":null},"0.2.12":{"created_at":"2015-01-04T16:04:41.951898Z","published_by":null},"0.2.13":{"created_at":"2015-01-06T17:00:05.103560Z","published_by":null},"0.2.14":{"created_at":"2015-01-07T16:16:30.791533Z","published_by":null},"0.2.15":{"created_at":"2015-01-09T18:11:41.569723Z","published_by":null},"0.2.16":{"created_at":"2015-01-17T06:30:45.347167Z","published_by":null},"0.2.17":{"created_at":"2015-01-23T18:22:02.650853Z","published_by":null},"0.2.18":{"created_at":"2015-01-28T16:48:06.849833Z","published_by":null},"0.3.0":{"created_at":"2015-02-03T17:00:10.547052Z","published_by":null},"0.3.1":{"created_at":"2015-02-03T18:10:57.926209Z","published_by":null},"0.3.2":{"created_at":"2015-02-09T07:40:04.851767Z","published_by":null},"0.3.3":{"created_at":"2015-02-09T08:05:43.666991Z","published_by":null},"0.3.4":{"created_at":"2015-02-12T03:59:18.882790Z","published_by":null},"0.3.5":{"created_at":"2015-02-12T05:31:48.787281Z","published_by":null},"0.3.6":{"created_at":"2015-02-12T21:23:05.294582Z","published_by":null},"0.4.0":{"created_at":"2015-02-14T07:36:17.013219Z","published_by":null},"0.4.1":{"created_at":"2015-02-17T07:29:48.513696Z","published_by":null},"0.4.2":{"created_at":"2015-02-19T17:14:30.674468Z","published_by":null},"0.4.3":{"created_at":"2015-02-20T21:44:42.271165Z","published_by":null},"0.5.0":{"created_at":"2015-02-28T03:48:59.196720Z","published_by":null},"0.5.1":{"created_at":"2015-03-05T18:16:04.777221Z","published_by":null},"0.5.2":{"created_at":"2015-03-25T18:37:51.464849Z","published_by":null},"0.5.3":{"created_at":"2015-03-29T17:21:53.955513Z","published_by":null},"0.5.4":{"created_at":"2015-04-02T18:17:27.243293Z","published_by":null},"0.5.5":{"created_at":"2015-04-03T15:44:33.667216Z","published_by":null},"0.6.0":{"created_at":"2015-04-05T23:50:28.373679Z","published_by":null},"0.6.1":{"created_at":"2015-04-22T22:08:50.736822Z","published_by":null},"0.6.2":{"created_at":"2015-04-30T16:10:41.461111Z","published_by":null},"0.6.3":{"created_at":"2015-06-26T06:28:22.145634Z","published_by":null},"0.6.4":{"created_at":"2015-07-06T18:14:24.745666Z","published_by":null},"0.6.5":{"created_at":"2015-09-01T02:20:00.789557Z","published_by":null},"0.6.6":{"created_at":"2015-10-05T21:46:17.779078Z","published_by":null},"0.6.7":{"created_at":"2015-10-15T02:30:21.550775Z","published_by":null},"0.7.0":{"created_at":"2015-11-17T05:47:19.503371Z","published_by":null},"0.7.1":{"created_at":"2015-11-29T00:20:51.072658Z","published_by":null},"0.7.2":{"created_at":"2015-12-16T04:07:32.601361Z","published_by":null},"0.7.3":{"created_at":"2015-12-18T05:30:41.354639Z","published_by":null},"0.7.4":{"created_at":"2015-12-19T06:49:39.346311Z","published_by":null},"0.7.5":{"created_at":"2016-01-23T00:16:44.835424Z","published_by":null},"0.7.6":{"created_at":"2016-02-10T17:45:21.993468Z","published_by":null},"0.7.7":{"created_at":"2016-03-17T16:09:31.273374Z","published_by":null},"0.7.8":{"created_at":"2016-03-18T16:00:16.046869Z","published_by":null},"0.7.9":{"created_at":"2016-04-07T04:44:30.317123Z","published_by":null},"0.7.10":{"created_at":"2016-04-17T04:01:57.647960Z","published_by":null},"0.7.11":{"created_at":"2016-05-05T20:37:36.692542Z","published_by":null},"0.7.12":{"created_at":"2016-05-17T06:09:46.549266Z","published_by":null},"0.7.13":{"created_at":"2016-05-20T23:07:37.686085Z","published_by":null},"0.7.14":{"created_at":"2016-07-01T22:46:24.720839Z","published_by":null},"0.8.0":{"created_at":"2016-08-12T04:14:22.558294Z","published_by":null},"0.8.1":{"created_at":"2016-08-16T01:51:12.582040Z","published_by":null},"0.8.2":{"created_at":"2016-08-18T20:05:28.242523Z","published_by":null},"0.8.3":{"created_at":"2016-09-09T16:20:41.489550Z","published_by":null},"0.9.0":{"created_at":"2016-11-06T03:11:39.197825Z","published_by":null},"0.9.1":{"created_at":"2016-11-11T16:47:58.606798Z","published_by":null},"0.9.2":{"created_at":"2016-11-28T06:24:47.426542Z","published_by":null},"0.9.3":{"created_at":"2016-12-10T05:54:43.324324Z","published_by":null},"0.9.4":{"created_at":"2016-12-23T18:39:07.530816Z","published_by":null},"0.9.5":{"created_at":"2017-01-04T00:10:30.135541Z","published_by":null},"0.9.6":{"created_at":"2017-01-10T04:53:19.006823Z","published_by":null},"0.9.7":{"created_at":"2017-02-11T22:35:19.751710Z","published_by":null},"0.9.8":{"created_at":"2017-03-09T09:34:27.028751Z","published_by":null},"0.9.9":{"created_at":"2017-03-14T19:56:22.058319Z","published_by":null},"0.9.10":{"created_at":"2017-03-26T17:49:52.613379Z","published_by":null},"0.9.11":{"created_at":"2017-04-14T23:58:01.231277Z","published_by":null},"0.9.12":{"created_at":"2017-05-12T18:49:08.942882Z","published_by":null},"0.9.13":{"created_at":"2017-05-30T00:46:52.773332Z","published_by":null},"0.9.14":{"created_at":"2017-06-15T03:00:20.779413Z","published_by":null},"0.9.15":{"created_at":"2017-07-20T02:36:16.254229Z","published_by":null},"0.9.16":{"created_at":"2017-08-11T05:18:20.276748Z","published_by":null},"0.9.17":{"created_at":"2017-08-15T00:14:27.377679Z","published_by":null},"0.9.18":{"created_at":"2017-09-20T14:24:40.939726Z","published_by":null},"0.9.19":{"created_at":"2017-09-20T20:51:44.153442Z","published_by":null},"0.9.20":{"created_at":"2017-10-14T21:37:27.540623Z","published_by":null},"0.9.21":{"created_at":"2017-11-17T17:16:50.709164Z","published_by":null},"0.9.22":{"created_at":"2017-11-29T17:38:47.001432Z","published_by":null},"0.9.23":{"created_at":"2017-12-06T05:59:05.484848Z","published_by":null},"0.9.24":{"created_at":"2018-02-12T19:19:35.753989Z","published_by":null},"0.10.0":{"created_at":"2018-01-11T06:08:30.148077Z","published_by":null},"0.10.1":{"created_at":"2018-01-11T06:30:26.972491Z","published_by":null},"0.10.2":{"created_at":"2018-01-12T01:34:39.044939Z","published_by":null},"0.10.3":{"created_at":"2018-02-12T18:56:38.024558Z","published_by":null},"0.10.4":{"created_at":"2018-02-18T18:50:25.875883Z","published_by":null},"0.10.5":{"created_at":"2018-02-28T22:33:26.057894Z","published_by":null},"0.10.6":{"created_at":"2018-04-05T18:12:26.599960Z","published_by":null},"0.10.7":{"created_at":"2018-05-01T03:41:50.213588Z","published_by":null},"0.10.8":{"created_at":"2018-05-21T04:03:34.395453Z","published_by":null},"0.10.9":{"created_at":"2018-06-02T03:59:46.478806Z","published_by":null},"0.10.10":{"created_at":"2018-06-06T20:37:44.923014Z","published_by":null},"0.10.11":{"created_at":"2018-08-04T17:13:17.609917Z","published_by":null},"0.10.12":{"created_at":"2018-09-14T02:23:29.327596Z","published_by":null},"0.10.13":{"created_at":"2018-10-14T23:10:49.121596Z","published_by":null},"0.10.14":{"created_at":"2018-10-19T03:17:12.526210Z","published_by":null},"0.10.15":{"created_at":"2018-10-22T16:04:48.167117Z","published_by":null},"0.10.16":{"created_at":"2018-12-16T17:04:47.852785Z","published_by":null},"0.10.17":{"created_at":"2019-02-22T17:52:02.737220Z","published_by":null},"0.10.18":{"created_at":"2019-02-22T19:34:31.235676Z","published_by":5},"0.10.19":{"created_at":"2019-03-01T20:40:44.581516Z","published_by":5},"0.10.20":{"created_at":"2019-03-20T14:45:55.827051Z","published_by":5},"0.10.21":{"created_at":"2019-05-01T04:59:21.568399Z","published_by":5},"0.10.22":{"created_at":"2019-05-09T01:47:02.738233Z","published_by":5},"0.10.23":{"created_at":"2019-05-18T19:13:59.086957Z","published_by":5},"0.10.24":{"created_at":"2019-07-19T14:45:49.957185Z","published_by":5},"0.10.25":{"created_at":"2019-10-03T00:52:54.623393Z","published_by":5},"0.10.26":{"created_at":"2019-11-22T23:35:24.792172Z","published_by":5},"0.10.27":{"created_at":"2020-01-29T23:40:19.505547Z","published_by":5},"0.10.28":{"created_at":"2020-02-04T21:59:04.091892Z","published_by":5},"0.10.29":{"created_at":"2020-04-08T01:01:07.918380Z","published_by":5},"0.10.30":{"created_at":"2020-06-26T01:23:11.728393Z","published_by":5},"0.10.31":{"created_at":"2020-12-09T22:45:06.487598Z","published_by":5},"0.10.32":{"created_at":"2020-12-24T13:15:40.011787Z","published_by":5},"0.10.33":{"created_at":"2021-03-13T20:52:54.299774Z","published_by":5},"0.10.34":{"created_at":"2021-04-28T23:45:04.116033Z","published_by":5},"0.10.35":{"created_at":"2021-06-19T00:33:14.015887Z","published_by":5},"0.10.36":{"created_at":"2021-08-17T17:18:40.897720Z","published_by":5},"0.10.37":{"created_at":"2021-10-27T21:12:10.898743Z","published_by":5},"0.10.38":{"created_at":"2021-10-31T18:15:50.458535Z","published_by":5},"0.10.39":{"created_at":"2022-05-03T01:11:47.178867Z","published_by":5},"0.10.40":{"created_at":"2022-05-04T11:23:40.491519Z","published_by":5},"0.10.41":{"created_at":"2022-07-09T13:33:21.943758Z","published_by":5},"0.10.42":{"created_at":"2022-09-26T23:23:27.140952Z","published_by":5},"0.10.43":{"created_at":"2022-11-24T01:53:22.899213Z","published_by":5},"0.10.44":{"created_at":"2022-12-06T12:42:07.836955Z","published_by":5},"0.10.45":{"created_at":"2022-12-20T14:42:15.938290Z","published_by":5},"0.10.46":{"created_at":"2023-03-15T01:21:36.262129Z","published_by":5},"0.10.47":{"created_at":"2023-03-19T23:58:23.754742Z","published_by":5},"0.10.48":{"created_at":"2023-03-24T01:49:22.391805Z","published_by":163},"0.10.49":{"created_at":"2023-04-01T14:09:01.548864Z","published_by":5},"0.10.50":{"created_at":"2023-04-10T00:06:45.858864Z","published_by":5},"0.10.51":{"created_at":"2023-04-20T22:49:11.946349Z","published_by":163},"0.10.52":{"created_at":"2023-04-24T23:07:58.062569Z","published_by":163}},"metadata":{"description":"OpenSSL bindings","repository":"https://github.com/sfackler/rust-openssl"}},"openssl-probe":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2016-11-06T05:25:41.005024Z","published_by":null},"0.1.1":{"created_at":"2017-04-12T19:46:16.757554Z","published_by":null},"0.1.2":{"created_at":"2017-12-13T05:28:41.783251Z","published_by":null},"0.1.3":{"created_at":"2020-08-04T19:23:14.487348Z","published_by":1},"0.1.4":{"created_at":"2021-05-11T14:55:52.148640Z","published_by":1},"0.1.5":{"created_at":"2022-01-12T15:23:24.140206Z","published_by":1}},"metadata":{"description":"Tool for helping to find SSL certificate locations on the system for OpenSSL\n","repository":"https://github.com/alexcrichton/openssl-probe"}},"openssl-sys":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.0.1":{"created_at":"2014-11-11T06:38:37.946154Z","published_by":null},"0.0.2":{"created_at":"2014-11-22T22:46:37.734196Z","published_by":null},"0.1.0":{"created_at":"2014-11-26T00:47:10.583351Z","published_by":null},"0.1.1":{"created_at":"2014-11-26T16:50:11.845795Z","published_by":null},"0.2.0":{"created_at":"2014-11-28T05:37:27.471766Z","published_by":null},"0.2.1":{"created_at":"2014-11-28T23:49:32.009130Z","published_by":null},"0.2.2":{"created_at":"2014-12-01T21:24:01.520088Z","published_by":null},"0.2.3":{"created_at":"2014-12-12T04:11:02.360514Z","published_by":null},"0.2.4":{"created_at":"2014-12-15T17:26:26.778425Z","published_by":null},"0.2.5":{"created_at":"2014-12-16T16:56:54.721326Z","published_by":null},"0.2.6":{"created_at":"2014-12-19T16:21:47.998936Z","published_by":null},"0.2.7":{"created_at":"2014-12-21T18:35:32.383323Z","published_by":null},"0.2.8":{"created_at":"2014-12-23T16:57:43.428351Z","published_by":null},"0.2.9":{"created_at":"2014-12-29T22:44:29.890768Z","published_by":null},"0.2.10":{"created_at":"2015-01-01T05:16:22.508206Z","published_by":null},"0.2.11":{"created_at":"2015-01-03T16:32:32.849431Z","published_by":null},"0.2.12":{"created_at":"2015-01-04T16:04:09.705523Z","published_by":null},"0.2.13":{"created_at":"2015-01-06T16:59:46.408695Z","published_by":null},"0.2.14":{"created_at":"2015-01-07T16:16:10.183803Z","published_by":null},"0.2.15":{"created_at":"2015-01-09T18:11:14.153028Z","published_by":null},"0.2.16":{"created_at":"2015-01-17T06:30:08.805861Z","published_by":null},"0.2.17":{"created_at":"2015-01-23T18:21:30.652799Z","published_by":null},"0.2.18":{"created_at":"2015-01-28T16:47:48.026026Z","published_by":null},"0.3.0":{"created_at":"2015-02-03T16:59:46.569507Z","published_by":null},"0.3.1":{"created_at":"2015-02-03T18:10:18.658908Z","published_by":null},"0.3.2":{"created_at":"2015-02-09T07:39:22.325241Z","published_by":null},"0.3.3":{"created_at":"2015-02-09T08:05:18.835036Z","published_by":null},"0.3.4":{"created_at":"2015-02-12T03:58:53.729688Z","published_by":null},"0.3.5":{"created_at":"2015-02-12T05:30:44.121527Z","published_by":null},"0.3.6":{"created_at":"2015-02-12T21:22:37.587057Z","published_by":null},"0.4.0":{"created_at":"2015-02-14T07:35:46.141855Z","published_by":null},"0.4.1":{"created_at":"2015-02-17T07:29:25.327822Z","published_by":null},"0.4.2":{"created_at":"2015-02-19T17:13:54.615751Z","published_by":null},"0.4.3":{"created_at":"2015-02-20T21:44:23.143505Z","published_by":null},"0.5.0":{"created_at":"2015-02-28T03:48:28.759673Z","published_by":null},"0.5.1":{"created_at":"2015-03-05T18:15:49.082437Z","published_by":null},"0.5.2":{"created_at":"2015-03-25T18:37:12.047935Z","published_by":null},"0.5.3":{"created_at":"2015-03-29T17:21:06.744001Z","published_by":null},"0.5.4":{"created_at":"2015-04-02T18:17:04.107321Z","published_by":null},"0.5.5":{"created_at":"2015-04-03T15:44:07.581123Z","published_by":null},"0.6.0":{"created_at":"2015-04-05T23:49:50.888324Z","published_by":null},"0.6.1":{"created_at":"2015-04-22T22:08:25.735692Z","published_by":null},"0.6.2":{"created_at":"2015-04-30T16:10:15.630697Z","published_by":null},"0.6.3":{"created_at":"2015-06-26T06:28:07.272116Z","published_by":null},"0.6.4":{"created_at":"2015-07-06T18:13:37.657590Z","published_by":null},"0.6.5":{"created_at":"2015-09-01T02:19:42.529245Z","published_by":null},"0.6.6":{"created_at":"2015-10-05T21:45:51.489237Z","published_by":null},"0.6.7":{"created_at":"2015-10-15T02:29:47.022464Z","published_by":null},"0.7.0":{"created_at":"2015-11-17T05:46:53.267564Z","published_by":null},"0.7.1":{"created_at":"2015-11-29T00:20:19.516145Z","published_by":null},"0.7.2":{"created_at":"2015-12-16T04:07:03.891470Z","published_by":null},"0.7.3":{"created_at":"2015-12-18T05:30:01.620066Z","published_by":null},"0.7.4":{"created_at":"2015-12-19T06:49:07.055102Z","published_by":null},"0.7.5":{"created_at":"2016-01-23T00:15:14.743906Z","published_by":null},"0.7.6":{"created_at":"2016-02-10T17:44:43.367742Z","published_by":null},"0.7.7":{"created_at":"2016-03-17T16:09:00.952411Z","published_by":null},"0.7.8":{"created_at":"2016-03-18T15:59:16.940962Z","published_by":null},"0.7.9":{"created_at":"2016-04-07T04:44:02.163652Z","published_by":null},"0.7.10":{"created_at":"2016-04-17T04:01:31.714937Z","published_by":null},"0.7.11":{"created_at":"2016-05-05T20:37:08.916491Z","published_by":null},"0.7.12":{"created_at":"2016-05-17T06:09:08.365411Z","published_by":null},"0.7.13":{"created_at":"2016-05-20T23:07:08.504343Z","published_by":null},"0.7.14":{"created_at":"2016-07-01T22:45:56.959751Z","published_by":null},"0.7.15":{"created_at":"2016-08-12T04:14:00.159950Z","published_by":null},"0.7.16":{"created_at":"2016-08-16T01:50:57.547998Z","published_by":null},"0.7.17":{"created_at":"2016-08-18T20:05:03.196666Z","published_by":null},"0.9.0":{"created_at":"2016-11-06T03:11:22.132278Z","published_by":null},"0.9.1":{"created_at":"2016-11-11T16:47:39.568010Z","published_by":null},"0.9.2":{"created_at":"2016-11-28T06:24:27.570497Z","published_by":null},"0.9.3":{"created_at":"2016-12-10T05:54:28.944762Z","published_by":null},"0.9.4":{"created_at":"2016-12-23T18:38:25.064324Z","published_by":null},"0.9.5":{"created_at":"2017-01-04T00:10:06.096622Z","published_by":null},"0.9.6":{"created_at":"2017-01-10T04:52:52.359111Z","published_by":null},"0.9.7":{"created_at":"2017-02-11T22:34:57.802746Z","published_by":null},"0.9.8":{"created_at":"2017-03-09T09:33:50.760268Z","published_by":null},"0.9.9":{"created_at":"2017-03-14T19:55:58.499742Z","published_by":null},"0.9.10":{"created_at":"2017-03-26T17:49:24.343188Z","published_by":null},"0.9.11":{"created_at":"2017-04-14T23:57:41.317167Z","published_by":null},"0.9.12":{"created_at":"2017-05-12T18:48:19.862200Z","published_by":null},"0.9.13":{"created_at":"2017-05-30T00:46:35.780958Z","published_by":null},"0.9.14":{"created_at":"2017-06-15T03:00:00.251472Z","published_by":null},"0.9.15":{"created_at":"2017-07-20T02:36:00.592009Z","published_by":null},"0.9.16":{"created_at":"2017-08-11T05:18:05.002821Z","published_by":null},"0.9.17":{"created_at":"2017-08-15T00:14:07.091843Z","published_by":null},"0.9.18":{"created_at":"2017-09-20T14:23:00.626388Z","published_by":null},"0.9.19":{"created_at":"2017-09-20T20:51:18.315942Z","published_by":null},"0.9.20":{"created_at":"2017-10-14T21:37:03.668838Z","published_by":null},"0.9.21":{"created_at":"2017-11-17T17:14:00.418769Z","published_by":null},"0.9.22":{"created_at":"2017-11-29T17:37:59.692196Z","published_by":null},"0.9.23":{"created_at":"2017-12-06T05:58:39.006524Z","published_by":null},"0.9.24":{"created_at":"2018-01-11T06:07:11.213165Z","published_by":null},"0.9.25":{"created_at":"2018-02-12T18:56:20.883823Z","published_by":null},"0.9.26":{"created_at":"2018-02-18T18:48:26.673053Z","published_by":null},"0.9.27":{"created_at":"2018-02-28T22:31:36.112788Z","published_by":null},"0.9.28":{"created_at":"2018-04-05T18:10:00.226064Z","published_by":null},"0.9.29":{"created_at":"2018-05-01T03:38:35.907775Z","published_by":null},"0.9.30":{"created_at":"2018-05-01T03:40:49.734120Z","published_by":null},"0.9.31":{"created_at":"2018-05-21T04:02:26.969030Z","published_by":null},"0.9.32":{"created_at":"2018-06-02T03:57:20.819015Z","published_by":null},"0.9.33":{"created_at":"2018-06-06T20:36:42.342966Z","published_by":null},"0.9.35":{"created_at":"2018-08-04T17:11:35.775374Z","published_by":null},"0.9.36":{"created_at":"2018-09-14T02:18:12.513660Z","published_by":null},"0.9.37":{"created_at":"2018-10-14T23:09:46.750617Z","published_by":null},"0.9.38":{"created_at":"2018-10-16T20:15:50.051317Z","published_by":null},"0.9.39":{"created_at":"2018-10-19T03:11:54.315623Z","published_by":null},"0.9.40":{"created_at":"2018-12-16T17:02:23.146474Z","published_by":null},"0.9.41":{"created_at":"2019-02-22T17:50:39.597621Z","published_by":null},"0.9.42":{"created_at":"2019-03-01T20:38:40.411494Z","published_by":5},"0.9.43":{"created_at":"2019-03-20T14:41:14.716344Z","published_by":5},"0.9.44":{"created_at":"2019-05-01T04:55:30.759616Z","published_by":5},"0.9.45":{"created_at":"2019-05-04T02:52:39.597888Z","published_by":5},"0.9.46":{"created_at":"2019-05-09T01:44:52.828618Z","published_by":5},"0.9.47":{"created_at":"2019-05-18T19:11:09.809080Z","published_by":5},"0.9.48":{"created_at":"2019-07-19T14:30:35.288287Z","published_by":5},"0.9.49":{"created_at":"2019-08-16T02:20:39.948286Z","published_by":5},"0.9.50":{"created_at":"2019-10-03T00:45:30.911043Z","published_by":5},"0.9.51":{"created_at":"2019-10-10T01:03:34.400262Z","published_by":5},"0.9.52":{"created_at":"2019-10-19T14:36:02.256358Z","published_by":5},"0.9.53":{"created_at":"2019-11-22T23:29:18.369438Z","published_by":5},"0.9.54":{"created_at":"2020-01-29T23:37:06.434383Z","published_by":5},"0.9.55":{"created_at":"2020-04-08T00:54:16.589319Z","published_by":5},"0.9.56":{"created_at":"2020-05-07T23:20:28.285196Z","published_by":5},"0.9.57":{"created_at":"2020-05-24T13:35:39.866529Z","published_by":5},"0.9.58":{"created_at":"2020-06-05T22:33:16.345444Z","published_by":5},"0.9.59":{"created_at":"2020-12-09T22:22:58.549655Z","published_by":5},"0.9.60":{"created_at":"2020-12-24T13:01:07.167292Z","published_by":5},"0.9.61":{"created_at":"2021-03-13T20:43:15.249371Z","published_by":5},"0.9.62":{"created_at":"2021-04-28T23:44:26.017079Z","published_by":5},"0.9.63":{"created_at":"2021-05-06T11:45:59.052762Z","published_by":5},"0.9.64":{"created_at":"2021-06-19T00:30:34.850927Z","published_by":5},"0.9.65":{"created_at":"2021-06-21T16:29:27.109407Z","published_by":5},"0.9.66":{"created_at":"2021-08-17T17:07:22.887798Z","published_by":5},"0.9.67":{"created_at":"2021-09-21T21:42:56.409641Z","published_by":5},"0.9.68":{"created_at":"2021-10-27T21:02:25.961097Z","published_by":5},"0.9.69":{"created_at":"2021-10-31T18:13:10.101852Z","published_by":5},"0.9.70":{"created_at":"2021-10-31T20:07:34.841844Z","published_by":5},"0.9.71":{"created_at":"2021-11-16T15:55:08.527462Z","published_by":5},"0.9.72":{"created_at":"2021-12-12T02:07:08.690929Z","published_by":5},"0.9.73":{"created_at":"2022-05-03T00:53:49.841014Z","published_by":5},"0.9.74":{"created_at":"2022-06-02T00:40:34.644087Z","published_by":5},"0.9.75":{"created_at":"2022-07-09T13:21:39.414129Z","published_by":5},"0.9.76":{"created_at":"2022-09-26T23:13:14.075748Z","published_by":5},"0.9.77":{"created_at":"2022-10-22T13:31:15.168899Z","published_by":5},"0.9.78":{"created_at":"2022-11-24T01:47:08.157823Z","published_by":5},"0.9.79":{"created_at":"2022-12-06T12:37:21.965425Z","published_by":5},"0.9.80":{"created_at":"2022-12-20T14:36:47.931515Z","published_by":5},"0.9.81":{"created_at":"2023-03-15T01:06:01.278282Z","published_by":5},"0.9.82":{"created_at":"2023-03-19T23:55:44.293600Z","published_by":5},"0.9.83":{"created_at":"2023-03-24T01:48:52.134915Z","published_by":163},"0.9.84":{"created_at":"2023-04-01T14:02:53.306611Z","published_by":5},"0.9.85":{"created_at":"2023-04-10T00:04:32.169056Z","published_by":5},"0.9.86":{"created_at":"2023-04-20T22:48:51.944946Z","published_by":163},"0.9.87":{"created_at":"2023-04-24T23:07:16.826686Z","published_by":163}},"metadata":{"description":"FFI bindings to OpenSSL","repository":"https://github.com/sfackler/rust-openssl"}},"os_str_bytes":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2019-11-28T22:56:27.497950Z","published_by":68756},"0.1.1":{"created_at":"2019-11-30T20:03:38.692789Z","published_by":68756},"0.1.2":{"created_at":"2019-12-01T21:46:26.974884Z","published_by":68756},"0.1.3":{"created_at":"2019-12-06T18:47:33.722416Z","published_by":68756},"0.2.0":{"created_at":"2019-12-07T15:50:02.302333Z","published_by":68756},"0.2.1":{"created_at":"2019-12-07T16:17:53.622620Z","published_by":68756},"0.3.0":{"created_at":"2019-12-14T20:44:31.571478Z","published_by":68756},"1.0.0":{"created_at":"2019-12-28T14:22:32.520937Z","published_by":68756},"1.0.1":{"created_at":"2020-02-27T20:33:39.516837Z","published_by":68756},"1.0.2":{"created_at":"2020-03-15T18:01:58.988222Z","published_by":68756},"1.0.3":{"created_at":"2020-03-26T15:37:44.235523Z","published_by":68756},"1.1.0":{"created_at":"2020-04-09T13:51:29.128164Z","published_by":68756},"2.0.0":{"created_at":"2020-04-18T20:38:14.303117Z","published_by":68756},"2.1.0":{"created_at":"2020-04-22T15:16:52.352198Z","published_by":68756},"2.2.0":{"created_at":"2020-04-27T17:29:51.194671Z","published_by":68756},"2.2.1":{"created_at":"2020-05-01T21:09:01.394533Z","published_by":68756},"2.3.0":{"created_at":"2020-05-01T21:38:03.358539Z","published_by":68756},"2.3.1":{"created_at":"2020-05-12T13:39:03.489001Z","published_by":68756},"2.3.2":{"created_at":"2020-08-02T03:39:18.491362Z","published_by":68756},"2.4.0":{"created_at":"2020-11-06T02:36:53.570446Z","published_by":68756},"3.0.0":{"created_at":"2021-03-01T03:16:16.913929Z","published_by":68756},"3.1.0":{"created_at":"2021-05-14T07:38:42.759850Z","published_by":68756},"4.0.0":{"created_at":"2021-08-21T22:19:54.032463Z","published_by":68756},"4.1.0":{"created_at":"2021-08-29T03:37:54.899905Z","published_by":68756},"4.1.1":{"created_at":"2021-08-29T04:24:19.098457Z","published_by":68756},"4.2.0":{"created_at":"2021-09-06T19:43:49.267313Z","published_by":68756},"5.0.0":{"created_at":"2021-10-24T04:03:54.891187Z","published_by":68756},"6.0.0":{"created_at":"2021-12-05T21:13:50.051508Z","published_by":68756},"6.0.1":{"created_at":"2022-05-14T17:32:06.443158Z","published_by":68756},"6.1.0":{"created_at":"2022-05-26T00:57:16.527817Z","published_by":68756},"6.2.0":{"created_at":"2022-07-16T22:02:25.767468Z","published_by":68756},"6.3.0":{"created_at":"2022-08-14T03:02:13.344415Z","published_by":68756},"6.3.1":{"created_at":"2022-10-28T02:01:35.226857Z","published_by":68756},"6.4.0":{"created_at":"2022-11-11T02:04:17.116273Z","published_by":68756},"6.4.1":{"created_at":"2022-11-21T02:37:14.569749Z","published_by":68756},"6.5.0":{"created_at":"2023-03-19T14:25:36.555836Z","published_by":68756}},"metadata":{"description":"Convert between byte sequences and platform-native strings\n","repository":"https://github.com/dylni/os_str_bytes"}},"percent-encoding":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"1.0.0":{"created_at":"2017-06-13T17:02:11.044209Z","published_by":null},"1.0.1":{"created_at":"2017-11-11T09:44:36.558814Z","published_by":null},"2.0.0":{"created_at":"2019-07-23T15:37:42.648703Z","published_by":7},"2.1.0":{"created_at":"2019-08-05T15:10:40.350032Z","published_by":7},"2.2.0":{"created_at":"2022-09-08T07:48:28.868254Z","published_by":72883}},"metadata":{"description":"Percent encoding and decoding","repository":"https://github.com/servo/rust-url/"}},"pin-project-lite":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2019-10-22T15:30:45.981586Z","published_by":33035},"0.1.1":{"created_at":"2019-11-15T12:34:46.985574Z","published_by":33035},"0.1.2":{"created_at":"2020-01-05T19:34:37.708485Z","published_by":33035},"0.1.3":{"created_at":"2020-01-20T02:23:49.390534Z","published_by":33035},"0.1.4":{"created_at":"2020-01-20T10:42:55.499425Z","published_by":33035},"0.1.5":{"created_at":"2020-05-07T03:20:54.664510Z","published_by":33035},"0.1.6":{"created_at":"2020-05-31T04:14:18.679608Z","published_by":33035},"0.1.7":{"created_at":"2020-06-04T18:39:57.264152Z","published_by":33035},"0.1.8":{"created_at":"2020-09-26T21:06:53.184865Z","published_by":33035},"0.1.9":{"created_at":"2020-09-29T03:54:11.925020Z","published_by":33035},"0.1.10":{"created_at":"2020-10-01T10:05:35.875597Z","published_by":33035},"0.1.11":{"created_at":"2020-10-20T14:08:06.992575Z","published_by":33035},"0.1.12":{"created_at":"2021-03-02T12:22:29.542505Z","published_by":33035},"0.2.0":{"created_at":"2020-11-13T17:19:46.906200Z","published_by":33035},"0.2.1":{"created_at":"2021-01-05T14:44:12.617852Z","published_by":33035},"0.2.2":{"created_at":"2021-01-09T08:27:04.430180Z","published_by":33035},"0.2.3":{"created_at":"2021-01-09T11:46:43.227425Z","published_by":33035},"0.2.4":{"created_at":"2021-01-11T04:22:09.040685Z","published_by":33035},"0.2.5":{"created_at":"2021-03-02T11:22:01.301114Z","published_by":33035},"0.2.6":{"created_at":"2021-03-04T15:25:16.435270Z","published_by":33035},"0.2.7":{"created_at":"2021-06-26T07:05:23.239365Z","published_by":33035},"0.2.8":{"created_at":"2021-12-31T13:25:41.319851Z","published_by":33035},"0.2.9":{"created_at":"2022-04-26T13:35:30.385333Z","published_by":33035}},"metadata":{"description":"A lightweight version of pin-project written with declarative macros.\n","repository":"https://github.com/taiki-e/pin-project-lite"}},"pin-utils":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0-alpha.1":{"created_at":"2018-08-06T15:00:31.609789Z","published_by":null},"0.1.0-alpha.2":{"created_at":"2018-08-30T18:16:20.641845Z","published_by":null},"0.1.0-alpha.3":{"created_at":"2018-09-24T22:20:32.696439Z","published_by":null},"0.1.0-alpha.4":{"created_at":"2018-12-26T22:07:15.059758Z","published_by":null},"0.1.0":{"created_at":"2020-04-22T16:13:27.095776Z","published_by":5149}},"metadata":{"description":"Utilities for pinning\n","repository":"https://github.com/rust-lang-nursery/pin-utils"}},"pkg-config":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.0.1":{"created_at":"2014-11-11T06:37:31.320021Z","published_by":null},"0.1.0":{"created_at":"2014-11-27T23:31:36.695922Z","published_by":null},"0.1.1":{"created_at":"2014-12-09T15:16:26.835531Z","published_by":null},"0.1.2":{"created_at":"2014-12-16T18:57:15.464344Z","published_by":null},"0.1.3":{"created_at":"2015-01-03T23:06:15.889541Z","published_by":null},"0.1.4":{"created_at":"2015-01-09T15:24:50.946527Z","published_by":null},"0.1.5":{"created_at":"2015-01-19T02:37:56.301830Z","published_by":null},"0.1.6":{"created_at":"2015-01-23T16:29:35.047281Z","published_by":null},"0.1.7":{"created_at":"2015-01-28T16:04:34.178645Z","published_by":null},"0.2.0":{"created_at":"2015-02-05T18:43:42.166610Z","published_by":null},"0.2.1":{"created_at":"2015-02-12T18:45:44.358870Z","published_by":null},"0.2.2":{"created_at":"2015-02-20T18:03:40.776603Z","published_by":null},"0.3.0":{"created_at":"2015-02-22T10:08:58.983525Z","published_by":null},"0.3.1":{"created_at":"2015-03-11T17:29:04.764772Z","published_by":null},"0.3.2":{"created_at":"2015-03-25T15:54:32.455225Z","published_by":null},"0.3.3":{"created_at":"2015-04-02T16:44:05.540563Z","published_by":null},"0.3.4":{"created_at":"2015-05-06T00:10:58.525739Z","published_by":null},"0.3.5":{"created_at":"2015-06-16T16:50:53.367430Z","published_by":null},"0.3.6":{"created_at":"2015-10-05T17:32:44.918251Z","published_by":null},"0.3.7":{"created_at":"2016-02-17T19:17:31.643154Z","published_by":null},"0.3.8":{"created_at":"2016-03-08T16:07:13.205308Z","published_by":null},"0.3.9":{"created_at":"2017-01-24T17:28:37.300153Z","published_by":null},"0.3.10":{"created_at":"2018-04-23T20:32:31.411874Z","published_by":null},"0.3.11":{"created_at":"2018-04-24T15:20:19.308697Z","published_by":null},"0.3.12":{"created_at":"2018-07-18T21:44:05.594861Z","published_by":null},"0.3.13":{"created_at":"2018-08-06T14:20:28.427334Z","published_by":null},"0.3.14":{"created_at":"2018-08-28T17:14:56.995241Z","published_by":null},"0.3.15":{"created_at":"2019-07-25T18:17:11.218738Z","published_by":3623},"0.3.16":{"created_at":"2019-09-09T09:04:54.477766Z","published_by":3623},"0.3.17":{"created_at":"2019-11-02T09:37:38.307880Z","published_by":3623},"0.3.18":{"created_at":"2020-07-11T15:47:41.245997Z","published_by":3623},"0.3.19":{"created_at":"2020-10-13T10:38:17.551277Z","published_by":3623},"0.3.20":{"created_at":"2021-09-25T20:23:21.852050Z","published_by":3623},"0.3.21":{"created_at":"2021-10-22T19:02:21.279127Z","published_by":3623},"0.3.22":{"created_at":"2021-10-24T14:50:28.873148Z","published_by":3623},"0.3.23":{"created_at":"2021-12-06T18:07:42.304084Z","published_by":3623},"0.3.24":{"created_at":"2021-12-11T08:57:26.720176Z","published_by":3623},"0.3.25":{"created_at":"2022-03-31T07:47:46.792193Z","published_by":3623},"0.3.26":{"created_at":"2022-10-26T06:39:25.874609Z","published_by":3623},"0.3.27":{"created_at":"2023-05-03T11:15:20.548299Z","published_by":3623}},"metadata":{"description":"A library to run the pkg-config system tool at build time in order to be used in\nCargo build scripts.\n","repository":"https://github.com/rust-lang/pkg-config-rs"}},"proc-macro2":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2017-07-06T01:01:58.064887Z","published_by":null},"0.1.1":{"created_at":"2017-07-06T01:10:18.053881Z","published_by":null},"0.1.2":{"created_at":"2017-07-14T13:23:40.630277Z","published_by":null},"0.1.3":{"created_at":"2017-08-28T19:47:32.486334Z","published_by":null},"0.1.4":{"created_at":"2017-11-19T16:06:58.738213Z","published_by":null},"0.1.5":{"created_at":"2017-11-21T15:35:23.005325Z","published_by":null},"0.1.6":{"created_at":"2017-12-25T23:36:38.098144Z","published_by":null},"0.1.7":{"created_at":"2017-12-31T17:34:58.740678Z","published_by":null},"0.1.8":{"created_at":"2018-01-03T04:39:02.898557Z","published_by":null},"0.1.9":{"created_at":"2018-01-05T22:17:24.253834Z","published_by":null},"0.1.10":{"created_at":"2018-01-06T17:20:21.093952Z","published_by":null},"0.2.0":{"created_at":"2018-01-08T16:05:47.644371Z","published_by":null},"0.2.1":{"created_at":"2018-01-08T22:07:57.850010Z","published_by":null},"0.2.2":{"created_at":"2018-01-22T05:35:55.319363Z","published_by":null},"0.2.3":{"created_at":"2018-02-22T18:52:37.295912Z","published_by":null},"0.3.0":{"created_at":"2018-03-31T09:41:44.566298Z","published_by":null},"0.3.1":{"created_at":"2018-03-31T19:29:58.670176Z","published_by":null},"0.3.2":{"created_at":"2018-04-04T15:00:02.420424Z","published_by":null},"0.3.3":{"created_at":"2018-04-04T22:05:36.392686Z","published_by":null},"0.3.4":{"created_at":"2018-04-06T00:47:28.480853Z","published_by":null},"0.3.5":{"created_at":"2018-04-06T00:58:47.665953Z","published_by":null},"0.3.6":{"created_at":"2018-04-07T16:47:36.694886Z","published_by":null},"0.3.7":{"created_at":"2018-04-24T02:18:56.333914Z","published_by":null},"0.3.8":{"created_at":"2018-04-29T23:40:04.021783Z","published_by":null},"0.4.0":{"created_at":"2018-05-17T14:09:37.104331Z","published_by":null},"0.4.1":{"created_at":"2018-05-17T18:04:25.949253Z","published_by":null},"0.4.2":{"created_at":"2018-05-19T03:57:16.243492Z","published_by":null},"0.4.3":{"created_at":"2018-05-21T02:33:48.531309Z","published_by":null},"0.4.4":{"created_at":"2018-05-29T03:14:07.586131Z","published_by":null},"0.4.5":{"created_at":"2018-06-02T22:54:19.535992Z","published_by":null},"0.4.6":{"created_at":"2018-06-04T08:02:04.531367Z","published_by":null},"0.4.7":{"created_at":"2018-07-17T05:49:53.255807Z","published_by":null},"0.4.8":{"created_at":"2018-07-17T05:59:29.418825Z","published_by":null},"0.4.9":{"created_at":"2018-07-22T01:54:10.737874Z","published_by":null},"0.4.10":{"created_at":"2018-08-12T05:14:29.481520Z","published_by":null},"0.4.11":{"created_at":"2018-08-12T07:05:05.759028Z","published_by":null},"0.4.12":{"created_at":"2018-08-13T18:01:06.611291Z","published_by":null},"0.4.13":{"created_at":"2018-08-17T06:08:17.400897Z","published_by":null},"0.4.14":{"created_at":"2018-08-28T17:27:35.081953Z","published_by":null},"0.4.15":{"created_at":"2018-08-29T00:22:03.462449Z","published_by":null},"0.4.16":{"created_at":"2018-09-02T02:41:32.638608Z","published_by":null},"0.4.17":{"created_at":"2018-09-03T17:33:38.796261Z","published_by":null},"0.4.18":{"created_at":"2018-09-06T16:56:27.495652Z","published_by":null},"0.4.19":{"created_at":"2018-09-08T23:23:19.769613Z","published_by":null},"0.4.20":{"created_at":"2018-10-04T16:14:06.058427Z","published_by":null},"0.4.21":{"created_at":"2018-11-08T17:16:00.949986Z","published_by":null},"0.4.22":{"created_at":"2018-11-11T19:32:57.700581Z","published_by":null},"0.4.23":{"created_at":"2018-11-11T23:27:49.150258Z","published_by":null},"0.4.24":{"created_at":"2018-11-16T11:56:31.635483Z","published_by":null},"0.4.25":{"created_at":"2019-01-16T20:29:52.029052Z","published_by":null},"0.4.26":{"created_at":"2019-01-20T04:54:44.281139Z","published_by":null},"0.4.27":{"created_at":"2019-01-31T23:31:09.028323Z","published_by":null},"0.4.28":{"created_at":"2019-04-23T00:40:56.252789Z","published_by":3618},"0.4.29":{"created_at":"2019-04-28T21:21:20.450296Z","published_by":3618},"0.4.30":{"created_at":"2019-05-08T20:40:03.732496Z","published_by":3618},"1.0.0":{"created_at":"2019-08-13T16:01:41.128256Z","published_by":3618},"1.0.1":{"created_at":"2019-08-16T16:09:31.676209Z","published_by":3618},"1.0.2":{"created_at":"2019-08-31T06:18:32.227092Z","published_by":3618},"1.0.3":{"created_at":"2019-09-07T03:32:40.085093Z","published_by":3618},"1.0.4":{"created_at":"2019-09-21T07:56:03.079780Z","published_by":3618},"1.0.5":{"created_at":"2019-10-04T20:06:33.579687Z","published_by":3618},"1.0.6":{"created_at":"2019-10-19T21:52:53.663568Z","published_by":3618},"1.0.7":{"created_at":"2020-01-01T17:07:32.463590Z","published_by":3618},"1.0.8":{"created_at":"2020-01-17T03:38:19.200012Z","published_by":3618},"1.0.9":{"created_at":"2020-02-24T23:48:36.082669Z","published_by":3618},"1.0.10":{"created_at":"2020-03-30T18:03:55.268751Z","published_by":3618},"1.0.11":{"created_at":"2020-05-01T22:48:56.529820Z","published_by":3618},"1.0.12":{"created_at":"2020-05-01T23:51:51.169378Z","published_by":3618},"1.0.13":{"created_at":"2020-05-16T04:39:06.036100Z","published_by":3618},"1.0.14":{"created_at":"2020-05-21T23:51:25.029383Z","published_by":3618},"1.0.15":{"created_at":"2020-05-22T17:14:14.601566Z","published_by":3618},"1.0.16":{"created_at":"2020-05-23T21:27:37.881056Z","published_by":3618},"1.0.17":{"created_at":"2020-05-23T21:53:14.068031Z","published_by":3618},"1.0.18":{"created_at":"2020-05-31T17:06:24.287509Z","published_by":3618},"1.0.19":{"created_at":"2020-07-20T00:14:17.845775Z","published_by":3618},"1.0.20":{"created_at":"2020-09-03T20:28:08.659419Z","published_by":3618},"1.0.21":{"created_at":"2020-09-09T19:44:50.852498Z","published_by":3618},"1.0.22":{"created_at":"2020-09-25T19:28:25.357756Z","published_by":3618},"1.0.23":{"created_at":"2020-09-26T15:20:39.017909Z","published_by":3618},"1.0.24":{"created_at":"2020-10-01T04:06:36.874781Z","published_by":3618},"1.0.25":{"created_at":"2021-04-01T02:17:24.389488Z","published_by":3618},"1.0.26":{"created_at":"2021-04-01T03:48:49.728310Z","published_by":3618},"1.0.27":{"created_at":"2021-05-20T02:49:53.183216Z","published_by":3618},"1.0.28":{"created_at":"2021-07-23T02:56:37.222961Z","published_by":3618},"1.0.29":{"created_at":"2021-08-30T19:24:40.259011Z","published_by":3618},"1.0.30":{"created_at":"2021-10-12T17:01:02.474687Z","published_by":3618},"1.0.31":{"created_at":"2021-10-26T17:55:59.077306Z","published_by":3618},"1.0.32":{"created_at":"2021-10-26T19:17:13.841983Z","published_by":3618},"1.0.33":{"created_at":"2021-12-05T19:49:49.080825Z","published_by":3618},"1.0.34":{"created_at":"2021-12-14T18:08:16.035015Z","published_by":3618},"1.0.35":{"created_at":"2021-12-26T22:03:55.592267Z","published_by":3618},"1.0.36":{"created_at":"2021-12-27T20:02:08.082005Z","published_by":3618},"1.0.37":{"created_at":"2022-04-06T04:06:08.413115Z","published_by":3618},"1.0.38":{"created_at":"2022-05-06T04:46:21.316712Z","published_by":3618},"1.0.39":{"created_at":"2022-05-16T22:30:11.847751Z","published_by":3618},"1.0.40":{"created_at":"2022-06-20T01:44:25.445630Z","published_by":3618},"1.0.41":{"created_at":"2022-07-25T04:45:22.061695Z","published_by":3618},"1.0.42":{"created_at":"2022-07-25T23:21:39.288003Z","published_by":3618},"1.0.43":{"created_at":"2022-08-03T03:52:10.363197Z","published_by":3618},"1.0.44":{"created_at":"2022-09-24T22:10:15.041239Z","published_by":3618},"1.0.45":{"created_at":"2022-09-28T20:39:27.389178Z","published_by":3618},"1.0.46":{"created_at":"2022-09-29T01:59:26.482977Z","published_by":3618},"1.0.47":{"created_at":"2022-10-15T08:24:04.265305Z","published_by":3618},"1.0.48":{"created_at":"2022-12-17T19:30:26.590869Z","published_by":3618},"1.0.49":{"created_at":"2022-12-18T17:31:07.427394Z","published_by":3618},"1.0.50":{"created_at":"2023-01-16T19:34:11.531517Z","published_by":3618},"1.0.51":{"created_at":"2023-02-04T23:33:10.431485Z","published_by":3618},"1.0.52":{"created_at":"2023-03-12T21:09:13.773536Z","published_by":3618},"1.0.53":{"created_at":"2023-03-22T05:54:17.585573Z","published_by":3618},"1.0.54":{"created_at":"2023-03-26T17:09:59.376281Z","published_by":3618},"1.0.55":{"created_at":"2023-04-01T20:16:35.754537Z","published_by":3618},"1.0.56":{"created_at":"2023-04-03T06:36:11.217052Z","published_by":3618},"1.0.57":{"created_at":"2023-05-15T09:01:01.714582Z","published_by":3618},"1.0.58":{"created_at":"2023-05-17T11:46:44.602078Z","published_by":3618}},"metadata":{"description":"A substitute implementation of the compiler's `proc_macro` API to decouple token-based libraries from the procedural macro use case.","repository":"https://github.com/dtolnay/proc-macro2"}},"quote":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2016-09-03T05:42:05.484129Z","published_by":null},"0.1.1":{"created_at":"2016-09-04T20:29:30.306248Z","published_by":null},"0.1.2":{"created_at":"2016-09-12T00:41:55.051384Z","published_by":null},"0.1.3":{"created_at":"2016-09-12T07:26:57.273418Z","published_by":null},"0.1.4":{"created_at":"2016-09-20T15:13:11.766803Z","published_by":null},"0.2.0":{"created_at":"2016-09-27T16:51:11.420995Z","published_by":null},"0.2.1":{"created_at":"2016-10-02T05:42:23.883672Z","published_by":null},"0.2.2":{"created_at":"2016-10-03T20:03:49.056511Z","published_by":null},"0.2.3":{"created_at":"2016-10-04T04:15:30.632946Z","published_by":null},"0.3.0-rc1":{"created_at":"2016-10-08T04:16:45.774099Z","published_by":null},"0.3.0-rc2":{"created_at":"2016-10-08T17:52:04.098050Z","published_by":null},"0.3.0":{"created_at":"2016-10-08T21:53:41.106508Z","published_by":null},"0.3.1":{"created_at":"2016-10-09T21:20:08.453881Z","published_by":null},"0.3.2":{"created_at":"2016-10-09T21:30:18.873123Z","published_by":null},"0.3.3":{"created_at":"2016-10-09T21:53:17.737721Z","published_by":null},"0.3.4":{"created_at":"2016-10-23T20:12:00.146987Z","published_by":null},"0.3.5":{"created_at":"2016-10-25T06:50:34.491707Z","published_by":null},"0.3.6":{"created_at":"2016-11-24T19:39:34.567167Z","published_by":null},"0.3.7":{"created_at":"2016-11-24T20:47:14.346971Z","published_by":null},"0.3.8":{"created_at":"2016-11-24T21:05:13.307672Z","published_by":null},"0.3.9":{"created_at":"2016-11-25T17:53:36.438280Z","published_by":null},"0.3.10":{"created_at":"2016-11-26T22:23:44.440603Z","published_by":null},"0.3.11":{"created_at":"2017-01-19T05:52:49.765546Z","published_by":null},"0.3.12":{"created_at":"2017-01-19T23:03:30.269375Z","published_by":null},"0.3.13":{"created_at":"2017-02-19T21:14:42.747070Z","published_by":null},"0.3.14":{"created_at":"2017-02-27T19:59:31.009377Z","published_by":null},"0.3.15":{"created_at":"2017-03-06T02:49:29.602398Z","published_by":null},"0.4.0":{"created_at":"2018-01-08T16:11:56.590484Z","published_by":null},"0.4.1":{"created_at":"2018-01-08T19:18:22.077570Z","published_by":null},"0.4.2":{"created_at":"2018-01-08T21:24:42.778901Z","published_by":null},"0.5.0":{"created_at":"2018-03-31T20:56:35.812698Z","published_by":null},"0.5.1":{"created_at":"2018-03-31T21:00:16.414707Z","published_by":null},"0.5.2":{"created_at":"2018-04-21T17:41:11.824716Z","published_by":null},"0.6.0":{"created_at":"2018-05-21T02:38:45.115089Z","published_by":null},"0.6.1":{"created_at":"2018-05-21T05:05:24.234357Z","published_by":null},"0.6.2":{"created_at":"2018-05-21T16:30:57.362932Z","published_by":null},"0.6.3":{"created_at":"2018-05-29T04:16:11.018768Z","published_by":null},"0.6.4":{"created_at":"2018-07-22T18:07:39.239122Z","published_by":null},"0.6.5":{"created_at":"2018-08-04T22:09:55.193063Z","published_by":null},"0.6.6":{"created_at":"2018-08-12T17:01:51.053717Z","published_by":null},"0.6.7":{"created_at":"2018-08-22T13:35:24.150790Z","published_by":null},"0.6.8":{"created_at":"2018-08-22T13:55:03.914317Z","published_by":null},"0.6.9":{"created_at":"2018-10-29T00:19:46.632193Z","published_by":null},"0.6.10":{"created_at":"2018-11-08T21:23:28.043658Z","published_by":null},"0.6.11":{"created_at":"2019-01-20T02:55:01.528634Z","published_by":null},"0.6.12":{"created_at":"2019-04-09T17:36:43.106309Z","published_by":3618},"0.6.13":{"created_at":"2019-07-11T16:36:13.638398Z","published_by":3618},"1.0.0":{"created_at":"2019-08-13T16:04:48.793888Z","published_by":3618},"1.0.1":{"created_at":"2019-08-16T16:11:52.260958Z","published_by":3618},"1.0.2":{"created_at":"2019-08-18T02:57:39.430996Z","published_by":3618},"1.0.3":{"created_at":"2020-03-04T23:58:13.303459Z","published_by":3618},"1.0.4":{"created_at":"2020-04-30T19:17:29.968449Z","published_by":3618},"1.0.5":{"created_at":"2020-05-13T06:02:32.457778Z","published_by":3618},"1.0.6":{"created_at":"2020-05-17T20:15:49.933943Z","published_by":3618},"1.0.7":{"created_at":"2020-06-07T18:09:26.309151Z","published_by":3618},"1.0.8":{"created_at":"2020-12-20T21:38:32.427861Z","published_by":3618},"1.0.9":{"created_at":"2021-02-12T06:50:01.443101Z","published_by":3618},"1.0.10":{"created_at":"2021-10-05T03:39:08.763037Z","published_by":3618},"1.0.11":{"created_at":"2021-12-27T20:30:58.929908Z","published_by":3618},"1.0.12":{"created_at":"2021-12-27T20:46:29.673168Z","published_by":3618},"1.0.13":{"created_at":"2021-12-27T22:31:29.568088Z","published_by":3618},"1.0.14":{"created_at":"2021-12-28T03:03:32.215450Z","published_by":3618},"1.0.15":{"created_at":"2022-01-22T01:07:12.920812Z","published_by":3618},"1.0.16":{"created_at":"2022-03-17T22:47:22.406686Z","published_by":3618},"1.0.17":{"created_at":"2022-03-25T22:26:55.712975Z","published_by":3618},"1.0.18":{"created_at":"2022-04-11T04:09:22.784274Z","published_by":3618},"1.0.19":{"created_at":"2022-06-18T23:44:00.130972Z","published_by":3618},"1.0.20":{"created_at":"2022-06-20T02:18:25.575218Z","published_by":3618},"1.0.21":{"created_at":"2022-08-03T03:58:22.276607Z","published_by":3618},"1.0.22":{"created_at":"2022-12-17T19:32:54.019337Z","published_by":3618},"1.0.23":{"created_at":"2022-12-18T17:27:55.647446Z","published_by":3618},"1.0.24":{"created_at":"2023-03-12T21:31:49.469595Z","published_by":3618},"1.0.25":{"created_at":"2023-03-12T22:32:45.729070Z","published_by":3618},"1.0.26":{"created_at":"2023-03-13T18:10:10.479392Z","published_by":3618},"1.0.27":{"created_at":"2023-05-08T15:18:09.216999Z","published_by":3618}},"metadata":{"description":"Quasi-quoting macro quote!(...)","repository":"https://github.com/dtolnay/quote"}},"redox_syscall":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2016-11-03T01:32:41.787114Z","published_by":null},"0.1.1":{"created_at":"2016-11-11T03:32:48.623541Z","published_by":null},"0.1.2":{"created_at":"2016-11-14T19:14:48.775246Z","published_by":null},"0.1.3":{"created_at":"2016-11-15T22:29:06.329084Z","published_by":null},"0.1.4":{"created_at":"2016-11-15T23:44:07.680055Z","published_by":null},"0.1.5":{"created_at":"2016-11-17T04:10:02.260163Z","published_by":null},"0.1.6":{"created_at":"2016-11-17T04:52:33.056656Z","published_by":null},"0.1.7":{"created_at":"2016-11-17T21:09:48.215641Z","published_by":null},"0.1.8":{"created_at":"2016-11-18T15:06:36.217043Z","published_by":null},"0.1.9":{"created_at":"2016-11-18T15:15:04.063758Z","published_by":null},"0.1.10":{"created_at":"2016-11-25T19:05:46.053004Z","published_by":null},"0.1.11":{"created_at":"2016-11-25T23:58:48.451771Z","published_by":null},"0.1.12":{"created_at":"2016-12-15T02:45:24.828723Z","published_by":null},"0.1.13":{"created_at":"2016-12-20T18:09:33.203184Z","published_by":null},"0.1.14":{"created_at":"2016-12-20T18:14:52.555542Z","published_by":null},"0.1.15":{"created_at":"2016-12-27T18:18:10.627069Z","published_by":null},"0.1.16":{"created_at":"2017-01-10T03:33:34.230911Z","published_by":null},"0.1.17":{"created_at":"2017-03-19T23:31:30.258656Z","published_by":null},"0.1.18":{"created_at":"2017-06-03T02:39:05.999693Z","published_by":null},"0.1.19":{"created_at":"2017-06-29T17:53:36.483147Z","published_by":null},"0.1.20":{"created_at":"2017-07-05T15:10:02.610914Z","published_by":null},"0.1.21":{"created_at":"2017-07-06T03:06:42.073403Z","published_by":null},"0.1.22":{"created_at":"2017-07-09T21:40:02.872507Z","published_by":null},"0.1.23":{"created_at":"2017-07-10T02:31:55.289524Z","published_by":null},"0.1.24":{"created_at":"2017-07-10T02:38:03.749115Z","published_by":null},"0.1.25":{"created_at":"2017-07-11T00:51:55.901004Z","published_by":null},"0.1.26":{"created_at":"2017-07-11T02:12:27.841727Z","published_by":null},"0.1.27":{"created_at":"2017-07-23T20:44:51.396704Z","published_by":null},"0.1.28":{"created_at":"2017-07-29T14:16:37.867959Z","published_by":null},"0.1.29":{"created_at":"2017-08-03T01:06:44.600311Z","published_by":null},"0.1.30":{"created_at":"2017-08-10T03:05:36.679206Z","published_by":null},"0.1.31":{"created_at":"2017-08-27T16:50:02.732638Z","published_by":null},"0.1.32":{"created_at":"2017-11-29T04:47:23.776407Z","published_by":null},"0.1.33":{"created_at":"2017-12-28T03:20:29.798258Z","published_by":null},"0.1.34":{"created_at":"2018-01-04T04:51:09.364157Z","published_by":null},"0.1.36":{"created_at":"2018-01-06T00:19:04.359103Z","published_by":null},"0.1.37":{"created_at":"2018-01-06T00:49:35.034378Z","published_by":null},"0.1.38":{"created_at":"2018-05-20T22:26:34.253896Z","published_by":null},"0.1.39":{"created_at":"2018-05-30T15:17:03.875017Z","published_by":null},"0.1.40":{"created_at":"2018-05-30T15:39:38.201991Z","published_by":null},"0.1.41":{"created_at":"2018-11-17T02:44:58.848376Z","published_by":null},"0.1.42":{"created_at":"2018-11-17T02:48:34.572721Z","published_by":null},"0.1.43":{"created_at":"2018-11-26T18:39:15.007017Z","published_by":null},"0.1.44":{"created_at":"2018-12-12T04:03:14.711389Z","published_by":null},"0.1.45":{"created_at":"2018-12-28T22:04:52.569524Z","published_by":null},"0.1.46":{"created_at":"2018-12-29T04:13:48.502632Z","published_by":null},"0.1.47":{"created_at":"2018-12-29T04:19:43.205474Z","published_by":null},"0.1.48":{"created_at":"2018-12-29T04:30:07.920954Z","published_by":null},"0.1.49":{"created_at":"2018-12-29T04:44:56.437247Z","published_by":null},"0.1.50":{"created_at":"2019-01-01T03:32:25.336340Z","published_by":null},"0.1.51":{"created_at":"2019-01-21T02:37:22.587070Z","published_by":null},"0.1.52":{"created_at":"2019-03-31T14:42:10.848191Z","published_by":2668},"0.1.53":{"created_at":"2019-04-07T00:58:25.247409Z","published_by":2668},"0.1.54":{"created_at":"2019-04-08T23:49:51.899571Z","published_by":2668},"0.1.55":{"created_at":"2019-07-04T03:29:50.547005Z","published_by":2668},"0.1.56":{"created_at":"2019-07-04T13:44:31.234549Z","published_by":2668},"0.1.57":{"created_at":"2020-07-10T16:56:09.746154Z","published_by":42742},"0.2.0":{"created_at":"2020-07-28T09:04:28.531442Z","published_by":42742},"0.2.1":{"created_at":"2020-08-27T15:36:33.247642Z","published_by":2668},"0.2.2":{"created_at":"2021-01-10T17:51:18.042805Z","published_by":2668},"0.2.3":{"created_at":"2021-01-11T13:46:35.648140Z","published_by":2668},"0.2.4":{"created_at":"2021-01-11T14:00:49.734037Z","published_by":2668},"0.2.5":{"created_at":"2021-02-14T20:43:10.531629Z","published_by":2668},"0.2.6":{"created_at":"2021-04-14T01:13:35.690624Z","published_by":2668},"0.2.7":{"created_at":"2021-04-29T01:58:31.972744Z","published_by":2668},"0.2.8":{"created_at":"2021-05-04T14:10:10.622651Z","published_by":2668},"0.2.9":{"created_at":"2021-06-16T11:26:19.028215Z","published_by":42742},"0.2.10":{"created_at":"2021-08-06T13:09:01.467697Z","published_by":42742},"0.2.11":{"created_at":"2022-02-28T22:04:58.455526Z","published_by":2668},"0.2.12":{"created_at":"2022-03-24T14:54:46.738534Z","published_by":42742},"0.2.13":{"created_at":"2022-03-30T16:33:39.848585Z","published_by":2668},"0.2.14":{"created_at":"2022-07-22T22:19:11.076726Z","published_by":2668},"0.2.15":{"created_at":"2022-07-22T22:24:18.435442Z","published_by":2668},"0.2.16":{"created_at":"2022-07-26T21:30:34.735593Z","published_by":2668},"0.3.0":{"created_at":"2022-07-27T15:33:28.589572Z","published_by":2668},"0.3.1":{"created_at":"2022-08-15T21:03:41.701044Z","published_by":2668},"0.3.2":{"created_at":"2022-08-24T14:47:25.406882Z","published_by":2668},"0.3.3":{"created_at":"2022-08-30T16:33:03.030190Z","published_by":2668},"0.3.4":{"created_at":"2022-08-31T13:48:42.983149Z","published_by":2668},"0.3.5":{"created_at":"2023-03-14T15:24:47.395655Z","published_by":2668}},"metadata":{"description":"A Rust library to access raw Redox system calls","repository":"https://gitlab.redox-os.org/redox-os/syscall"}},"remove_dir_all":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2017-03-29T00:02:22.651612Z","published_by":null},"0.2.0":{"created_at":"2017-04-25T20:41:05.614205Z","published_by":null},"0.3.0":{"created_at":"2017-10-04T10:56:47.503960Z","published_by":null},"0.5.0":{"created_at":"2018-02-15T02:24:28.039879Z","published_by":null},"0.5.1":{"created_at":"2018-04-19T14:01:56.462973Z","published_by":null},"0.5.2":{"created_at":"2019-06-13T16:25:44.421624Z","published_by":2678},"0.5.3":{"created_at":"2020-06-12T16:25:59.793697Z","published_by":2678},"0.6.0":{"created_at":"2020-08-25T07:39:24.265329Z","published_by":8585},"0.6.1":{"created_at":"2020-10-16T09:47:48.926212Z","published_by":2678},"0.7.0":{"created_at":"2021-03-05T23:55:50.741326Z","published_by":8585},"0.8.0":{"created_at":"2023-02-24T10:12:32.285594Z","published_by":2678},"0.8.1":{"created_at":"2023-02-24T16:43:54.392988Z","published_by":8585},"0.8.2":{"created_at":"2023-03-24T23:24:23.715854Z","published_by":2678}},"metadata":{"description":"A safe, reliable implementation of remove_dir_all for Windows","repository":"https://github.com/XAMPPRocky/remove_dir_all.git"}},"reqwest":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.0.0":{"created_at":"2016-10-16T17:29:41.599749Z","published_by":null},"0.1.0":{"created_at":"2016-11-10T01:23:56.873910Z","published_by":null},"0.2.0":{"created_at":"2016-12-14T18:11:00.701612Z","published_by":null},"0.3.0":{"created_at":"2017-01-11T20:23:13.786826Z","published_by":null},"0.4.0":{"created_at":"2017-01-31T22:52:05.265910Z","published_by":null},"0.5.0":{"created_at":"2017-03-24T23:43:24.948903Z","published_by":null},"0.5.1":{"created_at":"2017-04-05T18:47:54.070429Z","published_by":null},"0.5.2":{"created_at":"2017-05-05T21:28:31.154091Z","published_by":null},"0.6.0":{"created_at":"2017-05-09T22:34:50.754286Z","published_by":null},"0.6.1":{"created_at":"2017-05-13T05:10:02.865902Z","published_by":null},"0.6.2":{"created_at":"2017-05-18T21:38:17.561347Z","published_by":null},"0.7.0":{"created_at":"2017-07-11T20:31:10.511503Z","published_by":null},"0.7.1":{"created_at":"2017-07-13T19:35:09.203169Z","published_by":null},"0.7.2":{"created_at":"2017-07-25T19:53:25.385374Z","published_by":null},"0.7.3":{"created_at":"2017-08-19T23:12:27.404170Z","published_by":null},"0.8.0":{"created_at":"2017-10-02T19:30:45.484545Z","published_by":null},"0.8.1":{"created_at":"2017-11-07T05:54:11.134049Z","published_by":null},"0.8.2":{"created_at":"2017-12-19T21:33:06.524589Z","published_by":null},"0.8.3":{"created_at":"2018-01-20T01:52:08.540926Z","published_by":null},"0.8.4":{"created_at":"2018-01-22T17:42:57.197426Z","published_by":null},"0.8.5":{"created_at":"2018-02-15T20:23:43.932889Z","published_by":null},"0.8.6":{"created_at":"2018-05-31T19:11:47.669821Z","published_by":null},"0.8.7":{"created_at":"2018-07-31T20:45:33.830030Z","published_by":null},"0.8.8":{"created_at":"2018-08-17T17:58:15.137080Z","published_by":null},"0.9.0":{"created_at":"2018-09-18T21:39:49.911237Z","published_by":null},"0.9.1":{"created_at":"2018-09-20T21:19:05.387035Z","published_by":null},"0.9.2":{"created_at":"2018-09-25T18:57:40.115646Z","published_by":null},"0.9.3":{"created_at":"2018-10-17T21:28:55.509727Z","published_by":null},"0.9.4":{"created_at":"2018-10-26T21:11:02.823969Z","published_by":null},"0.9.5":{"created_at":"2018-11-13T20:41:25.417528Z","published_by":null},"0.9.6":{"created_at":"2019-01-07T23:57:22.199719Z","published_by":null},"0.9.7":{"created_at":"2019-01-10T22:03:44.191799Z","published_by":null},"0.9.8":{"created_at":"2019-01-12T01:37:23.183551Z","published_by":null},"0.9.9":{"created_at":"2019-01-23T20:24:02.688735Z","published_by":null},"0.9.10":{"created_at":"2019-02-18T19:57:31.026234Z","published_by":null},"0.9.11":{"created_at":"2019-03-04T19:45:08.793186Z","published_by":359},"0.9.12":{"created_at":"2019-03-20T21:22:30.543762Z","published_by":359},"0.9.13":{"created_at":"2019-04-02T01:48:38.892605Z","published_by":359},"0.9.14":{"created_at":"2019-04-09T20:18:37.712293Z","published_by":359},"0.9.15":{"created_at":"2019-04-15T19:41:39.641658Z","published_by":359},"0.9.16":{"created_at":"2019-04-30T22:32:12.148436Z","published_by":359},"0.9.17":{"created_at":"2019-05-15T20:27:53.871273Z","published_by":359},"0.9.18":{"created_at":"2019-06-06T18:37:41.146606Z","published_by":359},"0.9.19":{"created_at":"2019-07-19T19:27:17.044555Z","published_by":359},"0.9.20":{"created_at":"2019-08-20T21:22:50.920849Z","published_by":359},"0.9.21":{"created_at":"2019-10-02T23:42:03.803616Z","published_by":359},"0.9.22":{"created_at":"2019-10-09T21:23:26.050506Z","published_by":359},"0.9.23":{"created_at":"2019-12-11T19:53:56.519482Z","published_by":359},"0.9.24":{"created_at":"2019-12-11T20:02:57.388564Z","published_by":359},"0.10.0-alpha.1":{"created_at":"2019-10-08T21:04:03.791301Z","published_by":359},"0.10.0-alpha.2":{"created_at":"2019-11-12T18:18:49.059289Z","published_by":359},"0.10.0":{"created_at":"2019-12-30T18:30:07.229477Z","published_by":359},"0.10.1":{"created_at":"2020-01-09T21:53:52.454222Z","published_by":359},"0.10.2":{"created_at":"2020-02-21T20:45:39.571195Z","published_by":359},"0.10.3":{"created_at":"2020-02-27T00:51:32.016762Z","published_by":359},"0.10.4":{"created_at":"2020-03-04T01:15:13.844446Z","published_by":359},"0.10.5":{"created_at":"2020-05-28T21:33:19.078549Z","published_by":359},"0.10.6":{"created_at":"2020-05-29T17:52:23.605743Z","published_by":359},"0.10.7":{"created_at":"2020-07-24T14:16:15.763291Z","published_by":359},"0.10.8":{"created_at":"2020-08-25T16:33:01.468111Z","published_by":359},"0.10.9":{"created_at":"2020-11-20T00:07:26.813634Z","published_by":359},"0.10.10":{"created_at":"2020-12-14T22:41:05.183991Z","published_by":359},"0.11.0":{"created_at":"2021-01-05T18:22:02.848374Z","published_by":359},"0.11.1":{"created_at":"2021-02-18T18:46:46.260408Z","published_by":359},"0.11.2":{"created_at":"2021-03-09T19:08:59.274367Z","published_by":359},"0.11.3":{"created_at":"2021-04-12T22:51:54.096712Z","published_by":359},"0.11.4":{"created_at":"2021-06-21T22:02:56.080700Z","published_by":359},"0.11.5":{"created_at":"2021-10-07T19:17:33.290318Z","published_by":359},"0.11.6":{"created_at":"2021-10-18T22:58:49.420910Z","published_by":359},"0.11.7":{"created_at":"2021-11-30T18:10:14.825862Z","published_by":359},"0.11.8":{"created_at":"2021-12-20T20:46:39.692687Z","published_by":359},"0.11.9":{"created_at":"2022-01-10T23:09:51.373619Z","published_by":359},"0.11.10":{"created_at":"2022-03-14T18:41:38.639023Z","published_by":359},"0.11.11":{"created_at":"2022-06-13T20:54:22.697335Z","published_by":359},"0.11.12":{"created_at":"2022-09-20T18:16:47.954083Z","published_by":359},"0.11.13":{"created_at":"2022-11-16T15:47:06.249997Z","published_by":359},"0.11.14":{"created_at":"2023-01-19T19:38:11.448528Z","published_by":359},"0.11.15":{"created_at":"2023-03-20T16:05:41.407122Z","published_by":359},"0.11.16":{"created_at":"2023-03-27T15:36:33.057076Z","published_by":359},"0.11.17":{"created_at":"2023-04-28T16:12:43.471872Z","published_by":359},"0.11.18":{"created_at":"2023-05-16T21:05:29.185091Z","published_by":359}},"metadata":{"description":"higher level HTTP client library","repository":"https://github.com/seanmonstar/reqwest"}},"ryu":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2018-07-28T17:12:43.329564Z","published_by":null},"0.1.1":{"created_at":"2018-07-28T18:44:29.756955Z","published_by":null},"0.2.0":{"created_at":"2018-08-07T18:55:05.398449Z","published_by":null},"0.2.1":{"created_at":"2018-08-07T18:57:53.160507Z","published_by":null},"0.2.2":{"created_at":"2018-08-08T06:30:09.649945Z","published_by":null},"0.2.3":{"created_at":"2018-08-14T16:48:52.982641Z","published_by":null},"0.2.4":{"created_at":"2018-08-18T14:49:46.479186Z","published_by":null},"0.2.5":{"created_at":"2018-08-23T22:24:09.193131Z","published_by":null},"0.2.6":{"created_at":"2018-08-25T20:14:53.993736Z","published_by":null},"0.2.7":{"created_at":"2018-11-10T22:34:14.673843Z","published_by":null},"0.2.8":{"created_at":"2019-05-02T00:29:22.418047Z","published_by":3618},"1.0.0":{"created_at":"2019-06-11T21:16:22.766154Z","published_by":3618},"1.0.1":{"created_at":"2019-10-11T21:18:59.342286Z","published_by":3618},"1.0.2":{"created_at":"2019-10-14T14:26:26.069259Z","published_by":3618},"1.0.3":{"created_at":"2020-03-13T07:25:45.052816Z","published_by":3618},"1.0.4":{"created_at":"2020-04-23T00:39:21.950455Z","published_by":3618},"1.0.5":{"created_at":"2020-05-31T17:04:32.257932Z","published_by":3618},"1.0.6":{"created_at":"2021-11-28T19:32:06.148327Z","published_by":3618},"1.0.7":{"created_at":"2021-12-11T02:45:26.940969Z","published_by":3618},"1.0.8":{"created_at":"2021-12-12T05:13:02.168627Z","published_by":3618},"1.0.9":{"created_at":"2021-12-12T20:50:49.006386Z","published_by":3618},"1.0.10":{"created_at":"2022-05-15T21:30:39.601638Z","published_by":3618},"1.0.11":{"created_at":"2022-08-03T01:19:57.373409Z","published_by":3618},"1.0.12":{"created_at":"2022-12-17T19:44:03.241450Z","published_by":3618},"1.0.13":{"created_at":"2023-03-03T23:24:00.516673Z","published_by":3618}},"metadata":{"description":"Fast floating point to string conversion","repository":"https://github.com/dtolnay/ryu"}},"schannel":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.0.1":{"created_at":"2015-10-19T14:53:41.325352Z","published_by":null},"0.0.2":{"created_at":"2016-03-02T19:33:04.727431Z","published_by":null},"0.1.0":{"created_at":"2016-08-02T16:47:22.264619Z","published_by":null},"0.1.1":{"created_at":"2016-11-08T20:57:32.613165Z","published_by":null},"0.1.2":{"created_at":"2016-12-20T02:58:43.131110Z","published_by":null},"0.1.3":{"created_at":"2017-03-17T02:59:32.610757Z","published_by":null},"0.1.4":{"created_at":"2017-03-18T02:08:44.532415Z","published_by":null},"0.1.5":{"created_at":"2017-05-23T17:44:57.503873Z","published_by":null},"0.1.6":{"created_at":"2017-06-21T19:08:48.936530Z","published_by":null},"0.1.7":{"created_at":"2017-06-21T23:33:21.188785Z","published_by":null},"0.1.8":{"created_at":"2017-09-22T17:58:16.686499Z","published_by":null},"0.1.9":{"created_at":"2017-12-03T11:58:41.884707Z","published_by":null},"0.1.10":{"created_at":"2018-01-10T22:37:27.467791Z","published_by":null},"0.1.11":{"created_at":"2018-03-05T17:02:10.835379Z","published_by":null},"0.1.12":{"created_at":"2018-04-18T16:33:39.596954Z","published_by":null},"0.1.13":{"created_at":"2018-06-26T03:27:58.237167Z","published_by":null},"0.1.14":{"created_at":"2018-09-26T20:03:08.383702Z","published_by":null},"0.1.15":{"created_at":"2019-02-25T09:18:51.893274Z","published_by":2728},"0.1.16":{"created_at":"2019-09-16T10:44:59.490570Z","published_by":2728},"0.1.17":{"created_at":"2020-02-04T21:43:28.076297Z","published_by":2728},"0.1.18":{"created_at":"2020-03-20T14:57:17.172398Z","published_by":2728},"0.1.19":{"created_at":"2020-05-09T17:10:55.857795Z","published_by":2728},"0.1.20":{"created_at":"2022-05-18T06:15:04.467460Z","published_by":2728},"0.1.21":{"created_at":"2023-01-09T13:42:30.813267Z","published_by":2728}},"metadata":{"description":"Schannel bindings for rust, allowing SSL/TLS (e.g. https) without openssl","repository":"https://github.com/steffengy/schannel-rs"}},"security-framework":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2015-11-27T22:17:03.539454Z","published_by":null},"0.1.1":{"created_at":"2015-12-05T22:28:42.079551Z","published_by":null},"0.1.2":{"created_at":"2015-12-21T03:26:23.982243Z","published_by":null},"0.1.3":{"created_at":"2016-02-26T16:41:56.363436Z","published_by":null},"0.1.4":{"created_at":"2016-04-16T19:43:26.833730Z","published_by":null},"0.1.5":{"created_at":"2016-06-17T04:27:36.190623Z","published_by":null},"0.1.6":{"created_at":"2016-07-16T18:10:10.172688Z","published_by":null},"0.1.7":{"created_at":"2016-07-30T01:51:46.245727Z","published_by":null},"0.1.8":{"created_at":"2016-08-11T20:15:10.839513Z","published_by":null},"0.1.9":{"created_at":"2016-11-08T20:42:55.812977Z","published_by":null},"0.1.10":{"created_at":"2016-12-20T03:36:50.087539Z","published_by":null},"0.1.11":{"created_at":"2017-03-13T02:27:26.561329Z","published_by":null},"0.1.12":{"created_at":"2017-03-15T18:40:43.171170Z","published_by":null},"0.1.13":{"created_at":"2017-03-27T05:02:41.530639Z","published_by":null},"0.1.14":{"created_at":"2017-03-29T21:58:42.728619Z","published_by":null},"0.1.15":{"created_at":"2017-08-04T04:27:35.597956Z","published_by":null},"0.1.16":{"created_at":"2017-08-15T03:42:47.096139Z","published_by":null},"0.2.0":{"created_at":"2018-03-24T16:22:44.777568Z","published_by":null},"0.2.1":{"created_at":"2018-06-01T05:37:45.822838Z","published_by":null},"0.2.2":{"created_at":"2019-01-19T15:21:40.006677Z","published_by":null},"0.2.4-beta":{"created_at":"2019-02-09T01:03:47.953328Z","published_by":null},"0.2.4-beta.2":{"created_at":"2019-02-27T01:26:35.279554Z","published_by":988},"0.2.4":{"created_at":"2019-03-02T16:31:55.643274Z","published_by":988},"0.3.0":{"created_at":"2019-03-02T17:10:48.067891Z","published_by":988},"0.3.1-beta.1":{"created_at":"2019-04-20T18:11:30.476076Z","published_by":988},"0.3.1":{"created_at":"2019-04-24T21:43:11.447291Z","published_by":988},"0.3.2":{"created_at":"2019-11-04T17:15:58.924162Z","published_by":988},"0.3.3":{"created_at":"2019-11-06T16:18:34.350470Z","published_by":988},"0.3.4":{"created_at":"2019-11-21T23:42:11.588194Z","published_by":988},"0.4.0":{"created_at":"2019-12-13T14:00:37.698475Z","published_by":988},"0.4.1":{"created_at":"2020-02-01T14:43:53.972990Z","published_by":988},"0.4.2-alpha.1":{"created_at":"2020-03-22T18:13:17.423146Z","published_by":988},"0.4.2":{"created_at":"2020-03-30T14:44:52.414798Z","published_by":988},"0.4.3":{"created_at":"2020-04-23T22:57:44.368254Z","published_by":988},"0.4.4":{"created_at":"2020-05-08T18:10:58.188096Z","published_by":988},"1.0.0-alpha.1":{"created_at":"2020-04-17T17:20:17.264850Z","published_by":988},"1.0.0-alpha.2":{"created_at":"2020-05-08T18:44:55.021095Z","published_by":988},"1.0.0":{"created_at":"2020-06-10T12:10:08.202159Z","published_by":988},"2.0.0":{"created_at":"2020-08-03T00:43:12.241917Z","published_by":988},"2.1.0":{"created_at":"2021-02-24T14:28:44.146390Z","published_by":988},"2.1.1":{"created_at":"2021-02-25T19:34:51.202100Z","published_by":988},"2.1.2":{"created_at":"2021-03-08T12:20:25.263841Z","published_by":988},"2.2.0":{"created_at":"2021-03-27T06:56:18.946743Z","published_by":988},"2.3.0":{"created_at":"2021-06-05T10:48:09.198620Z","published_by":988},"2.3.1":{"created_at":"2021-06-07T15:23:41.439097Z","published_by":988},"2.4.0":{"created_at":"2021-08-25T22:29:23.057661Z","published_by":988},"2.4.1":{"created_at":"2021-08-29T21:22:36.298996Z","published_by":988},"2.4.2":{"created_at":"2021-08-30T14:33:02.884911Z","published_by":988},"2.5.0-alpha.1":{"created_at":"2021-12-25T22:57:37.918411Z","published_by":988},"2.5.0":{"created_at":"2022-01-16T23:19:21.196408Z","published_by":988},"2.6.0-beta.1":{"created_at":"2022-01-16T19:06:55.550173Z","published_by":988},"2.6.0":{"created_at":"2022-01-30T03:25:25.093379Z","published_by":988},"2.6.1":{"created_at":"2022-02-05T03:49:42.832884Z","published_by":988},"2.7.0":{"created_at":"2022-08-18T14:23:29.119997Z","published_by":988},"2.8.0-alpha.1":{"created_at":"2022-11-25T01:29:02.787421Z","published_by":988},"2.8.0-beta.1":{"created_at":"2022-12-03T00:03:12.073805Z","published_by":988},"2.8.0":{"created_at":"2023-01-19T03:44:54.919858Z","published_by":988},"2.8.1":{"created_at":"2023-01-24T20:15:40.226839Z","published_by":988},"2.8.2":{"created_at":"2023-01-28T23:11:21.396826Z","published_by":988},"2.9.0":{"created_at":"2023-05-12T22:01:17.070584Z","published_by":988},"2.9.1":{"created_at":"2023-05-18T22:54:15.678981Z","published_by":988}},"metadata":{"description":"Security.framework bindings for macOS and iOS","repository":"https://github.com/kornelski/rust-security-framework"}},"security-framework-sys":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2015-11-27T22:16:51.760190Z","published_by":null},"0.1.1":{"created_at":"2015-12-05T22:28:28.870822Z","published_by":null},"0.1.2":{"created_at":"2015-12-21T03:26:04.889297Z","published_by":null},"0.1.3":{"created_at":"2016-02-26T16:41:40.946779Z","published_by":null},"0.1.4":{"created_at":"2016-04-16T19:43:13.636560Z","published_by":null},"0.1.5":{"created_at":"2016-06-17T04:27:15.830806Z","published_by":null},"0.1.6":{"created_at":"2016-07-16T18:09:51.233362Z","published_by":null},"0.1.7":{"created_at":"2016-07-30T01:51:31.587220Z","published_by":null},"0.1.8":{"created_at":"2016-08-11T20:14:53.300228Z","published_by":null},"0.1.9":{"created_at":"2016-11-08T20:42:22.126998Z","published_by":null},"0.1.10":{"created_at":"2016-12-20T03:36:31.592075Z","published_by":null},"0.1.11":{"created_at":"2017-03-13T02:26:12.874794Z","published_by":null},"0.1.12":{"created_at":"2017-03-15T18:40:27.634998Z","published_by":null},"0.1.13":{"created_at":"2017-03-27T05:02:24.608891Z","published_by":null},"0.1.14":{"created_at":"2017-03-29T21:58:16.359065Z","published_by":null},"0.1.15":{"created_at":"2017-08-04T04:27:11.046465Z","published_by":null},"0.1.16":{"created_at":"2017-08-15T03:42:37.238414Z","published_by":null},"0.2.0":{"created_at":"2018-03-24T16:22:28.270760Z","published_by":null},"0.2.1":{"created_at":"2018-06-01T05:37:31.832642Z","published_by":null},"0.2.2":{"created_at":"2018-12-24T20:48:54.082938Z","published_by":null},"0.2.3":{"created_at":"2019-01-19T15:21:17.559105Z","published_by":null},"0.2.4-beta":{"created_at":"2019-02-09T01:03:19.863818Z","published_by":null},"0.2.4-beta.2":{"created_at":"2019-02-27T01:26:03.353571Z","published_by":988},"0.2.4":{"created_at":"2019-03-02T02:12:46.101824Z","published_by":988},"0.3.0":{"created_at":"2019-03-02T17:10:33.654778Z","published_by":988},"0.3.1-beta.1":{"created_at":"2019-04-20T18:11:17.207696Z","published_by":988},"0.3.1":{"created_at":"2019-04-24T21:42:56.598644Z","published_by":988},"0.3.2":{"created_at":"2019-11-04T17:15:42.146102Z","published_by":988},"0.3.3":{"created_at":"2019-11-06T16:18:10.746181Z","published_by":988},"0.4.0":{"created_at":"2019-12-13T14:00:17.173034Z","published_by":988},"0.4.1":{"created_at":"2020-02-01T14:43:27.589565Z","published_by":988},"0.4.2-alpha.1":{"created_at":"2020-03-22T18:12:55.719714Z","published_by":988},"0.4.2":{"created_at":"2020-03-30T14:44:35.993711Z","published_by":988},"0.4.3":{"created_at":"2020-04-23T22:57:25.176747Z","published_by":988},"1.0.0-alpha.1":{"created_at":"2020-04-17T17:19:42.761097Z","published_by":988},"1.0.0":{"created_at":"2020-06-10T12:09:17.267991Z","published_by":988},"2.0.0":{"created_at":"2020-08-03T00:42:49.240359Z","published_by":988},"2.1.0":{"created_at":"2021-02-24T14:28:26.005912Z","published_by":988},"2.1.1":{"created_at":"2021-02-25T19:34:28.535066Z","published_by":988},"2.2.0":{"created_at":"2021-03-27T06:56:00.619932Z","published_by":988},"2.3.0":{"created_at":"2021-06-05T10:47:29.656957Z","published_by":988},"2.4.0":{"created_at":"2021-08-25T22:28:56.982867Z","published_by":988},"2.4.1":{"created_at":"2021-08-29T21:22:24.952613Z","published_by":988},"2.4.2":{"created_at":"2021-08-30T14:32:54.540659Z","published_by":988},"2.5.0-alpha.1":{"created_at":"2021-12-25T22:57:16.103577Z","published_by":988},"2.5.0":{"created_at":"2022-01-16T23:18:53.710468Z","published_by":988},"2.6.0-beta.1":{"created_at":"2022-01-16T19:06:35.954514Z","published_by":988},"2.6.0":{"created_at":"2022-01-30T03:25:09.403929Z","published_by":988},"2.6.1":{"created_at":"2022-02-05T03:49:31.952828Z","published_by":988},"2.8.0-alpha.1":{"created_at":"2022-11-25T01:28:06.079193Z","published_by":988},"2.8.0-beta.1":{"created_at":"2022-12-03T00:02:56.347621Z","published_by":988},"2.8.0":{"created_at":"2023-01-19T03:43:50.485253Z","published_by":988},"2.9.0":{"created_at":"2023-05-12T22:00:22.064941Z","published_by":988}},"metadata":{"description":"Apple `Security.framework` low-level FFI bindings","repository":"https://github.com/kornelski/rust-security-framework"}},"serde":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.0.0":{"created_at":"2014-12-05T20:20:39.631482Z","published_by":null},"0.2.0":{"created_at":"2015-03-09T06:02:36.291060Z","published_by":null},"0.2.1":{"created_at":"2015-03-09T06:11:37.021008Z","published_by":null},"0.3.0":{"created_at":"2015-03-31T20:01:15.027569Z","published_by":null},"0.3.1":{"created_at":"2015-04-12T18:52:16.194945Z","published_by":null},"0.3.2":{"created_at":"2015-04-26T16:25:51.520106Z","published_by":null},"0.3.3":{"created_at":"2015-05-02T17:52:31.336678Z","published_by":null},"0.4.0":{"created_at":"2015-05-19T06:36:51.901400Z","published_by":null},"0.4.1":{"created_at":"2015-06-08T14:18:39.215348Z","published_by":null},"0.4.2":{"created_at":"2015-06-25T23:11:24.116256Z","published_by":null},"0.4.3":{"created_at":"2015-07-16T15:29:47.285835Z","published_by":null},"0.5.0":{"created_at":"2015-08-07T19:02:34.976043Z","published_by":null},"0.5.1":{"created_at":"2015-08-10T17:10:08.993884Z","published_by":null},"0.5.2":{"created_at":"2015-08-18T19:51:20.752141Z","published_by":null},"0.5.3":{"created_at":"2015-08-31T17:07:56.851507Z","published_by":null},"0.6.0":{"created_at":"2015-09-02T16:36:00.767160Z","published_by":null},"0.6.1":{"created_at":"2015-10-18T03:10:21.268213Z","published_by":null},"0.6.6":{"created_at":"2015-12-08T14:58:19.326231Z","published_by":null},"0.6.7":{"created_at":"2015-12-24T18:26:41.271604Z","published_by":null},"0.6.10":{"created_at":"2016-01-18T21:11:51.783563Z","published_by":null},"0.6.11":{"created_at":"2016-01-24T00:31:14.415485Z","published_by":null},"0.6.12":{"created_at":"2016-02-13T05:23:16.116440Z","published_by":null},"0.6.13":{"created_at":"2016-02-14T17:06:57.016920Z","published_by":null},"0.6.14":{"created_at":"2016-02-18T17:44:15.348456Z","published_by":null},"0.6.15":{"created_at":"2016-02-22T16:30:12.941823Z","published_by":null},"0.7.0":{"created_at":"2016-02-27T05:29:34.726289Z","published_by":null},"0.7.4":{"created_at":"2016-05-04T13:40:19.921295Z","published_by":null},"0.7.5":{"created_at":"2016-05-12T16:31:19.949104Z","published_by":null},"0.7.6":{"created_at":"2016-05-30T14:58:09.495864Z","published_by":null},"0.7.7":{"created_at":"2016-06-01T11:02:39.950184Z","published_by":null},"0.7.8":{"created_at":"2016-06-06T17:15:34.869838Z","published_by":null},"0.7.9":{"created_at":"2016-06-10T03:26:01.017017Z","published_by":null},"0.7.10":{"created_at":"2016-06-11T20:10:08.561746Z","published_by":null},"0.7.11":{"created_at":"2016-06-24T02:48:00.928179Z","published_by":null},"0.7.12":{"created_at":"2016-07-03T15:54:42.331068Z","published_by":null},"0.7.13":{"created_at":"2016-07-06T03:07:30.338256Z","published_by":null},"0.7.14":{"created_at":"2016-07-09T18:52:35.167274Z","published_by":null},"0.7.15":{"created_at":"2016-07-26T16:32:20.973468Z","published_by":null},"0.8.0-rc1":{"created_at":"2016-07-17T18:48:35.381095Z","published_by":null},"0.8.0-rc2":{"created_at":"2016-07-18T01:09:27.387033Z","published_by":null},"0.8.0-rc3":{"created_at":"2016-07-23T22:14:40.664795Z","published_by":null},"0.8.0":{"created_at":"2016-07-28T05:11:29.402883Z","published_by":null},"0.8.1":{"created_at":"2016-08-11T16:18:16.517148Z","published_by":null},"0.8.2":{"created_at":"2016-08-18T20:10:35.953676Z","published_by":null},"0.8.3":{"created_at":"2016-08-19T17:12:25.104124Z","published_by":null},"0.8.4":{"created_at":"2016-08-22T15:38:38.174942Z","published_by":null},"0.8.5":{"created_at":"2016-08-31T20:02:59.831604Z","published_by":null},"0.8.6":{"created_at":"2016-09-02T05:07:31.200813Z","published_by":null},"0.8.7":{"created_at":"2016-09-05T16:40:06.098108Z","published_by":null},"0.8.8":{"created_at":"2016-09-08T15:35:29.920836Z","published_by":null},"0.8.9":{"created_at":"2016-09-21T13:06:42.206136Z","published_by":null},"0.8.10":{"created_at":"2016-09-28T17:53:14.020154Z","published_by":null},"0.8.11":{"created_at":"2016-10-03T20:39:25.511559Z","published_by":null},"0.8.12":{"created_at":"2016-10-08T22:38:59.580579Z","published_by":null},"0.8.13":{"created_at":"2016-10-16T17:35:29.534241Z","published_by":null},"0.8.14":{"created_at":"2016-10-19T04:43:24.960074Z","published_by":null},"0.8.15":{"created_at":"2016-10-20T15:43:23.969449Z","published_by":null},"0.8.16":{"created_at":"2016-10-22T07:08:12.728465Z","published_by":null},"0.8.17":{"created_at":"2016-11-02T16:26:23.590766Z","published_by":null},"0.8.18":{"created_at":"2016-11-19T19:40:23.687110Z","published_by":null},"0.8.19":{"created_at":"2016-11-23T17:20:18.036792Z","published_by":null},"0.8.20":{"created_at":"2016-12-16T15:56:14.791559Z","published_by":null},"0.8.21":{"created_at":"2016-12-24T18:10:51.370945Z","published_by":null},"0.8.22":{"created_at":"2017-01-11T01:11:11.877005Z","published_by":null},"0.8.23":{"created_at":"2017-01-20T23:26:25.425694Z","published_by":null},"0.9.0-rc1":{"created_at":"2017-01-15T17:25:25.735564Z","published_by":null},"0.9.0-rc2":{"created_at":"2017-01-22T00:35:25.707259Z","published_by":null},"0.9.0-rc3":{"created_at":"2017-01-24T03:14:58.767671Z","published_by":null},"0.9.0-rc4":{"created_at":"2017-01-25T18:15:01.973196Z","published_by":null},"0.9.0":{"created_at":"2017-01-25T20:58:01.580835Z","published_by":null},"0.9.1":{"created_at":"2017-01-25T22:56:34.709475Z","published_by":null},"0.9.2":{"created_at":"2017-01-28T17:11:20.572945Z","published_by":null},"0.9.3":{"created_at":"2017-01-28T23:10:46.989016Z","published_by":null},"0.9.4":{"created_at":"2017-01-31T02:11:17.157806Z","published_by":null},"0.9.5":{"created_at":"2017-02-01T09:01:30.662972Z","published_by":null},"0.9.6":{"created_at":"2017-02-03T20:27:27.918917Z","published_by":null},"0.9.7":{"created_at":"2017-02-10T01:52:39.432867Z","published_by":null},"0.9.8":{"created_at":"2017-02-21T19:00:04.361061Z","published_by":null},"0.9.9":{"created_at":"2017-02-24T22:00:22.963246Z","published_by":null},"0.9.10":{"created_at":"2017-02-28T20:46:06.776294Z","published_by":null},"0.9.11":{"created_at":"2017-03-06T00:54:18.112735Z","published_by":null},"0.9.12":{"created_at":"2017-03-27T23:11:05.496603Z","published_by":null},"0.9.13":{"created_at":"2017-04-05T22:05:36.040405Z","published_by":null},"0.9.14":{"created_at":"2017-04-17T14:35:10.260224Z","published_by":null},"0.9.15":{"created_at":"2017-04-23T23:35:23.492836Z","published_by":null},"1.0.0":{"created_at":"2017-04-20T15:26:44.055136Z","published_by":null},"1.0.1":{"created_at":"2017-04-23T23:41:46.788655Z","published_by":null},"1.0.2":{"created_at":"2017-04-27T19:32:55.771003Z","published_by":null},"1.0.3":{"created_at":"2017-05-10T17:16:01.999254Z","published_by":null},"1.0.4":{"created_at":"2017-05-11T03:05:55.613491Z","published_by":null},"1.0.5":{"created_at":"2017-05-14T19:54:13.365768Z","published_by":null},"1.0.6":{"created_at":"2017-05-17T15:21:19.671537Z","published_by":null},"1.0.7":{"created_at":"2017-05-20T00:00:55.274628Z","published_by":null},"1.0.8":{"created_at":"2017-05-24T07:21:12.281576Z","published_by":null},"1.0.9":{"created_at":"2017-06-30T03:21:56.956472Z","published_by":null},"1.0.10":{"created_at":"2017-07-12T04:20:29.635880Z","published_by":null},"1.0.11":{"created_at":"2017-07-27T07:57:06.076991Z","published_by":null},"1.0.12":{"created_at":"2017-09-04T18:22:31.393147Z","published_by":null},"1.0.13":{"created_at":"2017-09-09T18:37:04.305602Z","published_by":null},"1.0.14":{"created_at":"2017-09-09T19:51:37.054095Z","published_by":null},"1.0.15":{"created_at":"2017-09-17T20:59:02.059063Z","published_by":null},"1.0.16":{"created_at":"2017-10-22T18:39:02.968297Z","published_by":null},"1.0.17":{"created_at":"2017-10-31T16:37:53.934631Z","published_by":null},"1.0.18":{"created_at":"2017-11-03T17:21:10.865818Z","published_by":null},"1.0.19":{"created_at":"2017-11-07T07:48:39.170579Z","published_by":null},"1.0.20":{"created_at":"2017-11-12T18:29:40.491469Z","published_by":null},"1.0.21":{"created_at":"2017-11-16T06:25:07.025779Z","published_by":null},"1.0.22":{"created_at":"2017-11-30T04:02:16.479489Z","published_by":null},"1.0.23":{"created_at":"2017-11-30T06:34:52.278029Z","published_by":null},"1.0.24":{"created_at":"2017-12-09T22:44:22.531447Z","published_by":null},"1.0.25":{"created_at":"2017-12-24T04:34:58.275899Z","published_by":null},"1.0.26":{"created_at":"2017-12-27T22:40:03.389978Z","published_by":null},"1.0.27":{"created_at":"2017-12-31T03:20:43.357396Z","published_by":null},"1.0.28":{"created_at":"2018-03-08T19:40:17.850067Z","published_by":null},"1.0.29":{"created_at":"2018-03-09T08:39:35.579383Z","published_by":null},"1.0.30":{"created_at":"2018-03-12T18:45:26.538855Z","published_by":null},"1.0.31":{"created_at":"2018-03-13T17:19:37.104850Z","published_by":null},"1.0.32":{"created_at":"2018-03-13T18:31:57.613343Z","published_by":null},"1.0.33":{"created_at":"2018-03-15T17:04:07.506767Z","published_by":null},"1.0.34":{"created_at":"2018-03-22T22:07:47.696413Z","published_by":null},"1.0.35":{"created_at":"2018-03-25T10:59:48.284360Z","published_by":null},"1.0.36":{"created_at":"2018-03-27T09:36:20.320372Z","published_by":null},"1.0.37":{"created_at":"2018-04-02T05:31:03.532745Z","published_by":null},"1.0.38":{"created_at":"2018-04-15T03:30:54.758942Z","published_by":null},"1.0.39":{"created_at":"2018-04-17T18:36:34.974650Z","published_by":null},"1.0.40":{"created_at":"2018-04-19T17:34:09.405882Z","published_by":null},"1.0.41":{"created_at":"2018-04-20T05:16:58.762402Z","published_by":null},"1.0.42":{"created_at":"2018-04-21T22:17:12.827972Z","published_by":null},"1.0.43":{"created_at":"2018-04-23T18:26:19.436425Z","published_by":null},"1.0.44":{"created_at":"2018-05-02T05:38:26.856121Z","published_by":null},"1.0.45":{"created_at":"2018-05-02T22:18:53.915312Z","published_by":null},"1.0.46":{"created_at":"2018-05-06T21:57:27.357063Z","published_by":null},"1.0.47":{"created_at":"2018-05-07T04:39:46.198492Z","published_by":null},"1.0.48":{"created_at":"2018-05-07T17:23:06.854168Z","published_by":null},"1.0.49":{"created_at":"2018-05-07T18:51:46.738674Z","published_by":null},"1.0.50":{"created_at":"2018-05-07T20:52:01.509459Z","published_by":null},"1.0.51":{"created_at":"2018-05-08T17:08:08.578780Z","published_by":null},"1.0.52":{"created_at":"2018-05-09T20:02:26.309268Z","published_by":null},"1.0.53":{"created_at":"2018-05-10T15:45:23.420992Z","published_by":null},"1.0.54":{"created_at":"2018-05-12T06:03:09.472444Z","published_by":null},"1.0.55":{"created_at":"2018-05-12T16:49:54.610108Z","published_by":null},"1.0.56":{"created_at":"2018-05-18T19:54:18.811840Z","published_by":null},"1.0.57":{"created_at":"2018-05-19T06:32:38.801968Z","published_by":null},"1.0.58":{"created_at":"2018-05-20T00:31:08.440812Z","published_by":null},"1.0.59":{"created_at":"2018-05-21T10:52:52.931158Z","published_by":null},"1.0.60":{"created_at":"2018-05-25T23:09:28.724557Z","published_by":null},"1.0.61":{"created_at":"2018-05-26T17:39:31.311488Z","published_by":null},"1.0.62":{"created_at":"2018-05-27T01:59:31.524542Z","published_by":null},"1.0.63":{"created_at":"2018-05-29T03:12:27.927580Z","published_by":null},"1.0.64":{"created_at":"2018-05-30T07:18:45.269537Z","published_by":null},"1.0.65":{"created_at":"2018-06-01T20:01:52.031585Z","published_by":null},"1.0.66":{"created_at":"2018-06-03T18:46:26.343193Z","published_by":null},"1.0.67":{"created_at":"2018-06-27T07:18:46.481142Z","published_by":null},"1.0.68":{"created_at":"2018-06-28T16:33:31.515587Z","published_by":null},"1.0.69":{"created_at":"2018-07-01T06:54:22.151875Z","published_by":null},"1.0.70":{"created_at":"2018-07-07T03:21:52.415378Z","published_by":null},"1.0.71":{"created_at":"2018-08-07T06:56:36.957526Z","published_by":null},"1.0.72":{"created_at":"2018-08-21T01:02:48.677127Z","published_by":null},"1.0.73":{"created_at":"2018-08-23T01:47:45.772330Z","published_by":null},"1.0.74":{"created_at":"2018-08-23T22:29:52.569771Z","published_by":null},"1.0.75":{"created_at":"2018-08-25T03:04:21.721448Z","published_by":null},"1.0.76":{"created_at":"2018-09-01T22:26:25.291313Z","published_by":null},"1.0.77":{"created_at":"2018-09-07T04:37:00.678360Z","published_by":null},"1.0.78":{"created_at":"2018-09-09T00:11:04.924484Z","published_by":null},"1.0.79":{"created_at":"2018-09-15T21:41:18.740586Z","published_by":null},"1.0.80":{"created_at":"2018-10-14T10:10:12.833373Z","published_by":null},"1.0.81":{"created_at":"2018-12-08T02:31:19.661527Z","published_by":null},"1.0.82":{"created_at":"2018-12-11T06:26:11.127839Z","published_by":null},"1.0.83":{"created_at":"2018-12-28T00:54:53.198109Z","published_by":null},"1.0.84":{"created_at":"2019-01-01T04:46:46.329727Z","published_by":null},"1.0.85":{"created_at":"2019-01-19T06:37:25.937213Z","published_by":null},"1.0.86":{"created_at":"2019-02-02T05:18:42.512675Z","published_by":null},"1.0.87":{"created_at":"2019-02-04T06:09:26.261687Z","published_by":null},"1.0.88":{"created_at":"2019-02-16T03:56:18.535191Z","published_by":null},"1.0.89":{"created_at":"2019-03-01T01:10:06.131012Z","published_by":3618},"1.0.90":{"created_at":"2019-04-03T16:42:20.175250Z","published_by":3618},"1.0.91":{"created_at":"2019-05-06T23:29:06.539382Z","published_by":3618},"1.0.92":{"created_at":"2019-05-31T21:01:26.365770Z","published_by":3618},"1.0.93":{"created_at":"2019-06-23T19:50:52.964314Z","published_by":3618},"1.0.94":{"created_at":"2019-06-27T17:55:32.789011Z","published_by":3618},"1.0.95":{"created_at":"2019-07-16T17:25:50.437072Z","published_by":3618},"1.0.96":{"created_at":"2019-07-17T19:04:39.087674Z","published_by":3618},"1.0.97":{"created_at":"2019-07-17T21:57:56.154948Z","published_by":3618},"1.0.98":{"created_at":"2019-07-28T17:34:04.301789Z","published_by":3618},"1.0.99":{"created_at":"2019-08-16T18:50:51.911989Z","published_by":3618},"1.0.100":{"created_at":"2019-09-08T01:56:06.955881Z","published_by":3618},"1.0.101":{"created_at":"2019-09-16T07:32:54.501518Z","published_by":3618},"1.0.102":{"created_at":"2019-10-27T20:40:21.535767Z","published_by":3618},"1.0.103":{"created_at":"2019-11-25T00:16:10.820610Z","published_by":3618},"1.0.104":{"created_at":"2019-12-16T04:09:49.363249Z","published_by":3618},"1.0.105":{"created_at":"2020-03-18T18:43:37.734428Z","published_by":3618},"1.0.106":{"created_at":"2020-04-03T21:27:28.816926Z","published_by":3618},"1.0.107":{"created_at":"2020-05-08T22:47:15.068247Z","published_by":3618},"1.0.108":{"created_at":"2020-05-10T00:54:08.256031Z","published_by":3618},"1.0.109":{"created_at":"2020-05-10T04:01:29.304849Z","published_by":3618},"1.0.110":{"created_at":"2020-05-10T06:07:39.645754Z","published_by":3618},"1.0.111":{"created_at":"2020-05-30T01:54:37.209695Z","published_by":3618},"1.0.112":{"created_at":"2020-06-14T18:17:04.879101Z","published_by":3618},"1.0.113":{"created_at":"2020-06-19T20:35:02.530651Z","published_by":3618},"1.0.114":{"created_at":"2020-06-22T00:36:52.568802Z","published_by":3618},"1.0.115":{"created_at":"2020-08-10T22:52:45.728770Z","published_by":3618},"1.0.116":{"created_at":"2020-09-11T18:57:11.704294Z","published_by":3618},"1.0.117":{"created_at":"2020-10-15T16:49:49.907091Z","published_by":3618},"1.0.118":{"created_at":"2020-12-05T21:46:36.990667Z","published_by":3618},"1.0.119":{"created_at":"2021-01-11T20:18:01.377435Z","published_by":3618},"1.0.120":{"created_at":"2021-01-19T06:56:09.275032Z","published_by":3618},"1.0.121":{"created_at":"2021-01-23T21:17:54.177776Z","published_by":3618},"1.0.122":{"created_at":"2021-01-25T00:18:13.886998Z","published_by":3618},"1.0.123":{"created_at":"2021-01-25T21:44:12.137298Z","published_by":3618},"1.0.124":{"created_at":"2021-03-06T03:56:14.490884Z","published_by":3618},"1.0.125":{"created_at":"2021-03-22T23:17:02.072292Z","published_by":3618},"1.0.126":{"created_at":"2021-05-12T17:19:08.816586Z","published_by":3618},"1.0.127":{"created_at":"2021-07-31T04:03:54.366836Z","published_by":3618},"1.0.128":{"created_at":"2021-08-21T17:40:24.419585Z","published_by":3618},"1.0.129":{"created_at":"2021-08-23T22:02:20.945377Z","published_by":3618},"1.0.130":{"created_at":"2021-08-28T18:32:06.551445Z","published_by":3618},"1.0.131":{"created_at":"2021-12-09T02:57:52.308905Z","published_by":3618},"1.0.132":{"created_at":"2021-12-16T19:07:37.309978Z","published_by":3618},"1.0.133":{"created_at":"2022-01-01T21:17:18.432649Z","published_by":3618},"1.0.134":{"created_at":"2022-01-21T06:23:35.725935Z","published_by":3618},"1.0.135":{"created_at":"2022-01-23T00:04:47.446032Z","published_by":3618},"1.0.136":{"created_at":"2022-01-25T21:35:08.517505Z","published_by":3618},"1.0.137":{"created_at":"2022-05-01T04:47:05.055925Z","published_by":3618},"1.0.138":{"created_at":"2022-07-02T03:10:35.944689Z","published_by":3618},"1.0.139":{"created_at":"2022-07-11T04:51:51.575970Z","published_by":3618},"1.0.140":{"created_at":"2022-07-20T16:27:01.360470Z","published_by":3618},"1.0.141":{"created_at":"2022-08-01T15:51:52.211522Z","published_by":3618},"1.0.142":{"created_at":"2022-08-03T14:09:50.346647Z","published_by":3618},"1.0.143":{"created_at":"2022-08-09T02:13:14.330750Z","published_by":3618},"1.0.144":{"created_at":"2022-08-21T03:25:01.347811Z","published_by":3618},"1.0.145":{"created_at":"2022-09-22T17:50:37.433846Z","published_by":3618},"1.0.146":{"created_at":"2022-10-21T08:04:20.061495Z","published_by":3618},"1.0.147":{"created_at":"2022-10-21T17:05:21.966777Z","published_by":3618},"1.0.148":{"created_at":"2022-11-28T01:58:53.623176Z","published_by":3618},"1.0.149":{"created_at":"2022-12-05T07:12:32.281276Z","published_by":3618},"1.0.150":{"created_at":"2022-12-12T00:25:28.357747Z","published_by":3618},"1.0.151":{"created_at":"2022-12-16T18:35:51.437522Z","published_by":3618},"1.0.152":{"created_at":"2022-12-26T17:22:03.944571Z","published_by":3618},"1.0.153":{"created_at":"2023-03-07T18:52:43.969569Z","published_by":3618},"1.0.154":{"created_at":"2023-03-08T20:09:39.824769Z","published_by":3618},"1.0.155":{"created_at":"2023-03-11T20:58:32.267787Z","published_by":3618},"1.0.156":{"created_at":"2023-03-14T08:03:03.285639Z","published_by":3618},"1.0.157":{"created_at":"2023-03-18T00:35:57.373851Z","published_by":3618},"1.0.158":{"created_at":"2023-03-20T11:25:05.896231Z","published_by":3618},"1.0.159":{"created_at":"2023-03-28T05:06:35.408554Z","published_by":3618},"1.0.160":{"created_at":"2023-04-11T05:16:23.859138Z","published_by":3618},"1.0.161":{"created_at":"2023-05-04T23:45:48.082490Z","published_by":3618},"1.0.162":{"created_at":"2023-05-05T01:47:41.417215Z","published_by":3618},"1.0.163":{"created_at":"2023-05-11T03:26:35.268848Z","published_by":3618}},"metadata":{"description":"A generic serialization/deserialization framework","repository":"https://github.com/serde-rs/serde"}},"serde_json":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.5.0":{"created_at":"2015-08-07T19:04:18.660960Z","published_by":null},"0.5.1":{"created_at":"2015-08-10T17:12:33.295576Z","published_by":null},"0.6.0":{"created_at":"2015-08-31T17:01:55.653589Z","published_by":null},"0.6.1":{"created_at":"2016-02-28T06:10:34.612284Z","published_by":null},"0.7.0":{"created_at":"2016-02-27T07:01:11.087761Z","published_by":null},"0.7.1":{"created_at":"2016-05-26T16:06:40.149099Z","published_by":null},"0.7.2":{"created_at":"2016-06-24T15:23:27.292311Z","published_by":null},"0.7.3":{"created_at":"2016-06-25T22:43:01.802863Z","published_by":null},"0.7.4":{"created_at":"2016-06-30T02:14:43.175331Z","published_by":null},"0.8.0-rc1":{"created_at":"2016-07-25T16:27:58.898453Z","published_by":null},"0.8.0":{"created_at":"2016-07-28T05:30:33.786147Z","published_by":null},"0.8.1":{"created_at":"2016-08-11T16:25:12.609963Z","published_by":null},"0.8.2":{"created_at":"2016-09-15T06:25:47.084801Z","published_by":null},"0.8.3":{"created_at":"2016-10-16T19:34:46.729774Z","published_by":null},"0.8.4":{"created_at":"2016-12-07T05:13:39.697550Z","published_by":null},"0.8.5":{"created_at":"2017-01-18T04:59:33.505584Z","published_by":null},"0.8.6":{"created_at":"2017-01-18T18:09:37.371443Z","published_by":null},"0.9.0-rc1":{"created_at":"2017-01-22T01:56:01.054438Z","published_by":null},"0.9.0-rc2":{"created_at":"2017-01-24T03:20:04.796111Z","published_by":null},"0.9.0-rc3":{"created_at":"2017-01-25T18:36:06.463435Z","published_by":null},"0.9.0":{"created_at":"2017-01-25T21:01:39.118080Z","published_by":null},"0.9.1":{"created_at":"2017-01-26T07:20:51.200441Z","published_by":null},"0.9.2":{"created_at":"2017-01-28T23:33:37.630324Z","published_by":null},"0.9.3":{"created_at":"2017-02-01T01:04:47.173253Z","published_by":null},"0.9.4":{"created_at":"2017-02-01T23:58:16.093981Z","published_by":null},"0.9.5":{"created_at":"2017-02-03T15:51:56.940808Z","published_by":null},"0.9.6":{"created_at":"2017-02-11T00:33:45.926813Z","published_by":null},"0.9.7":{"created_at":"2017-02-20T17:33:31.246219Z","published_by":null},"0.9.8":{"created_at":"2017-02-21T18:04:22.497177Z","published_by":null},"0.9.9":{"created_at":"2017-03-06T01:43:44.116389Z","published_by":null},"0.9.10":{"created_at":"2017-04-09T15:11:42.038050Z","published_by":null},"1.0.0":{"created_at":"2017-04-20T15:38:02.380071Z","published_by":null},"1.0.1":{"created_at":"2017-04-28T00:11:25.244429Z","published_by":null},"1.0.2":{"created_at":"2017-05-08T23:11:48.074415Z","published_by":null},"1.0.3":{"created_at":"2017-09-04T20:43:18.977914Z","published_by":null},"1.0.4":{"created_at":"2017-10-15T17:51:30.131211Z","published_by":null},"1.0.5":{"created_at":"2017-10-29T03:40:33.951268Z","published_by":null},"1.0.6":{"created_at":"2017-11-09T17:38:41.943044Z","published_by":null},"1.0.7":{"created_at":"2017-12-01T18:44:52.003561Z","published_by":null},"1.0.8":{"created_at":"2017-12-08T17:00:04.888194Z","published_by":null},"1.0.9":{"created_at":"2017-12-30T18:39:24.020967Z","published_by":null},"1.0.10":{"created_at":"2018-02-28T01:04:44.586617Z","published_by":null},"1.0.11":{"created_at":"2018-03-11T04:15:08.285252Z","published_by":null},"1.0.12":{"created_at":"2018-03-19T05:37:53.746047Z","published_by":null},"1.0.13":{"created_at":"2018-03-21T19:12:46.081461Z","published_by":null},"1.0.14":{"created_at":"2018-04-15T03:23:58.879923Z","published_by":null},"1.0.15":{"created_at":"2018-04-17T18:36:39.416977Z","published_by":null},"1.0.16":{"created_at":"2018-04-21T22:15:26.760998Z","published_by":null},"1.0.17":{"created_at":"2018-05-01T20:15:28.529314Z","published_by":null},"1.0.18":{"created_at":"2018-05-26T22:30:25.500509Z","published_by":null},"1.0.19":{"created_at":"2018-05-30T07:23:48.576034Z","published_by":null},"1.0.20":{"created_at":"2018-06-07T06:06:04.654942Z","published_by":null},"1.0.21":{"created_at":"2018-06-17T03:00:43.340256Z","published_by":null},"1.0.22":{"created_at":"2018-06-24T23:42:39.769578Z","published_by":null},"1.0.23":{"created_at":"2018-07-01T06:44:23.241870Z","published_by":null},"1.0.24":{"created_at":"2018-07-16T16:48:44.496110Z","published_by":null},"1.0.25":{"created_at":"2018-08-14T16:07:19.541920Z","published_by":null},"1.0.26":{"created_at":"2018-08-14T17:06:42.799447Z","published_by":null},"1.0.27":{"created_at":"2018-09-06T15:34:11.333880Z","published_by":null},"1.0.28":{"created_at":"2018-09-19T05:03:12.663178Z","published_by":null},"1.0.29":{"created_at":"2018-09-23T18:00:24.906042Z","published_by":null},"1.0.30":{"created_at":"2018-09-23T20:52:36.122202Z","published_by":null},"1.0.31":{"created_at":"2018-09-26T16:21:08.821231Z","published_by":null},"1.0.32":{"created_at":"2018-10-04T05:39:41.648778Z","published_by":null},"1.0.33":{"created_at":"2018-11-09T17:35:04.144812Z","published_by":null},"1.0.34":{"created_at":"2018-12-31T03:37:11.288759Z","published_by":null},"1.0.35":{"created_at":"2019-01-13T00:38:29.014136Z","published_by":null},"1.0.36":{"created_at":"2019-01-16T22:04:10.421669Z","published_by":null},"1.0.37":{"created_at":"2019-01-23T17:41:36.988468Z","published_by":null},"1.0.38":{"created_at":"2019-02-01T02:21:24.722854Z","published_by":null},"1.0.39":{"created_at":"2019-02-28T09:09:47.866922Z","published_by":3618},"1.0.40":{"created_at":"2019-06-30T18:04:11.292099Z","published_by":3618},"1.0.41":{"created_at":"2019-10-03T20:44:01.276174Z","published_by":3618},"1.0.42":{"created_at":"2019-11-24T22:30:10.273835Z","published_by":3618},"1.0.43":{"created_at":"2019-12-04T10:42:12.267723Z","published_by":3618},"1.0.44":{"created_at":"2019-12-04T20:27:29.901792Z","published_by":3618},"1.0.45":{"created_at":"2020-01-23T00:08:16.010816Z","published_by":3618},"1.0.46":{"created_at":"2020-02-02T20:47:34.254213Z","published_by":3618},"1.0.47":{"created_at":"2020-02-07T19:42:59.238436Z","published_by":3618},"1.0.48":{"created_at":"2020-02-13T05:39:35.658484Z","published_by":3618},"1.0.49":{"created_at":"2020-03-28T06:19:46.640954Z","published_by":3618},"1.0.50":{"created_at":"2020-03-28T18:31:48.553169Z","published_by":3618},"1.0.51":{"created_at":"2020-04-04T23:35:57.495281Z","published_by":3618},"1.0.52":{"created_at":"2020-04-28T11:01:26.758918Z","published_by":3618},"1.0.53":{"created_at":"2020-05-10T03:23:45.543292Z","published_by":3618},"1.0.54":{"created_at":"2020-06-09T19:43:58.861916Z","published_by":3618},"1.0.55":{"created_at":"2020-06-10T08:14:52.888699Z","published_by":3618},"1.0.56":{"created_at":"2020-06-29T16:42:04.284285Z","published_by":3618},"1.0.57":{"created_at":"2020-07-26T18:21:42.266283Z","published_by":3618},"1.0.58":{"created_at":"2020-09-30T21:05:00.910931Z","published_by":3618},"1.0.59":{"created_at":"2020-10-12T20:42:48.371332Z","published_by":3618},"1.0.60":{"created_at":"2020-12-02T21:19:20.521230Z","published_by":3618},"1.0.61":{"created_at":"2020-12-28T19:36:26.047134Z","published_by":3618},"1.0.62":{"created_at":"2021-02-05T22:05:46.720031Z","published_by":3618},"1.0.63":{"created_at":"2021-02-25T17:39:00.354614Z","published_by":3618},"1.0.64":{"created_at":"2021-02-28T04:59:07.569895Z","published_by":3618},"1.0.65":{"created_at":"2021-07-28T21:18:15.022547Z","published_by":3618},"1.0.66":{"created_at":"2021-07-29T21:39:35.759242Z","published_by":3618},"1.0.67":{"created_at":"2021-08-28T18:37:22.891821Z","published_by":3618},"1.0.68":{"created_at":"2021-09-14T20:13:01.461913Z","published_by":3618},"1.0.69":{"created_at":"2021-11-05T19:39:08.471192Z","published_by":3618},"1.0.70":{"created_at":"2021-11-13T03:09:01.514450Z","published_by":3618},"1.0.71":{"created_at":"2021-11-17T20:41:57.842894Z","published_by":3618},"1.0.72":{"created_at":"2021-11-25T05:29:44.522311Z","published_by":3618},"1.0.73":{"created_at":"2021-12-13T22:20:19.064433Z","published_by":3618},"1.0.74":{"created_at":"2022-01-01T20:14:10.494856Z","published_by":3618},"1.0.75":{"created_at":"2022-01-16T00:54:01.295686Z","published_by":3618},"1.0.76":{"created_at":"2022-01-22T13:08:12.018681Z","published_by":3618},"1.0.77":{"created_at":"2022-01-22T20:52:20.089292Z","published_by":3618},"1.0.78":{"created_at":"2022-01-22T23:57:26.295406Z","published_by":3618},"1.0.79":{"created_at":"2022-02-12T04:51:37.251648Z","published_by":3618},"1.0.80":{"created_at":"2022-04-30T19:16:37.799404Z","published_by":3618},"1.0.81":{"created_at":"2022-05-03T19:25:16.160307Z","published_by":3618},"1.0.82":{"created_at":"2022-06-29T18:46:45.063498Z","published_by":3618},"1.0.83":{"created_at":"2022-08-03T14:08:15.166952Z","published_by":3618},"1.0.84":{"created_at":"2022-08-21T20:30:39.164233Z","published_by":3618},"1.0.85":{"created_at":"2022-08-21T21:07:58.660209Z","published_by":3618},"1.0.86":{"created_at":"2022-10-09T19:50:21.286955Z","published_by":3618},"1.0.87":{"created_at":"2022-10-19T22:15:44.951440Z","published_by":3618},"1.0.88":{"created_at":"2022-11-18T07:25:43.365499Z","published_by":3618},"1.0.89":{"created_at":"2022-11-22T06:41:32.956303Z","published_by":3618},"1.0.90":{"created_at":"2022-12-17T19:52:11.477708Z","published_by":3618},"1.0.91":{"created_at":"2022-12-18T17:05:08.885934Z","published_by":3618},"1.0.92":{"created_at":"2023-02-05T05:05:51.837944Z","published_by":3618},"1.0.93":{"created_at":"2023-02-08T20:31:21.011865Z","published_by":3618},"1.0.94":{"created_at":"2023-03-05T18:27:44.432153Z","published_by":3618},"1.0.95":{"created_at":"2023-03-27T17:01:15.912600Z","published_by":3618},"1.0.96":{"created_at":"2023-04-12T23:39:01.733813Z","published_by":3618}},"metadata":{"description":"A JSON serialization file format","repository":"https://github.com/serde-rs/json"}},"serde_urlencoded":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2016-09-11T11:46:40.652101Z","published_by":null},"0.2.0":{"created_at":"2016-09-11T16:51:16.998306Z","published_by":null},"0.2.1":{"created_at":"2016-09-19T09:23:03.698433Z","published_by":null},"0.2.2":{"created_at":"2016-09-22T12:40:17.526305Z","published_by":null},"0.3.0":{"created_at":"2016-10-23T10:27:23.634140Z","published_by":null},"0.4.0":{"created_at":"2017-01-29T09:34:44.331031Z","published_by":null},"0.4.1":{"created_at":"2017-01-31T10:04:41.871498Z","published_by":null},"0.4.2":{"created_at":"2017-02-05T13:49:51.507800Z","published_by":null},"0.4.3":{"created_at":"2017-05-06T09:12:08.387944Z","published_by":null},"0.5.0":{"created_at":"2017-05-08T21:48:19.784325Z","published_by":null},"0.5.1":{"created_at":"2017-05-21T07:43:26.345614Z","published_by":null},"0.5.2":{"created_at":"2018-05-16T10:22:11.572102Z","published_by":null},"0.5.3":{"created_at":"2018-08-14T11:01:00.944120Z","published_by":null},"0.5.4":{"created_at":"2018-11-19T16:41:46.734875Z","published_by":null},"0.5.5":{"created_at":"2019-04-16T09:38:43.785435Z","published_by":3100},"0.6.0":{"created_at":"2019-07-30T12:59:58.637912Z","published_by":3100},"0.6.1":{"created_at":"2019-07-31T08:50:15.109890Z","published_by":3100},"0.7.0":{"created_at":"2020-09-05T09:19:02.027168Z","published_by":3100},"0.7.1":{"created_at":"2022-01-17T11:26:21.628923Z","published_by":3100}},"metadata":{"description":"`x-www-form-urlencoded` meets Serde","repository":"https://github.com/nox/serde_urlencoded"}},"slab":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2015-06-15T20:47:25.604378Z","published_by":null},"0.1.1":{"created_at":"2015-07-03T18:13:43.661875Z","published_by":null},"0.1.2":{"created_at":"2015-07-23T22:44:02.784557Z","published_by":null},"0.1.3":{"created_at":"2015-12-14T22:06:33.240661Z","published_by":null},"0.2.0":{"created_at":"2016-04-28T21:34:21.329365Z","published_by":null},"0.3.0":{"created_at":"2016-09-01T17:16:31.269663Z","published_by":null},"0.4.0":{"created_at":"2017-08-02T19:17:29.956810Z","published_by":null},"0.4.1":{"created_at":"2018-08-04T23:15:23.476115Z","published_by":null},"0.4.2":{"created_at":"2019-01-11T23:27:37.169511Z","published_by":null},"0.4.3":{"created_at":"2021-04-20T16:54:01.362364Z","published_by":33035},"0.4.4":{"created_at":"2021-08-06T14:17:44.140186Z","published_by":33035},"0.4.5":{"created_at":"2021-10-13T09:03:31.190896Z","published_by":6741},"0.4.6":{"created_at":"2022-04-02T06:29:25.995184Z","published_by":33035},"0.4.7":{"created_at":"2022-07-19T12:50:51.945503Z","published_by":33035},"0.4.8":{"created_at":"2023-02-19T17:24:48.053155Z","published_by":6741}},"metadata":{"description":"Pre-allocated storage for a uniform data type","repository":"https://github.com/tokio-rs/slab"}},"socket2":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2017-06-07T18:05:03.996775Z","published_by":null},"0.2.0":{"created_at":"2017-06-13T02:46:28.512742Z","published_by":null},"0.2.1":{"created_at":"2017-06-13T03:18:26.855232Z","published_by":null},"0.2.2":{"created_at":"2017-08-10T20:13:50.619306Z","published_by":null},"0.2.3":{"created_at":"2017-09-21T14:45:59.982728Z","published_by":null},"0.2.4":{"created_at":"2017-10-15T02:13:55.263593Z","published_by":null},"0.3.0":{"created_at":"2018-01-03T17:36:35.122530Z","published_by":null},"0.3.1":{"created_at":"2018-01-24T00:33:13.553818Z","published_by":null},"0.3.2":{"created_at":"2018-02-28T15:22:06.985349Z","published_by":null},"0.3.3":{"created_at":"2018-03-02T21:16:46.131Z","published_by":null},"0.3.4":{"created_at":"2018-03-13T14:11:11.060276Z","published_by":null},"0.3.5":{"created_at":"2018-04-17T03:12:11.223413Z","published_by":null},"0.3.6":{"created_at":"2018-06-01T17:23:15.636739Z","published_by":null},"0.3.7":{"created_at":"2018-06-27T14:23:22.553647Z","published_by":null},"0.3.8":{"created_at":"2018-08-30T16:39:21.792767Z","published_by":null},"0.3.9":{"created_at":"2019-05-06T17:45:49.697567Z","published_by":1},"0.3.10":{"created_at":"2019-07-24T15:06:19.341556Z","published_by":1},"0.3.11":{"created_at":"2019-08-07T14:03:36.078516Z","published_by":1},"0.3.12":{"created_at":"2020-04-01T13:54:42.165929Z","published_by":1},"0.3.13":{"created_at":"2020-08-14T13:38:39.340747Z","published_by":5153},"0.3.14":{"created_at":"2020-08-14T15:13:10.776323Z","published_by":5153},"0.3.15":{"created_at":"2020-09-09T17:01:56.514162Z","published_by":6025},"0.3.16":{"created_at":"2020-11-11T12:48:58.443722Z","published_by":6025},"0.3.17":{"created_at":"2020-11-21T10:54:34.649157Z","published_by":6025},"0.3.18":{"created_at":"2020-12-16T15:35:36.381826Z","published_by":6025},"0.3.19":{"created_at":"2020-12-21T11:22:17.203183Z","published_by":6025},"0.4.0-alpha.1":{"created_at":"2020-12-28T12:59:45.800271Z","published_by":6025},"0.4.0-alpha.2":{"created_at":"2021-01-08T15:54:33.532968Z","published_by":6025},"0.4.0-alpha.3":{"created_at":"2021-01-19T19:17:05.564948Z","published_by":6025},"0.4.0-alpha.4":{"created_at":"2021-01-28T12:33:44.587762Z","published_by":6025},"0.4.0-alpha.5":{"created_at":"2021-01-28T14:41:47.933864Z","published_by":6025},"0.4.0":{"created_at":"2021-03-13T17:37:14.982064Z","published_by":6025},"0.4.1":{"created_at":"2021-07-28T17:47:50.486656Z","published_by":6025},"0.4.2":{"created_at":"2021-09-15T17:15:01.011592Z","published_by":6025},"0.4.3":{"created_at":"2022-01-20T19:30:45.318151Z","published_by":6025},"0.4.4":{"created_at":"2022-01-27T08:38:58.335652Z","published_by":6025},"0.4.5":{"created_at":"2022-05-08T15:54:01.130555Z","published_by":6025},"0.4.6":{"created_at":"2022-08-26T12:36:59.985586Z","published_by":6025},"0.4.7":{"created_at":"2022-08-31T16:15:14.945825Z","published_by":6025},"0.4.8":{"created_at":"2023-03-03T09:53:39.802036Z","published_by":6025},"0.4.9":{"created_at":"2023-03-03T14:50:21.031462Z","published_by":6025},"0.5.0":{"created_at":"2023-02-25T20:29:44.233569Z","published_by":6025},"0.5.1":{"created_at":"2023-02-26T15:06:39.277641Z","published_by":6025},"0.5.2":{"created_at":"2023-04-11T09:19:22.231643Z","published_by":6025},"0.5.3":{"created_at":"2023-05-12T09:59:05.911068Z","published_by":6025}},"metadata":{"description":"Utilities for handling networking sockets with a maximal amount of configuration\npossible intended.\n","repository":"https://github.com/rust-lang/socket2"}},"syn":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.4.0":{"created_at":"2016-09-07T15:22:40.491981Z","published_by":null},"0.5.0":{"created_at":"2016-09-12T00:47:21.598194Z","published_by":null},"0.5.1":{"created_at":"2016-09-13T17:59:40.591384Z","published_by":null},"0.5.2":{"created_at":"2016-09-14T07:02:16.492002Z","published_by":null},"0.6.0":{"created_at":"2016-09-24T02:28:27.710822Z","published_by":null},"0.7.0":{"created_at":"2016-09-24T16:35:03.629686Z","published_by":null},"0.7.1":{"created_at":"2016-09-27T07:00:54.495266Z","published_by":null},"0.8.0":{"created_at":"2016-09-27T16:53:04.391503Z","published_by":null},"0.8.1":{"created_at":"2016-10-01T04:03:15.392205Z","published_by":null},"0.8.2":{"created_at":"2016-10-01T08:17:35.764727Z","published_by":null},"0.8.3":{"created_at":"2016-10-01T15:19:39.881278Z","published_by":null},"0.8.4":{"created_at":"2016-10-01T15:28:01.539626Z","published_by":null},"0.8.5":{"created_at":"2016-10-02T16:56:25.784412Z","published_by":null},"0.8.6":{"created_at":"2016-10-03T16:20:04.673986Z","published_by":null},"0.8.7":{"created_at":"2016-10-05T06:40:24.795454Z","published_by":null},"0.9.0":{"created_at":"2016-10-08T22:01:05.139632Z","published_by":null},"0.9.1":{"created_at":"2016-10-18T07:42:57.277853Z","published_by":null},"0.9.2":{"created_at":"2016-10-20T15:56:01.099402Z","published_by":null},"0.10.0-rc1":{"created_at":"2016-10-25T07:33:13.876540Z","published_by":null},"0.10.0":{"created_at":"2016-10-31T03:40:49.936665Z","published_by":null},"0.10.1":{"created_at":"2016-11-09T14:30:13.919741Z","published_by":null},"0.10.2":{"created_at":"2016-11-10T21:07:21.177869Z","published_by":null},"0.10.3":{"created_at":"2016-11-11T01:24:58.212677Z","published_by":null},"0.10.4":{"created_at":"2016-12-22T20:23:39.090578Z","published_by":null},"0.10.5":{"created_at":"2016-12-22T23:29:51.369200Z","published_by":null},"0.10.6":{"created_at":"2017-01-07T19:30:48.553479Z","published_by":null},"0.10.7":{"created_at":"2017-01-25T15:40:01.746592Z","published_by":null},"0.10.8":{"created_at":"2017-01-26T16:57:43.746182Z","published_by":null},"0.11.0":{"created_at":"2017-01-24T02:27:53.090416Z","published_by":null},"0.11.1":{"created_at":"2017-01-25T15:44:07.263385Z","published_by":null},"0.11.2":{"created_at":"2017-01-25T23:42:21.219183Z","published_by":null},"0.11.3":{"created_at":"2017-01-25T23:54:13.573744Z","published_by":null},"0.11.4":{"created_at":"2017-01-26T00:03:35.187879Z","published_by":null},"0.11.5":{"created_at":"2017-02-19T19:51:53.737845Z","published_by":null},"0.11.6":{"created_at":"2017-02-19T21:08:55.163176Z","published_by":null},"0.11.7":{"created_at":"2017-02-27T10:28:09.083067Z","published_by":null},"0.11.8":{"created_at":"2017-02-27T20:59:30.051072Z","published_by":null},"0.11.9":{"created_at":"2017-03-06T16:42:08.659682Z","published_by":null},"0.11.10":{"created_at":"2017-04-03T04:39:07.870052Z","published_by":null},"0.11.11":{"created_at":"2017-04-20T17:13:09.115066Z","published_by":null},"0.12.0":{"created_at":"2018-01-08T16:26:37.193710Z","published_by":null},"0.12.1":{"created_at":"2018-01-09T18:09:46.611658Z","published_by":null},"0.12.2":{"created_at":"2018-01-09T20:23:17.178156Z","published_by":null},"0.12.3":{"created_at":"2018-01-10T02:27:38.157515Z","published_by":null},"0.12.4":{"created_at":"2018-01-12T01:12:49.465109Z","published_by":null},"0.12.5":{"created_at":"2018-01-12T04:13:56.635874Z","published_by":null},"0.12.6":{"created_at":"2018-01-17T18:59:23.008055Z","published_by":null},"0.12.7":{"created_at":"2018-01-19T07:26:14.999675Z","published_by":null},"0.12.8":{"created_at":"2018-01-23T08:36:30.578959Z","published_by":null},"0.12.9":{"created_at":"2018-01-23T08:57:02.715748Z","published_by":null},"0.12.10":{"created_at":"2018-01-23T17:12:31.903079Z","published_by":null},"0.12.11":{"created_at":"2018-02-03T06:38:26.363020Z","published_by":null},"0.12.12":{"created_at":"2018-02-03T17:31:24.023627Z","published_by":null},"0.12.13":{"created_at":"2018-02-15T23:18:46.474568Z","published_by":null},"0.12.14":{"created_at":"2018-03-09T08:54:58.049236Z","published_by":null},"0.12.15":{"created_at":"2018-03-28T12:00:06.999948Z","published_by":null},"0.13.0":{"created_at":"2018-03-31T21:24:18.659652Z","published_by":null},"0.13.1":{"created_at":"2018-04-02T05:25:05.311309Z","published_by":null},"0.13.2":{"created_at":"2018-04-28T22:46:09.109541Z","published_by":null},"0.13.3":{"created_at":"2018-04-29T20:34:21.733682Z","published_by":null},"0.13.4":{"created_at":"2018-04-30T03:38:00.964510Z","published_by":null},"0.13.5":{"created_at":"2018-05-04T18:02:01.289839Z","published_by":null},"0.13.6":{"created_at":"2018-05-05T07:37:19.699442Z","published_by":null},"0.13.7":{"created_at":"2018-05-05T17:38:46.778221Z","published_by":null},"0.13.8":{"created_at":"2018-05-11T17:36:21.778640Z","published_by":null},"0.13.9":{"created_at":"2018-05-12T06:23:43.284548Z","published_by":null},"0.13.10":{"created_at":"2018-05-12T21:13:16.993646Z","published_by":null},"0.13.11":{"created_at":"2018-05-20T05:24:05.517060Z","published_by":null},"0.14.0":{"created_at":"2018-05-21T03:53:55.723018Z","published_by":null},"0.14.1":{"created_at":"2018-05-29T04:49:31.162348Z","published_by":null},"0.14.2":{"created_at":"2018-06-02T20:29:30.367104Z","published_by":null},"0.14.3":{"created_at":"2018-06-30T17:33:34.764111Z","published_by":null},"0.14.4":{"created_at":"2018-07-01T06:31:34.026302Z","published_by":null},"0.14.5":{"created_at":"2018-07-22T18:19:13.271446Z","published_by":null},"0.14.6":{"created_at":"2018-08-01T18:48:02.707361Z","published_by":null},"0.14.7":{"created_at":"2018-08-03T15:39:35.243230Z","published_by":null},"0.14.8":{"created_at":"2018-08-12T17:02:49.410772Z","published_by":null},"0.14.9":{"created_at":"2018-08-22T01:32:48.248267Z","published_by":null},"0.15.0":{"created_at":"2018-09-06T16:02:11.066465Z","published_by":null},"0.15.1":{"created_at":"2018-09-06T17:10:14.705100Z","published_by":null},"0.15.2":{"created_at":"2018-09-09T07:32:08.718613Z","published_by":null},"0.15.3":{"created_at":"2018-09-09T07:38:13.380951Z","published_by":null},"0.15.4":{"created_at":"2018-09-12T07:20:42.779013Z","published_by":null},"0.15.5":{"created_at":"2018-09-22T20:45:41.760472Z","published_by":null},"0.15.6":{"created_at":"2018-09-23T21:35:32.245201Z","published_by":null},"0.15.7":{"created_at":"2018-10-01T03:21:10.771604Z","published_by":null},"0.15.8":{"created_at":"2018-10-03T15:56:15.976980Z","published_by":null},"0.15.9":{"created_at":"2018-10-07T03:49:30.168673Z","published_by":null},"0.15.10":{"created_at":"2018-10-13T22:34:16.385965Z","published_by":null},"0.15.11":{"created_at":"2018-10-14T03:04:29.316622Z","published_by":null},"0.15.12":{"created_at":"2018-10-16T22:01:40.822931Z","published_by":null},"0.15.13":{"created_at":"2018-10-20T12:13:29.765531Z","published_by":null},"0.15.14":{"created_at":"2018-10-26T09:25:36.777360Z","published_by":null},"0.15.15":{"created_at":"2018-10-28T06:26:50.602963Z","published_by":null},"0.15.16":{"created_at":"2018-10-30T09:29:51.465906Z","published_by":null},"0.15.17":{"created_at":"2018-10-31T07:50:02.888918Z","published_by":null},"0.15.18":{"created_at":"2018-11-02T15:19:20.316525Z","published_by":null},"0.15.19":{"created_at":"2018-11-10T22:27:39.922760Z","published_by":null},"0.15.20":{"created_at":"2018-11-11T18:59:02.678379Z","published_by":null},"0.15.21":{"created_at":"2018-11-16T12:23:29.207550Z","published_by":null},"0.15.22":{"created_at":"2018-11-25T00:17:06.876048Z","published_by":null},"0.15.23":{"created_at":"2018-12-15T09:08:19.993260Z","published_by":null},"0.15.24":{"created_at":"2019-01-06T09:18:22.184492Z","published_by":null},"0.15.25":{"created_at":"2019-01-14T22:20:14.851752Z","published_by":null},"0.15.26":{"created_at":"2019-01-16T05:15:15.034844Z","published_by":null},"0.15.27":{"created_at":"2019-03-01T07:22:42.505447Z","published_by":3618},"0.15.28":{"created_at":"2019-03-08T07:41:03.713723Z","published_by":3618},"0.15.29":{"created_at":"2019-03-10T07:22:34.101698Z","published_by":3618},"0.15.30":{"created_at":"2019-04-02T16:15:11.111921Z","published_by":3618},"0.15.31":{"created_at":"2019-04-14T00:31:32.146847Z","published_by":3618},"0.15.32":{"created_at":"2019-04-17T19:00:47.185562Z","published_by":3618},"0.15.33":{"created_at":"2019-04-26T02:00:16.290863Z","published_by":3618},"0.15.34":{"created_at":"2019-05-07T20:10:29.410152Z","published_by":3618},"0.15.35":{"created_at":"2019-06-08T13:27:00.444999Z","published_by":3618},"0.15.36":{"created_at":"2019-06-14T05:48:55.849575Z","published_by":3618},"0.15.37":{"created_at":"2019-06-22T07:30:08.716415Z","published_by":3618},"0.15.38":{"created_at":"2019-06-23T21:23:04.988200Z","published_by":3618},"0.15.39":{"created_at":"2019-06-27T16:08:35.528925Z","published_by":3618},"0.15.40":{"created_at":"2019-07-20T19:40:08.337098Z","published_by":3618},"0.15.41":{"created_at":"2019-07-23T16:07:37.428366Z","published_by":3618},"0.15.42":{"created_at":"2019-07-24T02:37:10.998857Z","published_by":3618},"0.15.43":{"created_at":"2019-08-07T08:43:27.036417Z","published_by":3618},"0.15.44":{"created_at":"2019-08-10T19:40:41.301241Z","published_by":3618},"1.0.0":{"created_at":"2019-08-13T16:07:32.425428Z","published_by":3618},"1.0.1":{"created_at":"2019-08-13T18:41:09.608066Z","published_by":3618},"1.0.2":{"created_at":"2019-08-16T16:13:00.412024Z","published_by":3618},"1.0.3":{"created_at":"2019-08-18T01:44:54.822136Z","published_by":3618},"1.0.4":{"created_at":"2019-08-24T04:36:54.343634Z","published_by":3618},"1.0.5":{"created_at":"2019-08-26T07:46:22.379071Z","published_by":3618},"1.0.6":{"created_at":"2019-10-27T01:29:46.037665Z","published_by":3618},"1.0.7":{"created_at":"2019-10-27T15:30:23.998368Z","published_by":3618},"1.0.8":{"created_at":"2019-11-08T19:29:50.485524Z","published_by":3618},"1.0.9":{"created_at":"2019-11-29T02:02:27.325931Z","published_by":3618},"1.0.10":{"created_at":"2019-11-30T23:19:24.081950Z","published_by":3618},"1.0.11":{"created_at":"2019-12-01T01:07:24.304955Z","published_by":3618},"1.0.12":{"created_at":"2020-01-01T19:19:39.760586Z","published_by":3618},"1.0.13":{"created_at":"2020-01-03T17:15:12.385623Z","published_by":3618},"1.0.14":{"created_at":"2020-01-20T09:46:46.599866Z","published_by":3618},"1.0.15":{"created_at":"2020-02-21T21:10:59.491198Z","published_by":3618},"1.0.16":{"created_at":"2020-02-24T22:14:01.115879Z","published_by":3618},"1.0.17":{"created_at":"2020-03-21T00:48:56.492391Z","published_by":3618},"1.0.18":{"created_at":"2020-04-23T18:58:52.371558Z","published_by":3618},"1.0.19":{"created_at":"2020-05-06T23:22:48.877447Z","published_by":3618},"1.0.20":{"created_at":"2020-05-12T03:02:18.963770Z","published_by":3618},"1.0.21":{"created_at":"2020-05-13T05:38:21.988927Z","published_by":3618},"1.0.22":{"created_at":"2020-05-16T05:12:11.852742Z","published_by":3618},"1.0.23":{"created_at":"2020-05-21T00:46:09.104471Z","published_by":3618},"1.0.24":{"created_at":"2020-05-25T00:21:54.336815Z","published_by":3618},"1.0.25":{"created_at":"2020-05-25T19:15:42.032148Z","published_by":3618},"1.0.26":{"created_at":"2020-05-26T18:34:34.249541Z","published_by":3618},"1.0.27":{"created_at":"2020-05-26T22:23:32.125949Z","published_by":3618},"1.0.28":{"created_at":"2020-05-29T16:20:11.754527Z","published_by":3618},"1.0.29":{"created_at":"2020-05-30T01:57:01.693980Z","published_by":3618},"1.0.30":{"created_at":"2020-05-31T17:09:12.116553Z","published_by":3618},"1.0.31":{"created_at":"2020-06-10T03:28:07.623896Z","published_by":3618},"1.0.32":{"created_at":"2020-06-20T21:38:26.954741Z","published_by":3618},"1.0.33":{"created_at":"2020-06-22T00:27:13.534358Z","published_by":3618},"1.0.34":{"created_at":"2020-07-12T18:36:09.117545Z","published_by":3618},"1.0.35":{"created_at":"2020-07-20T16:53:56.814628Z","published_by":3618},"1.0.36":{"created_at":"2020-07-27T03:52:56.960309Z","published_by":3618},"1.0.37":{"created_at":"2020-08-03T18:46:10.690058Z","published_by":3618},"1.0.38":{"created_at":"2020-08-04T22:23:26.756001Z","published_by":3618},"1.0.39":{"created_at":"2020-08-21T19:10:22.556901Z","published_by":3618},"1.0.40":{"created_at":"2020-09-06T04:38:19.361444Z","published_by":3618},"1.0.41":{"created_at":"2020-09-14T16:37:41.442684Z","published_by":3618},"1.0.42":{"created_at":"2020-09-26T15:24:59.967169Z","published_by":3618},"1.0.43":{"created_at":"2020-10-09T16:57:01.248667Z","published_by":3618},"1.0.44":{"created_at":"2020-10-10T23:03:35.975621Z","published_by":3618},"1.0.45":{"created_at":"2020-10-17T17:56:42.852978Z","published_by":3618},"1.0.46":{"created_at":"2020-10-21T23:16:28.613288Z","published_by":3618},"1.0.47":{"created_at":"2020-10-24T20:51:56.389413Z","published_by":3618},"1.0.48":{"created_at":"2020-10-24T21:10:40.343027Z","published_by":3618},"1.0.50":{"created_at":"2020-11-21T22:29:21.176516Z","published_by":3618},"1.0.51":{"created_at":"2020-11-24T00:48:07.520232Z","published_by":3618},"1.0.52":{"created_at":"2020-11-27T19:18:01.178327Z","published_by":3618},"1.0.53":{"created_at":"2020-11-30T05:26:24.165664Z","published_by":3618},"1.0.54":{"created_at":"2020-12-07T18:49:09.241569Z","published_by":3618},"1.0.55":{"created_at":"2020-12-20T21:06:20.407857Z","published_by":3618},"1.0.56":{"created_at":"2020-12-26T19:49:24.373644Z","published_by":3618},"1.0.57":{"created_at":"2021-01-01T20:55:19.715801Z","published_by":3618},"1.0.58":{"created_at":"2021-01-05T21:49:03.402576Z","published_by":3618},"1.0.59":{"created_at":"2021-01-23T07:55:36.737675Z","published_by":3618},"1.0.60":{"created_at":"2021-01-24T20:32:04.684467Z","published_by":3618},"1.0.61":{"created_at":"2021-03-05T02:14:36.199684Z","published_by":3618},"1.0.62":{"created_at":"2021-03-06T20:47:13.334064Z","published_by":3618},"1.0.63":{"created_at":"2021-03-09T19:44:15.744648Z","published_by":3618},"1.0.64":{"created_at":"2021-03-14T04:00:49.425696Z","published_by":3618},"1.0.65":{"created_at":"2021-03-26T17:32:54.903538Z","published_by":3618},"1.0.66":{"created_at":"2021-03-29T02:08:16.812983Z","published_by":3618},"1.0.67":{"created_at":"2021-03-29T04:54:58.505738Z","published_by":3618},"1.0.68":{"created_at":"2021-04-01T04:05:22.568040Z","published_by":3618},"1.0.69":{"created_at":"2021-04-08T03:09:17.374102Z","published_by":3618},"1.0.70":{"created_at":"2021-04-22T00:21:59.367547Z","published_by":3618},"1.0.71":{"created_at":"2021-04-28T01:58:49.901833Z","published_by":3618},"1.0.72":{"created_at":"2021-05-04T03:32:26.101081Z","published_by":3618},"1.0.73":{"created_at":"2021-06-09T18:42:35.468792Z","published_by":3618},"1.0.74":{"created_at":"2021-07-21T22:01:01.942940Z","published_by":3618},"1.0.75":{"created_at":"2021-08-20T21:42:04.844054Z","published_by":3618},"1.0.76":{"created_at":"2021-09-03T21:31:24.024051Z","published_by":3618},"1.0.77":{"created_at":"2021-09-23T23:22:46.997787Z","published_by":3618},"1.0.78":{"created_at":"2021-10-03T01:41:44.310533Z","published_by":3618},"1.0.79":{"created_at":"2021-10-06T01:44:36.305976Z","published_by":3618},"1.0.80":{"created_at":"2021-10-06T03:11:46.047893Z","published_by":3618},"1.0.81":{"created_at":"2021-10-26T19:28:34.386577Z","published_by":3618},"1.0.82":{"created_at":"2021-11-25T02:33:53.901778Z","published_by":3618},"1.0.83":{"created_at":"2021-12-23T00:22:07.633583Z","published_by":3618},"1.0.84":{"created_at":"2021-12-26T03:16:28.626094Z","published_by":3618},"1.0.85":{"created_at":"2022-01-06T05:07:22.199708Z","published_by":3618},"1.0.86":{"created_at":"2022-01-20T01:01:20.253144Z","published_by":3618},"1.0.87":{"created_at":"2022-03-14T05:56:23.800805Z","published_by":3618},"1.0.88":{"created_at":"2022-03-15T03:10:18.180627Z","published_by":3618},"1.0.89":{"created_at":"2022-03-16T19:02:33.631516Z","published_by":3618},"1.0.90":{"created_at":"2022-03-28T16:36:17.620652Z","published_by":3618},"1.0.91":{"created_at":"2022-04-05T23:34:16.384376Z","published_by":3618},"1.0.92":{"created_at":"2022-04-29T01:43:58.533434Z","published_by":3618},"1.0.93":{"created_at":"2022-05-10T01:45:13.460702Z","published_by":3618},"1.0.94":{"created_at":"2022-05-13T05:27:08.316286Z","published_by":3618},"1.0.95":{"created_at":"2022-05-16T22:38:45.305717Z","published_by":3618},"1.0.96":{"created_at":"2022-06-02T20:23:48.678409Z","published_by":3618},"1.0.97":{"created_at":"2022-06-18T20:15:58.524639Z","published_by":3618},"1.0.98":{"created_at":"2022-06-19T00:40:16.443916Z","published_by":3618},"1.0.99":{"created_at":"2022-08-03T03:56:13.453347Z","published_by":3618},"1.0.100":{"created_at":"2022-09-19T00:19:02.780119Z","published_by":3618},"1.0.101":{"created_at":"2022-09-26T17:59:50.525805Z","published_by":3618},"1.0.102":{"created_at":"2022-10-07T00:43:44.866577Z","published_by":3618},"1.0.103":{"created_at":"2022-10-20T21:38:20.551842Z","published_by":3618},"1.0.104":{"created_at":"2022-11-28T01:42:33.398247Z","published_by":3618},"1.0.105":{"created_at":"2022-12-01T07:52:01.403073Z","published_by":3618},"1.0.106":{"created_at":"2022-12-17T20:03:43.407223Z","published_by":3618},"1.0.107":{"created_at":"2022-12-18T16:56:57.113737Z","published_by":3618},"1.0.108":{"created_at":"2023-02-23T02:47:58.502659Z","published_by":3618},"1.0.109":{"created_at":"2023-02-24T09:58:43.791097Z","published_by":3618},"2.0.0":{"created_at":"2023-03-17T23:39:27.346849Z","published_by":3618},"2.0.1":{"created_at":"2023-03-18T21:22:47.170803Z","published_by":3618},"2.0.2":{"created_at":"2023-03-18T23:01:54.603653Z","published_by":3618},"2.0.3":{"created_at":"2023-03-20T11:16:36.721456Z","published_by":3618},"2.0.4":{"created_at":"2023-03-20T20:58:35.201062Z","published_by":3618},"2.0.5":{"created_at":"2023-03-22T04:57:18.486307Z","published_by":3618},"2.0.6":{"created_at":"2023-03-22T20:27:58.148623Z","published_by":3618},"2.0.7":{"created_at":"2023-03-23T00:31:47.030586Z","published_by":3618},"2.0.8":{"created_at":"2023-03-23T05:09:23.273937Z","published_by":3618},"2.0.9":{"created_at":"2023-03-24T18:48:05.512594Z","published_by":3618},"2.0.10":{"created_at":"2023-03-25T03:40:05.344499Z","published_by":3618},"2.0.11":{"created_at":"2023-03-28T05:21:11.129797Z","published_by":3618},"2.0.12":{"created_at":"2023-03-30T20:26:11.678619Z","published_by":3618},"2.0.13":{"created_at":"2023-04-01T20:23:31.705642Z","published_by":3618},"2.0.14":{"created_at":"2023-04-11T09:27:13.147567Z","published_by":3618},"2.0.15":{"created_at":"2023-04-13T15:54:20.094122Z","published_by":3618},"2.0.16":{"created_at":"2023-05-14T09:38:29.744956Z","published_by":3618}},"metadata":{"description":"Parser for Rust source code","repository":"https://github.com/dtolnay/syn"}},"tempfile":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.0.1":{"created_at":"2015-04-14T18:26:06.257529Z","published_by":null},"0.0.2":{"created_at":"2015-04-15T00:22:19.840699Z","published_by":null},"0.0.3":{"created_at":"2015-04-16T00:28:37.293567Z","published_by":null},"0.1.0":{"created_at":"2015-04-23T20:11:25.911932Z","published_by":null},"0.2.0":{"created_at":"2015-04-29T16:13:17.250362Z","published_by":null},"0.3.0":{"created_at":"2015-05-17T21:34:02.948019Z","published_by":null},"0.4.0":{"created_at":"2015-05-21T18:58:14.689634Z","published_by":null},"0.5.0":{"created_at":"2015-05-22T00:32:15.169908Z","published_by":null},"0.5.1":{"created_at":"2015-05-22T15:18:55.802266Z","published_by":null},"1.0.0":{"created_at":"2015-06-29T12:27:52.177292Z","published_by":null},"1.1.0":{"created_at":"2015-07-10T15:15:44.242482Z","published_by":null},"1.1.1":{"created_at":"2015-08-15T22:28:23.091430Z","published_by":null},"1.1.2":{"created_at":"2015-11-08T14:58:50.804358Z","published_by":null},"1.1.3":{"created_at":"2015-11-21T03:15:01.967508Z","published_by":null},"2.0.0":{"created_at":"2016-01-05T23:51:53.326812Z","published_by":null},"2.0.1":{"created_at":"2016-01-18T22:25:37.181318Z","published_by":null},"2.1.0":{"created_at":"2016-02-11T21:17:54.811330Z","published_by":null},"2.1.1":{"created_at":"2016-02-17T23:11:29.582929Z","published_by":null},"2.1.2":{"created_at":"2016-04-11T16:59:43.811113Z","published_by":null},"2.1.3":{"created_at":"2016-04-21T17:02:30.319037Z","published_by":null},"2.1.4":{"created_at":"2016-06-14T14:12:24.836821Z","published_by":null},"2.1.5":{"created_at":"2017-01-19T21:32:34.927936Z","published_by":null},"2.1.6":{"created_at":"2017-07-16T07:17:01.479678Z","published_by":null},"2.2.0":{"created_at":"2017-09-27T16:19:37.468643Z","published_by":null},"3.0.0":{"created_at":"2018-03-25T20:23:05.156559Z","published_by":null},"3.0.1":{"created_at":"2018-04-18T00:00:17.174105Z","published_by":null},"3.0.2":{"created_at":"2018-05-10T16:40:19.374619Z","published_by":null},"3.0.3":{"created_at":"2018-07-17T19:18:41.723689Z","published_by":null},"3.0.4":{"created_at":"2018-09-14T02:17:41.611212Z","published_by":null},"3.0.5":{"created_at":"2018-11-26T20:39:20.058235Z","published_by":null},"3.0.6":{"created_at":"2019-02-06T20:18:30.967112Z","published_by":null},"3.0.7":{"created_at":"2019-02-17T19:12:10.656766Z","published_by":null},"3.0.8":{"created_at":"2019-05-19T02:26:35.625707Z","published_by":280},"3.0.9":{"created_at":"2019-06-30T07:38:45.528033Z","published_by":280},"3.1.0":{"created_at":"2019-07-01T08:33:32.780852Z","published_by":280},"3.2.0":{"created_at":"2021-01-12T05:54:09.804294Z","published_by":280},"3.3.0":{"created_at":"2022-01-08T19:27:21.782657Z","published_by":280},"3.4.0":{"created_at":"2023-02-25T17:20:12.634149Z","published_by":280},"3.5.0":{"created_at":"2023-03-29T03:29:20.319636Z","published_by":280}},"metadata":{"description":"A library for managing temporary files and directories.","repository":"https://github.com/Stebalien/tempfile"}},"termcolor":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2016-11-20T20:45:01.476057Z","published_by":null},"0.1.1":{"created_at":"2016-11-22T01:33:41.674089Z","published_by":null},"0.2.0":{"created_at":"2017-01-14T04:47:46.933557Z","published_by":null},"0.3.0":{"created_at":"2017-02-18T20:07:46.926934Z","published_by":null},"0.3.1":{"created_at":"2017-03-13T01:34:12.410876Z","published_by":null},"0.3.2":{"created_at":"2017-03-15T10:58:12.802028Z","published_by":null},"0.3.3":{"created_at":"2017-08-27T15:05:57.542190Z","published_by":null},"0.3.4":{"created_at":"2018-02-11T18:39:22.786352Z","published_by":null},"0.3.5":{"created_at":"2018-02-21T01:15:28.232190Z","published_by":null},"0.3.6":{"created_at":"2018-03-26T21:28:45.076748Z","published_by":null},"1.0.0":{"created_at":"2018-07-17T22:36:09.926429Z","published_by":null},"1.0.1":{"created_at":"2018-07-21T17:17:12.085696Z","published_by":null},"1.0.2":{"created_at":"2018-08-25T04:17:37.348112Z","published_by":null},"1.0.3":{"created_at":"2018-08-31T02:55:57.229944Z","published_by":null},"1.0.4":{"created_at":"2018-09-14T01:32:55.887875Z","published_by":null},"1.0.5":{"created_at":"2019-06-04T14:07:31.795340Z","published_by":189},"1.1.0":{"created_at":"2020-01-11T15:08:12.013085Z","published_by":189},"1.1.1":{"created_at":"2020-11-18T19:08:28.154526Z","published_by":189},"1.1.2":{"created_at":"2020-11-19T13:21:55.781354Z","published_by":189},"1.1.3":{"created_at":"2022-03-02T15:43:10.441102Z","published_by":189},"1.2.0":{"created_at":"2023-01-15T13:40:28.992220Z","published_by":189}},"metadata":{"description":"A simple cross platform library for writing colored text to a terminal.\n","repository":"https://github.com/BurntSushi/termcolor"}},"textwrap":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2016-12-17T01:34:45.436054Z","published_by":null},"0.2.0":{"created_at":"2016-12-29T12:44:18.953335Z","published_by":null},"0.3.0":{"created_at":"2017-01-07T00:08:55.966749Z","published_by":null},"0.4.0":{"created_at":"2017-01-24T06:50:41.659719Z","published_by":null},"0.5.0":{"created_at":"2017-05-15T16:03:34.430418Z","published_by":null},"0.6.0":{"created_at":"2017-05-22T21:44:46.093790Z","published_by":null},"0.7.0":{"created_at":"2017-07-20T21:39:36.627232Z","published_by":null},"0.8.0":{"created_at":"2017-09-04T20:16:41.608380Z","published_by":null},"0.9.0":{"created_at":"2017-10-05T20:42:24.178076Z","published_by":null},"0.10.0":{"created_at":"2018-04-28T09:57:54.060096Z","published_by":null},"0.11.0":{"created_at":"2018-12-09T22:22:57.536513Z","published_by":null},"0.12.0":{"created_at":"2020-06-26T17:55:09.435507Z","published_by":5484},"0.12.1":{"created_at":"2020-07-03T10:18:27.615015Z","published_by":5484},"0.13.0":{"created_at":"2020-12-05T22:36:07.994856Z","published_by":5484},"0.13.1":{"created_at":"2020-12-10T21:26:20.208146Z","published_by":5484},"0.13.2":{"created_at":"2020-12-30T16:15:04.542362Z","published_by":5484},"0.13.3":{"created_at":"2021-02-20T19:01:58.857484Z","published_by":5484},"0.13.4":{"created_at":"2021-02-23T21:41:04.841022Z","published_by":5484},"0.14.0":{"created_at":"2021-06-05T08:19:14.595217Z","published_by":5484},"0.14.1":{"created_at":"2021-06-26T21:31:19.626012Z","published_by":5484},"0.14.2":{"created_at":"2021-06-27T06:59:35.103873Z","published_by":5484},"0.15.0":{"created_at":"2022-02-27T20:24:21.186226Z","published_by":5484},"0.15.1":{"created_at":"2022-09-15T21:46:49.867798Z","published_by":5484},"0.15.2":{"created_at":"2022-10-24T15:30:09.013297Z","published_by":5484},"0.16.0":{"created_at":"2022-10-23T19:39:59.561429Z","published_by":5484}},"metadata":{"description":"Powerful library for word wrapping, indenting, and dedenting strings","repository":"https://github.com/mgeisler/textwrap"}},"tinyvec":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.0.0":{"created_at":"2020-01-07T02:30:11.536811Z","published_by":7211},"0.1.0":{"created_at":"2020-01-10T05:24:12.071325Z","published_by":7211},"0.1.1":{"created_at":"2020-01-10T09:31:38.255709Z","published_by":7211},"0.1.2":{"created_at":"2020-01-12T01:29:01.966495Z","published_by":7211},"0.2.0":{"created_at":"2020-01-14T19:03:44.991160Z","published_by":7211},"0.3.0":{"created_at":"2020-01-19T22:02:44.359056Z","published_by":7211},"0.3.1":{"created_at":"2020-01-21T01:59:24.097197Z","published_by":7211},"0.3.2":{"created_at":"2020-01-30T01:54:17.632904Z","published_by":7211},"0.3.3":{"created_at":"2020-03-26T00:07:07.495885Z","published_by":7211},"0.3.4":{"created_at":"2020-08-17T21:48:09.584193Z","published_by":7211},"0.4.0-alpha.1":{"created_at":"2020-07-08T13:05:17.384710Z","published_by":7211},"0.4.0-alpha.2":{"created_at":"2020-07-08T13:19:59.259561Z","published_by":7211},"0.4.0-alpha.4":{"created_at":"2020-07-16T03:14:40.263860Z","published_by":7211},"0.4.0":{"created_at":"2020-07-18T06:27:33.279343Z","published_by":7211},"0.4.1":{"created_at":"2020-07-24T01:05:47.608109Z","published_by":7211},"1.0.0-alpha.1":{"created_at":"2020-08-09T17:12:38.195853Z","published_by":7211},"1.0.0-alpha.2":{"created_at":"2020-08-09T18:16:17.030880Z","published_by":7211},"1.0.0-alpha.3":{"created_at":"2020-08-17T13:40:27.582128Z","published_by":7211},"1.0.0-alpha.4":{"created_at":"2020-08-21T21:32:37.459529Z","published_by":7211},"1.0.0":{"created_at":"2020-08-28T03:25:19.615697Z","published_by":7211},"1.0.1":{"created_at":"2020-09-08T21:13:31.085211Z","published_by":7211},"1.1.0-alpha.1":{"created_at":"2020-10-14T18:48:38.635595Z","published_by":7211},"1.1.0":{"created_at":"2020-11-19T23:59:53.854824Z","published_by":7211},"1.1.1":{"created_at":"2021-01-26T01:26:47.051429Z","published_by":7211},"1.2.0":{"created_at":"2021-04-04T16:36:32.432405Z","published_by":7211},"1.3.0":{"created_at":"2021-07-20T13:40:54.074319Z","published_by":7211},"1.3.1":{"created_at":"2021-07-22T00:14:42.225121Z","published_by":7211},"1.4.0":{"created_at":"2021-09-12T01:06:16.666148Z","published_by":7211},"1.5.0":{"created_at":"2021-09-25T00:59:19.481327Z","published_by":7211},"1.5.1":{"created_at":"2021-11-09T19:27:55.910425Z","published_by":7211},"1.6.0":{"created_at":"2022-04-25T16:34:34.909296Z","published_by":7211}},"metadata":{"description":"`tinyvec` provides 100% safe vec-like data structures.","repository":"https://github.com/Lokathor/tinyvec"}},"tinyvec_macros":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2020-08-09T16:30:41.144543Z","published_by":94492},"0.1.1":{"created_at":"2023-02-02T23:03:04.651564Z","published_by":94492}},"metadata":{"description":"Some macros for tiny containers","repository":"https://github.com/Soveu/tinyvec_macros"}},"tokio":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.0.0":{"created_at":"2016-07-01T20:39:07.497766Z","published_by":null},"0.1.0":{"created_at":"2018-02-07T21:30:22.157279Z","published_by":null},"0.1.1":{"created_at":"2018-02-09T22:32:23.273624Z","published_by":null},"0.1.2":{"created_at":"2018-03-09T04:23:23.455326Z","published_by":null},"0.1.3":{"created_at":"2018-03-09T19:42:38.995818Z","published_by":null},"0.1.4":{"created_at":"2018-03-23T17:56:41.805921Z","published_by":null},"0.1.5":{"created_at":"2018-03-30T22:39:25.161656Z","published_by":null},"0.1.6":{"created_at":"2018-05-02T19:25:07.927596Z","published_by":null},"0.1.7":{"created_at":"2018-06-07T03:20:24.962151Z","published_by":null},"0.1.8":{"created_at":"2018-08-24T16:01:20.442997Z","published_by":null},"0.1.9":{"created_at":"2018-09-27T05:45:26.168146Z","published_by":null},"0.1.10":{"created_at":"2018-09-27T20:09:11.617717Z","published_by":null},"0.1.11":{"created_at":"2018-09-28T18:34:07.373460Z","published_by":null},"0.1.12":{"created_at":"2018-10-24T05:06:12.575394Z","published_by":null},"0.1.13":{"created_at":"2018-11-22T01:24:35.398065Z","published_by":null},"0.1.14":{"created_at":"2019-01-07T07:35:25.940758Z","published_by":null},"0.1.15":{"created_at":"2019-01-25T18:33:02.174643Z","published_by":null},"0.1.16":{"created_at":"2019-03-02T05:10:20.560393Z","published_by":10},"0.1.17":{"created_at":"2019-03-13T18:22:24.260867Z","published_by":10},"0.1.18":{"created_at":"2019-03-22T21:07:01.198770Z","published_by":10},"0.1.19":{"created_at":"2019-04-22T22:33:20.092674Z","published_by":10},"0.1.20":{"created_at":"2019-05-14T18:22:45.203524Z","published_by":10},"0.1.21":{"created_at":"2019-05-30T21:40:02.761208Z","published_by":10},"0.1.22":{"created_at":"2019-07-03T16:25:16.652298Z","published_by":10},"0.2.0-alpha.1":{"created_at":"2019-08-08T20:18:10.812426Z","published_by":10},"0.2.0-alpha.2":{"created_at":"2019-08-18T07:49:30.624929Z","published_by":10},"0.2.0-alpha.3":{"created_at":"2019-08-28T22:10:09.255668Z","published_by":10},"0.2.0-alpha.4":{"created_at":"2019-08-29T20:05:37.442068Z","published_by":10},"0.2.0-alpha.5":{"created_at":"2019-09-19T20:47:40.253276Z","published_by":10},"0.2.0-alpha.6":{"created_at":"2019-10-01T00:49:11.181139Z","published_by":3959},"0.2.0":{"created_at":"2019-11-26T17:21:53.520103Z","published_by":10},"0.2.1":{"created_at":"2019-11-27T05:46:23.596442Z","published_by":10},"0.2.2":{"created_at":"2019-11-29T19:10:39.125974Z","published_by":10},"0.2.3":{"created_at":"2019-12-06T17:49:19.635295Z","published_by":10},"0.2.4":{"created_at":"2019-12-07T03:51:00.644455Z","published_by":10},"0.2.5":{"created_at":"2019-12-18T21:27:30.019891Z","published_by":10},"0.2.6":{"created_at":"2019-12-19T22:02:51.778262Z","published_by":10},"0.2.7":{"created_at":"2020-01-07T19:41:34.493346Z","published_by":10},"0.2.8":{"created_at":"2020-01-07T22:30:58.823233Z","published_by":10},"0.2.9":{"created_at":"2020-01-09T22:21:55.932099Z","published_by":10},"0.2.10":{"created_at":"2020-01-21T21:37:43.913432Z","published_by":10},"0.2.11":{"created_at":"2020-01-27T18:33:10.192565Z","published_by":10},"0.2.12":{"created_at":"2020-02-27T18:20:22.418964Z","published_by":10},"0.2.13":{"created_at":"2020-02-28T21:51:42.875811Z","published_by":10},"0.2.14":{"created_at":"2020-04-01T20:55:54.983248Z","published_by":10},"0.2.15":{"created_at":"2020-04-02T16:41:12.126013Z","published_by":3959},"0.2.16":{"created_at":"2020-04-04T00:02:32.803058Z","published_by":1249},"0.2.17":{"created_at":"2020-04-09T20:49:42.151713Z","published_by":1249},"0.2.18":{"created_at":"2020-04-13T03:41:17.268702Z","published_by":10},"0.2.19":{"created_at":"2020-04-24T22:14:48.933125Z","published_by":10},"0.2.20":{"created_at":"2020-04-28T23:34:01.926166Z","published_by":10},"0.2.21":{"created_at":"2020-05-13T19:02:28.818729Z","published_by":10},"0.2.22":{"created_at":"2020-07-22T00:53:08.507453Z","published_by":1249},"0.2.23":{"created_at":"2020-11-12T19:41:02.592538Z","published_by":10},"0.2.24":{"created_at":"2020-12-09T16:58:45.845509Z","published_by":3959},"0.2.25":{"created_at":"2021-01-28T14:36:10.390521Z","published_by":6741},"0.3.0":{"created_at":"2020-10-15T16:24:41.738266Z","published_by":10},"0.3.1":{"created_at":"2020-10-21T23:23:52.372603Z","published_by":10},"0.3.2":{"created_at":"2020-10-27T22:35:41.656747Z","published_by":10},"0.3.3":{"created_at":"2020-11-02T23:36:41.797153Z","published_by":10},"0.3.4":{"created_at":"2020-11-18T21:08:50.369677Z","published_by":10},"0.3.5":{"created_at":"2020-11-30T20:58:05.744920Z","published_by":10},"0.3.6":{"created_at":"2020-12-14T22:03:41.922497Z","published_by":107629},"0.3.7":{"created_at":"2021-01-28T14:37:50.797028Z","published_by":6741},"1.0.0":{"created_at":"2020-12-23T17:28:58.747764Z","published_by":10},"1.0.1":{"created_at":"2020-12-25T21:20:37.345132Z","published_by":6741},"1.0.2":{"created_at":"2021-01-15T00:39:23.852733Z","published_by":10},"1.0.3":{"created_at":"2021-01-29T01:50:08.426069Z","published_by":10},"1.1.0":{"created_at":"2021-01-22T22:06:18.315337Z","published_by":6741},"1.1.1":{"created_at":"2021-01-29T16:32:49.663102Z","published_by":10},"1.2.0":{"created_at":"2021-02-05T22:28:24.597709Z","published_by":6741},"1.3.0":{"created_at":"2021-03-09T21:10:41.081925Z","published_by":6741},"1.4.0":{"created_at":"2021-03-20T11:19:47.129330Z","published_by":6741},"1.5.0":{"created_at":"2021-04-12T19:25:14.273291Z","published_by":6741},"1.5.1":{"created_at":"2021-07-06T20:42:03.780515Z","published_by":10},"1.6.0":{"created_at":"2021-05-14T16:19:55.794446Z","published_by":6741},"1.6.1":{"created_at":"2021-05-28T17:01:36.700244Z","published_by":10},"1.6.2":{"created_at":"2021-06-15T00:51:00.864416Z","published_by":10},"1.6.3":{"created_at":"2021-07-06T21:32:45.045113Z","published_by":10},"1.6.4":{"created_at":"2021-07-19T18:32:43.525896Z","published_by":10},"1.7.0":{"created_at":"2021-06-15T17:40:38.696556Z","published_by":6741},"1.7.1":{"created_at":"2021-06-18T23:35:08.580233Z","published_by":10},"1.7.2":{"created_at":"2021-07-06T22:33:52.641057Z","published_by":10},"1.7.3":{"created_at":"2021-07-19T18:25:14.198807Z","published_by":10},"1.8.0":{"created_at":"2021-07-02T20:22:01.240092Z","published_by":6741},"1.8.1":{"created_at":"2021-07-06T23:18:25.877864Z","published_by":10},"1.8.2":{"created_at":"2021-07-19T18:20:43.808797Z","published_by":10},"1.8.3":{"created_at":"2021-07-26T18:57:35.781150Z","published_by":6741},"1.8.4":{"created_at":"2021-11-16T20:22:55.422077Z","published_by":1249},"1.8.5":{"created_at":"2022-01-30T19:46:15.445749Z","published_by":10},"1.9.0":{"created_at":"2021-07-22T10:06:02.053194Z","published_by":6741},"1.10.0":{"created_at":"2021-08-12T19:58:01.840659Z","published_by":6741},"1.10.1":{"created_at":"2021-08-24T15:50:05.951323Z","published_by":6741},"1.11.0":{"created_at":"2021-08-31T21:26:56.838750Z","published_by":6741},"1.12.0":{"created_at":"2021-09-21T15:16:09.196096Z","published_by":6741},"1.13.0":{"created_at":"2021-10-29T16:36:12.856786Z","published_by":6741},"1.13.1":{"created_at":"2021-11-15T22:05:45.912725Z","published_by":1249},"1.14.0":{"created_at":"2021-11-16T08:55:02.468744Z","published_by":6741},"1.14.1":{"created_at":"2022-01-31T20:58:35.379443Z","published_by":10},"1.15.0":{"created_at":"2021-12-15T18:43:35.659052Z","published_by":10},"1.16.0":{"created_at":"2022-01-27T23:22:01.653482Z","published_by":10},"1.16.1":{"created_at":"2022-01-28T09:30:58.848498Z","published_by":6741},"1.17.0":{"created_at":"2022-02-16T18:52:08.281908Z","published_by":1249},"1.18.0":{"created_at":"2022-04-27T17:28:56.214358Z","published_by":1249},"1.18.1":{"created_at":"2022-05-02T15:09:53.461793Z","published_by":6741},"1.18.2":{"created_at":"2022-05-08T19:44:32.947340Z","published_by":6741},"1.18.3":{"created_at":"2022-09-27T20:32:13.190720Z","published_by":6741},"1.18.4":{"created_at":"2023-01-03T22:20:40.063970Z","published_by":10},"1.18.5":{"created_at":"2023-01-17T18:28:06.492647Z","published_by":10},"1.19.0":{"created_at":"2022-06-03T18:42:11.771942Z","published_by":6741},"1.19.1":{"created_at":"2022-06-05T11:29:03.817889Z","published_by":6741},"1.19.2":{"created_at":"2022-06-06T16:01:26.644336Z","published_by":6741},"1.20.0":{"created_at":"2022-07-13T16:08:24.226903Z","published_by":6741},"1.20.1":{"created_at":"2022-07-25T13:00:23.784411Z","published_by":6741},"1.20.2":{"created_at":"2022-09-27T20:32:17.330735Z","published_by":6741},"1.20.3":{"created_at":"2023-01-03T22:21:41.792615Z","published_by":10},"1.20.4":{"created_at":"2023-01-17T19:44:06.647093Z","published_by":10},"1.21.0":{"created_at":"2022-09-02T11:52:55.010375Z","published_by":6741},"1.21.1":{"created_at":"2022-09-13T11:06:14.164574Z","published_by":6741},"1.21.2":{"created_at":"2022-09-27T20:32:21.777276Z","published_by":6741},"1.22.0":{"created_at":"2022-11-18T21:15:41.852799Z","published_by":10},"1.23.0":{"created_at":"2022-12-05T23:23:34.728987Z","published_by":10},"1.23.1":{"created_at":"2023-01-04T19:14:13.070446Z","published_by":10},"1.24.0":{"created_at":"2023-01-05T19:22:15.204407Z","published_by":10},"1.24.1":{"created_at":"2023-01-06T10:49:32.719523Z","published_by":6741},"1.24.2":{"created_at":"2023-01-17T20:57:36.631542Z","published_by":10},"1.25.0":{"created_at":"2023-01-29T21:45:22.810818Z","published_by":6741},"1.26.0":{"created_at":"2023-03-01T22:11:54.240360Z","published_by":10},"1.27.0":{"created_at":"2023-03-27T21:56:20.142353Z","published_by":6741},"1.28.0":{"created_at":"2023-04-25T18:23:14.554991Z","published_by":6741},"1.28.1":{"created_at":"2023-05-10T08:44:40.557654Z","published_by":6741}},"metadata":{"description":"An event-driven, non-blocking I/O platform for writing asynchronous I/O\nbacked applications.\n","repository":"https://github.com/tokio-rs/tokio"}},"tokio-native-tls":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2020-04-03T14:28:46.429426Z","published_by":3959},"0.2.0":{"created_at":"2020-10-16T15:03:10.944321Z","published_by":3959},"0.3.0":{"created_at":"2020-12-24T17:24:10.790373Z","published_by":3959},"0.3.1":{"created_at":"2023-02-07T17:08:45.567482Z","published_by":65916}},"metadata":{"description":"An implementation of TLS/SSL streams for Tokio using native-tls giving an implementation of TLS\nfor nonblocking I/O streams.\n","repository":"https://github.com/tokio-rs/tls"}},"tokio-util":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.0.0":{"created_at":"2018-02-01T03:25:03.010037Z","published_by":null},"0.2.0":{"created_at":"2019-11-26T17:23:18.728734Z","published_by":10},"0.3.0":{"created_at":"2020-03-04T23:44:54.502328Z","published_by":3959},"0.3.1":{"created_at":"2020-03-19T05:30:52.589908Z","published_by":3959},"0.4.0":{"created_at":"2020-10-15T16:26:25.252075Z","published_by":10},"0.5.0":{"created_at":"2020-10-30T18:26:50.441163Z","published_by":10},"0.5.1":{"created_at":"2020-12-03T23:31:27.075335Z","published_by":1249},"0.6.0":{"created_at":"2020-12-23T17:30:56.039162Z","published_by":10},"0.6.1":{"created_at":"2021-01-12T11:47:43.995804Z","published_by":6741},"0.6.2":{"created_at":"2021-01-21T10:47:49.693565Z","published_by":6741},"0.6.3":{"created_at":"2021-01-31T11:40:31.180711Z","published_by":6741},"0.6.4":{"created_at":"2021-03-09T21:12:07.034211Z","published_by":6741},"0.6.5":{"created_at":"2021-03-20T11:20:26.290042Z","published_by":6741},"0.6.6":{"created_at":"2021-04-12T19:24:11.330973Z","published_by":6741},"0.6.7":{"created_at":"2021-05-14T16:22:54.705456Z","published_by":6741},"0.6.8":{"created_at":"2021-09-03T14:59:30.691500Z","published_by":6741},"0.6.9":{"created_at":"2021-10-29T16:36:31.085312Z","published_by":6741},"0.6.10":{"created_at":"2022-05-14T20:30:35.944981Z","published_by":6741},"0.7.0":{"created_at":"2022-02-10T21:30:38.809946Z","published_by":6741},"0.7.1":{"created_at":"2022-03-28T12:35:24.161670Z","published_by":6741},"0.7.2":{"created_at":"2022-05-15T08:10:38.655653Z","published_by":6741},"0.7.3":{"created_at":"2022-06-04T20:05:00.881242Z","published_by":6741},"0.7.4":{"created_at":"2022-09-08T08:35:14.062411Z","published_by":6741},"0.7.5":{"created_at":"2023-02-09T16:37:13.409910Z","published_by":6741},"0.7.6":{"created_at":"2023-02-10T09:49:38.731070Z","published_by":6741},"0.7.7":{"created_at":"2023-02-12T11:43:18.337123Z","published_by":6741},"0.7.8":{"created_at":"2023-04-25T18:23:36.229576Z","published_by":6741}},"metadata":{"description":"Additional utilities for working with Tokio.\n","repository":"https://github.com/tokio-rs/tokio"}},"tower-service":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.0.0":{"created_at":"2018-02-19T17:50:18.964360Z","published_by":null},"0.1.0":{"created_at":"2018-08-09T17:09:16.733637Z","published_by":null},"0.2.0":{"created_at":"2018-12-12T22:02:52.638750Z","published_by":null},"0.3.0-alpha.1":{"created_at":"2019-08-20T18:35:34.188241Z","published_by":3959},"0.3.0-alpha.2":{"created_at":"2019-09-30T23:42:23.058217Z","published_by":3959},"0.3.0":{"created_at":"2019-11-29T21:11:13.329641Z","published_by":3959},"0.3.1":{"created_at":"2021-01-23T11:31:38.371499Z","published_by":12432},"0.3.2":{"created_at":"2022-06-17T19:44:45.753232Z","published_by":1249}},"metadata":{"description":"Trait representing an asynchronous, request / response based, client or server.\n","repository":"https://github.com/tower-rs/tower"}},"tracing":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.0.0":{"created_at":"2017-11-27T18:43:02.901436Z","published_by":null},"0.1.0":{"created_at":"2019-06-28T21:03:37.255477Z","published_by":1249},"0.1.1":{"created_at":"2019-07-03T21:15:45.794380Z","published_by":1249},"0.1.2":{"created_at":"2019-07-06T19:13:54.875968Z","published_by":1249},"0.1.3":{"created_at":"2019-07-11T21:36:58.811251Z","published_by":1249},"0.1.4":{"created_at":"2019-08-09T00:17:34.004554Z","published_by":1249},"0.1.5":{"created_at":"2019-08-10T01:58:56.413914Z","published_by":1249},"0.1.6":{"created_at":"2019-08-20T18:07:44.070644Z","published_by":1249},"0.1.7":{"created_at":"2019-08-30T22:17:07.067425Z","published_by":1249},"0.1.8":{"created_at":"2019-09-04T00:14:07.089425Z","published_by":1249},"0.1.9":{"created_at":"2019-09-13T21:48:10.929792Z","published_by":1249},"0.1.10":{"created_at":"2019-10-23T23:49:21.734816Z","published_by":1249},"0.1.11":{"created_at":"2019-12-20T23:43:20.785012Z","published_by":1249},"0.1.12":{"created_at":"2020-01-15T22:54:27.474593Z","published_by":1249},"0.1.13":{"created_at":"2020-02-27T00:24:32.898477Z","published_by":1249},"0.1.14":{"created_at":"2020-05-14T23:53:24.320202Z","published_by":1249},"0.1.15":{"created_at":"2020-06-03T01:43:38.516286Z","published_by":1249},"0.1.16":{"created_at":"2020-07-08T23:59:56.920356Z","published_by":1249},"0.1.17":{"created_at":"2020-07-22T18:15:16.724722Z","published_by":1249},"0.1.18":{"created_at":"2020-07-31T19:06:11.626576Z","published_by":1249},"0.1.19":{"created_at":"2020-08-11T00:35:26.351801Z","published_by":1249},"0.1.20":{"created_at":"2020-08-24T23:20:27.478607Z","published_by":1249},"0.1.21":{"created_at":"2020-09-28T19:01:26.619254Z","published_by":1249},"0.1.22":{"created_at":"2020-11-23T23:35:39.786582Z","published_by":1249},"0.1.23":{"created_at":"2021-02-04T21:34:13.198642Z","published_by":1249},"0.1.24":{"created_at":"2021-02-17T23:36:05.010984Z","published_by":1249},"0.1.25":{"created_at":"2021-02-23T22:05:20.810750Z","published_by":1249},"0.1.26":{"created_at":"2021-04-30T23:12:15.515027Z","published_by":1249},"0.1.27":{"created_at":"2021-09-13T19:05:51.105675Z","published_by":1249},"0.1.28":{"created_at":"2021-09-17T21:26:53.794132Z","published_by":1249},"0.1.29":{"created_at":"2021-10-06T01:40:38.439273Z","published_by":1249},"0.1.30":{"created_at":"2022-02-04T01:24:03.246162Z","published_by":1249},"0.1.31":{"created_at":"2022-02-17T22:12:29.875796Z","published_by":1249},"0.1.32":{"created_at":"2022-03-09T17:03:15.472551Z","published_by":1249},"0.1.33":{"created_at":"2022-04-09T20:56:43.693942Z","published_by":1249},"0.1.34":{"created_at":"2022-04-14T22:33:28.273399Z","published_by":1249},"0.1.35":{"created_at":"2022-06-08T15:50:45.386851Z","published_by":1249},"0.1.36":{"created_at":"2022-07-29T21:18:36.226626Z","published_by":1249},"0.1.37":{"created_at":"2022-10-06T19:39:27.292589Z","published_by":1249},"0.1.38":{"created_at":"2023-04-25T17:31:06.607215Z","published_by":1249}},"metadata":{"description":"Application-level tracing for Rust.\n","repository":"https://github.com/tokio-rs/tracing"}},"tracing-attributes":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2019-08-08T22:42:06.221385Z","published_by":1249},"0.1.1":{"created_at":"2019-08-09T23:58:26.631694Z","published_by":1249},"0.1.2":{"created_at":"2019-08-20T17:24:47.012292Z","published_by":1249},"0.1.3":{"created_at":"2019-09-12T21:37:14.193307Z","published_by":1249},"0.1.4":{"created_at":"2019-09-26T20:21:46.175547Z","published_by":1249},"0.1.5":{"created_at":"2019-10-23T18:37:41.971122Z","published_by":1249},"0.1.6":{"created_at":"2019-12-20T23:23:23.157114Z","published_by":1249},"0.1.7":{"created_at":"2020-02-26T23:14:48.912363Z","published_by":1249},"0.1.8":{"created_at":"2020-05-14T21:29:40.054251Z","published_by":1249},"0.1.9":{"created_at":"2020-07-08T17:09:50.581392Z","published_by":1249},"0.1.10":{"created_at":"2020-08-10T23:34:39.781842Z","published_by":1249},"0.1.11":{"created_at":"2020-08-18T19:35:42.517347Z","published_by":1249},"0.1.12":{"created_at":"2021-02-04T20:43:24.663159Z","published_by":1249},"0.1.13":{"created_at":"2021-02-17T23:14:51.375961Z","published_by":1249},"0.1.14":{"created_at":"2021-03-10T21:07:33.181258Z","published_by":1249},"0.1.15":{"created_at":"2021-03-12T20:52:26.733748Z","published_by":1249},"0.1.16":{"created_at":"2021-09-13T18:42:40.101926Z","published_by":1249},"0.1.17":{"created_at":"2021-10-01T23:59:07.120003Z","published_by":1249},"0.1.18":{"created_at":"2021-10-05T21:11:17.699685Z","published_by":1249},"0.1.19":{"created_at":"2022-02-04T01:01:23.732947Z","published_by":1249},"0.1.20":{"created_at":"2022-03-08T22:48:51.265538Z","published_by":1249},"0.1.21":{"created_at":"2022-04-27T00:04:40.924865Z","published_by":1249},"0.1.22":{"created_at":"2022-07-01T18:17:59.560507Z","published_by":1249},"0.1.23":{"created_at":"2022-10-06T19:01:38.306100Z","published_by":1249},"0.1.24":{"created_at":"2023-04-24T16:37:32.433625Z","published_by":1249}},"metadata":{"description":"Procedural macro attributes for automatically instrumenting functions.\n","repository":"https://github.com/tokio-rs/tracing"}},"tracing-core":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.0.0":{"created_at":"2019-06-20T17:36:00.464363Z","published_by":1249},"0.1.0":{"created_at":"2019-06-27T23:31:35.996107Z","published_by":1249},"0.1.1":{"created_at":"2019-07-06T18:18:34.126381Z","published_by":1249},"0.1.2":{"created_at":"2019-07-10T23:55:49.877165Z","published_by":1249},"0.1.3":{"created_at":"2019-08-08T23:31:30.016792Z","published_by":1249},"0.1.4":{"created_at":"2019-08-09T23:28:00.693334Z","published_by":1249},"0.1.5":{"created_at":"2019-08-17T00:01:21.725966Z","published_by":1249},"0.1.6":{"created_at":"2019-09-12T20:32:30.480293Z","published_by":1249},"0.1.7":{"created_at":"2019-10-21T22:15:50.423407Z","published_by":1249},"0.1.8":{"created_at":"2019-12-20T22:39:48.089816Z","published_by":1249},"0.1.9":{"created_at":"2020-01-10T22:50:19.910614Z","published_by":1249},"0.1.10":{"created_at":"2020-02-24T19:32:33.498610Z","published_by":1249},"0.1.11":{"created_at":"2020-07-08T23:58:07.123737Z","published_by":1249},"0.1.12":{"created_at":"2020-07-31T18:20:03.236900Z","published_by":1249},"0.1.13":{"created_at":"2020-08-05T03:09:21.505234Z","published_by":1249},"0.1.14":{"created_at":"2020-08-10T22:13:13.995032Z","published_by":1249},"0.1.15":{"created_at":"2020-08-23T00:31:50.557239Z","published_by":1249},"0.1.16":{"created_at":"2020-09-08T19:08:10.971263Z","published_by":1249},"0.1.17":{"created_at":"2020-09-28T17:40:25.384225Z","published_by":1249},"0.1.18":{"created_at":"2021-04-30T22:12:53.517159Z","published_by":1249},"0.1.19":{"created_at":"2021-08-17T22:05:06.898227Z","published_by":1249},"0.1.20":{"created_at":"2021-09-12T22:29:40.779875Z","published_by":1249},"0.1.21":{"created_at":"2021-10-01T23:57:10.614455Z","published_by":1249},"0.1.22":{"created_at":"2022-02-04T00:59:42.218718Z","published_by":1249},"0.1.23":{"created_at":"2022-03-09T00:27:22.717169Z","published_by":1249},"0.1.24":{"created_at":"2022-04-01T20:52:59.537588Z","published_by":1249},"0.1.25":{"created_at":"2022-04-12T21:56:41.448456Z","published_by":1249},"0.1.26":{"created_at":"2022-04-14T21:48:43.958749Z","published_by":1249},"0.1.27":{"created_at":"2022-06-07T21:19:08.362159Z","published_by":1249},"0.1.28":{"created_at":"2022-06-24T00:35:48.513622Z","published_by":1249},"0.1.29":{"created_at":"2022-07-29T19:45:50.396529Z","published_by":1249},"0.1.30":{"created_at":"2022-10-06T17:52:22.940896Z","published_by":1249},"0.1.31":{"created_at":"2023-05-11T23:22:50.611140Z","published_by":1249}},"metadata":{"description":"Core primitives for application-level tracing.\n","repository":"https://github.com/tokio-rs/tracing"}},"try-lock":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2018-03-15T19:23:44.274814Z","published_by":null},"0.2.0":{"created_at":"2018-06-14T16:22:15.752415Z","published_by":null},"0.2.1":{"created_at":"2018-06-14T16:41:15.447024Z","published_by":null},"0.2.2":{"created_at":"2018-06-15T01:43:20.472972Z","published_by":null},"0.2.3":{"created_at":"2020-07-10T22:09:57.554828Z","published_by":359},"0.2.4":{"created_at":"2023-01-06T15:29:36.961006Z","published_by":359}},"metadata":{"description":"A lightweight atomic lock.","repository":"https://github.com/seanmonstar/try-lock"}},"unicode-bidi":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2015-06-11T18:06:04.436453Z","published_by":null},"0.1.1":{"created_at":"2015-06-11T18:11:27.571329Z","published_by":null},"0.1.2":{"created_at":"2015-06-25T22:49:13.142363Z","published_by":null},"0.1.3":{"created_at":"2015-06-26T04:59:59.779591Z","published_by":null},"0.1.4":{"created_at":"2015-07-01T19:56:21.240818Z","published_by":null},"0.1.5":{"created_at":"2015-07-01T23:49:39.722037Z","published_by":null},"0.2.0":{"created_at":"2015-07-23T16:11:13.285023Z","published_by":null},"0.2.1":{"created_at":"2015-08-05T15:52:53.914195Z","published_by":null},"0.2.2":{"created_at":"2015-09-25T18:58:47.657901Z","published_by":null},"0.2.3":{"created_at":"2015-12-17T02:07:11.708712Z","published_by":null},"0.2.4":{"created_at":"2016-12-19T21:30:50.236986Z","published_by":null},"0.2.5":{"created_at":"2017-02-10T19:54:45.389260Z","published_by":null},"0.2.6":{"created_at":"2017-05-17T18:18:19.487863Z","published_by":null},"0.3.0":{"created_at":"2017-05-17T18:19:37.834060Z","published_by":null},"0.3.1":{"created_at":"2017-05-22T18:42:23.968971Z","published_by":null},"0.3.2":{"created_at":"2017-05-24T05:44:55.226463Z","published_by":null},"0.3.3":{"created_at":"2017-05-29T18:39:35.401854Z","published_by":null},"0.3.4":{"created_at":"2017-07-06T23:45:23.182722Z","published_by":null},"0.3.5":{"created_at":"2021-04-06T18:34:53.835907Z","published_by":2017},"0.3.6":{"created_at":"2021-08-13T07:22:05.570345Z","published_by":1139},"0.3.7":{"created_at":"2021-10-07T16:28:55.835136Z","published_by":2017},"0.3.8":{"created_at":"2022-04-26T15:41:49.464878Z","published_by":1139},"0.3.9":{"created_at":"2023-01-19T04:14:17.114092Z","published_by":1139},"0.3.10":{"created_at":"2023-01-20T17:37:46.998465Z","published_by":1139},"0.3.11":{"created_at":"2023-03-08T19:49:48.297584Z","published_by":1139},"0.3.12":{"created_at":"2023-03-17T16:39:10.317509Z","published_by":1139},"0.3.13":{"created_at":"2023-03-20T17:56:49.286975Z","published_by":1139}},"metadata":{"description":"Implementation of the Unicode Bidirectional Algorithm","repository":"https://github.com/servo/unicode-bidi"}},"unicode-normalization":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.0.1":{"created_at":"2015-04-15T01:55:51.167064Z","published_by":null},"0.0.2":{"created_at":"2015-04-15T02:02:55.733156Z","published_by":null},"0.0.3":{"created_at":"2015-04-15T19:40:44.652275Z","published_by":null},"0.1.0":{"created_at":"2015-04-21T18:31:02.159152Z","published_by":null},"0.1.1":{"created_at":"2015-07-09T02:59:20.707547Z","published_by":null},"0.1.2":{"created_at":"2016-01-18T16:13:26.188308Z","published_by":null},"0.1.3":{"created_at":"2016-12-19T23:06:35.977599Z","published_by":null},"0.1.4":{"created_at":"2017-02-04T16:24:05.384276Z","published_by":null},"0.1.5":{"created_at":"2017-06-15T15:39:48.859248Z","published_by":null},"0.1.6":{"created_at":"2018-05-02T16:05:47.735355Z","published_by":null},"0.1.7":{"created_at":"2018-05-09T19:49:17.628674Z","published_by":null},"0.1.8":{"created_at":"2019-01-21T21:14:05.521904Z","published_by":null},"0.1.9":{"created_at":"2019-11-06T21:47:34.553532Z","published_by":1139},"0.1.10":{"created_at":"2019-11-21T21:45:19.337597Z","published_by":1139},"0.1.11":{"created_at":"2019-11-22T16:31:44.290445Z","published_by":1139},"0.1.12":{"created_at":"2020-01-21T22:34:27.191917Z","published_by":1139},"0.1.13":{"created_at":"2020-06-16T19:28:31.229964Z","published_by":1139},"0.1.14":{"created_at":"2020-11-11T16:18:00.086904Z","published_by":1139},"0.1.15":{"created_at":"2020-11-18T03:45:40.312023Z","published_by":1139},"0.1.16":{"created_at":"2020-11-18T21:33:23.492057Z","published_by":1139},"0.1.17":{"created_at":"2021-02-09T01:01:44.481650Z","published_by":1139},"0.1.18":{"created_at":"2021-05-28T18:45:32.104925Z","published_by":1139},"0.1.19":{"created_at":"2021-06-02T15:32:48.614482Z","published_by":1139},"0.1.20":{"created_at":"2022-06-24T14:39:28.724455Z","published_by":1139},"0.1.21":{"created_at":"2022-07-01T16:05:04.406672Z","published_by":1139},"0.1.22":{"created_at":"2022-09-16T15:37:14.410882Z","published_by":1139}},"metadata":{"description":"This crate provides functions for normalization of\nUnicode strings, including Canonical and Compatible\nDecomposition and Recomposition, as described in\nUnicode Standard Annex #15.\n","repository":"https://github.com/unicode-rs/unicode-normalization"}},"unicode-xid":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.0.1":{"created_at":"2015-04-27T01:12:08.863461Z","published_by":null},"0.0.2":{"created_at":"2015-07-09T03:28:03.339559Z","published_by":null},"0.0.3":{"created_at":"2015-08-20T15:14:04.270121Z","published_by":null},"0.0.4":{"created_at":"2016-12-24T00:04:16.026120Z","published_by":null},"0.1.0":{"created_at":"2017-05-08T17:43:23.257438Z","published_by":null},"0.2.0":{"created_at":"2019-07-25T13:26:11.852698Z","published_by":1139},"0.2.1":{"created_at":"2020-06-24T03:44:16.807839Z","published_by":1139},"0.2.2":{"created_at":"2021-04-29T22:45:20.067194Z","published_by":1139},"0.2.3":{"created_at":"2022-05-02T12:54:06.268676Z","published_by":1139},"0.2.4":{"created_at":"2022-09-15T17:14:52.826034Z","published_by":1139}},"metadata":{"description":"Determine whether characters have the XID_Start\nor XID_Continue properties according to\nUnicode Standard Annex #31.\n","repository":"https://github.com/unicode-rs/unicode-xid"}},"url":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2014-11-14T18:37:08.693771Z","published_by":null},"0.1.1":{"created_at":"2014-11-14T20:07:37.181030Z","published_by":null},"0.2.0":{"created_at":"2014-11-21T11:48:36.443098Z","published_by":null},"0.2.1":{"created_at":"2014-11-24T08:13:04.978536Z","published_by":null},"0.2.2":{"created_at":"2014-12-03T21:11:36.633362Z","published_by":null},"0.2.3":{"created_at":"2014-12-12T09:07:20.278184Z","published_by":null},"0.2.4":{"created_at":"2014-12-14T23:00:23.821090Z","published_by":null},"0.2.5":{"created_at":"2014-12-15T16:34:38.346197Z","published_by":null},"0.2.6":{"created_at":"2014-12-19T15:48:31.259514Z","published_by":null},"0.2.7":{"created_at":"2014-12-23T09:43:07.773490Z","published_by":null},"0.2.8":{"created_at":"2014-12-25T11:50:10.617553Z","published_by":null},"0.2.9":{"created_at":"2014-12-29T13:07:58.767886Z","published_by":null},"0.2.10":{"created_at":"2015-01-02T10:18:47.365716Z","published_by":null},"0.2.11":{"created_at":"2015-01-02T22:02:56.086740Z","published_by":null},"0.2.12":{"created_at":"2015-01-03T22:24:29.596132Z","published_by":null},"0.2.13":{"created_at":"2015-01-04T21:57:38.229322Z","published_by":null},"0.2.14":{"created_at":"2015-01-06T21:32:51.019098Z","published_by":null},"0.2.15":{"created_at":"2015-01-10T00:31:09.360713Z","published_by":null},"0.2.16":{"created_at":"2015-01-10T01:45:19.638309Z","published_by":null},"0.2.17":{"created_at":"2015-01-23T20:40:48.164758Z","published_by":null},"0.2.18":{"created_at":"2015-02-04T02:47:51.754217Z","published_by":null},"0.2.19":{"created_at":"2015-02-05T06:56:34.901567Z","published_by":null},"0.2.20":{"created_at":"2015-02-06T23:49:54.014680Z","published_by":null},"0.2.21":{"created_at":"2015-02-20T17:38:23.054682Z","published_by":null},"0.2.22":{"created_at":"2015-02-27T18:15:35.557785Z","published_by":null},"0.2.23":{"created_at":"2015-03-01T11:37:09.245157Z","published_by":null},"0.2.24":{"created_at":"2015-03-16T13:45:13.656032Z","published_by":null},"0.2.25":{"created_at":"2015-03-16T14:47:40.759645Z","published_by":null},"0.2.26":{"created_at":"2015-03-17T08:47:05.683219Z","published_by":null},"0.2.27":{"created_at":"2015-03-17T13:51:34.290391Z","published_by":null},"0.2.28":{"created_at":"2015-03-25T18:16:53.502004Z","published_by":null},"0.2.29":{"created_at":"2015-04-03T08:00:28.927264Z","published_by":null},"0.2.30":{"created_at":"2015-04-22T14:56:59.341317Z","published_by":null},"0.2.31":{"created_at":"2015-04-23T08:08:11.646564Z","published_by":null},"0.2.32":{"created_at":"2015-05-04T13:23:44.685861Z","published_by":null},"0.2.33":{"created_at":"2015-05-05T06:26:07.634915Z","published_by":null},"0.2.34":{"created_at":"2015-05-10T09:26:05.177677Z","published_by":null},"0.2.35":{"created_at":"2015-06-01T19:45:10.039903Z","published_by":null},"0.2.36":{"created_at":"2015-07-11T09:35:14.397142Z","published_by":null},"0.2.37":{"created_at":"2015-08-13T14:56:34.654839Z","published_by":null},"0.2.38":{"created_at":"2015-11-18T16:33:28.237679Z","published_by":null},"0.3.0":{"created_at":"2015-11-18T15:28:37.906329Z","published_by":null},"0.4.0":{"created_at":"2015-11-19T20:20:02.236355Z","published_by":null},"0.5.0":{"created_at":"2015-11-21T07:36:16.449939Z","published_by":null},"0.5.2":{"created_at":"2015-12-15T19:06:25.679370Z","published_by":null},"0.5.3":{"created_at":"2016-01-18T16:31:35.951741Z","published_by":null},"0.5.4":{"created_at":"2016-02-02T01:13:47.752905Z","published_by":null},"0.5.5":{"created_at":"2016-02-10T15:54:31.486869Z","published_by":null},"0.5.7":{"created_at":"2016-03-09T02:17:30.851560Z","published_by":null},"0.5.8":{"created_at":"2016-04-06T15:35:06.764289Z","published_by":null},"0.5.9":{"created_at":"2016-04-13T11:51:05.280532Z","published_by":null},"0.5.10":{"created_at":"2016-08-21T07:57:14.741418Z","published_by":null},"1.0.0":{"created_at":"2016-04-21T21:13:34.992105Z","published_by":null},"1.1.0":{"created_at":"2016-05-03T17:38:47.553588Z","published_by":null},"1.1.1":{"created_at":"2016-05-27T15:09:29.662812Z","published_by":null},"1.2.0":{"created_at":"2016-08-03T17:22:05.302983Z","published_by":null},"1.2.1":{"created_at":"2016-09-28T09:37:07.945351Z","published_by":null},"1.2.2":{"created_at":"2016-10-20T14:59:22.622468Z","published_by":null},"1.2.3":{"created_at":"2016-10-30T17:03:09.877950Z","published_by":null},"1.2.4":{"created_at":"2016-12-17T06:10:15.399810Z","published_by":null},"1.3.0":{"created_at":"2017-01-17T12:32:36.773412Z","published_by":null},"1.4.0":{"created_at":"2017-01-25T15:02:23.543560Z","published_by":null},"1.4.1":{"created_at":"2017-05-28T23:38:47.841953Z","published_by":null},"1.5.0":{"created_at":"2017-06-13T17:02:49.321034Z","published_by":null},"1.5.1":{"created_at":"2017-06-25T07:34:14.867975Z","published_by":null},"1.6.0":{"created_at":"2017-10-31T17:40:30.604413Z","published_by":null},"1.6.1":{"created_at":"2018-08-23T17:02:01.330227Z","published_by":null},"1.7.0":{"created_at":"2018-02-20T14:16:46.197664Z","published_by":null},"1.7.1":{"created_at":"2018-07-05T23:25:26.598090Z","published_by":null},"1.7.2":{"created_at":"2018-11-06T18:52:26.528175Z","published_by":null},"2.0.0":{"created_at":"2019-07-23T15:38:17.365030Z","published_by":7},"2.1.0":{"created_at":"2019-08-05T15:03:04.833091Z","published_by":7},"2.1.1":{"created_at":"2020-01-09T10:19:11.192982Z","published_by":72883},"2.2.0":{"created_at":"2020-11-06T07:47:50.822536Z","published_by":72883},"2.2.1":{"created_at":"2021-02-18T21:17:15.231030Z","published_by":1139},"2.2.2":{"created_at":"2021-05-07T12:50:02.647172Z","published_by":72883},"2.3.0":{"created_at":"2022-09-07T11:08:12.051965Z","published_by":72883},"2.3.1":{"created_at":"2022-09-08T07:55:49.105213Z","published_by":72883}},"metadata":{"description":"URL library for Rust, based on the WHATWG URL Standard","repository":"https://github.com/servo/rust-url"}},"vcpkg":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.0.1":{"created_at":"2017-04-20T21:35:56.630487Z","published_by":null},"0.0.2":{"created_at":"2017-04-22T02:09:04.520123Z","published_by":null},"0.1.0":{"created_at":"2017-04-24T18:12:12.716765Z","published_by":null},"0.1.1":{"created_at":"2017-04-25T01:43:22.852683Z","published_by":null},"0.1.2":{"created_at":"2017-04-25T14:15:17.880336Z","published_by":null},"0.1.3":{"created_at":"2017-04-26T00:19:40.996489Z","published_by":null},"0.1.4":{"created_at":"2017-04-26T17:25:37.769463Z","published_by":null},"0.1.5":{"created_at":"2017-04-28T19:40:04.353597Z","published_by":null},"0.1.6":{"created_at":"2017-04-28T20:28:00.124751Z","published_by":null},"0.1.7":{"created_at":"2017-05-05T15:13:17.646231Z","published_by":null},"0.1.8":{"created_at":"2017-05-09T19:01:27.473365Z","published_by":null},"0.2.0":{"created_at":"2017-05-16T01:53:43.012242Z","published_by":null},"0.2.1":{"created_at":"2017-06-14T18:25:11.608838Z","published_by":null},"0.2.2":{"created_at":"2017-06-15T19:27:10.665235Z","published_by":null},"0.2.3":{"created_at":"2018-04-12T20:46:13.748077Z","published_by":null},"0.2.4":{"created_at":"2018-06-14T18:35:30.575600Z","published_by":null},"0.2.5":{"created_at":"2018-08-15T20:38:41.030213Z","published_by":null},"0.2.6":{"created_at":"2018-08-21T14:53:24.958131Z","published_by":null},"0.2.7":{"created_at":"2019-06-30T02:15:16.022405Z","published_by":6457},"0.2.8":{"created_at":"2019-12-02T03:39:00.213909Z","published_by":6457},"0.2.9":{"created_at":"2020-05-31T22:22:00.463951Z","published_by":6457},"0.2.10":{"created_at":"2020-06-10T05:26:30.961131Z","published_by":6457},"0.2.11":{"created_at":"2020-12-10T17:59:01.099522Z","published_by":6457},"0.2.12":{"created_at":"2021-04-16T19:16:53.275049Z","published_by":83082},"0.2.13":{"created_at":"2021-05-22T05:03:31.099659Z","published_by":83082},"0.2.14":{"created_at":"2021-06-16T22:10:27.174728Z","published_by":83082},"0.2.15":{"created_at":"2021-06-19T16:35:46.573605Z","published_by":83082}},"metadata":{"description":"A library to find native dependencies in a vcpkg tree at build\ntime in order to be used in Cargo build scripts.\n","repository":"https://github.com/mcgoo/vcpkg-rs"}},"want":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.0.0":{"created_at":"2018-03-15T19:26:09.951353Z","published_by":null},"0.0.1":{"created_at":"2018-03-15T22:28:23.282099Z","published_by":null},"0.0.2":{"created_at":"2018-03-16T21:48:37.944134Z","published_by":null},"0.0.3":{"created_at":"2018-04-13T17:01:18.077963Z","published_by":null},"0.0.4":{"created_at":"2018-04-25T23:32:27.995605Z","published_by":null},"0.0.5":{"created_at":"2018-06-14T16:37:26.511910Z","published_by":null},"0.0.6":{"created_at":"2018-06-29T18:37:18.412323Z","published_by":null},"0.1.0":{"created_at":"2018-04-06T22:53:16.603842Z","published_by":null},"0.2.0":{"created_at":"2019-03-19T19:45:02.824044Z","published_by":359},"0.3.0":{"created_at":"2019-08-20T18:59:45.301907Z","published_by":359}},"metadata":{"description":"Detect when another Future wants a result.","repository":"https://github.com/seanmonstar/want"}},"wasi":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.0.0":{"created_at":"2019-03-27T16:47:22.126268Z","published_by":13388},"0.3.0":{"created_at":"2019-07-22T14:00:42.466303Z","published_by":6825},"0.4.0":{"created_at":"2019-07-22T18:58:23.299530Z","published_by":6825},"0.5.0":{"created_at":"2019-07-23T20:00:17.937100Z","published_by":6825},"0.6.0":{"created_at":"2019-08-29T15:21:16.213647Z","published_by":6825},"0.7.0":{"created_at":"2019-08-29T15:32:47.106849Z","published_by":6825},"0.9.0+wasi-snapshot-preview1":{"created_at":"2019-12-02T16:14:46.160830Z","published_by":6825},"0.10.0+wasi-snapshot-preview1":{"created_at":"2020-06-03T18:34:32.452551Z","published_by":1},"0.10.1+wasi-snapshot-preview1":{"created_at":"2021-01-08T15:33:21.495608Z","published_by":1},"0.10.2+wasi-snapshot-preview1":{"created_at":"2021-01-28T15:05:13.663813Z","published_by":1},"0.10.3+wasi-snapshot-preview1":{"created_at":"2022-01-18T19:55:25.873931Z","published_by":1},"0.11.0+wasi-snapshot-preview1":{"created_at":"2022-01-19T15:08:37.966206Z","published_by":1}},"metadata":{"description":"Experimental WASI API bindings for Rust","repository":"https://github.com/bytecodealliance/wasi"}},"wasm-bindgen":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2018-03-06T04:24:26.947567Z","published_by":null},"0.2.0":{"created_at":"2018-04-03T21:04:02.179988Z","published_by":null},"0.2.1":{"created_at":"2018-04-09T22:21:18.035434Z","published_by":null},"0.2.2":{"created_at":"2018-04-13T14:52:36.418041Z","published_by":null},"0.2.3":{"created_at":"2018-04-17T20:08:49.992471Z","published_by":null},"0.2.4":{"created_at":"2018-04-18T14:18:48.039395Z","published_by":null},"0.2.5":{"created_at":"2018-04-20T01:47:37.073985Z","published_by":null},"0.2.6":{"created_at":"2018-04-27T02:14:41.947643Z","published_by":null},"0.2.7":{"created_at":"2018-04-28T02:49:31.870279Z","published_by":null},"0.2.8":{"created_at":"2018-05-01T02:23:56.844162Z","published_by":null},"0.2.9":{"created_at":"2018-05-11T23:05:41.935514Z","published_by":null},"0.2.10":{"created_at":"2018-05-17T17:41:01.723139Z","published_by":null},"0.2.11":{"created_at":"2018-05-24T15:56:52.328105Z","published_by":null},"0.2.12":{"created_at":"2018-07-19T20:00:32.188268Z","published_by":null},"0.2.13":{"created_at":"2018-07-22T04:11:16.426907Z","published_by":null},"0.2.14":{"created_at":"2018-07-25T20:10:25.654735Z","published_by":null},"0.2.15":{"created_at":"2018-07-26T21:58:09.348788Z","published_by":null},"0.2.16":{"created_at":"2018-08-13T21:37:58.505075Z","published_by":null},"0.2.17":{"created_at":"2018-08-17T06:37:23.534342Z","published_by":null},"0.2.18":{"created_at":"2018-08-27T20:36:19.835607Z","published_by":null},"0.2.19":{"created_at":"2018-08-27T20:39:42.535488Z","published_by":null},"0.2.20":{"created_at":"2018-09-06T21:50:27.510549Z","published_by":null},"0.2.21":{"created_at":"2018-09-07T05:10:32.394196Z","published_by":null},"0.2.22":{"created_at":"2018-09-21T22:49:03.209179Z","published_by":null},"0.2.23":{"created_at":"2018-09-26T14:55:16.978155Z","published_by":null},"0.2.24":{"created_at":"2018-10-05T18:23:07.379842Z","published_by":null},"0.2.25":{"created_at":"2018-10-10T20:20:26.977728Z","published_by":null},"0.2.26":{"created_at":"2018-10-29T19:58:59.920811Z","published_by":null},"0.2.27":{"created_at":"2018-10-29T21:32:17.678594Z","published_by":null},"0.2.28":{"created_at":"2018-11-12T18:33:23.199996Z","published_by":null},"0.2.29":{"created_at":"2018-12-04T14:25:52.579131Z","published_by":null},"0.2.30":{"created_at":"2019-01-07T15:48:26.276289Z","published_by":null},"0.2.31":{"created_at":"2019-01-09T17:19:26.165699Z","published_by":null},"0.2.32":{"created_at":"2019-01-16T21:25:59.457920Z","published_by":null},"0.2.33":{"created_at":"2019-01-18T23:34:32.016046Z","published_by":null},"0.2.34":{"created_at":"2019-02-12T03:55:49.129759Z","published_by":null},"0.2.35":{"created_at":"2019-02-12T20:39:01.632656Z","published_by":null},"0.2.36":{"created_at":"2019-02-12T22:59:59.314381Z","published_by":null},"0.2.37":{"created_at":"2019-02-15T16:18:09.646851Z","published_by":null},"0.2.38":{"created_at":"2019-03-04T17:55:46.235776Z","published_by":1},"0.2.39":{"created_at":"2019-03-13T19:48:50.671802Z","published_by":1},"0.2.40":{"created_at":"2019-03-22T01:29:17.793728Z","published_by":1},"0.2.41":{"created_at":"2019-04-10T21:13:42.703334Z","published_by":1},"0.2.42":{"created_at":"2019-04-11T14:40:45.264508Z","published_by":1},"0.2.43":{"created_at":"2019-04-29T16:21:55.717656Z","published_by":1},"0.2.44":{"created_at":"2019-05-16T16:30:40.084811Z","published_by":1},"0.2.45":{"created_at":"2019-05-20T17:51:32.096538Z","published_by":1},"0.2.46":{"created_at":"2019-06-14T18:45:53.991886Z","published_by":1},"0.2.47":{"created_at":"2019-06-19T21:47:33.377899Z","published_by":1},"0.2.48":{"created_at":"2019-07-11T22:17:35.808037Z","published_by":1},"0.2.49":{"created_at":"2019-08-14T18:47:23.161525Z","published_by":1},"0.2.50":{"created_at":"2019-08-19T11:24:10.707686Z","published_by":1},"0.2.51":{"created_at":"2019-09-26T19:08:42.324286Z","published_by":1},"0.2.52":{"created_at":"2019-10-24T21:09:38.750623Z","published_by":1},"0.2.53":{"created_at":"2019-10-29T14:38:51.307900Z","published_by":1},"0.2.54":{"created_at":"2019-11-07T19:00:37.317361Z","published_by":1},"0.2.55":{"created_at":"2019-11-19T17:06:24.788155Z","published_by":1},"0.2.56":{"created_at":"2019-12-20T16:43:25.286788Z","published_by":4798},"0.2.57":{"created_at":"2020-01-06T19:18:13.079542Z","published_by":1},"0.2.58":{"created_at":"2020-01-07T19:49:02.606013Z","published_by":1},"0.2.59":{"created_at":"2020-03-03T17:34:54.875430Z","published_by":1},"0.2.60":{"created_at":"2020-03-26T00:36:32.046917Z","published_by":1},"0.2.61":{"created_at":"2020-04-29T16:23:16.402643Z","published_by":1},"0.2.62":{"created_at":"2020-05-01T15:35:03.770354Z","published_by":1},"0.2.63":{"created_at":"2020-05-28T13:58:11.369755Z","published_by":1},"0.2.64":{"created_at":"2020-06-29T14:49:01.851512Z","published_by":1},"0.2.65":{"created_at":"2020-07-15T14:59:23.975414Z","published_by":1},"0.2.66":{"created_at":"2020-07-28T18:10:02.597129Z","published_by":1},"0.2.67":{"created_at":"2020-07-28T21:27:31.475766Z","published_by":1},"0.2.68":{"created_at":"2020-09-09T00:58:14.075581Z","published_by":1},"0.2.69":{"created_at":"2020-11-30T18:36:51.371877Z","published_by":1},"0.2.70":{"created_at":"2021-01-25T16:56:19.194615Z","published_by":1},"0.2.71":{"created_at":"2021-02-26T16:38:32.552882Z","published_by":1},"0.2.72":{"created_at":"2021-03-18T16:05:58.801563Z","published_by":1},"0.2.73":{"created_at":"2021-03-29T14:57:29.659828Z","published_by":1},"0.2.74":{"created_at":"2021-05-10T14:08:44.709818Z","published_by":1},"0.2.75":{"created_at":"2021-08-02T15:40:30.208866Z","published_by":1},"0.2.76":{"created_at":"2021-08-19T15:07:40.225335Z","published_by":1},"0.2.77":{"created_at":"2021-09-08T16:02:41.422105Z","published_by":1},"0.2.78":{"created_at":"2021-09-15T16:18:32.966678Z","published_by":1},"0.2.79":{"created_at":"2022-01-19T21:00:32.333996Z","published_by":1},"0.2.80":{"created_at":"2022-04-07T20:16:42.883145Z","published_by":1},"0.2.81":{"created_at":"2022-06-14T15:07:22.209955Z","published_by":1},"0.2.82":{"created_at":"2022-07-25T15:07:46.801713Z","published_by":1},"0.2.83":{"created_at":"2022-09-12T14:27:55.042287Z","published_by":1},"0.2.84":{"created_at":"2023-02-01T17:14:05.256679Z","published_by":1},"0.2.85":{"created_at":"2023-05-09T14:17:23.573973Z","published_by":1},"0.2.86":{"created_at":"2023-05-15T22:26:59.836969Z","published_by":1}},"metadata":{"description":"Easy support for interacting between JS and Rust.\n","repository":"https://github.com/rustwasm/wasm-bindgen"}},"wasm-bindgen-backend":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.2.0":{"created_at":"2018-04-03T21:02:46.090931Z","published_by":null},"0.2.1":{"created_at":"2018-04-09T22:19:49.417598Z","published_by":null},"0.2.2":{"created_at":"2018-04-13T14:51:24.371533Z","published_by":null},"0.2.3":{"created_at":"2018-04-17T20:08:19.939767Z","published_by":null},"0.2.4":{"created_at":"2018-04-18T14:18:47.911526Z","published_by":null},"0.2.5":{"created_at":"2018-04-20T01:47:31.083362Z","published_by":null},"0.2.6":{"created_at":"2018-04-27T02:14:30.591729Z","published_by":null},"0.2.7":{"created_at":"2018-04-28T02:49:26.194156Z","published_by":null},"0.2.8":{"created_at":"2018-05-01T02:23:50.748735Z","published_by":null},"0.2.9":{"created_at":"2018-05-11T23:05:22.855341Z","published_by":null},"0.2.10":{"created_at":"2018-05-17T17:40:55.827009Z","published_by":null},"0.2.11":{"created_at":"2018-05-24T15:56:46.905038Z","published_by":null},"0.2.12":{"created_at":"2018-07-19T19:58:47.587234Z","published_by":null},"0.2.13":{"created_at":"2018-07-22T04:11:22.975264Z","published_by":null},"0.2.14":{"created_at":"2018-07-25T16:47:45.764765Z","published_by":null},"0.2.15":{"created_at":"2018-07-26T21:53:54.838423Z","published_by":null},"0.2.16":{"created_at":"2018-08-13T21:31:25.096983Z","published_by":null},"0.2.17":{"created_at":"2018-08-17T06:37:30.588654Z","published_by":null},"0.2.18":{"created_at":"2018-08-27T20:37:23.121397Z","published_by":null},"0.2.19":{"created_at":"2018-08-27T20:40:45.116277Z","published_by":null},"0.2.20":{"created_at":"2018-09-06T21:51:47.248982Z","published_by":null},"0.2.21":{"created_at":"2018-09-07T05:11:48.809869Z","published_by":null},"0.2.22":{"created_at":"2018-09-21T22:48:09.647027Z","published_by":null},"0.2.23":{"created_at":"2018-09-26T14:55:41.816412Z","published_by":null},"0.2.24":{"created_at":"2018-10-05T18:22:16.976751Z","published_by":null},"0.2.25":{"created_at":"2018-10-10T20:20:59.961931Z","published_by":null},"0.2.26":{"created_at":"2018-10-29T19:58:13.044722Z","published_by":null},"0.2.27":{"created_at":"2018-10-29T21:31:10.089909Z","published_by":null},"0.2.28":{"created_at":"2018-11-12T18:32:23.035197Z","published_by":null},"0.2.29":{"created_at":"2018-12-04T14:24:32.718623Z","published_by":null},"0.2.30":{"created_at":"2019-01-07T15:47:26.047260Z","published_by":null},"0.2.31":{"created_at":"2019-01-09T17:18:13.549960Z","published_by":null},"0.2.32":{"created_at":"2019-01-16T21:24:08.531627Z","published_by":null},"0.2.33":{"created_at":"2019-01-18T23:32:47.907985Z","published_by":null},"0.2.34":{"created_at":"2019-02-12T03:53:41.136640Z","published_by":null},"0.2.35":{"created_at":"2019-02-12T20:37:48.790541Z","published_by":null},"0.2.36":{"created_at":"2019-02-12T22:56:06.094856Z","published_by":null},"0.2.37":{"created_at":"2019-02-15T16:16:58.021723Z","published_by":null},"0.2.38":{"created_at":"2019-03-04T17:54:21.075811Z","published_by":1},"0.2.39":{"created_at":"2019-03-13T19:48:23.847237Z","published_by":1},"0.2.40":{"created_at":"2019-03-22T01:29:01.072366Z","published_by":1},"0.2.41":{"created_at":"2019-04-10T21:11:58.368324Z","published_by":1},"0.2.42":{"created_at":"2019-04-11T14:40:25.308567Z","published_by":1},"0.2.43":{"created_at":"2019-04-29T16:21:34.678498Z","published_by":1},"0.2.44":{"created_at":"2019-05-16T16:30:09.563921Z","published_by":1},"0.2.45":{"created_at":"2019-05-20T17:51:15.005866Z","published_by":1},"0.2.46":{"created_at":"2019-06-14T18:45:35.660010Z","published_by":1},"0.2.47":{"created_at":"2019-06-19T21:47:13.067101Z","published_by":1},"0.2.48":{"created_at":"2019-07-11T22:17:12.589563Z","published_by":1},"0.2.49":{"created_at":"2019-08-14T18:47:04.378934Z","published_by":1},"0.2.50":{"created_at":"2019-08-19T11:22:40.845596Z","published_by":1},"0.2.51":{"created_at":"2019-09-26T19:08:01.634428Z","published_by":1},"0.2.52":{"created_at":"2019-10-24T21:09:19.627494Z","published_by":1},"0.2.53":{"created_at":"2019-10-29T14:38:17.887509Z","published_by":1},"0.2.54":{"created_at":"2019-11-07T18:59:32.231482Z","published_by":1},"0.2.55":{"created_at":"2019-11-19T17:05:16.559399Z","published_by":1},"0.2.56":{"created_at":"2019-12-20T16:42:52.328860Z","published_by":4798},"0.2.57":{"created_at":"2020-01-06T19:17:47.721219Z","published_by":1},"0.2.58":{"created_at":"2020-01-07T19:48:40.818567Z","published_by":1},"0.2.59":{"created_at":"2020-03-03T17:34:43.647317Z","published_by":1},"0.2.60":{"created_at":"2020-03-26T00:36:22.806597Z","published_by":1},"0.2.61":{"created_at":"2020-04-29T16:23:04.512378Z","published_by":1},"0.2.62":{"created_at":"2020-05-01T15:34:54.800249Z","published_by":1},"0.2.63":{"created_at":"2020-05-28T13:58:03.140133Z","published_by":1},"0.2.64":{"created_at":"2020-06-29T14:48:52.952042Z","published_by":1},"0.2.65":{"created_at":"2020-07-15T14:59:15.905932Z","published_by":1},"0.2.66":{"created_at":"2020-07-28T18:09:52.807162Z","published_by":1},"0.2.67":{"created_at":"2020-07-28T21:27:23.317111Z","published_by":1},"0.2.68":{"created_at":"2020-09-09T00:58:03.291281Z","published_by":1},"0.2.69":{"created_at":"2020-11-30T18:36:41.974859Z","published_by":1},"0.2.70":{"created_at":"2021-01-25T16:56:09.156223Z","published_by":1},"0.2.71":{"created_at":"2021-02-26T16:38:22.739030Z","published_by":1},"0.2.72":{"created_at":"2021-03-18T16:05:48.170469Z","published_by":1},"0.2.73":{"created_at":"2021-03-29T14:57:19.600751Z","published_by":1},"0.2.74":{"created_at":"2021-05-10T14:08:33.608196Z","published_by":1},"0.2.75":{"created_at":"2021-08-02T15:40:19.749576Z","published_by":1},"0.2.76":{"created_at":"2021-08-19T15:07:28.590588Z","published_by":1},"0.2.77":{"created_at":"2021-09-08T16:02:32.524528Z","published_by":1},"0.2.78":{"created_at":"2021-09-15T16:18:22.999384Z","published_by":1},"0.2.79":{"created_at":"2022-01-19T21:00:21.191622Z","published_by":1},"0.2.80":{"created_at":"2022-04-07T20:16:22.819451Z","published_by":1},"0.2.81":{"created_at":"2022-06-14T15:06:56.478373Z","published_by":1},"0.2.82":{"created_at":"2022-07-25T15:07:58.331203Z","published_by":1},"0.2.83":{"created_at":"2022-09-12T14:27:45.936793Z","published_by":1},"0.2.84":{"created_at":"2023-02-01T17:07:39.589276Z","published_by":1},"0.2.85":{"created_at":"2023-05-09T14:16:25.903787Z","published_by":1},"0.2.86":{"created_at":"2023-05-15T22:26:02.706226Z","published_by":1}},"metadata":{"description":"Backend code generation of the wasm-bindgen tool\n","repository":"https://github.com/rustwasm/wasm-bindgen/tree/master/crates/backend"}},"wasm-bindgen-futures":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.2.16":{"created_at":"2018-08-13T21:35:58.500192Z","published_by":null},"0.2.17":{"created_at":"2018-08-20T00:27:51.365892Z","published_by":null},"0.2.18":{"created_at":"2018-08-27T20:36:42.513115Z","published_by":null},"0.2.19":{"created_at":"2018-08-27T20:40:04.445727Z","published_by":null},"0.2.20":{"created_at":"2018-09-06T21:51:04.140321Z","published_by":null},"0.2.21":{"created_at":"2018-09-07T05:10:59.364184Z","published_by":null},"0.2.22":{"created_at":"2018-09-21T22:48:22.924646Z","published_by":null},"0.3.0":{"created_at":"2018-09-26T14:55:59.813489Z","published_by":null},"0.3.1":{"created_at":"2018-10-05T18:22:33.112849Z","published_by":null},"0.3.2":{"created_at":"2018-10-10T20:21:48.335224Z","published_by":null},"0.3.3":{"created_at":"2018-10-29T19:58:31.155139Z","published_by":null},"0.3.4":{"created_at":"2018-10-29T21:32:25.059839Z","published_by":null},"0.3.5":{"created_at":"2018-11-12T18:33:31.052068Z","published_by":null},"0.3.6":{"created_at":"2018-12-04T14:25:59.330989Z","published_by":null},"0.3.7":{"created_at":"2019-01-07T15:48:33.173678Z","published_by":null},"0.3.8":{"created_at":"2019-01-09T17:19:33.080508Z","published_by":null},"0.3.9":{"created_at":"2019-01-16T21:26:11.208540Z","published_by":null},"0.3.10":{"created_at":"2019-01-18T23:34:38.274360Z","published_by":null},"0.3.11":{"created_at":"2019-02-12T03:56:03.792256Z","published_by":null},"0.3.12":{"created_at":"2019-02-12T20:39:15.519637Z","published_by":null},"0.3.13":{"created_at":"2019-02-12T23:00:16.919241Z","published_by":null},"0.3.14":{"created_at":"2019-02-15T16:18:16.735655Z","published_by":null},"0.3.15":{"created_at":"2019-03-04T17:55:55.063308Z","published_by":1},"0.3.16":{"created_at":"2019-03-13T19:48:55.812766Z","published_by":1},"0.3.17":{"created_at":"2019-03-22T01:29:21.967639Z","published_by":1},"0.3.18":{"created_at":"2019-04-10T21:13:46.362907Z","published_by":1},"0.3.19":{"created_at":"2019-04-11T14:40:53.259676Z","published_by":1},"0.3.20":{"created_at":"2019-04-29T16:22:00.542396Z","published_by":1},"0.3.21":{"created_at":"2019-05-16T16:30:45.263233Z","published_by":1},"0.3.22":{"created_at":"2019-05-20T17:51:36.097107Z","published_by":1},"0.3.23":{"created_at":"2019-06-14T18:45:57.366380Z","published_by":1},"0.3.24":{"created_at":"2019-06-19T21:47:41.165526Z","published_by":1},"0.3.25":{"created_at":"2019-07-11T22:17:39.386623Z","published_by":1},"0.3.26":{"created_at":"2019-08-14T18:47:27.237658Z","published_by":1},"0.3.27":{"created_at":"2019-08-19T11:24:14.068309Z","published_by":1},"0.4.1":{"created_at":"2019-09-26T19:08:48.716794Z","published_by":1},"0.4.2":{"created_at":"2019-10-24T21:09:44.164289Z","published_by":1},"0.4.3":{"created_at":"2019-10-29T14:38:55.318253Z","published_by":1},"0.4.4":{"created_at":"2019-11-07T19:00:40.471657Z","published_by":1},"0.4.5":{"created_at":"2019-11-19T17:06:28.742258Z","published_by":1},"0.4.6":{"created_at":"2019-12-20T17:00:54.954836Z","published_by":4798},"0.4.7":{"created_at":"2020-01-06T19:18:17.462787Z","published_by":1},"0.4.8":{"created_at":"2020-01-07T19:49:08.271249Z","published_by":1},"0.4.9":{"created_at":"2020-03-03T17:34:56.699952Z","published_by":1},"0.4.10":{"created_at":"2020-03-26T00:36:32.943332Z","published_by":1},"0.4.11":{"created_at":"2020-04-29T16:23:17.260179Z","published_by":1},"0.4.12":{"created_at":"2020-05-01T15:35:04.715468Z","published_by":1},"0.4.13":{"created_at":"2020-05-28T13:58:12.848150Z","published_by":1},"0.4.14":{"created_at":"2020-06-29T14:49:02.742593Z","published_by":1},"0.4.15":{"created_at":"2020-07-15T14:59:24.692369Z","published_by":1},"0.4.16":{"created_at":"2020-07-28T18:10:03.498247Z","published_by":1},"0.4.17":{"created_at":"2020-07-28T21:27:32.190951Z","published_by":1},"0.4.18":{"created_at":"2020-09-09T00:58:15.410474Z","published_by":1},"0.4.19":{"created_at":"2020-11-30T18:36:52.228085Z","published_by":1},"0.4.20":{"created_at":"2021-01-25T16:56:20.115060Z","published_by":1},"0.4.21":{"created_at":"2021-02-26T16:38:33.508969Z","published_by":1},"0.4.22":{"created_at":"2021-03-18T16:05:59.755911Z","published_by":1},"0.4.23":{"created_at":"2021-03-29T14:57:30.631844Z","published_by":1},"0.4.24":{"created_at":"2021-05-10T14:08:45.662385Z","published_by":1},"0.4.25":{"created_at":"2021-08-02T15:40:31.219176Z","published_by":1},"0.4.26":{"created_at":"2021-08-19T15:07:41.558741Z","published_by":1},"0.4.27":{"created_at":"2021-09-08T16:02:42.377160Z","published_by":1},"0.4.28":{"created_at":"2021-09-15T16:18:34.017150Z","published_by":1},"0.4.29":{"created_at":"2022-01-19T21:00:33.808818Z","published_by":1},"0.4.30":{"created_at":"2022-04-07T20:16:44.193245Z","published_by":1},"0.4.31":{"created_at":"2022-06-14T15:07:23.516525Z","published_by":1},"0.4.32":{"created_at":"2022-07-25T15:07:48.543127Z","published_by":1},"0.4.33":{"created_at":"2022-09-12T14:27:56.187475Z","published_by":1},"0.4.34":{"created_at":"2023-02-01T17:15:06.593675Z","published_by":1},"0.4.35":{"created_at":"2023-05-09T14:17:29.370714Z","published_by":1},"0.4.36":{"created_at":"2023-05-15T22:27:05.981032Z","published_by":1}},"metadata":{"description":"Bridging the gap between Rust Futures and JavaScript Promises","repository":"https://github.com/rustwasm/wasm-bindgen/tree/master/crates/futures"}},"wasm-bindgen-macro":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2018-03-06T04:21:14.486723Z","published_by":null},"0.1.1":{"created_at":"2018-03-06T21:51:37.796480Z","published_by":null},"0.2.0":{"created_at":"2018-04-03T21:03:24.825610Z","published_by":null},"0.2.1":{"created_at":"2018-04-09T22:20:32.571331Z","published_by":null},"0.2.2":{"created_at":"2018-04-13T14:51:59.257428Z","published_by":null},"0.2.3":{"created_at":"2018-04-17T20:08:18.398837Z","published_by":null},"0.2.4":{"created_at":"2018-04-18T14:18:31.454276Z","published_by":null},"0.2.5":{"created_at":"2018-04-20T01:47:50.512595Z","published_by":null},"0.2.6":{"created_at":"2018-04-27T02:14:29.379654Z","published_by":null},"0.2.7":{"created_at":"2018-04-28T02:49:41.854460Z","published_by":null},"0.2.8":{"created_at":"2018-05-01T02:24:08.957036Z","published_by":null},"0.2.9":{"created_at":"2018-05-11T23:05:41.785078Z","published_by":null},"0.2.10":{"created_at":"2018-05-17T17:41:14.562322Z","published_by":null},"0.2.11":{"created_at":"2018-05-24T15:57:03.902448Z","published_by":null},"0.2.12":{"created_at":"2018-07-19T19:59:22.729788Z","published_by":null},"0.2.13":{"created_at":"2018-07-22T04:11:33.537837Z","published_by":null},"0.2.14":{"created_at":"2018-07-25T16:51:23.452661Z","published_by":null},"0.2.15":{"created_at":"2018-07-26T21:55:01.321710Z","published_by":null},"0.2.16":{"created_at":"2018-08-13T21:32:10.741412Z","published_by":null},"0.2.17":{"created_at":"2018-08-17T06:37:46.793263Z","published_by":null},"0.2.18":{"created_at":"2018-08-27T20:36:48.701371Z","published_by":null},"0.2.19":{"created_at":"2018-08-27T20:40:11.252186Z","published_by":null},"0.2.20":{"created_at":"2018-09-06T21:51:10.058722Z","published_by":null},"0.2.21":{"created_at":"2018-09-07T05:11:10.858970Z","published_by":null},"0.2.22":{"created_at":"2018-09-21T22:47:26.760348Z","published_by":null},"0.2.23":{"created_at":"2018-09-26T14:56:17.641844Z","published_by":null},"0.2.24":{"created_at":"2018-10-05T18:21:37.306881Z","published_by":null},"0.2.25":{"created_at":"2018-10-10T20:21:19.815617Z","published_by":null},"0.2.26":{"created_at":"2018-10-29T19:57:25.727217Z","published_by":null},"0.2.27":{"created_at":"2018-10-29T21:31:24.711410Z","published_by":null},"0.2.28":{"created_at":"2018-11-12T18:32:37.210492Z","published_by":null},"0.2.29":{"created_at":"2018-12-04T14:24:49.831264Z","published_by":null},"0.2.30":{"created_at":"2019-01-07T15:47:41.047622Z","published_by":null},"0.2.31":{"created_at":"2019-01-09T17:18:27.652098Z","published_by":null},"0.2.32":{"created_at":"2019-01-16T21:24:30.229217Z","published_by":null},"0.2.33":{"created_at":"2019-01-18T23:32:59.748730Z","published_by":null},"0.2.34":{"created_at":"2019-02-12T03:53:57.801619Z","published_by":null},"0.2.35":{"created_at":"2019-02-12T20:37:58.921264Z","published_by":null},"0.2.36":{"created_at":"2019-02-12T22:56:21.387403Z","published_by":null},"0.2.37":{"created_at":"2019-02-15T16:17:10.692933Z","published_by":null},"0.2.38":{"created_at":"2019-03-04T17:54:35.393793Z","published_by":1},"0.2.39":{"created_at":"2019-03-13T19:48:26.626296Z","published_by":1},"0.2.40":{"created_at":"2019-03-22T01:29:03.355385Z","published_by":1},"0.2.41":{"created_at":"2019-04-10T21:12:01.003751Z","published_by":1},"0.2.42":{"created_at":"2019-04-11T14:40:27.528239Z","published_by":1},"0.2.43":{"created_at":"2019-04-29T16:21:37.469271Z","published_by":1},"0.2.44":{"created_at":"2019-05-16T16:30:14.048018Z","published_by":1},"0.2.45":{"created_at":"2019-05-20T17:51:17.440284Z","published_by":1},"0.2.46":{"created_at":"2019-06-14T18:45:38.198719Z","published_by":1},"0.2.47":{"created_at":"2019-06-19T21:47:15.659197Z","published_by":1},"0.2.48":{"created_at":"2019-07-11T22:17:15.041996Z","published_by":1},"0.2.49":{"created_at":"2019-08-14T18:47:06.859150Z","published_by":1},"0.2.50":{"created_at":"2019-08-19T11:22:43.133462Z","published_by":1},"0.2.51":{"created_at":"2019-09-26T19:08:04.167881Z","published_by":1},"0.2.52":{"created_at":"2019-10-24T21:09:21.662062Z","published_by":1},"0.2.53":{"created_at":"2019-10-29T14:38:20.043194Z","published_by":1},"0.2.54":{"created_at":"2019-11-07T18:59:34.089266Z","published_by":1},"0.2.55":{"created_at":"2019-11-19T17:05:20.320014Z","published_by":1},"0.2.56":{"created_at":"2019-12-20T16:42:55.395727Z","published_by":4798},"0.2.57":{"created_at":"2020-01-06T19:17:49.650352Z","published_by":1},"0.2.58":{"created_at":"2020-01-07T19:48:42.945080Z","published_by":1},"0.2.59":{"created_at":"2020-03-03T17:34:45.367244Z","published_by":1},"0.2.60":{"created_at":"2020-03-26T00:36:24.517178Z","published_by":1},"0.2.61":{"created_at":"2020-04-29T16:23:05.888765Z","published_by":1},"0.2.62":{"created_at":"2020-05-01T15:34:56.356627Z","published_by":1},"0.2.63":{"created_at":"2020-05-28T13:58:04.690088Z","published_by":1},"0.2.64":{"created_at":"2020-06-29T14:48:54.519647Z","published_by":1},"0.2.65":{"created_at":"2020-07-15T14:59:17.287872Z","published_by":1},"0.2.66":{"created_at":"2020-07-28T18:09:54.522702Z","published_by":1},"0.2.67":{"created_at":"2020-07-28T21:27:24.804110Z","published_by":1},"0.2.68":{"created_at":"2020-09-09T00:58:05.140247Z","published_by":1},"0.2.69":{"created_at":"2020-11-30T18:36:43.523610Z","published_by":1},"0.2.70":{"created_at":"2021-01-25T16:56:10.911903Z","published_by":1},"0.2.71":{"created_at":"2021-02-26T16:38:24.575799Z","published_by":1},"0.2.72":{"created_at":"2021-03-18T16:05:49.702530Z","published_by":1},"0.2.73":{"created_at":"2021-03-29T14:57:21.509029Z","published_by":1},"0.2.74":{"created_at":"2021-05-10T14:08:35.552017Z","published_by":1},"0.2.75":{"created_at":"2021-08-02T15:40:21.688160Z","published_by":1},"0.2.76":{"created_at":"2021-08-19T15:07:30.343034Z","published_by":1},"0.2.77":{"created_at":"2021-09-08T16:02:34.234884Z","published_by":1},"0.2.78":{"created_at":"2021-09-15T16:18:24.794648Z","published_by":1},"0.2.79":{"created_at":"2022-01-19T21:00:23.247674Z","published_by":1},"0.2.80":{"created_at":"2022-04-07T20:16:24.465159Z","published_by":1},"0.2.81":{"created_at":"2022-06-14T15:06:57.660486Z","published_by":1},"0.2.82":{"created_at":"2022-07-25T15:07:34.886222Z","published_by":1},"0.2.83":{"created_at":"2022-09-12T14:27:47.320551Z","published_by":1},"0.2.84":{"created_at":"2023-02-01T17:09:25.680406Z","published_by":1},"0.2.85":{"created_at":"2023-05-09T14:16:34.737210Z","published_by":1},"0.2.86":{"created_at":"2023-05-15T22:26:12.867245Z","published_by":1}},"metadata":{"description":"Definition of the `#[wasm_bindgen]` attribute, an internal dependency\n","repository":"https://github.com/rustwasm/wasm-bindgen/tree/master/crates/macro"}},"wasm-bindgen-macro-support":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.2.16":{"created_at":"2018-08-13T21:31:57.296968Z","published_by":null},"0.2.17":{"created_at":"2018-08-17T06:39:56.910998Z","published_by":null},"0.2.18":{"created_at":"2018-08-27T20:37:17.890614Z","published_by":null},"0.2.19":{"created_at":"2018-08-27T20:40:39.268717Z","published_by":null},"0.2.20":{"created_at":"2018-09-06T21:51:41.628942Z","published_by":null},"0.2.21":{"created_at":"2018-09-07T05:11:43.353998Z","published_by":null},"0.2.22":{"created_at":"2018-09-21T22:48:16.680747Z","published_by":null},"0.2.23":{"created_at":"2018-09-26T14:56:23.252956Z","published_by":null},"0.2.24":{"created_at":"2018-10-05T18:22:20.721165Z","published_by":null},"0.2.25":{"created_at":"2018-10-10T20:20:41.827188Z","published_by":null},"0.2.26":{"created_at":"2018-10-29T19:58:20.087350Z","published_by":null},"0.2.27":{"created_at":"2018-10-29T21:31:18.518935Z","published_by":null},"0.2.28":{"created_at":"2018-11-12T18:32:33.367997Z","published_by":null},"0.2.29":{"created_at":"2018-12-04T14:24:42.005774Z","published_by":null},"0.2.30":{"created_at":"2019-01-07T15:47:36.866425Z","published_by":null},"0.2.31":{"created_at":"2019-01-09T17:18:22.723319Z","published_by":null},"0.2.32":{"created_at":"2019-01-16T21:24:22.683361Z","published_by":null},"0.2.33":{"created_at":"2019-01-18T23:32:55.821106Z","published_by":null},"0.2.34":{"created_at":"2019-02-12T03:53:52.759581Z","published_by":null},"0.2.35":{"created_at":"2019-02-12T20:37:54.152978Z","published_by":null},"0.2.36":{"created_at":"2019-02-12T22:56:15.199355Z","published_by":null},"0.2.37":{"created_at":"2019-02-15T16:17:04.078949Z","published_by":null},"0.2.38":{"created_at":"2019-03-04T17:54:28.304567Z","published_by":1},"0.2.39":{"created_at":"2019-03-13T19:48:25.314847Z","published_by":1},"0.2.40":{"created_at":"2019-03-22T01:29:02.321707Z","published_by":1},"0.2.41":{"created_at":"2019-04-10T21:11:59.853693Z","published_by":1},"0.2.42":{"created_at":"2019-04-11T14:40:26.331328Z","published_by":1},"0.2.43":{"created_at":"2019-04-29T16:21:36.376154Z","published_by":1},"0.2.44":{"created_at":"2019-05-16T16:30:11.384024Z","published_by":1},"0.2.45":{"created_at":"2019-05-20T17:51:16.132972Z","published_by":1},"0.2.46":{"created_at":"2019-06-14T18:45:37.163734Z","published_by":1},"0.2.47":{"created_at":"2019-06-19T21:47:14.360985Z","published_by":1},"0.2.48":{"created_at":"2019-07-11T22:17:13.766938Z","published_by":1},"0.2.49":{"created_at":"2019-08-14T18:47:05.844004Z","published_by":1},"0.2.50":{"created_at":"2019-08-19T11:22:42.060011Z","published_by":1},"0.2.51":{"created_at":"2019-09-26T19:08:02.862162Z","published_by":1},"0.2.52":{"created_at":"2019-10-24T21:09:20.516697Z","published_by":1},"0.2.53":{"created_at":"2019-10-29T14:38:19.053839Z","published_by":1},"0.2.54":{"created_at":"2019-11-07T18:59:33.168220Z","published_by":1},"0.2.55":{"created_at":"2019-11-19T17:05:17.572588Z","published_by":1},"0.2.56":{"created_at":"2019-12-20T16:42:53.798509Z","published_by":4798},"0.2.57":{"created_at":"2020-01-06T19:17:48.877961Z","published_by":1},"0.2.58":{"created_at":"2020-01-07T19:48:41.987479Z","published_by":1},"0.2.59":{"created_at":"2020-03-03T17:34:44.611113Z","published_by":1},"0.2.60":{"created_at":"2020-03-26T00:36:23.789159Z","published_by":1},"0.2.61":{"created_at":"2020-04-29T16:23:05.203224Z","published_by":1},"0.2.62":{"created_at":"2020-05-01T15:34:55.586950Z","published_by":1},"0.2.63":{"created_at":"2020-05-28T13:58:04.039795Z","published_by":1},"0.2.64":{"created_at":"2020-06-29T14:48:53.803712Z","published_by":1},"0.2.65":{"created_at":"2020-07-15T14:59:16.614537Z","published_by":1},"0.2.66":{"created_at":"2020-07-28T18:09:53.762527Z","published_by":1},"0.2.67":{"created_at":"2020-07-28T21:27:23.985105Z","published_by":1},"0.2.68":{"created_at":"2020-09-09T00:58:04.327715Z","published_by":1},"0.2.69":{"created_at":"2020-11-30T18:36:42.702120Z","published_by":1},"0.2.70":{"created_at":"2021-01-25T16:56:10.152831Z","published_by":1},"0.2.71":{"created_at":"2021-02-26T16:38:23.752890Z","published_by":1},"0.2.72":{"created_at":"2021-03-18T16:05:48.948054Z","published_by":1},"0.2.73":{"created_at":"2021-03-29T14:57:20.662872Z","published_by":1},"0.2.74":{"created_at":"2021-05-10T14:08:34.440845Z","published_by":1},"0.2.75":{"created_at":"2021-08-02T15:40:20.830299Z","published_by":1},"0.2.76":{"created_at":"2021-08-19T15:07:29.480276Z","published_by":1},"0.2.77":{"created_at":"2021-09-08T16:02:33.465007Z","published_by":1},"0.2.78":{"created_at":"2021-09-15T16:18:23.856973Z","published_by":1},"0.2.79":{"created_at":"2022-01-19T21:00:22.275068Z","published_by":1},"0.2.80":{"created_at":"2022-04-07T20:16:23.677417Z","published_by":1},"0.2.81":{"created_at":"2022-06-14T15:06:57.072034Z","published_by":1},"0.2.82":{"created_at":"2022-07-25T15:07:34.114700Z","published_by":1},"0.2.83":{"created_at":"2022-09-12T14:27:46.577719Z","published_by":1},"0.2.84":{"created_at":"2023-02-01T17:08:41.023205Z","published_by":1},"0.2.85":{"created_at":"2023-05-09T14:16:30.279611Z","published_by":1},"0.2.86":{"created_at":"2023-05-15T22:26:07.789202Z","published_by":1}},"metadata":{"description":"The part of the implementation of the `#[wasm_bindgen]` attribute that is not in the shared backend crate\n","repository":"https://github.com/rustwasm/wasm-bindgen/tree/master/crates/macro-support"}},"wasm-bindgen-shared":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2018-03-06T04:18:13.413080Z","published_by":null},"0.1.1":{"created_at":"2018-03-06T04:20:54.035676Z","published_by":null},"0.2.0":{"created_at":"2018-04-03T21:00:39.359899Z","published_by":null},"0.2.1":{"created_at":"2018-04-09T22:19:06.755568Z","published_by":null},"0.2.2":{"created_at":"2018-04-13T14:50:52.836463Z","published_by":null},"0.2.3":{"created_at":"2018-04-17T20:08:00.666160Z","published_by":null},"0.2.4":{"created_at":"2018-04-18T14:18:26.929864Z","published_by":null},"0.2.5":{"created_at":"2018-04-20T01:47:55.696581Z","published_by":null},"0.2.6":{"created_at":"2018-04-27T02:14:24.414756Z","published_by":null},"0.2.7":{"created_at":"2018-04-28T02:49:46.266227Z","published_by":null},"0.2.8":{"created_at":"2018-05-01T02:24:16.298243Z","published_by":null},"0.2.9":{"created_at":"2018-05-11T23:05:19.099639Z","published_by":null},"0.2.10":{"created_at":"2018-05-17T17:41:19.694969Z","published_by":null},"0.2.11":{"created_at":"2018-05-24T15:57:14.577705Z","published_by":null},"0.2.12":{"created_at":"2018-07-19T19:58:15.487471Z","published_by":null},"0.2.13":{"created_at":"2018-07-22T04:11:34.947639Z","published_by":null},"0.2.14":{"created_at":"2018-07-25T16:47:11.079730Z","published_by":null},"0.2.15":{"created_at":"2018-07-26T21:53:18.920583Z","published_by":null},"0.2.16":{"created_at":"2018-08-13T21:31:16.343488Z","published_by":null},"0.2.17":{"created_at":"2018-08-17T06:41:03.325104Z","published_by":null},"0.2.18":{"created_at":"2018-08-27T20:36:32.743785Z","published_by":null},"0.2.19":{"created_at":"2018-08-27T20:39:53.534671Z","published_by":null},"0.2.20":{"created_at":"2018-09-06T21:50:41.153648Z","published_by":null},"0.2.21":{"created_at":"2018-09-07T05:10:45.590123Z","published_by":null},"0.2.22":{"created_at":"2018-09-21T22:48:02.936702Z","published_by":null},"0.2.23":{"created_at":"2018-09-26T14:56:31.047381Z","published_by":null},"0.2.24":{"created_at":"2018-10-05T18:22:13.237800Z","published_by":null},"0.2.25":{"created_at":"2018-10-10T20:21:14.514922Z","published_by":null},"0.2.26":{"created_at":"2018-10-29T19:58:01.178888Z","published_by":null},"0.2.27":{"created_at":"2018-10-29T21:30:52.979731Z","published_by":null},"0.2.28":{"created_at":"2018-11-12T18:32:18.637992Z","published_by":null},"0.2.29":{"created_at":"2018-12-04T14:24:24.913886Z","published_by":null},"0.2.30":{"created_at":"2019-01-07T15:47:22.121761Z","published_by":null},"0.2.31":{"created_at":"2019-01-09T17:18:09.372474Z","published_by":null},"0.2.32":{"created_at":"2019-01-16T21:24:01.770847Z","published_by":null},"0.2.33":{"created_at":"2019-01-18T23:32:42.983424Z","published_by":null},"0.2.34":{"created_at":"2019-02-12T03:53:22.872037Z","published_by":null},"0.2.35":{"created_at":"2019-02-12T20:37:40.602271Z","published_by":null},"0.2.36":{"created_at":"2019-02-12T22:55:57.231804Z","published_by":null},"0.2.37":{"created_at":"2019-02-15T16:16:48.543313Z","published_by":null},"0.2.38":{"created_at":"2019-03-04T17:54:11.024175Z","published_by":1},"0.2.39":{"created_at":"2019-03-13T19:48:22.651336Z","published_by":1},"0.2.40":{"created_at":"2019-03-22T01:28:59.762395Z","published_by":1},"0.2.41":{"created_at":"2019-04-10T21:11:57.038637Z","published_by":1},"0.2.42":{"created_at":"2019-04-11T14:40:23.022443Z","published_by":1},"0.2.43":{"created_at":"2019-04-29T16:21:33.286259Z","published_by":1},"0.2.44":{"created_at":"2019-05-16T16:30:07.776758Z","published_by":1},"0.2.45":{"created_at":"2019-05-20T17:51:13.653550Z","published_by":1},"0.2.46":{"created_at":"2019-06-14T18:45:34.049307Z","published_by":1},"0.2.47":{"created_at":"2019-06-19T21:47:11.677897Z","published_by":1},"0.2.48":{"created_at":"2019-07-11T22:17:11.381387Z","published_by":1},"0.2.49":{"created_at":"2019-08-14T18:47:03.085333Z","published_by":1},"0.2.50":{"created_at":"2019-08-19T11:22:39.502900Z","published_by":1},"0.2.51":{"created_at":"2019-09-26T19:07:59.938875Z","published_by":1},"0.2.52":{"created_at":"2019-10-24T21:09:18.312798Z","published_by":1},"0.2.53":{"created_at":"2019-10-29T14:38:16.621883Z","published_by":1},"0.2.54":{"created_at":"2019-11-07T18:59:31.058951Z","published_by":1},"0.2.55":{"created_at":"2019-11-19T17:05:15.337059Z","published_by":1},"0.2.56":{"created_at":"2019-12-20T16:42:50.954069Z","published_by":4798},"0.2.57":{"created_at":"2020-01-06T19:17:46.579690Z","published_by":1},"0.2.58":{"created_at":"2020-01-07T19:48:39.838351Z","published_by":1},"0.2.59":{"created_at":"2020-03-03T17:34:42.694682Z","published_by":1},"0.2.60":{"created_at":"2020-03-26T00:36:20.998560Z","published_by":1},"0.2.61":{"created_at":"2020-04-29T16:23:03.652598Z","published_by":1},"0.2.62":{"created_at":"2020-05-01T15:34:53.792858Z","published_by":1},"0.2.63":{"created_at":"2020-05-28T13:58:02.196435Z","published_by":1},"0.2.64":{"created_at":"2020-06-29T14:48:52.038777Z","published_by":1},"0.2.65":{"created_at":"2020-07-15T14:59:14.954262Z","published_by":1},"0.2.66":{"created_at":"2020-07-28T18:09:51.818340Z","published_by":1},"0.2.67":{"created_at":"2020-07-28T21:27:22.437694Z","published_by":1},"0.2.68":{"created_at":"2020-09-09T00:58:02.360758Z","published_by":1},"0.2.69":{"created_at":"2020-11-30T18:36:40.956034Z","published_by":1},"0.2.70":{"created_at":"2021-01-25T16:56:08.222574Z","published_by":1},"0.2.71":{"created_at":"2021-02-26T16:38:21.801061Z","published_by":1},"0.2.72":{"created_at":"2021-03-18T16:05:47.202534Z","published_by":1},"0.2.73":{"created_at":"2021-03-29T14:57:18.602105Z","published_by":1},"0.2.74":{"created_at":"2021-05-10T14:08:32.580879Z","published_by":1},"0.2.75":{"created_at":"2021-08-02T15:40:18.703611Z","published_by":1},"0.2.76":{"created_at":"2021-08-19T15:07:27.550644Z","published_by":1},"0.2.77":{"created_at":"2021-09-08T16:02:29.477798Z","published_by":1},"0.2.78":{"created_at":"2021-09-15T16:18:22.035653Z","published_by":1},"0.2.79":{"created_at":"2022-01-19T21:00:15.214819Z","published_by":1},"0.2.80":{"created_at":"2022-04-07T20:16:21.938088Z","published_by":1},"0.2.81":{"created_at":"2022-06-14T15:06:55.701305Z","published_by":1},"0.2.82":{"created_at":"2022-07-25T15:07:27.233756Z","published_by":1},"0.2.83":{"created_at":"2022-09-12T14:27:40.134259Z","published_by":1},"0.2.84":{"created_at":"2023-02-01T17:07:20.435537Z","published_by":1},"0.2.85":{"created_at":"2023-05-09T14:16:20.592778Z","published_by":1},"0.2.86":{"created_at":"2023-05-15T22:25:57.895843Z","published_by":1}},"metadata":{"description":"Shared support between wasm-bindgen and wasm-bindgen cli, an internal\ndependency.\n","repository":"https://github.com/rustwasm/wasm-bindgen/tree/master/crates/shared"}},"web-sys":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.0.1":{"created_at":"2018-07-19T19:42:48.824097Z","published_by":null},"0.3.0":{"created_at":"2018-09-26T15:07:08.442725Z","published_by":null},"0.3.1":{"created_at":"2018-10-05T18:22:24.660338Z","published_by":null},"0.3.2":{"created_at":"2018-10-10T20:21:36.836307Z","published_by":null},"0.3.3":{"created_at":"2018-10-29T19:58:25.922368Z","published_by":null},"0.3.4":{"created_at":"2018-10-29T21:32:38.098090Z","published_by":null},"0.3.5":{"created_at":"2018-11-12T18:33:42.597763Z","published_by":null},"0.3.6":{"created_at":"2018-12-04T14:26:15.237344Z","published_by":null},"0.3.7":{"created_at":"2019-01-07T15:48:43.464134Z","published_by":null},"0.3.8":{"created_at":"2019-01-09T17:19:45.038940Z","published_by":null},"0.3.9":{"created_at":"2019-01-16T21:26:24.404274Z","published_by":null},"0.3.10":{"created_at":"2019-01-18T23:34:49.030713Z","published_by":null},"0.3.11":{"created_at":"2019-02-12T03:56:17.309194Z","published_by":null},"0.3.12":{"created_at":"2019-02-12T20:39:27.220098Z","published_by":null},"0.3.13":{"created_at":"2019-02-12T23:00:33.501328Z","published_by":null},"0.3.14":{"created_at":"2019-02-15T16:18:30.763178Z","published_by":null},"0.3.15":{"created_at":"2019-03-04T17:56:08.456097Z","published_by":1},"0.3.16":{"created_at":"2019-03-13T19:49:00.250376Z","published_by":1},"0.3.17":{"created_at":"2019-03-22T01:29:24.718232Z","published_by":1},"0.3.18":{"created_at":"2019-04-10T21:13:50.834991Z","published_by":1},"0.3.19":{"created_at":"2019-04-11T14:40:58.219253Z","published_by":1},"0.3.20":{"created_at":"2019-04-29T16:22:06.006729Z","published_by":1},"0.3.21":{"created_at":"2019-05-16T16:30:49.177798Z","published_by":1},"0.3.22":{"created_at":"2019-05-20T17:51:40.007447Z","published_by":1},"0.3.23":{"created_at":"2019-06-14T18:46:00.657509Z","published_by":1},"0.3.24":{"created_at":"2019-06-19T21:47:44.918245Z","published_by":1},"0.3.25":{"created_at":"2019-07-11T22:17:42.616884Z","published_by":1},"0.3.26":{"created_at":"2019-08-14T18:47:36.700045Z","published_by":1},"0.3.27":{"created_at":"2019-08-19T11:24:17.602365Z","published_by":1},"0.3.28":{"created_at":"2019-09-26T19:08:52.431511Z","published_by":1},"0.3.29":{"created_at":"2019-10-24T21:09:47.051347Z","published_by":1},"0.3.30":{"created_at":"2019-10-29T14:38:58.965105Z","published_by":1},"0.3.31":{"created_at":"2019-11-07T19:00:43.533721Z","published_by":1},"0.3.32":{"created_at":"2019-11-19T17:06:32.193990Z","published_by":1},"0.3.33":{"created_at":"2019-12-20T17:00:58.761675Z","published_by":4798},"0.3.34":{"created_at":"2020-01-06T19:18:19.668769Z","published_by":1},"0.3.35":{"created_at":"2020-01-07T19:49:10.381507Z","published_by":1},"0.3.36":{"created_at":"2020-03-03T17:34:58.540639Z","published_by":1},"0.3.37":{"created_at":"2020-03-26T00:36:35.896249Z","published_by":1},"0.3.38":{"created_at":"2020-04-29T16:23:19.481502Z","published_by":1},"0.3.39":{"created_at":"2020-05-01T15:35:06.896163Z","published_by":1},"0.3.40":{"created_at":"2020-05-28T13:58:14.619152Z","published_by":1},"0.3.41":{"created_at":"2020-06-29T14:49:04.662921Z","published_by":1},"0.3.42":{"created_at":"2020-07-15T14:59:26.655110Z","published_by":1},"0.3.43":{"created_at":"2020-07-28T18:10:05.492330Z","published_by":1},"0.3.44":{"created_at":"2020-07-28T21:27:34.920366Z","published_by":1},"0.3.45":{"created_at":"2020-09-09T00:58:20.904454Z","published_by":1},"0.3.46":{"created_at":"2020-11-30T18:36:54.475520Z","published_by":1},"0.3.47":{"created_at":"2021-01-25T16:56:22.547814Z","published_by":1},"0.3.48":{"created_at":"2021-02-26T16:38:36.501969Z","published_by":1},"0.3.49":{"created_at":"2021-03-18T16:06:02.719335Z","published_by":1},"0.3.50":{"created_at":"2021-03-29T14:57:33.037395Z","published_by":1},"0.3.51":{"created_at":"2021-05-10T14:08:48.953053Z","published_by":1},"0.3.52":{"created_at":"2021-08-02T15:40:33.578564Z","published_by":1},"0.3.53":{"created_at":"2021-08-19T15:07:44.982905Z","published_by":1},"0.3.54":{"created_at":"2021-09-08T16:02:46.275331Z","published_by":1},"0.3.55":{"created_at":"2021-09-15T16:18:36.272005Z","published_by":1},"0.3.56":{"created_at":"2022-01-19T21:00:36.312999Z","published_by":1},"0.3.57":{"created_at":"2022-04-07T20:16:46.482809Z","published_by":1},"0.3.58":{"created_at":"2022-06-14T15:07:25.834335Z","published_by":1},"0.3.59":{"created_at":"2022-07-25T15:07:52.718575Z","published_by":1},"0.3.60":{"created_at":"2022-09-12T14:27:58.396206Z","published_by":1},"0.3.61":{"created_at":"2023-02-01T17:16:06.050440Z","published_by":1},"0.3.62":{"created_at":"2023-05-09T14:17:40.248737Z","published_by":1},"0.3.63":{"created_at":"2023-05-15T22:27:17.908036Z","published_by":1}},"metadata":{"description":"Bindings for all Web APIs, a procedurally generated crate from WebIDL\n","repository":"https://github.com/rustwasm/wasm-bindgen/tree/master/crates/web-sys"}},"winapi":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.0.1":{"created_at":"2014-11-29T13:35:24.394949Z","published_by":null},"0.0.2":{"created_at":"2014-12-17T05:49:07.369016Z","published_by":null},"0.0.3":{"created_at":"2014-12-30T00:38:51.688063Z","published_by":null},"0.0.4":{"created_at":"2015-01-03T21:14:20.508987Z","published_by":null},"0.0.5":{"created_at":"2015-01-09T20:13:22.720399Z","published_by":null},"0.0.6":{"created_at":"2015-01-18T12:19:12.934087Z","published_by":null},"0.0.7":{"created_at":"2015-01-19T03:12:50.200495Z","published_by":null},"0.0.8":{"created_at":"2015-01-19T14:42:32.764743Z","published_by":null},"0.1.0":{"created_at":"2015-01-20T04:54:39.864255Z","published_by":null},"0.1.1":{"created_at":"2015-01-20T06:53:57.048520Z","published_by":null},"0.1.2":{"created_at":"2015-01-23T23:30:14.632865Z","published_by":null},"0.1.3":{"created_at":"2015-01-26T02:47:13.395503Z","published_by":null},"0.1.4":{"created_at":"2015-01-28T23:40:48.342041Z","published_by":null},"0.1.5":{"created_at":"2015-01-29T22:07:11.426864Z","published_by":null},"0.1.6":{"created_at":"2015-01-31T00:30:09.513129Z","published_by":null},"0.1.7":{"created_at":"2015-02-01T04:34:11.051544Z","published_by":null},"0.1.8":{"created_at":"2015-02-02T00:05:51.260901Z","published_by":null},"0.1.9":{"created_at":"2015-02-05T04:50:31.585498Z","published_by":null},"0.1.10":{"created_at":"2015-02-08T01:07:39.889665Z","published_by":null},"0.1.11":{"created_at":"2015-02-09T18:29:32.376418Z","published_by":null},"0.1.12":{"created_at":"2015-02-19T00:08:44.626906Z","published_by":null},"0.1.13":{"created_at":"2015-02-23T08:40:16.928594Z","published_by":null},"0.1.14":{"created_at":"2015-02-24T23:45:09.307128Z","published_by":null},"0.1.15":{"created_at":"2015-03-10T09:17:11.214593Z","published_by":null},"0.1.16":{"created_at":"2015-03-29T23:47:41.490845Z","published_by":null},"0.1.17":{"created_at":"2015-04-04T05:59:10.088884Z","published_by":null},"0.1.18":{"created_at":"2015-05-20T22:28:28.796828Z","published_by":null},"0.1.19":{"created_at":"2015-05-27T12:25:18.871513Z","published_by":null},"0.1.20":{"created_at":"2015-05-29T11:23:40.481517Z","published_by":null},"0.1.21":{"created_at":"2015-06-11T22:19:41.422519Z","published_by":null},"0.1.22":{"created_at":"2015-06-12T18:18:11.746369Z","published_by":null},"0.1.23":{"created_at":"2015-06-24T23:05:19.948585Z","published_by":null},"0.2.0":{"created_at":"2015-07-14T00:04:34.385474Z","published_by":null},"0.2.1":{"created_at":"2015-07-17T14:58:45.140838Z","published_by":null},"0.2.2":{"created_at":"2015-08-13T04:02:52.983927Z","published_by":null},"0.2.3":{"created_at":"2015-09-10T09:07:42.506011Z","published_by":null},"0.2.4":{"created_at":"2015-09-17T18:15:31.345657Z","published_by":null},"0.2.5":{"created_at":"2015-11-09T21:19:45.329541Z","published_by":null},"0.2.6":{"created_at":"2016-03-15T01:19:39.733785Z","published_by":null},"0.2.7":{"created_at":"2016-05-10T13:35:35.695458Z","published_by":null},"0.2.8":{"created_at":"2016-07-12T02:45:28.933499Z","published_by":null},"0.3.0":{"created_at":"2017-12-18T06:59:31.560006Z","published_by":null},"0.3.1":{"created_at":"2017-12-18T07:03:22.382928Z","published_by":null},"0.3.2":{"created_at":"2017-12-18T07:39:21.110702Z","published_by":null},"0.3.3":{"created_at":"2018-01-03T17:04:22.217115Z","published_by":null},"0.3.4":{"created_at":"2018-01-18T00:58:14.375896Z","published_by":null},"0.3.5":{"created_at":"2018-06-04T18:39:29.632848Z","published_by":null},"0.3.6":{"created_at":"2018-09-21T10:44:31.494407Z","published_by":null},"0.3.7":{"created_at":"2019-04-03T11:41:54.912809Z","published_by":63},"0.3.8":{"created_at":"2019-08-28T04:25:17.721648Z","published_by":63},"0.3.9":{"created_at":"2020-06-26T11:34:33.480855Z","published_by":63}},"metadata":{"description":"Raw FFI bindings for all of Windows API.","repository":"https://github.com/retep998/winapi-rs"}},"winapi-i686-pc-windows-gnu":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.3.0":{"created_at":"2016-11-23T19:48:14.544324Z","published_by":null},"0.3.1":{"created_at":"2017-03-14T20:14:09.937758Z","published_by":null},"0.3.2":{"created_at":"2017-12-28T06:37:42.925434Z","published_by":null},"0.4.0":{"created_at":"2018-01-18T00:55:38.910834Z","published_by":null}},"metadata":{"description":"Import libraries for the i686-pc-windows-gnu target. Please don't use this crate directly, depend on winapi instead.","repository":"https://github.com/retep998/winapi-rs"}},"winapi-util":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2018-08-25T02:49:16.521473Z","published_by":null},"0.1.1":{"created_at":"2018-08-25T03:10:28.666934Z","published_by":null},"0.1.2":{"created_at":"2019-01-27T17:06:35.145811Z","published_by":null},"0.1.3":{"created_at":"2020-01-11T14:09:50.005548Z","published_by":189},"0.1.4":{"created_at":"2020-03-30T21:30:30.697937Z","published_by":189},"0.1.5":{"created_at":"2020-04-20T14:33:22.216392Z","published_by":189}},"metadata":{"description":"A dumping ground for high level safe wrappers over winapi.","repository":"https://github.com/BurntSushi/winapi-util"}},"winapi-x86_64-pc-windows-gnu":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.3.0":{"created_at":"2016-11-23T19:48:47.829720Z","published_by":null},"0.3.1":{"created_at":"2017-03-14T20:13:19.342957Z","published_by":null},"0.3.2":{"created_at":"2017-12-28T06:35:15.954599Z","published_by":null},"0.4.0":{"created_at":"2018-01-18T00:55:12.008307Z","published_by":null}},"metadata":{"description":"Import libraries for the x86_64-pc-windows-gnu target. Please don't use this crate directly, depend on winapi instead.","repository":"https://github.com/retep998/winapi-rs"}},"winreg":{"last_fetched":"2023-01-01T12:00:00Z","versions":{"0.1.0":{"created_at":"2015-04-17T22:26:16.708718Z","published_by":null},"0.1.1":{"created_at":"2015-04-18T21:12:37.681036Z","published_by":null},"0.2.0":{"created_at":"2015-04-28T20:22:18.911854Z","published_by":null},"0.2.5":{"created_at":"2015-05-06T20:03:38.334110Z","published_by":null},"0.2.6":{"created_at":"2015-05-27T14:22:21.153134Z","published_by":null},"0.3.0":{"created_at":"2015-07-11T11:53:19.676583Z","published_by":null},"0.3.1":{"created_at":"2015-07-16T18:46:59.763815Z","published_by":null},"0.3.2":{"created_at":"2015-10-06T19:30:51.306959Z","published_by":null},"0.3.3":{"created_at":"2015-11-27T18:31:14.177999Z","published_by":null},"0.3.4":{"created_at":"2016-03-13T17:59:13.664310Z","published_by":null},"0.3.5":{"created_at":"2016-04-24T11:37:49.591486Z","published_by":null},"0.4.0":{"created_at":"2016-10-31T20:51:54.173826Z","published_by":null},"0.5.0":{"created_at":"2017-12-18T14:54:08.871876Z","published_by":null},"0.5.1":{"created_at":"2018-06-01T19:48:14.522375Z","published_by":null},"0.6.0":{"created_at":"2018-11-21T22:10:04.082458Z","published_by":null},"0.6.1":{"created_at":"2019-07-17T18:18:03.961017Z","published_by":530},"0.6.2":{"created_at":"2019-07-31T21:20:54.288182Z","published_by":530},"0.7.0":{"created_at":"2020-02-26T12:09:32.641363Z","published_by":530},"0.8.0":{"created_at":"2020-12-11T19:13:14.596631Z","published_by":530},"0.9.0":{"created_at":"2021-05-22T13:54:42.322610Z","published_by":530},"0.10.0":{"created_at":"2021-09-12T17:43:31.671289Z","published_by":530},"0.10.1":{"created_at":"2021-09-12T18:49:10.302175Z","published_by":530},"0.11.0":{"created_at":"2023-02-17T18:43:21.940231Z","published_by":530},"0.50.0":{"created_at":"2023-04-03T18:55:14.809435Z","published_by":530}},"metadata":{"description":"Rust bindings to MS Windows Registry API","repository":"https://github.com/gentoo90/winreg-rs"}}}}
\ No newline at end of file
diff --git a/tests/cache/diff-cache.toml b/tests/cache/diff-cache.toml
new file mode 100644
index 0000000..0a807d2
--- /dev/null
+++ b/tests/cache/diff-cache.toml
@@ -0,0 +1,576 @@
+version = "2"
+
+[diffs.bumpalo."3.9.1"]
+insertions = 10099
+deletions = 0
+files_changed = 17
+
+[diffs.bytes."1.1.0"]
+insertions = 9207
+deletions = 0
+files_changed = 43
+
+[diffs.cc."1.0.73"]
+insertions = 6554
+deletions = 0
+files_changed = 19
+
+[diffs.cfg-if."0.1.10"]
+insertions = 579
+deletions = 0
+files_changed = 9
+
+[diffs.cfg-if."1.0.0"]
+insertions = 582
+deletions = 0
+files_changed = 9
+
+[diffs.core-foundation."0.9.3"]
+insertions = 3953
+deletions = 0
+files_changed = 26
+
+[diffs.core-foundation-sys."0.8.3"]
+insertions = 1965
+deletions = 0
+files_changed = 26
+
+[diffs.encoding_rs."0.8.31"]
+insertions = 507245
+deletions = 0
+files_changed = 102
+
+[diffs.fastrand."1.7.0"]
+insertions = 1368
+deletions = 0
+files_changed = 10
+
+[diffs.fastrand."1.9.0 -> 1.7.0"]
+insertions = 24
+deletions = 107
+files_changed = 6
+
+[diffs.fnv."1.0.7"]
+insertions = 730
+deletions = 0
+files_changed = 8
+
+[diffs.foreign-types."0.3.2"]
+insertions = 583
+deletions = 0
+files_changed = 6
+
+[diffs.foreign-types-shared."0.1.1"]
+insertions = 302
+deletions = 0
+files_changed = 5
+
+[diffs.form_urlencoded."1.0.1"]
+insertions = 689
+deletions = 0
+files_changed = 5
+
+[diffs.form_urlencoded."1.1.0 -> 1.0.1"]
+insertions = 19
+deletions = 13
+files_changed = 3
+
+[diffs.futures-channel."0.3.21"]
+insertions = 3992
+deletions = 0
+files_changed = 18
+
+[diffs.futures-core."0.3.21"]
+insertions = 1175
+deletions = 0
+files_changed = 14
+
+[diffs.futures-sink."0.3.21"]
+insertions = 544
+deletions = 0
+files_changed = 6
+
+[diffs.futures-task."0.3.21"]
+insertions = 1180
+deletions = 0
+files_changed = 14
+
+[diffs.futures-util."0.3.21"]
+insertions = 25067
+deletions = 0
+files_changed = 185
+
+[diffs.h2."0.3.13"]
+insertions = 25417
+deletions = 0
+files_changed = 63
+
+[diffs.hashbrown."0.11.2"]
+insertions = 14603
+deletions = 0
+files_changed = 31
+
+[diffs.hashbrown."0.12.3 -> 0.11.2"]
+insertions = 894
+deletions = 5219
+files_changed = 23
+
+[diffs.hermit-abi."0.1.19"]
+insertions = 932
+deletions = 0
+files_changed = 9
+
+[diffs.http."0.2.6"]
+insertions = 16819
+deletions = 0
+files_changed = 39
+
+[diffs.http-body."0.4.4"]
+insertions = 1256
+deletions = 0
+files_changed = 17
+
+[diffs.httparse."1.7.0"]
+insertions = 7258
+deletions = 0
+files_changed = 18
+
+[diffs.httpdate."1.0.2"]
+insertions = 995
+deletions = 0
+files_changed = 10
+
+[diffs.hyper."0.14.18"]
+insertions = 24841
+deletions = 0
+files_changed = 69
+
+[diffs.hyper-tls."0.5.0"]
+insertions = 673
+deletions = 0
+files_changed = 11
+
+[diffs.idna."0.2.3"]
+insertions = 32532
+deletions = 0
+files_changed = 17
+
+[diffs.idna."0.3.0 -> 0.2.3"]
+insertions = 62
+deletions = 79
+files_changed = 8
+
+[diffs.indexmap."1.8.1"]
+insertions = 9445
+deletions = 0
+files_changed = 31
+
+[diffs.instant."0.1.12"]
+insertions = 672
+deletions = 0
+files_changed = 12
+
+[diffs.ipnet."2.4.0"]
+insertions = 3973
+deletions = 0
+files_changed = 13
+
+[diffs.itoa."1.0.1"]
+insertions = 789
+deletions = 0
+files_changed = 13
+
+[diffs.js-sys."0.3.57"]
+insertions = 12874
+deletions = 0
+files_changed = 62
+
+[diffs.lazy_static."1.4.0"]
+insertions = 876
+deletions = 0
+files_changed = 11
+
+[diffs.libc."0.2.123"]
+insertions = 94060
+deletions = 0
+files_changed = 215
+
+[diffs.log."0.4.16"]
+insertions = 5625
+deletions = 0
+files_changed = 19
+
+[diffs.log."0.4.17 -> 0.4.16"]
+insertions = 19
+deletions = 82
+files_changed = 6
+
+[diffs.matches."0.1.9"]
+insertions = 210
+deletions = 0
+files_changed = 6
+
+[diffs.memchr."2.4.1"]
+insertions = 8712
+deletions = 0
+files_changed = 46
+
+[diffs.mime."0.3.16"]
+insertions = 1715
+deletions = 0
+files_changed = 13
+
+[diffs.mio."0.8.2"]
+insertions = 11719
+deletions = 0
+files_changed = 62
+
+[diffs.miow."0.3.7"]
+insertions = 3129
+deletions = 0
+files_changed = 14
+
+[diffs.native-tls."0.2.10"]
+insertions = 3715
+deletions = 0
+files_changed = 18
+
+[diffs.ntapi."0.3.7"]
+insertions = 21227
+deletions = 0
+files_changed = 42
+
+[diffs.once_cell."1.10.0"]
+insertions = 3766
+deletions = 0
+files_changed = 21
+
+[diffs.openssl."0.10.38"]
+insertions = 28435
+deletions = 0
+files_changed = 82
+
+[diffs.openssl-probe."0.1.5"]
+insertions = 442
+deletions = 0
+files_changed = 9
+
+[diffs.openssl-sys."0.9.72"]
+insertions = 9477
+deletions = 0
+files_changed = 46
+
+[diffs.os_str_bytes."6.0.0"]
+insertions = 2911
+deletions = 0
+files_changed = 21
+
+[diffs.percent-encoding."2.1.0"]
+insertions = 702
+deletions = 0
+files_changed = 5
+
+[diffs.percent-encoding."2.2.0 -> 2.1.0"]
+insertions = 40
+deletions = 69
+files_changed = 4
+
+[diffs.pin-project-lite."0.2.8"]
+insertions = 6100
+deletions = 0
+files_changed = 70
+
+[diffs.pin-utils."0.1.0"]
+insertions = 538
+deletions = 0
+files_changed = 13
+
+[diffs.pkg-config."0.3.25"]
+insertions = 1747
+deletions = 0
+files_changed = 14
+
+[diffs.proc-macro2."1.0.37"]
+insertions = 5695
+deletions = 0
+files_changed = 20
+
+[diffs.proc-macro2."1.0.37@git:4445659b0f753a928059244c875a58bb12f791e9"]
+insertions = 5691
+deletions = 0
+files_changed = 19
+
+[diffs.proc-macro2."1.0.37 -> 1.0.37@git:4445659b0f753a928059244c875a58bb12f791e9"]
+insertions = 63
+deletions = 7
+files_changed = 3
+
+[diffs.proc-macro2."1.0.39 -> 1.0.37"]
+insertions = 16
+deletions = 71
+files_changed = 14
+
+[diffs.proc-macro2."1.0.39 -> 1.0.37@git:4445659b0f753a928059244c875a58bb12f791e9"]
+insertions = 7
+deletions = 6
+files_changed = 12
+
+[diffs.quote."1.0.18"]
+insertions = 3838
+deletions = 0
+files_changed = 33
+
+[diffs.redox_syscall."0.2.13"]
+insertions = 3890
+deletions = 0
+files_changed = 30
+
+[diffs.remove_dir_all."0.5.3"]
+insertions = 592
+deletions = 0
+files_changed = 7
+
+[diffs.reqwest."0.11.10"]
+insertions = 19797
+deletions = 0
+files_changed = 60
+
+[diffs.ryu."1.0.9"]
+insertions = 4362
+deletions = 0
+files_changed = 34
+
+[diffs.schannel."0.1.19"]
+insertions = 4601
+deletions = 0
+files_changed = 32
+
+[diffs.security-framework."2.6.1"]
+insertions = 9316
+deletions = 0
+files_changed = 42
+
+[diffs.security-framework-sys."2.6.1"]
+insertions = 2016
+deletions = 0
+files_changed = 27
+
+[diffs.serde."1.0.136"]
+insertions = 16141
+deletions = 0
+files_changed = 27
+
+[diffs.serde_json."1.0.79"]
+insertions = 22848
+deletions = 0
+files_changed = 86
+
+[diffs.serde_urlencoded."0.7.1"]
+insertions = 2120
+deletions = 0
+files_changed = 17
+
+[diffs.slab."0.4.6"]
+insertions = 2615
+deletions = 0
+files_changed = 9
+
+[diffs.socket2."0.4.4"]
+insertions = 6011
+deletions = 0
+files_changed = 11
+
+[diffs.syn."1.0.0 -> 1.0.91"]
+insertions = 21772
+deletions = 6698
+files_changed = 92
+
+[diffs.tempfile."3.3.0"]
+insertions = 4059
+deletions = 0
+files_changed = 22
+
+[diffs.termcolor."1.1.3"]
+insertions = 2578
+deletions = 0
+files_changed = 10
+
+[diffs.textwrap."0.15.0"]
+insertions = 5196
+deletions = 0
+files_changed = 15
+
+[diffs.tinyvec."1.5.1"]
+insertions = 16751
+deletions = 0
+files_changed = 25
+
+[diffs.tinyvec."1.6.0 -> 1.5.1"]
+insertions = 12
+deletions = 304
+files_changed = 6
+
+[diffs.tinyvec_macros."0.1.0"]
+insertions = 81
+deletions = 0
+files_changed = 5
+
+[diffs.tokio."1.17.0"]
+insertions = 91271
+deletions = 0
+files_changed = 403
+
+[diffs.tokio-native-tls."0.3.0"]
+insertions = 1089
+deletions = 0
+files_changed = 16
+
+[diffs.tokio-util."0.7.1"]
+insertions = 13870
+deletions = 0
+files_changed = 65
+
+[diffs.tower-service."0.3.1"]
+insertions = 501
+deletions = 0
+files_changed = 6
+
+[diffs.tracing."0.1.33"]
+insertions = 10950
+deletions = 0
+files_changed = 32
+
+[diffs.tracing-attributes."0.1.20"]
+insertions = 3957
+deletions = 0
+files_changed = 18
+
+[diffs.tracing-core."0.1.25"]
+insertions = 6156
+deletions = 0
+files_changed = 26
+
+[diffs.try-lock."0.2.3"]
+insertions = 380
+deletions = 0
+files_changed = 6
+
+[diffs.unicode-bidi."0.2.0"]
+insertions = 595896
+deletions = 0
+files_changed = 13
+
+[diffs.unicode-bidi."0.3.7"]
+insertions = 3217
+deletions = 0
+files_changed = 20
+
+[diffs.unicode-bidi."0.3.8 -> 0.2.0"]
+insertions = 595493
+deletions = 3137
+files_changed = 23
+
+[diffs.unicode-bidi."0.3.8 -> 0.3.7"]
+insertions = 526
+deletions = 849
+files_changed = 14
+
+[diffs.unicode-normalization."0.1.19"]
+insertions = 28622
+deletions = 0
+files_changed = 24
+
+[diffs.unicode-xid."0.2.2"]
+insertions = 2044
+deletions = 0
+files_changed = 12
+
+[diffs.url."2.2.2"]
+insertions = 16344
+deletions = 0
+files_changed = 15
+
+[diffs.url."2.3.1 -> 2.2.2"]
+insertions = 1509
+deletions = 2748
+files_changed = 13
+
+[diffs.vcpkg."0.2.15"]
+insertions = 42642
+deletions = 0
+files_changed = 832
+
+[diffs.want."0.3.0"]
+insertions = 697
+deletions = 0
+files_changed = 7
+
+[diffs.wasi."0.11.0+wasi-snapshot-preview1"]
+insertions = 3302
+deletions = 0
+files_changed = 15
+
+[diffs.wasm-bindgen."0.2.80"]
+insertions = 20977
+deletions = 0
+files_changed = 242
+
+[diffs.wasm-bindgen-backend."0.2.80"]
+insertions = 3018
+deletions = 0
+files_changed = 10
+
+[diffs.wasm-bindgen-futures."0.4.30"]
+insertions = 1303
+deletions = 0
+files_changed = 12
+
+[diffs.wasm-bindgen-macro."0.2.80"]
+insertions = 1274
+deletions = 0
+files_changed = 45
+
+[diffs.wasm-bindgen-macro-support."0.2.80"]
+insertions = 1965
+deletions = 0
+files_changed = 6
+
+[diffs.wasm-bindgen-shared."0.2.80"]
+insertions = 515
+deletions = 0
+files_changed = 7
+
+[diffs.wasm-bindgen-shared."0.2.83 -> 0.2.80"]
+insertions = 7
+deletions = 10
+files_changed = 4
+
+[diffs.web-sys."0.3.57"]
+insertions = 197013
+deletions = 0
+files_changed = 2202
+
+[diffs.winapi."0.3.9"]
+insertions = 181323
+deletions = 0
+files_changed = 410
+
+[diffs.winapi-i686-pc-windows-gnu."0.4.0"]
+insertions = 1132
+deletions = 0
+files_changed = 1391
+
+[diffs.winapi-util."0.1.5"]
+insertions = 1095
+deletions = 0
+files_changed = 13
+
+[diffs.winapi-x86_64-pc-windows-gnu."0.4.0"]
+insertions = 1167
+deletions = 0
+files_changed = 1420
+
+[diffs.winreg."0.10.1"]
+insertions = 3920
+deletions = 0
+files_changed = 26
diff --git a/tests/snapshots/test_cli__long-help.snap b/tests/snapshots/test_cli__long-help.snap
new file mode 100644
index 0000000..b87cb4b
--- /dev/null
+++ b/tests/snapshots/test_cli__long-help.snap
@@ -0,0 +1,167 @@
+---
+source: tests/test-cli.rs
+expression: format_outputs(&output)
+---
+stdout:
+cargo-vet 0.8.0
+Supply-chain security for Rust
+
+When run without a subcommand, `cargo vet` will invoke the `check` subcommand. See `cargo vet help
+check` for more details.
+
+USAGE:
+    cargo vet [OPTIONS]
+    cargo vet <SUBCOMMAND>
+
+OPTIONS:
+    -h, --help
+            Print help information
+
+    -V, --version
+            Print version information
+
+GLOBAL OPTIONS:
+        --manifest-path <PATH>
+            Path to Cargo.toml
+
+        --store-path <STORE_PATH>
+            Path to the supply-chain directory
+
+        --no-all-features
+            Don't use --all-features
+            
+            We default to passing --all-features to `cargo metadata` because we want to analyze your
+            full dependency tree
+
+        --no-default-features
+            Do not activate the `default` feature
+
+        --features <FEATURES>
+            Space-separated list of features to activate
+
+        --locked
+            Do not fetch new imported audits
+
+        --frozen
+            Avoid the network entirely, requiring either that the cargo cache is populated or the
+            dependencies are vendored. Requires --locked
+
+        --no-minimize-exemptions
+            Prevent commands such as `check` and `certify` from automatically cleaning up unused
+            exemptions
+
+        --no-registry-suggestions
+            Prevent commands such as `check` and `suggest` from suggesting registry imports
+
+        --verbose <VERBOSE>
+            How verbose logging should be (log level)
+            
+            [default: warn]
+            [possible values: off, error, warn, info, debug, trace]
+
+        --output-file <OUTPUT_FILE>
+            Instead of stdout, write output to this file
+
+        --log-file <LOG_FILE>
+            Instead of stderr, write logs to this file (only used after successful CLI parsing)
+
+        --output-format <OUTPUT_FORMAT>
+            The format of the output
+            
+            [default: human]
+            [possible values: human, json]
+
+        --cache-dir <CACHE_DIR>
+            Use the following path instead of the global cache directory
+            
+            The cache stores information such as the summary results used by vet's suggestion
+            machinery, cached results from crates.io APIs, and checkouts of crates from crates.io in
+            some cases. This is generally automatically managed in the system cache directory.
+            
+            This mostly exists for testing vet itself.
+
+        --filter-graph <FILTER_GRAPH>
+            Filter out different parts of the build graph and pretend that's the true graph
+            
+            Example: `--filter-graph="exclude(any(eq(is_dev_only(true)),eq(name(serde_derive))))"`
+            
+            This mostly exists to debug or reduce projects that cargo-vet is mishandling.
+            Combining this with `cargo vet --output-format=json dump-graph` can produce an
+            input that can be added to vet's test suite.
+            
+            
+            The resulting graph is computed as follows:
+            
+            1. First compute the original graph
+            2. Then apply the filters to find the new set of nodes
+            3. Create a new empty graph
+            4. For each workspace member that still exists, recursively add it and its dependencies
+            
+            This means that any non-workspace package that becomes "orphaned" by the filters will
+            be implicitly discarded even if it passes the filters.
+            
+            Possible filters:
+            
+            * `include($query)`: only include packages that match this filter
+            * `exclude($query)`: exclude packages that match this filter
+            
+            
+            Possible queries:
+            
+            * `any($query1, $query2, ...)`: true if any of the listed queries are true
+            * `all($query1, $query2, ...)`: true if all of the listed queries are true
+            * `not($query)`: true if the query is false
+            * `$property`: true if the package has this property
+            
+            
+            Possible properties:
+            
+            * `name($string)`: the package's name (i.e. `serde`)
+            * `version($version)`: the package's version (i.e. `1.2.0`)
+            * `is_root($bool)`: whether it's a root in the original graph (ignoring dev-deps)
+            * `is_workspace_member($bool)`: whether the package is a workspace-member (can be
+            tested)
+            * `is_third_party($bool)`: whether the package is considered third-party by vet
+            * `is_dev_only($bool)`: whether it's only used by dev (test) builds in the original
+            graph
+
+SUBCOMMANDS:
+    check
+            \[default\] Check that the current project has been vetted
+    suggest
+            Suggest some low-hanging fruit to review
+    init
+            Initialize cargo-vet for your project
+    inspect
+            Fetch the source of a package
+    diff
+            Yield a diff against the last reviewed version
+    certify
+            Mark a package as audited
+    import
+            Import a new peer's imports
+    trust
+            Trust a given crate and publisher
+    regenerate
+            Explicitly regenerate various pieces of information
+    add-exemption
+            Mark a package as exempted from review
+    record-violation
+            Declare that some versions of a package violate certain audit criteria
+    fmt
+            Reformat all of vet's files (in case you hand-edited them)
+    prune
+            Prune unnecessary imports and exemptions
+    aggregate
+            Fetch and merge audits from multiple sources into a single `audits.toml` file
+    dump-graph
+            Print the cargo build graph as understood by `cargo vet`
+    gc
+            Clean up old packages from the vet cache
+    renew
+            Renew wildcard audit expirations
+    help
+            Print this message or the help of the given subcommand(s)
+
+stderr:
+
diff --git a/tests/snapshots/test_cli__markdown-help.snap b/tests/snapshots/test_cli__markdown-help.snap
new file mode 100644
index 0000000..f956fc0
--- /dev/null
+++ b/tests/snapshots/test_cli__markdown-help.snap
@@ -0,0 +1,905 @@
+---
+source: tests/test-cli.rs
+expression: format_outputs(&output)
+---
+stdout:
+# cargo vet CLI manual
+
+> This manual can be regenerated with `cargo vet help-markdown`
+
+Version: `cargo-vet 0.8.0`
+
+Supply-chain security for Rust
+
+When run without a subcommand, `cargo vet` will invoke the `check` subcommand. See `cargo vet help
+check` for more details.
+
+### USAGE
+```
+cargo vet [OPTIONS]
+```
+```
+cargo vet <SUBCOMMAND>
+```
+
+### OPTIONS
+#### `-h, --help`
+Print help information
+
+#### `-V, --version`
+Print version information
+
+### GLOBAL OPTIONS
+#### `--manifest-path <PATH>`
+Path to Cargo.toml
+
+#### `--store-path <STORE_PATH>`
+Path to the supply-chain directory
+
+#### `--no-all-features`
+Don't use --all-features
+
+We default to passing --all-features to `cargo metadata` because we want to analyze your
+full dependency tree
+
+#### `--no-default-features`
+Do not activate the `default` feature
+
+#### `--features <FEATURES>`
+Space-separated list of features to activate
+
+#### `--locked`
+Do not fetch new imported audits
+
+#### `--frozen`
+Avoid the network entirely, requiring either that the cargo cache is populated or the
+dependencies are vendored. Requires --locked
+
+#### `--no-minimize-exemptions`
+Prevent commands such as `check` and `certify` from automatically cleaning up unused
+exemptions
+
+#### `--no-registry-suggestions`
+Prevent commands such as `check` and `suggest` from suggesting registry imports
+
+#### `--verbose <VERBOSE>`
+How verbose logging should be (log level)
+
+\[default: warn]  
+\[possible values: off, error, warn, info, debug, trace]  
+
+#### `--output-file <OUTPUT_FILE>`
+Instead of stdout, write output to this file
+
+#### `--log-file <LOG_FILE>`
+Instead of stderr, write logs to this file (only used after successful CLI parsing)
+
+#### `--output-format <OUTPUT_FORMAT>`
+The format of the output
+
+\[default: human]  
+\[possible values: human, json]  
+
+#### `--cache-dir <CACHE_DIR>`
+Use the following path instead of the global cache directory
+
+The cache stores information such as the summary results used by vet's suggestion
+machinery, cached results from crates.io APIs, and checkouts of crates from crates.io in
+some cases. This is generally automatically managed in the system cache directory.
+
+This mostly exists for testing vet itself.
+
+#### `--filter-graph <FILTER_GRAPH>`
+Filter out different parts of the build graph and pretend that's the true graph
+
+Example: `--filter-graph="exclude(any(eq(is_dev_only(true)),eq(name(serde_derive))))"`
+
+This mostly exists to debug or reduce projects that cargo-vet is mishandling.
+Combining this with `cargo vet --output-format=json dump-graph` can produce an
+input that can be added to vet's test suite.
+
+
+The resulting graph is computed as follows:
+
+1. First compute the original graph
+2. Then apply the filters to find the new set of nodes
+3. Create a new empty graph
+4. For each workspace member that still exists, recursively add it and its dependencies
+
+This means that any non-workspace package that becomes "orphaned" by the filters will
+be implicitly discarded even if it passes the filters.
+
+Possible filters:
+
+* `include($query)`: only include packages that match this filter
+* `exclude($query)`: exclude packages that match this filter
+
+
+Possible queries:
+
+* `any($query1, $query2, ...)`: true if any of the listed queries are true
+* `all($query1, $query2, ...)`: true if all of the listed queries are true
+* `not($query)`: true if the query is false
+* `$property`: true if the package has this property
+
+
+Possible properties:
+
+* `name($string)`: the package's name (i.e. `serde`)
+* `version($version)`: the package's version (i.e. `1.2.0`)
+* `is_root($bool)`: whether it's a root in the original graph (ignoring dev-deps)
+* `is_workspace_member($bool)`: whether the package is a workspace-member (can be
+tested)
+* `is_third_party($bool)`: whether the package is considered third-party by vet
+* `is_dev_only($bool)`: whether it's only used by dev (test) builds in the original
+graph
+
+### SUBCOMMANDS
+* [check](#cargo-vet-check): \[default\] Check that the current project has been vetted
+* [suggest](#cargo-vet-suggest): Suggest some low-hanging fruit to review
+* [init](#cargo-vet-init): Initialize cargo-vet for your project
+* [inspect](#cargo-vet-inspect): Fetch the source of a package
+* [diff](#cargo-vet-diff): Yield a diff against the last reviewed version
+* [certify](#cargo-vet-certify): Mark a package as audited
+* [import](#cargo-vet-import): Import a new peer's imports
+* [trust](#cargo-vet-trust): Trust a given crate and publisher
+* [regenerate](#cargo-vet-regenerate): Explicitly regenerate various pieces of information
+* [add-exemption](#cargo-vet-add-exemption): Mark a package as exempted from review
+* [record-violation](#cargo-vet-record-violation): Declare that some versions of a package violate certain audit criteria
+* [fmt](#cargo-vet-fmt): Reformat all of vet's files (in case you hand-edited them)
+* [prune](#cargo-vet-prune): Prune unnecessary imports and exemptions
+* [aggregate](#cargo-vet-aggregate): Fetch and merge audits from multiple sources into a single `audits.toml` file
+* [dump-graph](#cargo-vet-dump-graph): Print the cargo build graph as understood by `cargo vet`
+* [gc](#cargo-vet-gc): Clean up old packages from the vet cache
+* [renew](#cargo-vet-renew): Renew wildcard audit expirations
+* [help](#cargo-vet-help): Print this message or the help of the given subcommand(s)
+
+<br><br><br>
+## cargo vet check
+\[default\] Check that the current project has been vetted
+
+This is the default behaviour if no subcommand is specified.
+
+If the check fails due to lack of audits, we will do our best to explain why vetting failed, and
+what should be done to fix it. This can involve a certain amount of guesswork, as there are many
+possible solutions and we only want to recommend the "best" one to keep things simple.
+
+Failures and suggestions can either be "Certain" or "Speculative". Speculative items are greyed
+out and sorted lower to indicate that the Certain entries should be looked at first. Speculative
+items are for packages that probably need audits too, but only appear as transitive dependencies of
+Certain items.
+
+During review of Certain issues you may take various actions that change what's needed for the
+Speculative ones. For instance you may discover you're enabling a feature you don't need, and
+that's the only reason the Speculative package is in your tree. Or you may determine that the
+Certain package only needs to be safe-to-run, which may make the Speculative requirements weaker or
+completely resolved. For these reasons we recommend fixing problems "top down", and Certain items
+are The Top.
+
+Suggested fixes are grouped by the criteria they should be reviewed for and sorted by how easy the
+review should be (in terms of lines of code). We only ever suggest audits (and provide the command
+you need to run to do it), but there are other possible fixes like an `exemption` or `policy`
+change.
+
+The most aggressive solution is to run `cargo vet regenerate exemptions` which will add whatever
+exemptions necessary to make `check` pass (and remove uneeded ones). Ideally you should avoid doing
+this and prefer adding audits, but if you've done all the audits you plan on doing, that's the way
+to finish the job.
+
+### USAGE
+```
+cargo vet check [OPTIONS]
+```
+
+### OPTIONS
+#### `-h, --help`
+Print help information
+
+### GLOBAL OPTIONS
+This subcommand accepts all the [global options](#global-options)
+
+<br><br><br>
+## cargo vet suggest
+Suggest some low-hanging fruit to review
+
+This is essentially the same as `check` but with all your `exemptions` temporarily removed as a way
+to inspect your "review backlog". As such, we recommend against running this command while `check`
+is failing, because this will just give you worse information.
+
+If you don't consider an exemption to be "backlog", add `suggest = false` to its entry and we won't
+remove it while suggesting.
+
+See also `regenerate exemptions`, which can be used to "garbage collect" your backlog (if you run it
+while `check` is passing).
+
+### USAGE
+```
+cargo vet suggest [OPTIONS]
+```
+
+### OPTIONS
+#### `-h, --help`
+Print help information
+
+### GLOBAL OPTIONS
+This subcommand accepts all the [global options](#global-options)
+
+<br><br><br>
+## cargo vet init
+Initialize cargo-vet for your project
+
+This will add `exemptions` and `audit-as-crates-io = false` for all packages that need it to make
+`check` pass immediately and make it easy to start using vet with your project.
+
+At this point you can either configure your project further or start working on your review backlog
+with `suggest`.
+
+### USAGE
+```
+cargo vet init [OPTIONS]
+```
+
+### OPTIONS
+#### `-h, --help`
+Print help information
+
+### GLOBAL OPTIONS
+This subcommand accepts all the [global options](#global-options)
+
+<br><br><br>
+## cargo vet inspect
+Fetch the source of a package
+
+We will attempt to guess what criteria you want to audit the package for based on the current check/
+suggest status, and show you the meaning of those criteria ahead of time.
+
+### USAGE
+```
+cargo vet inspect [OPTIONS] <PACKAGE> <VERSION>
+```
+
+### ARGS
+#### `<PACKAGE>`
+The package to inspect
+
+#### `<VERSION>`
+The version to inspect
+
+### OPTIONS
+#### `--mode <MODE>`
+How to inspect the source
+
+\[default: sourcegraph]  
+\[possible values: local, sourcegraph]  
+
+#### `-h, --help`
+Print help information
+
+### GLOBAL OPTIONS
+This subcommand accepts all the [global options](#global-options)
+
+<br><br><br>
+## cargo vet diff
+Yield a diff against the last reviewed version
+
+We will attempt to guess what criteria you want to audit the package for based on the current check/
+suggest status, and show you the meaning of those criteria ahead of time.
+
+### USAGE
+```
+cargo vet diff [OPTIONS] <PACKAGE> <VERSION1> <VERSION2>
+```
+
+### ARGS
+#### `<PACKAGE>`
+The package to diff
+
+#### `<VERSION1>`
+The base version to diff
+
+#### `<VERSION2>`
+The target version to diff
+
+### OPTIONS
+#### `--mode <MODE>`
+How to inspect the source
+
+\[default: sourcegraph]  
+\[possible values: local, sourcegraph]  
+
+#### `-h, --help`
+Print help information
+
+### GLOBAL OPTIONS
+This subcommand accepts all the [global options](#global-options)
+
+<br><br><br>
+## cargo vet certify
+Mark a package as audited
+
+This command will do its best to guess what you want to be certifying.
+
+If invoked with no args, it will try to certify the last thing you looked at with `inspect` or
+`diff`. Otherwise you must either supply the package name and one version (for a full audit) or two
+versions (for a delta audit).
+
+Once the package+version(s) have been selected, we will try to guess what criteria to certify it
+for. First we will `check`, and if the check fails and your audit would seemingly fix this package,
+we will use the criteria recommended for that fix. If `check` passes, we will assume you are working
+on your backlog and instead use the recommendations of `suggest`.
+
+If this removes the need for an `exemption` will we automatically remove it.
+
+### USAGE
+```
+cargo vet certify [OPTIONS] [ARGS]
+```
+
+### ARGS
+#### `<PACKAGE>`
+The package to certify as audited
+
+#### `<VERSION1>`
+The version to certify as audited
+
+#### `<VERSION2>`
+If present, instead certify a diff from version1->version2
+
+### OPTIONS
+#### `--wildcard <WILDCARD>`
+If present, certify a wildcard audit for the user with the given username.
+
+Use the --start-date and --end-date options to specify the date range to certify for.
+
+#### `--criteria <CRITERIA>`
+The criteria to certify for this audit
+
+If not provided, we will prompt you for this information.
+
+#### `--who <WHO>`
+Who to name as the auditor
+
+If not provided, we will collect this information from the local git.
+
+#### `--notes <NOTES>`
+A free-form string to include with the new audit entry
+
+If not provided, there will be no notes.
+
+#### `--start-date <START_DATE>`
+Start date to create a wildcard audit from.
+
+Only valid with `--wildcard`.
+
+If not provided, will be the publication date of the first version published by the
+given user.
+
+#### `--end-date <END_DATE>`
+End date to create a wildcard audit from. May be at most 1 year in the future.
+
+Only valid with `--wildcard`.
+
+If not provided, will be 1 year from the current date.
+
+#### `--accept-all`
+Accept all criteria without an interactive prompt
+
+#### `--force`
+Force the command to ignore whether the package/version makes sense
+
+To catch typos/mistakes, we check if the thing you're trying to talk about is part of
+your current build, but this flag disables that.
+
+#### `-h, --help`
+Print help information
+
+### GLOBAL OPTIONS
+This subcommand accepts all the [global options](#global-options)
+
+<br><br><br>
+## cargo vet import
+Import a new peer's imports
+
+If invoked without a URL parameter, it will look up the named peer in the cargo-vet registry, and
+import that peer.
+
+### USAGE
+```
+cargo vet import [OPTIONS] <NAME> [URL]...
+```
+
+### ARGS
+#### `<NAME>`
+The name of the peer to import
+
+#### `<URL>...`
+The URL(s) of the peer's audits.toml file(s).
+
+If a URL is not provided, a peer with the given name will be looked up in the cargo-vet
+registry to determine the import URL(s).
+
+### OPTIONS
+#### `-h, --help`
+Print help information
+
+### GLOBAL OPTIONS
+This subcommand accepts all the [global options](#global-options)
+
+<br><br><br>
+## cargo vet trust
+Trust a given crate and publisher
+
+### USAGE
+```
+cargo vet trust [OPTIONS] [ARGS]
+```
+
+### ARGS
+#### `<PACKAGE>`
+The package to trust
+
+Must be specified unless --all has been specified.
+
+#### `<PUBLISHER_LOGIN>`
+The username of the publisher to trust
+
+If not provided, will be inferred to be the sole known publisher of the given crate.
+If there is more than one publisher for the given crate, the login must be provided
+explicitly.
+
+### OPTIONS
+#### `--criteria <CRITERIA>`
+The criteria to certify for this trust entry
+
+If not provided, we will prompt you for this information.
+
+#### `--start-date <START_DATE>`
+Start date to create the trust entry from.
+
+If not provided, will be the publication date of the first version published by the
+given user.
+
+#### `--end-date <END_DATE>`
+End date to create the trust entry from. May be at most 1 year in the future.
+
+If not provided, will be 1 year from the current date.
+
+#### `--notes <NOTES>`
+A free-form string to include with the new audit entry
+
+If not provided, there will be no notes.
+
+#### `--all <ALL>`
+If specified, trusts all packages with exemptions or failures which are solely published
+by the given user
+
+#### `--allow-multiple-publishers`
+If specified along with --all, also trusts packages with multiple publishers, so long as
+at least one version was published by the given user
+
+#### `-h, --help`
+Print help information
+
+### GLOBAL OPTIONS
+This subcommand accepts all the [global options](#global-options)
+
+<br><br><br>
+## cargo vet regenerate
+Explicitly regenerate various pieces of information
+
+There are several things that `cargo vet` *can* do for you automatically but we choose to make
+manual just to keep a human in the loop of those decisions. Some of these might one day become
+automatic if we agree they're boring/reliable enough.
+
+See the subcommands for specifics.
+
+### USAGE
+```
+cargo vet regenerate [OPTIONS] <SUBCOMMAND>
+```
+
+### OPTIONS
+#### `-h, --help`
+Print help information
+
+### GLOBAL OPTIONS
+This subcommand accepts all the [global options](#global-options)
+### SUBCOMMANDS
+* [exemptions](#cargo-vet-exemptions): Regenerate your exemptions to make `check` pass minimally
+* [imports](#cargo-vet-imports): Regenerate your imports and accept changes to criteria
+* [audit-as-crates-io](#cargo-vet-audit-as-crates-io): Add `audit-as-crates-io` to the policy entry for all crates which require one
+* [unpublished](#cargo-vet-unpublished): Remove all outdated `unpublished` entries for crates which have since been published, or
+should now be audited as a more-recent version
+* [help](#cargo-vet-help): Print this message or the help of the given subcommand(s)
+
+<br><br><br>
+## cargo vet exemptions
+Regenerate your exemptions to make `check` pass minimally
+
+This command can be used for two purposes: to force your supply-chain to pass `check` when it's
+currently failing, or to minimize/garbage-collect your exemptions when it's already passing. These
+are ultimately the same operation.
+
+We will try our best to preserve existing exemptions, removing only those that aren't needed,
+and adding only those that are needed. Exemptions that are overbroad may also be weakened (i.e.
+safe-to-deploy may be reduced to safe-to-run).
+
+### USAGE
+```
+cargo vet regenerate exemptions [OPTIONS]
+```
+
+### OPTIONS
+#### `-h, --help`
+Print help information
+
+### GLOBAL OPTIONS
+This subcommand accepts all the [global options](#global-options)
+
+<br><br><br>
+## cargo vet imports
+Regenerate your imports and accept changes to criteria
+
+This is equivalent to `cargo vet fetch-imports` but it won't produce an error if the descriptions of
+foreign criteria change.
+
+### USAGE
+```
+cargo vet regenerate imports [OPTIONS]
+```
+
+### OPTIONS
+#### `-h, --help`
+Print help information
+
+### GLOBAL OPTIONS
+This subcommand accepts all the [global options](#global-options)
+
+<br><br><br>
+## cargo vet audit-as-crates-io
+Add `audit-as-crates-io` to the policy entry for all crates which require one.
+
+Crates which have a matching `description` and `repository` entry to a published crate on crates.io
+will be marked as `audit-as-crates-io = true`.
+
+### USAGE
+```
+cargo vet regenerate audit-as-crates-io [OPTIONS]
+```
+
+### OPTIONS
+#### `-h, --help`
+Print help information
+
+### GLOBAL OPTIONS
+This subcommand accepts all the [global options](#global-options)
+
+<br><br><br>
+## cargo vet unpublished
+Remove all outdated `unpublished` entries for crates which have since been published, or should now
+be audited as a more-recent version.
+
+Unlike `cargo vet prune`, this will remove outdated `unpublished` entries even if it will cause
+`check` to start failing.
+
+### USAGE
+```
+cargo vet regenerate unpublished [OPTIONS]
+```
+
+### OPTIONS
+#### `-h, --help`
+Print help information
+
+### GLOBAL OPTIONS
+This subcommand accepts all the [global options](#global-options)
+
+<br><br><br>
+## cargo vet help
+Print this message or the help of the given subcommand(s)
+
+### USAGE
+```
+cargo vet regenerate help [OPTIONS] [SUBCOMMAND]...
+```
+
+### ARGS
+#### `<SUBCOMMAND>...`
+The subcommand whose help message to display
+
+### GLOBAL OPTIONS
+This subcommand accepts all the [global options](#global-options)
+
+<br><br><br>
+## cargo vet add-exemption
+Mark a package as exempted from review
+
+Exemptions are *usually* just "backlog" and the expectation is that you will review them
+"eventually". You should usually only be trying to remove them, but sometimes additions are
+necessary to make progress.
+
+`regenerate exemptions` will do this for your automatically to make `check` pass (and remove any
+unnecessary ones), so we recommend using that over `add-exemption`. This command mostly exists as
+"plumbing" for building tools on top of `cargo vet`.
+
+### USAGE
+```
+cargo vet add-exemption [OPTIONS] <PACKAGE> <VERSION>
+```
+
+### ARGS
+#### `<PACKAGE>`
+The package to mark as exempted
+
+#### `<VERSION>`
+The version to mark as exempted
+
+### OPTIONS
+#### `--criteria <CRITERIA>`
+The criteria to assume (trust)
+
+If not provided, we will prompt you for this information.
+
+#### `--notes <NOTES>`
+A free-form string to include with the new forbid entry
+
+If not provided, there will be no notes.
+
+#### `--no-suggest`
+Suppress suggesting this exemption for review
+
+#### `--force`
+Force the command to ignore whether the package/version makes sense
+
+To catch typos/mistakes, we check if the thing you're trying to talk about is part of
+your current build, but this flag disables that.
+
+#### `-h, --help`
+Print help information
+
+### GLOBAL OPTIONS
+This subcommand accepts all the [global options](#global-options)
+
+<br><br><br>
+## cargo vet record-violation
+Declare that some versions of a package violate certain audit criteria
+
+**IMPORTANT**: violations take *VersionReqs* not *Versions*. This is the same syntax used by
+Cargo.toml when specifying dependencies. A bare `1.0.0` actually means `^1.0.0`. If you want to
+forbid a *specific* version, use `=1.0.0`. This command can be a bit awkward because syntax like `*`
+has special meaning in scripts and terminals. It's probably easier to just manually add the entry to
+your audits.toml, but the command's here in case you want it.
+
+Violations are essentially treated as integrity constraints on your supply-chain, and will only
+result in errors if you have `exemptions` or `audits` (including imported ones) that claim criteria
+that are contradicted by the `violation`. It is not inherently an error to depend on a package with
+a `violation`.
+
+For instance, someone may review a package and determine that it's horribly unsound in the face of
+untrusted inputs, and therefore *un*safe-to-deploy. They would then add a "safe-to-deploy" violation
+for whatever versions of that package seem to have that problem. But if the package basically works
+fine on trusted inputs, it might still be safe-to-run. So if you use it in your tests and have an
+audit that only claims safe-to-run, we won't mention it.
+
+When a violation *does* cause an integrity error, it's up to you and your peers to figure out what
+to do about it. There isn't yet a mechanism for dealing with disagreements with a peer's published
+violations.
+
+### USAGE
+```
+cargo vet record-violation [OPTIONS] <PACKAGE> <VERSIONS>
+```
+
+### ARGS
+#### `<PACKAGE>`
+The package to forbid
+
+#### `<VERSIONS>`
+The versions to forbid
+
+### OPTIONS
+#### `--criteria <CRITERIA>`
+The criteria that have failed to be satisfied.
+
+If not provided, we will prompt you for this information(?)
+
+#### `--who <WHO>`
+Who to name as the auditor
+
+If not provided, we will collect this information from the local git.
+
+#### `--notes <NOTES>`
+A free-form string to include with the new forbid entry
+
+If not provided, there will be no notes.
+
+#### `--force`
+Force the command to ignore whether the package/version makes sense
+
+To catch typos/mistakes, we check if the thing you're trying to talk about is part of
+your current build, but this flag disables that.
+
+#### `-h, --help`
+Print help information
+
+### GLOBAL OPTIONS
+This subcommand accepts all the [global options](#global-options)
+
+<br><br><br>
+## cargo vet fmt
+Reformat all of vet's files (in case you hand-edited them)
+
+Most commands will implicitly do this, so this mostly exists as "plumbing" for building tools on top
+of vet, or in case you don't want to run another command.
+
+### USAGE
+```
+cargo vet fmt [OPTIONS]
+```
+
+### OPTIONS
+#### `-h, --help`
+Print help information
+
+### GLOBAL OPTIONS
+This subcommand accepts all the [global options](#global-options)
+
+<br><br><br>
+## cargo vet prune
+Prune unnecessary imports and exemptions
+
+This will fetch the updated state of imports, and attempt to remove any now-unnecessary imports or
+exemptions from the supply-chain.
+
+### USAGE
+```
+cargo vet prune [OPTIONS]
+```
+
+### OPTIONS
+#### `--no-imports`
+Don't prune unused imports
+
+#### `--no-exemptions`
+Don't prune unused exemptions
+
+#### `-h, --help`
+Print help information
+
+### GLOBAL OPTIONS
+This subcommand accepts all the [global options](#global-options)
+
+<br><br><br>
+## cargo vet aggregate
+Fetch and merge audits from multiple sources into a single `audits.toml` file.
+
+Will fetch the audits from each URL in the provided file, combining them into a single file. Custom
+criteria will be merged by-name, and must have identical descriptions in each source audit file.
+
+### USAGE
+```
+cargo vet aggregate [OPTIONS] <SOURCES>
+```
+
+### ARGS
+#### `<SOURCES>`
+Path to a file containing a list of URLs to aggregate the audits from
+
+### OPTIONS
+#### `-h, --help`
+Print help information
+
+### GLOBAL OPTIONS
+This subcommand accepts all the [global options](#global-options)
+
+<br><br><br>
+## cargo vet dump-graph
+Print the cargo build graph as understood by `cargo vet`
+
+This is a debugging command, the output's format is not guaranteed. Use `cargo metadata` to get a
+stable version of what *cargo* thinks the build graph is. Our graph is based on that result.
+
+With `--output-format=human` (the default) this will print out mermaid-js diagrams, which things
+like github natively support rendering of.
+
+With `--output-format=json` we will print out more raw statistics for you to search/analyze.
+
+Most projects will have unreadably complex build graphs, so you may want to use the global
+`--filter-graph` argument to narrow your focus on an interesting subgraph. `--filter-graph` is
+applied *before* doing any semantic analysis, so if you filter out a package and it was the problem,
+the problem will disappear. This can be used to bisect a problem if you get ambitious enough with
+your filters.
+
+### USAGE
+```
+cargo vet dump-graph [OPTIONS]
+```
+
+### OPTIONS
+#### `--depth <DEPTH>`
+The depth of the graph to print (for a large project, the full graph is a HUGE MESS)
+
+\[default: first-party]  
+\[possible values: roots, workspace, first-party, first-party-and-directs, full]  
+
+#### `-h, --help`
+Print help information
+
+### GLOBAL OPTIONS
+This subcommand accepts all the [global options](#global-options)
+
+<br><br><br>
+## cargo vet gc
+Clean up old packages from the vet cache
+
+Removes packages which haven't been accessed in a while, and deletes any extra files which aren't
+recognized by cargo-vet.
+
+In the future, many cargo-vet subcommands will implicitly do this.
+
+### USAGE
+```
+cargo vet gc [OPTIONS]
+```
+
+### OPTIONS
+#### `--max-package-age-days <MAX_PACKAGE_AGE_DAYS>`
+Packages in the vet cache which haven't been used for this many days will be removed
+
+\[default: 30]  
+
+#### `--clean`
+Remove the entire cache directory, forcing it to be regenerated next time you use cargo
+vet
+
+#### `-h, --help`
+Print help information
+
+### GLOBAL OPTIONS
+This subcommand accepts all the [global options](#global-options)
+
+<br><br><br>
+## cargo vet renew
+Renew wildcard audit expirations
+
+This will set a wildcard audit expiration to be one year in the future from when it is run. It can
+optionally do this for all audits which are expiring soon.
+
+### USAGE
+```
+cargo vet renew [OPTIONS] [CRATE]
+```
+
+### ARGS
+#### `<CRATE>`
+The name of a crate to renew
+
+### OPTIONS
+#### `--expiring`
+Renew all wildcard audits which will have expired six weeks from now
+
+#### `-h, --help`
+Print help information
+
+### GLOBAL OPTIONS
+This subcommand accepts all the [global options](#global-options)
+
+<br><br><br>
+## cargo vet help
+Print this message or the help of the given subcommand(s)
+
+### USAGE
+```
+cargo vet help [OPTIONS] [SUBCOMMAND]...
+```
+
+### ARGS
+#### `<SUBCOMMAND>...`
+The subcommand whose help message to display
+
+### GLOBAL OPTIONS
+This subcommand accepts all the [global options](#global-options)
+
+
+stderr:
+
diff --git a/tests/snapshots/test_cli__short-help.snap b/tests/snapshots/test_cli__short-help.snap
new file mode 100644
index 0000000..ecee0b9
--- /dev/null
+++ b/tests/snapshots/test_cli__short-help.snap
@@ -0,0 +1,88 @@
+---
+source: tests/test-cli.rs
+expression: format_outputs(&output)
+---
+stdout:
+cargo-vet 0.8.0
+Supply-chain security for Rust
+
+USAGE:
+    cargo vet [OPTIONS]
+    cargo vet <SUBCOMMAND>
+
+OPTIONS:
+    -h, --help       Print help information
+    -V, --version    Print version information
+
+GLOBAL OPTIONS:
+        --manifest-path <PATH>
+            Path to Cargo.toml
+
+        --store-path <STORE_PATH>
+            Path to the supply-chain directory
+
+        --no-all-features
+            Don't use --all-features
+
+        --no-default-features
+            Do not activate the `default` feature
+
+        --features <FEATURES>
+            Space-separated list of features to activate
+
+        --locked
+            Do not fetch new imported audits
+
+        --frozen
+            Avoid the network entirely, requiring either that the cargo cache is populated or the
+            dependencies are vendored. Requires --locked
+
+        --no-minimize-exemptions
+            Prevent commands such as `check` and `certify` from automatically cleaning up unused
+            exemptions
+
+        --no-registry-suggestions
+            Prevent commands such as `check` and `suggest` from suggesting registry imports
+
+        --verbose <VERBOSE>
+            How verbose logging should be (log level) [default: warn] [possible values: off, error,
+            warn, info, debug, trace]
+
+        --output-file <OUTPUT_FILE>
+            Instead of stdout, write output to this file
+
+        --log-file <LOG_FILE>
+            Instead of stderr, write logs to this file (only used after successful CLI parsing)
+
+        --output-format <OUTPUT_FORMAT>
+            The format of the output [default: human] [possible values: human, json]
+
+        --cache-dir <CACHE_DIR>
+            Use the following path instead of the global cache directory
+
+        --filter-graph <FILTER_GRAPH>
+            Filter out different parts of the build graph and pretend that's the true graph
+
+SUBCOMMANDS:
+    check               \[default\] Check that the current project has been vetted
+    suggest             Suggest some low-hanging fruit to review
+    init                Initialize cargo-vet for your project
+    inspect             Fetch the source of a package
+    diff                Yield a diff against the last reviewed version
+    certify             Mark a package as audited
+    import              Import a new peer's imports
+    trust               Trust a given crate and publisher
+    regenerate          Explicitly regenerate various pieces of information
+    add-exemption       Mark a package as exempted from review
+    record-violation    Declare that some versions of a package violate certain audit criteria
+    fmt                 Reformat all of vet's files (in case you hand-edited them)
+    prune               Prune unnecessary imports and exemptions
+    aggregate           Fetch and merge audits from multiple sources into a single `audits.toml`
+                            file
+    dump-graph          Print the cargo build graph as understood by `cargo vet`
+    gc                  Clean up old packages from the vet cache
+    renew               Renew wildcard audit expirations
+    help                Print this message or the help of the given subcommand(s)
+
+stderr:
+
diff --git a/tests/snapshots/test_cli__test-project-bad-certify-human.snap b/tests/snapshots/test_cli__test-project-bad-certify-human.snap
new file mode 100644
index 0000000..65efe76
--- /dev/null
+++ b/tests/snapshots/test_cli__test-project-bad-certify-human.snap
@@ -0,0 +1,10 @@
+---
+source: tests/test-cli.rs
+expression: format_outputs(&output)
+---
+stdout:
+
+stderr:
+ERROR   × 'asdfsdfs' isn't one of your foreign packages
+  help: use --force to ignore this error
+
diff --git a/tests/snapshots/test_cli__test-project-bad-certify-json.snap b/tests/snapshots/test_cli__test-project-bad-certify-json.snap
new file mode 100644
index 0000000..36ecc4e
--- /dev/null
+++ b/tests/snapshots/test_cli__test-project-bad-certify-json.snap
@@ -0,0 +1,9 @@
+---
+source: tests/test-cli.rs
+expression: format_outputs(&output)
+---
+stdout:
+{"error": {"message": "'asdfsdfs' isn't one of your foreign packages","severity": "error","causes": [],"help": "use --force to ignore this error","labels": [],"related": []}}
+
+stderr:
+
diff --git a/tests/snapshots/test_cli__test-project-diff-output-git.snap b/tests/snapshots/test_cli__test-project-diff-output-git.snap
new file mode 100644
index 0000000..c26a628
--- /dev/null
+++ b/tests/snapshots/test_cli__test-project-diff-output-git.snap
@@ -0,0 +1,159 @@
+---
+source: tests/test-cli.rs
+expression: format_diff_outputs(&output)
+---
+stdout:
+You are about to diff versions 1.0.37 and 1.0.37@git:4445659b0f753a928059244c875a58bb12f791e9 of 'proc-macro2'
+Other software projects may rely on this audit. Ask for help if you're not sure.
+
+(press ENTER to inspect locally)
+index 1f5da1a..d70c3be 100644
+--- a/ci.yml
++++ b/ci.yml
+@@ -17,7 +17,7 @@ jobs:
+       matrix:
+         rust: [1.31.0, stable, beta]
+     steps:
+-      - uses: actions/checkout@v2
++      - uses: actions/checkout@v3
+       - uses: dtolnay/rust-toolchain@master
+         with:
+           toolchain: ${{matrix.rust}}
+@@ -37,7 +37,7 @@ jobs:
+     name: Rust nightly
+     runs-on: ubuntu-latest
+     steps:
+-      - uses: actions/checkout@v2
++      - uses: actions/checkout@v3
+       - uses: dtolnay/rust-toolchain@nightly
+       - run: cargo test
+       - run: cargo test --no-default-features
+@@ -62,7 +62,7 @@ jobs:
+     name: WebAssembly
+     runs-on: ubuntu-latest
+     steps:
+-      - uses: actions/checkout@v2
++      - uses: actions/checkout@v3
+       - uses: dtolnay/rust-toolchain@nightly
+         with:
+           target: wasm32-unknown-unknown
+@@ -73,16 +73,26 @@ jobs:
+     runs-on: ubuntu-latest
+     if: github.event_name != 'pull_request'
+     steps:
+-      - uses: actions/checkout@v2
++      - uses: actions/checkout@v3
+       - uses: dtolnay/rust-toolchain@clippy
+       - run: cargo clippy --tests -- -Dclippy::all -Dclippy::pedantic
+       - run: cargo clippy --tests --all-features -- -Dclippy::all -Dclippy::pedantic
+ 
++  miri:
++    name: Miri
++    runs-on: ubuntu-latest
++    steps:
++      - uses: actions/checkout@v3
++      - uses: dtolnay/rust-toolchain@miri
++      - run: cargo miri test
++        env:
++          MIRIFLAGS: -Zmiri-tag-raw-pointers
++
+   outdated:
+     name: Outdated
+     runs-on: ubuntu-latest
+     if: github.event_name != 'pull_request'
+     steps:
+-      - uses: actions/checkout@v2
++      - uses: actions/checkout@v3
+       - uses: dtolnay/install@cargo-outdated
+       - run: cargo outdated --exit-code 1
+index be53877..21ede03 100644
+--- a/fallback.rs
++++ b/fallback.rs
+@@ -4,7 +4,7 @@ use crate::{Delimiter, Spacing, TokenTree};
+ use std::cell::RefCell;
+ #[cfg(span_locations)]
+ use std::cmp;
+-use std::fmt::{self, Debug, Display};
++use std::fmt::{self, Debug, Display, Write};
+ use std::iter::FromIterator;
+ use std::mem;
+ use std::ops::RangeBounds;
+@@ -876,7 +876,9 @@ impl Literal {
+                 b'"' => escaped.push_str("\\\""),
+                 b'\\' => escaped.push_str("\\\\"),
+                 b'\x20'..=b'\x7E' => escaped.push(*b as char),
+-                _ => escaped.push_str(&format!("\\x{:02X}", b)),
++                _ => {
++                    let _ = write!(escaped, "\\x{:02X}", b);
++                }
+             }
+         }
+         escaped.push('"');
+index ab82390..a3e0d32 100644
+--- a/test.rs
++++ b/test.rs
+@@ -106,6 +106,15 @@ fn literal_raw_string() {
+     "r\"\r\n\"".parse::<TokenStream>().unwrap();
+ }
+ 
++#[test]
++fn literal_byte_string() {
++    assert_eq!(Literal::byte_string(b"").to_string(), "b\"\"");
++    assert_eq!(
++        Literal::byte_string(b"\0\t\n\r\"\\2\x10").to_string(),
++        "b\"\\0\\t\\n\\r\\\"\\\\2\\x10\"",
++    );
++}
++
+ #[test]
+ fn literal_character() {
+     assert_eq!(Literal::character('x').to_string(), "'x'");
+@@ -113,9 +122,44 @@ fn literal_character() {
+     assert_eq!(Literal::character('"').to_string(), "'\"'");
+ }
+ 
++#[test]
++fn literal_integer() {
++    assert_eq!(Literal::u8_suffixed(10).to_string(), "10u8");
++    assert_eq!(Literal::u16_suffixed(10).to_string(), "10u16");
++    assert_eq!(Literal::u32_suffixed(10).to_string(), "10u32");
++    assert_eq!(Literal::u64_suffixed(10).to_string(), "10u64");
++    assert_eq!(Literal::u128_suffixed(10).to_string(), "10u128");
++    assert_eq!(Literal::usize_suffixed(10).to_string(), "10usize");
++
++    assert_eq!(Literal::i8_suffixed(10).to_string(), "10i8");
++    assert_eq!(Literal::i16_suffixed(10).to_string(), "10i16");
++    assert_eq!(Literal::i32_suffixed(10).to_string(), "10i32");
++    assert_eq!(Literal::i64_suffixed(10).to_string(), "10i64");
++    assert_eq!(Literal::i128_suffixed(10).to_string(), "10i128");
++    assert_eq!(Literal::isize_suffixed(10).to_string(), "10isize");
++
++    assert_eq!(Literal::u8_unsuffixed(10).to_string(), "10");
++    assert_eq!(Literal::u16_unsuffixed(10).to_string(), "10");
++    assert_eq!(Literal::u32_unsuffixed(10).to_string(), "10");
++    assert_eq!(Literal::u64_unsuffixed(10).to_string(), "10");
++    assert_eq!(Literal::u128_unsuffixed(10).to_string(), "10");
++    assert_eq!(Literal::usize_unsuffixed(10).to_string(), "10");
++
++    assert_eq!(Literal::i8_unsuffixed(10).to_string(), "10");
++    assert_eq!(Literal::i16_unsuffixed(10).to_string(), "10");
++    assert_eq!(Literal::i32_unsuffixed(10).to_string(), "10");
++    assert_eq!(Literal::i64_unsuffixed(10).to_string(), "10");
++    assert_eq!(Literal::i128_unsuffixed(10).to_string(), "10");
++    assert_eq!(Literal::isize_unsuffixed(10).to_string(), "10");
++}
++
+ #[test]
+ fn literal_float() {
++    assert_eq!(Literal::f32_suffixed(10.0).to_string(), "10f32");
++    assert_eq!(Literal::f64_suffixed(10.0).to_string(), "10f64");
++
+     assert_eq!(Literal::f32_unsuffixed(10.0).to_string(), "10.0");
++    assert_eq!(Literal::f64_unsuffixed(10.0).to_string(), "10.0");
+ }
+ 
+ #[test]
+
+Use |cargo vet certify| to record your audit.
+stderr:
+ WARN unable to determine likely criteria, this may not be a relevant audit for this project.
diff --git a/tests/snapshots/test_cli__test-project-diff-output.snap b/tests/snapshots/test_cli__test-project-diff-output.snap
new file mode 100644
index 0000000..efbf66c
--- /dev/null
+++ b/tests/snapshots/test_cli__test-project-diff-output.snap
@@ -0,0 +1,220 @@
+---
+source: tests/test-cli.rs
+expression: formatted
+---
+stdout:
+You are about to diff versions 1.0.90 and 1.0.91 of 'syn'
+Other software projects may rely on this audit. Ask for help if you're not sure.
+
+(press ENTER to inspect locally)
+index d6d7b5c..5dda6e6 100644
+--- a/Cargo.toml
++++ b/Cargo.toml
+@@ -13,7 +13,7 @@
+ edition = "2018"
+ rust-version = "1.31"
+ name = "syn"
+-version = "1.0.90"
++version = "1.0.91"
+ authors = ["David Tolnay <dtolnay@gmail.com>"]
+ include = [
+     "/benches/**",
+index 3ae869d..baa1f4e 100644
+--- a/Cargo.toml.orig
++++ b/Cargo.toml.orig
+@@ -1,6 +1,6 @@
+ [package]
+ name = "syn"
+-version = "1.0.90" # don't forget to update html_root_url and syn.json
++version = "1.0.91" # don't forget to update html_root_url and syn.json
+ authors = ["David Tolnay <dtolnay@gmail.com>"]
+ license = "MIT OR Apache-2.0"
+ description = "Parser for Rust source code"
+index b6d0616..276c016 100644
+--- a/expr.rs
++++ b/expr.rs
+@@ -1603,27 +1603,7 @@ pub(crate) mod parsing {
+ 
+                 let member: Member = input.parse()?;
+                 let turbofish = if member.is_named() && input.peek(Token![::]) {
+-                    Some(MethodTurbofish {
+-                        colon2_token: input.parse()?,
+-                        lt_token: input.parse()?,
+-                        args: {
+-                            let mut args = Punctuated::new();
+-                            loop {
+-                                if input.peek(Token![>]) {
+-                                    break;
+-                                }
+-                                let value = input.call(generic_method_argument)?;
+-                                args.push_value(value);
+-                                if input.peek(Token![>]) {
+-                                    break;
+-                                }
+-                                let punct = input.parse()?;
+-                                args.push_punct(punct);
+-                            }
+-                            args
+-                        },
+-                        gt_token: input.parse()?,
+-                    })
++                    Some(input.parse::<MethodTurbofish>()?)
+                 } else {
+                     None
+                 };
+@@ -2099,18 +2079,49 @@ pub(crate) mod parsing {
+     }
+ 
+     #[cfg(feature = "full")]
+-    fn generic_method_argument(input: ParseStream) -> Result<GenericMethodArgument> {
+-        if input.peek(Lit) {
+-            let lit = input.parse()?;
+-            return Ok(GenericMethodArgument::Const(Expr::Lit(lit)));
+-        }
++    #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))]
++    impl Parse for GenericMethodArgument {
++        fn parse(input: ParseStream) -> Result<Self> {
++            if input.peek(Lit) {
++                let lit = input.parse()?;
++                return Ok(GenericMethodArgument::Const(Expr::Lit(lit)));
++            }
++
++            if input.peek(token::Brace) {
++                let block: ExprBlock = input.parse()?;
++                return Ok(GenericMethodArgument::Const(Expr::Block(block)));
++            }
+ 
+-        if input.peek(token::Brace) {
+-            let block: ExprBlock = input.parse()?;
+-            return Ok(GenericMethodArgument::Const(Expr::Block(block)));
++            input.parse().map(GenericMethodArgument::Type)
+         }
++    }
+ 
+-        input.parse().map(GenericMethodArgument::Type)
++    #[cfg(feature = "full")]
++    #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))]
++    impl Parse for MethodTurbofish {
++        fn parse(input: ParseStream) -> Result<Self> {
++            Ok(MethodTurbofish {
++                colon2_token: input.parse()?,
++                lt_token: input.parse()?,
++                args: {
++                    let mut args = Punctuated::new();
++                    loop {
++                        if input.peek(Token![>]) {
++                            break;
++                        }
++                        let value: GenericMethodArgument = input.parse()?;
++                        args.push_value(value);
++                        if input.peek(Token![>]) {
++                            break;
++                        }
++                        let punct = input.parse()?;
++                        args.push_punct(punct);
++                    }
++                    args
++                },
++                gt_token: input.parse()?,
++            })
++        }
+     }
+ 
+     #[cfg(feature = "full")]
+@@ -2277,18 +2288,19 @@ pub(crate) mod parsing {
+     }
+ 
+     impl_by_parsing_expr! {
+-        ExprCall, Call, "expected function call expression",
+-        ExprMethodCall, MethodCall, "expected method call expression",
+-        ExprTuple, Tuple, "expected tuple expression",
+-        ExprBinary, Binary, "expected binary operation",
+-        ExprCast, Cast, "expected cast expression",
+-        ExprType, Type, "expected type ascription expression",
+         ExprAssign, Assign, "expected assignment expression",
+         ExprAssignOp, AssignOp, "expected compound assignment expression",
++        ExprAwait, Await, "expected await expression",
++        ExprBinary, Binary, "expected binary operation",
++        ExprCall, Call, "expected function call expression",
++        ExprCast, Cast, "expected cast expression",
+         ExprField, Field, "expected struct field access",
+         ExprIndex, Index, "expected indexing expression",
++        ExprMethodCall, MethodCall, "expected method call expression",
+         ExprRange, Range, "expected range expression",
+         ExprTry, Try, "expected try expression",
++        ExprTuple, Tuple, "expected tuple expression",
++        ExprType, Type, "expected type ascription expression",
+     }
+ 
+     #[cfg(feature = "full")]
+@@ -3346,14 +3358,22 @@ pub(crate) mod printing {
+ 
+     #[cfg(feature = "full")]
+     #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))]
+-    impl ToTokens for ExprRange {
++    impl ToTokens for RangeLimits {
+         fn to_tokens(&self, tokens: &mut TokenStream) {
+-            outer_attrs_to_tokens(&self.attrs, tokens);
+-            self.from.to_tokens(tokens);
+-            match &self.limits {
++            match self {
+                 RangeLimits::HalfOpen(t) => t.to_tokens(tokens),
+                 RangeLimits::Closed(t) => t.to_tokens(tokens),
+             }
++        }
++    }
++
++    #[cfg(feature = "full")]
++    #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))]
++    impl ToTokens for ExprRange {
++        fn to_tokens(&self, tokens: &mut TokenStream) {
++            outer_attrs_to_tokens(&self.attrs, tokens);
++            self.from.to_tokens(tokens);
++            self.limits.to_tokens(tokens);
+             self.to.to_tokens(tokens);
+         }
+     }
+index a7d750b..b59a8df 100644
+--- a/lib.rs
++++ b/lib.rs
+@@ -250,7 +250,7 @@
+ //!   dynamic library libproc_macro from rustc toolchain.
+ 
+ // Syn types in rustdoc of other crates get linked to here.
+-#![doc(html_root_url = "https://docs.rs/syn/1.0.90")]
++#![doc(html_root_url = "https://docs.rs/syn/1.0.91")]
+ #![cfg_attr(doc_cfg, feature(doc_cfg))]
+ #![allow(non_camel_case_types)]
+ // Ignored clippy lints.
+index f0ed628..0cf5cf5 100644
+--- a/lookahead.rs
++++ b/lookahead.rs
+@@ -18,6 +18,9 @@ use std::cell::RefCell;
+ /// [`ParseStream::peek`]: crate::parse::ParseBuffer::peek
+ /// [`ParseStream::lookahead1`]: crate::parse::ParseBuffer::lookahead1
+ ///
++/// Consuming tokens from the source stream after constructing a lookahead
++/// object does not also advance the lookahead object.
++///
+ /// # Example
+ ///
+ /// ```
+index 630bf9d..fa0818c 100644
+--- a/pat.rs
++++ b/pat.rs
+@@ -878,10 +878,7 @@ mod printing {
+         fn to_tokens(&self, tokens: &mut TokenStream) {
+             tokens.append_all(self.attrs.outer());
+             self.lo.to_tokens(tokens);
+-            match &self.limits {
+-                RangeLimits::HalfOpen(t) => t.to_tokens(tokens),
+-                RangeLimits::Closed(t) => t.to_tokens(tokens),
+-            }
++            self.limits.to_tokens(tokens);
+             self.hi.to_tokens(tokens);
+         }
+     }
+
+Use |cargo vet certify| to record your audit.
+stderr:
+ WARN unable to determine likely criteria, this may not be a relevant audit for this project.
diff --git a/tests/snapshots/test_cli__test-project-dump-graph-full-json.snap b/tests/snapshots/test_cli__test-project-dump-graph-full-json.snap
new file mode 100644
index 0000000..c5e1de9
--- /dev/null
+++ b/tests/snapshots/test_cli__test-project-dump-graph-full-json.snap
@@ -0,0 +1,2799 @@
+---
+source: tests/test-cli.rs
+expression: format_outputs(&output)
+---
+stdout:
+[
+  {
+    "package_id": "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "atty",
+    "version": "0.2.14",
+    "normal_deps": [
+      25,
+      39,
+      102
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      25,
+      39,
+      102
+    ],
+    "all_deps": [
+      25,
+      39,
+      102
+    ],
+    "reverse_deps": [
+      9
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "autocfg 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "autocfg",
+    "version": "1.1.0",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      33,
+      51
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "base64 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "base64",
+    "version": "0.13.0",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      62
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "bitflags 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "bitflags",
+    "version": "1.3.2",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      9,
+      49,
+      60,
+      65
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "bumpalo 3.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "bumpalo",
+    "version": "3.9.1",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      96
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "bytes 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "bytes",
+    "version": "1.1.0",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      23,
+      26,
+      27,
+      30,
+      31,
+      62,
+      80,
+      82
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "cc 1.0.73 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "cc",
+    "version": "1.0.73",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      51
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "cfg-if",
+    "version": "0.1.10",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      76
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "cfg-if",
+    "version": "1.0.0",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      12,
+      34,
+      40,
+      49,
+      74,
+      84,
+      95,
+      97
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "clap 3.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "clap",
+    "version": "3.1.8",
+    "normal_deps": [
+      0,
+      3,
+      33,
+      52,
+      72,
+      75,
+      77
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      0,
+      3,
+      33,
+      52,
+      72,
+      75,
+      77
+    ],
+    "all_deps": [
+      0,
+      3,
+      33,
+      52,
+      72,
+      75,
+      77
+    ],
+    "reverse_deps": [
+      76
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "core-foundation 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "core-foundation",
+    "version": "0.9.3",
+    "normal_deps": [
+      11,
+      39
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      11,
+      39
+    ],
+    "all_deps": [
+      11,
+      39
+    ],
+    "reverse_deps": [
+      65
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "core-foundation-sys 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "core-foundation-sys",
+    "version": "0.8.3",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      10,
+      65,
+      66
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "encoding_rs 0.8.31 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "encoding_rs",
+    "version": "0.8.31",
+    "normal_deps": [
+      8
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      8
+    ],
+    "all_deps": [
+      8
+    ],
+    "reverse_deps": [
+      62
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "fastrand 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "fastrand",
+    "version": "1.7.0",
+    "normal_deps": [
+      34
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      34
+    ],
+    "all_deps": [
+      34
+    ],
+    "reverse_deps": [
+      74
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "fnv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "fnv",
+    "version": "1.0.7",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      23,
+      26
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "foreign-types",
+    "version": "0.3.2",
+    "normal_deps": [
+      16
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      16
+    ],
+    "all_deps": [
+      16
+    ],
+    "reverse_deps": [
+      49
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "foreign-types-shared",
+    "version": "0.1.1",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      15
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "form_urlencoded 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "form_urlencoded",
+    "version": "1.0.1",
+    "normal_deps": [
+      41,
+      53
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      41,
+      53
+    ],
+    "all_deps": [
+      41,
+      53
+    ],
+    "reverse_deps": [
+      69,
+      91
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "futures-channel 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "futures-channel",
+    "version": "0.3.21",
+    "normal_deps": [
+      19
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      19
+    ],
+    "all_deps": [
+      19
+    ],
+    "reverse_deps": [
+      30
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "futures-core 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "futures-core",
+    "version": "0.3.21",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      18,
+      22,
+      23,
+      30,
+      62,
+      82
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "futures-sink 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "futures-sink",
+    "version": "0.3.21",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      23,
+      82
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "futures-task 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "futures-task",
+    "version": "0.3.21",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      22
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "futures-util 0.3.21 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "futures-util",
+    "version": "0.3.21",
+    "normal_deps": [
+      19,
+      21,
+      54,
+      55
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      19,
+      21,
+      54,
+      55
+    ],
+    "all_deps": [
+      19,
+      21,
+      54,
+      55
+    ],
+    "reverse_deps": [
+      23,
+      30,
+      62
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "h2 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "h2",
+    "version": "0.3.13",
+    "normal_deps": [
+      5,
+      14,
+      19,
+      20,
+      22,
+      26,
+      33,
+      70,
+      80,
+      82,
+      84
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      5,
+      14,
+      19,
+      20,
+      22,
+      26,
+      33,
+      70,
+      80,
+      82,
+      84
+    ],
+    "all_deps": [
+      5,
+      14,
+      19,
+      20,
+      22,
+      26,
+      33,
+      70,
+      80,
+      82,
+      84
+    ],
+    "reverse_deps": [
+      30,
+      62
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "hashbrown 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "hashbrown",
+    "version": "0.11.2",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      33
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "hermit-abi 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "hermit-abi",
+    "version": "0.1.19",
+    "normal_deps": [
+      39
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      39
+    ],
+    "all_deps": [
+      39
+    ],
+    "reverse_deps": [
+      0
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "http 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "http",
+    "version": "0.2.6",
+    "normal_deps": [
+      5,
+      14,
+      36
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      5,
+      14,
+      36
+    ],
+    "all_deps": [
+      5,
+      14,
+      36
+    ],
+    "reverse_deps": [
+      23,
+      27,
+      30,
+      62
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "http-body 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "http-body",
+    "version": "0.4.4",
+    "normal_deps": [
+      5,
+      26,
+      54
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      5,
+      26,
+      54
+    ],
+    "all_deps": [
+      5,
+      26,
+      54
+    ],
+    "reverse_deps": [
+      30,
+      62
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "httparse 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "httparse",
+    "version": "1.7.0",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      30
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "httpdate 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "httpdate",
+    "version": "1.0.2",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      30
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "hyper 0.14.18 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "hyper",
+    "version": "0.14.18",
+    "normal_deps": [
+      5,
+      18,
+      19,
+      22,
+      23,
+      26,
+      27,
+      28,
+      29,
+      36,
+      54,
+      71,
+      80,
+      83,
+      84,
+      93
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      5,
+      18,
+      19,
+      22,
+      23,
+      26,
+      27,
+      28,
+      29,
+      36,
+      54,
+      71,
+      80,
+      83,
+      84,
+      93
+    ],
+    "all_deps": [
+      5,
+      18,
+      19,
+      22,
+      23,
+      26,
+      27,
+      28,
+      29,
+      36,
+      54,
+      71,
+      80,
+      83,
+      84,
+      93
+    ],
+    "reverse_deps": [
+      31,
+      62
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "hyper-tls 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "hyper-tls",
+    "version": "0.5.0",
+    "normal_deps": [
+      5,
+      30,
+      46,
+      80,
+      81
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      5,
+      30,
+      46,
+      80,
+      81
+    ],
+    "all_deps": [
+      5,
+      30,
+      46,
+      80,
+      81
+    ],
+    "reverse_deps": [
+      62
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "idna 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "idna",
+    "version": "0.2.3",
+    "normal_deps": [
+      41,
+      88,
+      89
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      41,
+      88,
+      89
+    ],
+    "all_deps": [
+      41,
+      88,
+      89
+    ],
+    "reverse_deps": [
+      91
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "indexmap 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "indexmap",
+    "version": "1.8.1",
+    "normal_deps": [
+      24
+    ],
+    "build_deps": [
+      1
+    ],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      1,
+      24
+    ],
+    "all_deps": [
+      1,
+      24
+    ],
+    "reverse_deps": [
+      9,
+      23
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "instant 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "instant",
+    "version": "0.1.12",
+    "normal_deps": [
+      8
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      8
+    ],
+    "all_deps": [
+      8
+    ],
+    "reverse_deps": [
+      13
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "ipnet 2.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "ipnet",
+    "version": "2.4.0",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      62
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "itoa 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "itoa",
+    "version": "1.0.1",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      26,
+      30,
+      68,
+      69
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "js-sys 0.3.57 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "js-sys",
+    "version": "0.3.57",
+    "normal_deps": [
+      95
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      95
+    ],
+    "all_deps": [
+      95
+    ],
+    "reverse_deps": [
+      62,
+      97,
+      101
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "lazy_static",
+    "version": "1.4.0",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      46,
+      62,
+      64,
+      86,
+      96
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "libc 0.2.123 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "libc",
+    "version": "0.2.123",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      0,
+      10,
+      25,
+      44,
+      46,
+      49,
+      51,
+      65,
+      66,
+      71,
+      74,
+      80
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "log 0.4.16 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "log",
+    "version": "0.4.16",
+    "normal_deps": [
+      8
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      8
+    ],
+    "all_deps": [
+      8
+    ],
+    "reverse_deps": [
+      44,
+      46,
+      62,
+      93,
+      96
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "matches 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "matches",
+    "version": "0.1.9",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      17,
+      32,
+      91
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "memchr 2.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "memchr",
+    "version": "2.4.1",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      52,
+      80
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "mime",
+    "version": "0.3.16",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      62
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "mio 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "mio",
+    "version": "0.8.2",
+    "normal_deps": [
+      39,
+      40,
+      45,
+      47,
+      94,
+      102
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      39,
+      40,
+      45,
+      47,
+      94,
+      102
+    ],
+    "all_deps": [
+      39,
+      40,
+      45,
+      47,
+      94,
+      102
+    ],
+    "reverse_deps": [
+      80
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "miow 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "miow",
+    "version": "0.3.7",
+    "normal_deps": [
+      102
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      102
+    ],
+    "all_deps": [
+      102
+    ],
+    "reverse_deps": [
+      44
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "native-tls 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "native-tls",
+    "version": "0.2.10",
+    "normal_deps": [
+      38,
+      39,
+      40,
+      49,
+      50,
+      51,
+      64,
+      65,
+      66,
+      74
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      38,
+      39,
+      40,
+      49,
+      50,
+      51,
+      64,
+      65,
+      66,
+      74
+    ],
+    "all_deps": [
+      38,
+      39,
+      40,
+      49,
+      50,
+      51,
+      64,
+      65,
+      66,
+      74
+    ],
+    "reverse_deps": [
+      31,
+      62,
+      81
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "ntapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "ntapi",
+    "version": "0.3.7",
+    "normal_deps": [
+      102
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      102
+    ],
+    "all_deps": [
+      102
+    ],
+    "reverse_deps": [
+      44
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "once_cell 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "once_cell",
+    "version": "1.10.0",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      49
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "openssl 0.10.38 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "openssl",
+    "version": "0.10.38",
+    "normal_deps": [
+      3,
+      8,
+      15,
+      39,
+      48,
+      51
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      3,
+      8,
+      15,
+      39,
+      48,
+      51
+    ],
+    "all_deps": [
+      3,
+      8,
+      15,
+      39,
+      48,
+      51
+    ],
+    "reverse_deps": [
+      46
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "openssl-probe 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "openssl-probe",
+    "version": "0.1.5",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      46
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "openssl-sys 0.9.72 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "openssl-sys",
+    "version": "0.9.72",
+    "normal_deps": [
+      39
+    ],
+    "build_deps": [
+      1,
+      6,
+      56,
+      92
+    ],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      1,
+      6,
+      39,
+      56,
+      92
+    ],
+    "all_deps": [
+      1,
+      6,
+      39,
+      56,
+      92
+    ],
+    "reverse_deps": [
+      46,
+      49
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "os_str_bytes 6.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "os_str_bytes",
+    "version": "6.0.0",
+    "normal_deps": [
+      42
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      42
+    ],
+    "all_deps": [
+      42
+    ],
+    "reverse_deps": [
+      9
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "percent-encoding",
+    "version": "2.1.0",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      17,
+      62,
+      91
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "pin-project-lite 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "pin-project-lite",
+    "version": "0.2.8",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      22,
+      27,
+      30,
+      62,
+      80,
+      82,
+      84
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "pin-utils 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "pin-utils",
+    "version": "0.1.0",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      22
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "pkg-config 0.3.25 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "pkg-config",
+    "version": "0.3.25",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      51
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "proc-macro2 1.0.37 (git+https://github.com/dtolnay/proc-macro2?rev=4445659b0f753a928059244c875a58bb12f791e9#4445659b0f753a928059244c875a58bb12f791e9)",
+    "name": "proc-macro2",
+    "version": "1.0.37@git:4445659b0f753a928059244c875a58bb12f791e9",
+    "normal_deps": [
+      90
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      90
+    ],
+    "all_deps": [
+      90
+    ],
+    "reverse_deps": [
+      76
+    ],
+    "is_workspace_member": false,
+    "is_third_party": false,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "proc-macro2 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "proc-macro2",
+    "version": "1.0.37",
+    "normal_deps": [
+      90
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      90
+    ],
+    "all_deps": [
+      90
+    ],
+    "reverse_deps": [
+      59,
+      73,
+      85,
+      96,
+      99
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "quote 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "quote",
+    "version": "1.0.18",
+    "normal_deps": [
+      58
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      58
+    ],
+    "all_deps": [
+      58
+    ],
+    "reverse_deps": [
+      73,
+      85,
+      96,
+      98,
+      99
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "redox_syscall 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "redox_syscall",
+    "version": "0.2.13",
+    "normal_deps": [
+      3
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      3
+    ],
+    "all_deps": [
+      3
+    ],
+    "reverse_deps": [
+      74
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "remove_dir_all 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "remove_dir_all",
+    "version": "0.5.3",
+    "normal_deps": [
+      102
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      102
+    ],
+    "all_deps": [
+      102
+    ],
+    "reverse_deps": [
+      74
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "reqwest 0.11.10 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "reqwest",
+    "version": "0.11.10",
+    "normal_deps": [
+      2,
+      5,
+      12,
+      19,
+      22,
+      23,
+      26,
+      27,
+      30,
+      31,
+      35,
+      37,
+      38,
+      40,
+      43,
+      46,
+      53,
+      54,
+      67,
+      68,
+      69,
+      80,
+      81,
+      91,
+      95,
+      97,
+      101,
+      106
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      2,
+      5,
+      12,
+      19,
+      22,
+      23,
+      26,
+      27,
+      30,
+      31,
+      35,
+      37,
+      38,
+      40,
+      43,
+      46,
+      53,
+      54,
+      67,
+      68,
+      69,
+      80,
+      81,
+      91,
+      95,
+      97,
+      101,
+      106
+    ],
+    "all_deps": [
+      2,
+      5,
+      12,
+      19,
+      22,
+      23,
+      26,
+      27,
+      30,
+      31,
+      35,
+      37,
+      38,
+      40,
+      43,
+      46,
+      53,
+      54,
+      67,
+      68,
+      69,
+      80,
+      81,
+      91,
+      95,
+      97,
+      101,
+      106
+    ],
+    "reverse_deps": [
+      76
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "ryu 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "ryu",
+    "version": "1.0.9",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      68,
+      69
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "schannel 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "schannel",
+    "version": "0.1.19",
+    "normal_deps": [
+      38,
+      102
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      38,
+      102
+    ],
+    "all_deps": [
+      38,
+      102
+    ],
+    "reverse_deps": [
+      46
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "security-framework 2.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "security-framework",
+    "version": "2.6.1",
+    "normal_deps": [
+      3,
+      10,
+      11,
+      39,
+      66
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      3,
+      10,
+      11,
+      39,
+      66
+    ],
+    "all_deps": [
+      3,
+      10,
+      11,
+      39,
+      66
+    ],
+    "reverse_deps": [
+      46
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "security-framework-sys 2.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "security-framework-sys",
+    "version": "2.6.1",
+    "normal_deps": [
+      11,
+      39
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      11,
+      39
+    ],
+    "all_deps": [
+      11,
+      39
+    ],
+    "reverse_deps": [
+      46,
+      65
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "serde 1.0.136 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "serde",
+    "version": "1.0.136",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      62,
+      68,
+      69
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "serde_json 1.0.79 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "serde_json",
+    "version": "1.0.79",
+    "normal_deps": [
+      36,
+      63,
+      67
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      36,
+      63,
+      67
+    ],
+    "all_deps": [
+      36,
+      63,
+      67
+    ],
+    "reverse_deps": [
+      62,
+      76
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "serde_urlencoded 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "serde_urlencoded",
+    "version": "0.7.1",
+    "normal_deps": [
+      17,
+      36,
+      63,
+      67
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      17,
+      36,
+      63,
+      67
+    ],
+    "all_deps": [
+      17,
+      36,
+      63,
+      67
+    ],
+    "reverse_deps": [
+      62
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "slab 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "slab",
+    "version": "0.4.6",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      23
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "socket2 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "socket2",
+    "version": "0.4.4",
+    "normal_deps": [
+      39,
+      102
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      39,
+      102
+    ],
+    "all_deps": [
+      39,
+      102
+    ],
+    "reverse_deps": [
+      30,
+      80
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "strsim 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "strsim",
+    "version": "0.10.0",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      9
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "syn 1.0.91 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "syn",
+    "version": "1.0.91",
+    "normal_deps": [
+      58,
+      59,
+      90
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      58,
+      59,
+      90
+    ],
+    "all_deps": [
+      58,
+      59,
+      90
+    ],
+    "reverse_deps": [
+      85,
+      96,
+      99
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "tempfile 3.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "tempfile",
+    "version": "3.3.0",
+    "normal_deps": [
+      8,
+      13,
+      39,
+      60,
+      61,
+      102
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      8,
+      13,
+      39,
+      60,
+      61,
+      102
+    ],
+    "all_deps": [
+      8,
+      13,
+      39,
+      60,
+      61,
+      102
+    ],
+    "reverse_deps": [
+      46
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "termcolor 1.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "termcolor",
+    "version": "1.1.3",
+    "normal_deps": [
+      104
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      104
+    ],
+    "all_deps": [
+      104
+    ],
+    "reverse_deps": [
+      9
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "name": "test-project",
+    "version": "0.1.0",
+    "normal_deps": [
+      7,
+      9,
+      57,
+      62,
+      68,
+      80
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      7,
+      9,
+      57,
+      62,
+      68,
+      80
+    ],
+    "all_deps": [
+      7,
+      9,
+      57,
+      62,
+      68,
+      80
+    ],
+    "reverse_deps": [],
+    "is_workspace_member": true,
+    "is_third_party": false,
+    "is_root": true,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "textwrap 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "textwrap",
+    "version": "0.15.0",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      9
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "tinyvec 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "tinyvec",
+    "version": "1.5.1",
+    "normal_deps": [
+      79
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      79
+    ],
+    "all_deps": [
+      79
+    ],
+    "reverse_deps": [
+      89
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "tinyvec_macros 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "tinyvec_macros",
+    "version": "0.1.0",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      78
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "tokio 1.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "tokio",
+    "version": "1.17.0",
+    "normal_deps": [
+      5,
+      39,
+      42,
+      44,
+      54,
+      71,
+      102
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      5,
+      39,
+      42,
+      44,
+      54,
+      71,
+      102
+    ],
+    "all_deps": [
+      5,
+      39,
+      42,
+      44,
+      54,
+      71,
+      102
+    ],
+    "reverse_deps": [
+      23,
+      30,
+      31,
+      62,
+      76,
+      81,
+      82
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "tokio-native-tls 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "tokio-native-tls",
+    "version": "0.3.0",
+    "normal_deps": [
+      46,
+      80
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      46,
+      80
+    ],
+    "all_deps": [
+      46,
+      80
+    ],
+    "reverse_deps": [
+      31,
+      62
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "tokio-util 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "tokio-util",
+    "version": "0.7.1",
+    "normal_deps": [
+      5,
+      19,
+      20,
+      54,
+      80,
+      84
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      5,
+      19,
+      20,
+      54,
+      80,
+      84
+    ],
+    "all_deps": [
+      5,
+      19,
+      20,
+      54,
+      80,
+      84
+    ],
+    "reverse_deps": [
+      23
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "tower-service 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "tower-service",
+    "version": "0.3.1",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      30
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "tracing 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "tracing",
+    "version": "0.1.33",
+    "normal_deps": [
+      8,
+      54,
+      85,
+      86
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      8,
+      54,
+      85,
+      86
+    ],
+    "all_deps": [
+      8,
+      54,
+      85,
+      86
+    ],
+    "reverse_deps": [
+      23,
+      30,
+      82
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "tracing-attributes 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "tracing-attributes",
+    "version": "0.1.20",
+    "normal_deps": [
+      58,
+      59,
+      73
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      58,
+      59,
+      73
+    ],
+    "all_deps": [
+      58,
+      59,
+      73
+    ],
+    "reverse_deps": [
+      84
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "tracing-core 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "tracing-core",
+    "version": "0.1.25",
+    "normal_deps": [
+      38
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      38
+    ],
+    "all_deps": [
+      38
+    ],
+    "reverse_deps": [
+      84
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "try-lock 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "try-lock",
+    "version": "0.2.3",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      93
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "unicode-bidi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "unicode-bidi",
+    "version": "0.3.7",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      32
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "unicode-normalization 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "unicode-normalization",
+    "version": "0.1.19",
+    "normal_deps": [
+      78
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      78
+    ],
+    "all_deps": [
+      78
+    ],
+    "reverse_deps": [
+      32
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "unicode-xid 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "unicode-xid",
+    "version": "0.2.2",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      57,
+      58,
+      73
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "url 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "url",
+    "version": "2.2.2",
+    "normal_deps": [
+      17,
+      32,
+      41,
+      53
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      17,
+      32,
+      41,
+      53
+    ],
+    "all_deps": [
+      17,
+      32,
+      41,
+      53
+    ],
+    "reverse_deps": [
+      62
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "vcpkg 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "vcpkg",
+    "version": "0.2.15",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      51
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "want 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "want",
+    "version": "0.3.0",
+    "normal_deps": [
+      40,
+      87
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      40,
+      87
+    ],
+    "all_deps": [
+      40,
+      87
+    ],
+    "reverse_deps": [
+      30
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "wasi 0.11.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "wasi",
+    "version": "0.11.0+wasi-snapshot-preview1",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      44
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "wasm-bindgen 0.2.80 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "wasm-bindgen",
+    "version": "0.2.80",
+    "normal_deps": [
+      8,
+      98
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      8,
+      98
+    ],
+    "all_deps": [
+      8,
+      98
+    ],
+    "reverse_deps": [
+      37,
+      62,
+      97,
+      101
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "wasm-bindgen-backend 0.2.80 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "wasm-bindgen-backend",
+    "version": "0.2.80",
+    "normal_deps": [
+      4,
+      38,
+      40,
+      58,
+      59,
+      73,
+      100
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      4,
+      38,
+      40,
+      58,
+      59,
+      73,
+      100
+    ],
+    "all_deps": [
+      4,
+      38,
+      40,
+      58,
+      59,
+      73,
+      100
+    ],
+    "reverse_deps": [
+      99
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "wasm-bindgen-futures 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "wasm-bindgen-futures",
+    "version": "0.4.30",
+    "normal_deps": [
+      8,
+      37,
+      95,
+      101
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      8,
+      37,
+      95,
+      101
+    ],
+    "all_deps": [
+      8,
+      37,
+      95,
+      101
+    ],
+    "reverse_deps": [
+      62
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "wasm-bindgen-macro 0.2.80 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "wasm-bindgen-macro",
+    "version": "0.2.80",
+    "normal_deps": [
+      59,
+      99
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      59,
+      99
+    ],
+    "all_deps": [
+      59,
+      99
+    ],
+    "reverse_deps": [
+      95
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "wasm-bindgen-macro-support 0.2.80 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "wasm-bindgen-macro-support",
+    "version": "0.2.80",
+    "normal_deps": [
+      58,
+      59,
+      73,
+      96,
+      100
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      58,
+      59,
+      73,
+      96,
+      100
+    ],
+    "all_deps": [
+      58,
+      59,
+      73,
+      96,
+      100
+    ],
+    "reverse_deps": [
+      98
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "wasm-bindgen-shared 0.2.80 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "wasm-bindgen-shared",
+    "version": "0.2.80",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      96,
+      99
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "web-sys 0.3.57 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "web-sys",
+    "version": "0.3.57",
+    "normal_deps": [
+      37,
+      95
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      37,
+      95
+    ],
+    "all_deps": [
+      37,
+      95
+    ],
+    "reverse_deps": [
+      62,
+      97
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "winapi",
+    "version": "0.3.9",
+    "normal_deps": [
+      103,
+      105
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      103,
+      105
+    ],
+    "all_deps": [
+      103,
+      105
+    ],
+    "reverse_deps": [
+      0,
+      44,
+      45,
+      47,
+      61,
+      64,
+      71,
+      74,
+      80,
+      104,
+      106
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "winapi-i686-pc-windows-gnu",
+    "version": "0.4.0",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      102
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "winapi-util",
+    "version": "0.1.5",
+    "normal_deps": [
+      102
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      102
+    ],
+    "all_deps": [
+      102
+    ],
+    "reverse_deps": [
+      75
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "winapi-x86_64-pc-windows-gnu",
+    "version": "0.4.0",
+    "normal_deps": [],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [],
+    "all_deps": [],
+    "reverse_deps": [
+      102
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  },
+  {
+    "package_id": "winreg 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
+    "name": "winreg",
+    "version": "0.10.1",
+    "normal_deps": [
+      102
+    ],
+    "build_deps": [],
+    "dev_deps": [],
+    "normal_and_build_deps": [
+      102
+    ],
+    "all_deps": [
+      102
+    ],
+    "reverse_deps": [
+      62
+    ],
+    "is_workspace_member": false,
+    "is_third_party": true,
+    "is_root": false,
+    "is_dev_only": false
+  }
+]
+stderr:
+
diff --git a/tests/snapshots/test_cli__test-project-dump-graph-full.snap b/tests/snapshots/test_cli__test-project-dump-graph-full.snap
new file mode 100644
index 0000000..ac028a1
--- /dev/null
+++ b/tests/snapshots/test_cli__test-project-dump-graph-full.snap
@@ -0,0 +1,347 @@
+---
+source: tests/test-cli.rs
+expression: format_outputs(&output)
+---
+stdout:
+graph LR
+    subgraph roots
+        node76{test-project:0.1.0}
+    end
+    subgraph workspace-members
+    end
+    subgraph first-party
+        node57[proc-macro2:1.0.37@git:4445659b0f753a928059244c875a58bb12f791e9]
+    end
+    subgraph third-party
+        node0(atty:0.2.14)
+        node1(autocfg:1.1.0)
+        node2(base64:0.13.0)
+        node3(bitflags:1.3.2)
+        node4(bumpalo:3.9.1)
+        node5(bytes:1.1.0)
+        node6(cc:1.0.73)
+        node7(cfg-if:0.1.10)
+        node8(cfg-if:1.0.0)
+        node9(clap:3.1.8)
+        node10(core-foundation:0.9.3)
+        node11(core-foundation-sys:0.8.3)
+        node12(encoding_rs:0.8.31)
+        node13(fastrand:1.7.0)
+        node14(fnv:1.0.7)
+        node15(foreign-types:0.3.2)
+        node16(foreign-types-shared:0.1.1)
+        node17(form_urlencoded:1.0.1)
+        node18(futures-channel:0.3.21)
+        node19(futures-core:0.3.21)
+        node20(futures-sink:0.3.21)
+        node21(futures-task:0.3.21)
+        node22(futures-util:0.3.21)
+        node23(h2:0.3.13)
+        node24(hashbrown:0.11.2)
+        node25(hermit-abi:0.1.19)
+        node26(http:0.2.6)
+        node27(http-body:0.4.4)
+        node28(httparse:1.7.0)
+        node29(httpdate:1.0.2)
+        node30(hyper:0.14.18)
+        node31(hyper-tls:0.5.0)
+        node32(idna:0.2.3)
+        node33(indexmap:1.8.1)
+        node34(instant:0.1.12)
+        node35(ipnet:2.4.0)
+        node36(itoa:1.0.1)
+        node37(js-sys:0.3.57)
+        node38(lazy_static:1.4.0)
+        node39(libc:0.2.123)
+        node40(log:0.4.16)
+        node41(matches:0.1.9)
+        node42(memchr:2.4.1)
+        node43(mime:0.3.16)
+        node44(mio:0.8.2)
+        node45(miow:0.3.7)
+        node46(native-tls:0.2.10)
+        node47(ntapi:0.3.7)
+        node48(once_cell:1.10.0)
+        node49(openssl:0.10.38)
+        node50(openssl-probe:0.1.5)
+        node51(openssl-sys:0.9.72)
+        node52(os_str_bytes:6.0.0)
+        node53(percent-encoding:2.1.0)
+        node54(pin-project-lite:0.2.8)
+        node55(pin-utils:0.1.0)
+        node56(pkg-config:0.3.25)
+        node58(proc-macro2:1.0.37)
+        node59(quote:1.0.18)
+        node60(redox_syscall:0.2.13)
+        node61(remove_dir_all:0.5.3)
+        node62(reqwest:0.11.10)
+        node63(ryu:1.0.9)
+        node64(schannel:0.1.19)
+        node65(security-framework:2.6.1)
+        node66(security-framework-sys:2.6.1)
+        node67(serde:1.0.136)
+        node68(serde_json:1.0.79)
+        node69(serde_urlencoded:0.7.1)
+        node70(slab:0.4.6)
+        node71(socket2:0.4.4)
+        node72(strsim:0.10.0)
+        node73(syn:1.0.91)
+        node74(tempfile:3.3.0)
+        node75(termcolor:1.1.3)
+        node77(textwrap:0.15.0)
+        node78(tinyvec:1.5.1)
+        node79(tinyvec_macros:0.1.0)
+        node80(tokio:1.17.0)
+        node81(tokio-native-tls:0.3.0)
+        node82(tokio-util:0.7.1)
+        node83(tower-service:0.3.1)
+        node84(tracing:0.1.33)
+        node85(tracing-attributes:0.1.20)
+        node86(tracing-core:0.1.25)
+        node87(try-lock:0.2.3)
+        node88(unicode-bidi:0.3.7)
+        node89(unicode-normalization:0.1.19)
+        node90(unicode-xid:0.2.2)
+        node91(url:2.2.2)
+        node92(vcpkg:0.2.15)
+        node93(want:0.3.0)
+        node94(wasi:0.11.0+wasi-snapshot-preview1)
+        node95(wasm-bindgen:0.2.80)
+        node96(wasm-bindgen-backend:0.2.80)
+        node97(wasm-bindgen-futures:0.4.30)
+        node98(wasm-bindgen-macro:0.2.80)
+        node99(wasm-bindgen-macro-support:0.2.80)
+        node100(wasm-bindgen-shared:0.2.80)
+        node101(web-sys:0.3.57)
+        node102(winapi:0.3.9)
+        node103(winapi-i686-pc-windows-gnu:0.4.0)
+        node104(winapi-util:0.1.5)
+        node105(winapi-x86_64-pc-windows-gnu:0.4.0)
+        node106(winreg:0.10.1)
+    end
+    node0 --> node25
+    node0 --> node39
+    node0 --> node102
+    node9 --> node0
+    node9 --> node3
+    node9 --> node33
+    node9 --> node52
+    node9 --> node72
+    node9 --> node75
+    node9 --> node77
+    node10 --> node11
+    node10 --> node39
+    node12 --> node8
+    node13 --> node34
+    node15 --> node16
+    node17 --> node41
+    node17 --> node53
+    node18 --> node19
+    node22 --> node19
+    node22 --> node21
+    node22 --> node54
+    node22 --> node55
+    node23 --> node5
+    node23 --> node14
+    node23 --> node19
+    node23 --> node20
+    node23 --> node22
+    node23 --> node26
+    node23 --> node33
+    node23 --> node70
+    node23 --> node80
+    node23 --> node82
+    node23 --> node84
+    node25 --> node39
+    node26 --> node5
+    node26 --> node14
+    node26 --> node36
+    node27 --> node5
+    node27 --> node26
+    node27 --> node54
+    node30 --> node5
+    node30 --> node18
+    node30 --> node19
+    node30 --> node22
+    node30 --> node23
+    node30 --> node26
+    node30 --> node27
+    node30 --> node28
+    node30 --> node29
+    node30 --> node36
+    node30 --> node54
+    node30 --> node71
+    node30 --> node80
+    node30 --> node83
+    node30 --> node84
+    node30 --> node93
+    node31 --> node5
+    node31 --> node30
+    node31 --> node46
+    node31 --> node80
+    node31 --> node81
+    node32 --> node41
+    node32 --> node88
+    node32 --> node89
+    node33 --> node1
+    node33 --> node24
+    node34 --> node8
+    node37 --> node95
+    node40 --> node8
+    node44 --> node39
+    node44 --> node40
+    node44 --> node45
+    node44 --> node47
+    node44 --> node94
+    node44 --> node102
+    node45 --> node102
+    node46 --> node38
+    node46 --> node39
+    node46 --> node40
+    node46 --> node49
+    node46 --> node50
+    node46 --> node51
+    node46 --> node64
+    node46 --> node65
+    node46 --> node66
+    node46 --> node74
+    node47 --> node102
+    node49 --> node3
+    node49 --> node8
+    node49 --> node15
+    node49 --> node39
+    node49 --> node48
+    node49 --> node51
+    node51 --> node1
+    node51 --> node6
+    node51 --> node39
+    node51 --> node56
+    node51 --> node92
+    node52 --> node42
+    node57 --> node90
+    node58 --> node90
+    node59 --> node58
+    node60 --> node3
+    node61 --> node102
+    node62 --> node2
+    node62 --> node5
+    node62 --> node12
+    node62 --> node19
+    node62 --> node22
+    node62 --> node23
+    node62 --> node26
+    node62 --> node27
+    node62 --> node30
+    node62 --> node31
+    node62 --> node35
+    node62 --> node37
+    node62 --> node38
+    node62 --> node40
+    node62 --> node43
+    node62 --> node46
+    node62 --> node53
+    node62 --> node54
+    node62 --> node67
+    node62 --> node68
+    node62 --> node69
+    node62 --> node80
+    node62 --> node81
+    node62 --> node91
+    node62 --> node95
+    node62 --> node97
+    node62 --> node101
+    node62 --> node106
+    node64 --> node38
+    node64 --> node102
+    node65 --> node3
+    node65 --> node10
+    node65 --> node11
+    node65 --> node39
+    node65 --> node66
+    node66 --> node11
+    node66 --> node39
+    node68 --> node36
+    node68 --> node63
+    node68 --> node67
+    node69 --> node17
+    node69 --> node36
+    node69 --> node63
+    node69 --> node67
+    node71 --> node39
+    node71 --> node102
+    node73 --> node58
+    node73 --> node59
+    node73 --> node90
+    node74 --> node8
+    node74 --> node13
+    node74 --> node39
+    node74 --> node60
+    node74 --> node61
+    node74 --> node102
+    node75 --> node104
+    node76 --> node7
+    node76 --> node9
+    node76 --> node57
+    node76 --> node62
+    node76 --> node68
+    node76 --> node80
+    node78 --> node79
+    node80 --> node5
+    node80 --> node39
+    node80 --> node42
+    node80 --> node44
+    node80 --> node54
+    node80 --> node71
+    node80 --> node102
+    node81 --> node46
+    node81 --> node80
+    node82 --> node5
+    node82 --> node19
+    node82 --> node20
+    node82 --> node54
+    node82 --> node80
+    node82 --> node84
+    node84 --> node8
+    node84 --> node54
+    node84 --> node85
+    node84 --> node86
+    node85 --> node58
+    node85 --> node59
+    node85 --> node73
+    node86 --> node38
+    node89 --> node78
+    node91 --> node17
+    node91 --> node32
+    node91 --> node41
+    node91 --> node53
+    node93 --> node40
+    node93 --> node87
+    node95 --> node8
+    node95 --> node98
+    node96 --> node4
+    node96 --> node38
+    node96 --> node40
+    node96 --> node58
+    node96 --> node59
+    node96 --> node73
+    node96 --> node100
+    node97 --> node8
+    node97 --> node37
+    node97 --> node95
+    node97 --> node101
+    node98 --> node59
+    node98 --> node99
+    node99 --> node58
+    node99 --> node59
+    node99 --> node73
+    node99 --> node96
+    node99 --> node100
+    node101 --> node37
+    node101 --> node95
+    node102 --> node103
+    node102 --> node105
+    node104 --> node102
+    node106 --> node102
+
+stderr:
+
diff --git a/tests/snapshots/test_cli__test-project-json.snap b/tests/snapshots/test_cli__test-project-json.snap
new file mode 100644
index 0000000..15ae60d
--- /dev/null
+++ b/tests/snapshots/test_cli__test-project-json.snap
@@ -0,0 +1,440 @@
+---
+source: tests/test-cli.rs
+expression: format_outputs(&output)
+---
+stdout:
+{
+  "conclusion": "success",
+  "vetted_fully": [
+    {
+      "name": "atty",
+      "version": "0.2.14"
+    },
+    {
+      "name": "autocfg",
+      "version": "1.1.0"
+    },
+    {
+      "name": "base64",
+      "version": "0.13.0"
+    },
+    {
+      "name": "bitflags",
+      "version": "1.3.2"
+    },
+    {
+      "name": "clap",
+      "version": "3.1.8"
+    },
+    {
+      "name": "serde",
+      "version": "1.0.136"
+    },
+    {
+      "name": "tracing",
+      "version": "0.1.33"
+    }
+  ],
+  "vetted_partially": [
+    {
+      "name": "cfg-if",
+      "version": "1.0.0"
+    },
+    {
+      "name": "unicode-bidi",
+      "version": "0.3.7"
+    }
+  ],
+  "vetted_with_exemptions": [
+    {
+      "name": "bumpalo",
+      "version": "3.9.1"
+    },
+    {
+      "name": "bytes",
+      "version": "1.1.0"
+    },
+    {
+      "name": "cc",
+      "version": "1.0.73"
+    },
+    {
+      "name": "cfg-if",
+      "version": "0.1.10"
+    },
+    {
+      "name": "core-foundation",
+      "version": "0.9.3"
+    },
+    {
+      "name": "core-foundation-sys",
+      "version": "0.8.3"
+    },
+    {
+      "name": "encoding_rs",
+      "version": "0.8.31"
+    },
+    {
+      "name": "fastrand",
+      "version": "1.7.0"
+    },
+    {
+      "name": "fnv",
+      "version": "1.0.7"
+    },
+    {
+      "name": "foreign-types",
+      "version": "0.3.2"
+    },
+    {
+      "name": "foreign-types-shared",
+      "version": "0.1.1"
+    },
+    {
+      "name": "form_urlencoded",
+      "version": "1.0.1"
+    },
+    {
+      "name": "futures-channel",
+      "version": "0.3.21"
+    },
+    {
+      "name": "futures-core",
+      "version": "0.3.21"
+    },
+    {
+      "name": "futures-sink",
+      "version": "0.3.21"
+    },
+    {
+      "name": "futures-task",
+      "version": "0.3.21"
+    },
+    {
+      "name": "futures-util",
+      "version": "0.3.21"
+    },
+    {
+      "name": "h2",
+      "version": "0.3.13"
+    },
+    {
+      "name": "hashbrown",
+      "version": "0.11.2"
+    },
+    {
+      "name": "hermit-abi",
+      "version": "0.1.19"
+    },
+    {
+      "name": "http",
+      "version": "0.2.6"
+    },
+    {
+      "name": "http-body",
+      "version": "0.4.4"
+    },
+    {
+      "name": "httparse",
+      "version": "1.7.0"
+    },
+    {
+      "name": "httpdate",
+      "version": "1.0.2"
+    },
+    {
+      "name": "hyper",
+      "version": "0.14.18"
+    },
+    {
+      "name": "hyper-tls",
+      "version": "0.5.0"
+    },
+    {
+      "name": "idna",
+      "version": "0.2.3"
+    },
+    {
+      "name": "indexmap",
+      "version": "1.8.1"
+    },
+    {
+      "name": "instant",
+      "version": "0.1.12"
+    },
+    {
+      "name": "ipnet",
+      "version": "2.4.0"
+    },
+    {
+      "name": "itoa",
+      "version": "1.0.1"
+    },
+    {
+      "name": "js-sys",
+      "version": "0.3.57"
+    },
+    {
+      "name": "lazy_static",
+      "version": "1.4.0"
+    },
+    {
+      "name": "libc",
+      "version": "0.2.123"
+    },
+    {
+      "name": "log",
+      "version": "0.4.16"
+    },
+    {
+      "name": "matches",
+      "version": "0.1.9"
+    },
+    {
+      "name": "memchr",
+      "version": "2.4.1"
+    },
+    {
+      "name": "mime",
+      "version": "0.3.16"
+    },
+    {
+      "name": "mio",
+      "version": "0.8.2"
+    },
+    {
+      "name": "miow",
+      "version": "0.3.7"
+    },
+    {
+      "name": "native-tls",
+      "version": "0.2.10"
+    },
+    {
+      "name": "ntapi",
+      "version": "0.3.7"
+    },
+    {
+      "name": "once_cell",
+      "version": "1.10.0"
+    },
+    {
+      "name": "openssl",
+      "version": "0.10.38"
+    },
+    {
+      "name": "openssl-probe",
+      "version": "0.1.5"
+    },
+    {
+      "name": "openssl-sys",
+      "version": "0.9.72"
+    },
+    {
+      "name": "os_str_bytes",
+      "version": "6.0.0"
+    },
+    {
+      "name": "percent-encoding",
+      "version": "2.1.0"
+    },
+    {
+      "name": "pin-project-lite",
+      "version": "0.2.8"
+    },
+    {
+      "name": "pin-utils",
+      "version": "0.1.0"
+    },
+    {
+      "name": "pkg-config",
+      "version": "0.3.25"
+    },
+    {
+      "name": "proc-macro2",
+      "version": "1.0.37@git:4445659b0f753a928059244c875a58bb12f791e9"
+    },
+    {
+      "name": "proc-macro2",
+      "version": "1.0.37"
+    },
+    {
+      "name": "quote",
+      "version": "1.0.18"
+    },
+    {
+      "name": "redox_syscall",
+      "version": "0.2.13"
+    },
+    {
+      "name": "remove_dir_all",
+      "version": "0.5.3"
+    },
+    {
+      "name": "reqwest",
+      "version": "0.11.10"
+    },
+    {
+      "name": "ryu",
+      "version": "1.0.9"
+    },
+    {
+      "name": "schannel",
+      "version": "0.1.19"
+    },
+    {
+      "name": "security-framework",
+      "version": "2.6.1"
+    },
+    {
+      "name": "security-framework-sys",
+      "version": "2.6.1"
+    },
+    {
+      "name": "serde_json",
+      "version": "1.0.79"
+    },
+    {
+      "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": "termcolor",
+      "version": "1.1.3"
+    },
+    {
+      "name": "textwrap",
+      "version": "0.15.0"
+    },
+    {
+      "name": "tinyvec",
+      "version": "1.5.1"
+    },
+    {
+      "name": "tinyvec_macros",
+      "version": "0.1.0"
+    },
+    {
+      "name": "tokio",
+      "version": "1.17.0"
+    },
+    {
+      "name": "tokio-native-tls",
+      "version": "0.3.0"
+    },
+    {
+      "name": "tokio-util",
+      "version": "0.7.1"
+    },
+    {
+      "name": "tower-service",
+      "version": "0.3.1"
+    },
+    {
+      "name": "tracing-attributes",
+      "version": "0.1.20"
+    },
+    {
+      "name": "tracing-core",
+      "version": "0.1.25"
+    },
+    {
+      "name": "try-lock",
+      "version": "0.2.3"
+    },
+    {
+      "name": "unicode-normalization",
+      "version": "0.1.19"
+    },
+    {
+      "name": "unicode-xid",
+      "version": "0.2.2"
+    },
+    {
+      "name": "url",
+      "version": "2.2.2"
+    },
+    {
+      "name": "vcpkg",
+      "version": "0.2.15"
+    },
+    {
+      "name": "want",
+      "version": "0.3.0"
+    },
+    {
+      "name": "wasi",
+      "version": "0.11.0+wasi-snapshot-preview1"
+    },
+    {
+      "name": "wasm-bindgen",
+      "version": "0.2.80"
+    },
+    {
+      "name": "wasm-bindgen-backend",
+      "version": "0.2.80"
+    },
+    {
+      "name": "wasm-bindgen-futures",
+      "version": "0.4.30"
+    },
+    {
+      "name": "wasm-bindgen-macro",
+      "version": "0.2.80"
+    },
+    {
+      "name": "wasm-bindgen-macro-support",
+      "version": "0.2.80"
+    },
+    {
+      "name": "wasm-bindgen-shared",
+      "version": "0.2.80"
+    },
+    {
+      "name": "web-sys",
+      "version": "0.3.57"
+    },
+    {
+      "name": "winapi",
+      "version": "0.3.9"
+    },
+    {
+      "name": "winapi-i686-pc-windows-gnu",
+      "version": "0.4.0"
+    },
+    {
+      "name": "winapi-util",
+      "version": "0.1.5"
+    },
+    {
+      "name": "winapi-x86_64-pc-windows-gnu",
+      "version": "0.4.0"
+    },
+    {
+      "name": "winreg",
+      "version": "0.10.1"
+    }
+  ]
+}
+stderr:
+
diff --git a/tests/snapshots/test_cli__test-project-suggest-json.snap b/tests/snapshots/test_cli__test-project-suggest-json.snap
new file mode 100644
index 0000000..56546f8
--- /dev/null
+++ b/tests/snapshots/test_cli__test-project-suggest-json.snap
@@ -0,0 +1,3813 @@
+---
+source: tests/test-cli.rs
+expression: format_outputs(&output)
+---
+stdout:
+{
+  "conclusion": "fail (vetting)",
+  "failures": [
+    {
+      "name": "bumpalo",
+      "version": "3.9.1",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "bytes",
+      "version": "1.1.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "cc",
+      "version": "1.0.73",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "cfg-if",
+      "version": "0.1.10",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "cfg-if",
+      "version": "1.0.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "core-foundation",
+      "version": "0.9.3",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "core-foundation-sys",
+      "version": "0.8.3",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "encoding_rs",
+      "version": "0.8.31",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "fastrand",
+      "version": "1.7.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "fnv",
+      "version": "1.0.7",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "foreign-types",
+      "version": "0.3.2",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "foreign-types-shared",
+      "version": "0.1.1",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "form_urlencoded",
+      "version": "1.0.1",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "futures-channel",
+      "version": "0.3.21",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "futures-core",
+      "version": "0.3.21",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "futures-sink",
+      "version": "0.3.21",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "futures-task",
+      "version": "0.3.21",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "futures-util",
+      "version": "0.3.21",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "h2",
+      "version": "0.3.13",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "hashbrown",
+      "version": "0.11.2",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "hermit-abi",
+      "version": "0.1.19",
+      "missing_criteria": [
+        "safe-to-run"
+      ]
+    },
+    {
+      "name": "http",
+      "version": "0.2.6",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "http-body",
+      "version": "0.4.4",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "httparse",
+      "version": "1.7.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "httpdate",
+      "version": "1.0.2",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "hyper",
+      "version": "0.14.18",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "hyper-tls",
+      "version": "0.5.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "idna",
+      "version": "0.2.3",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "indexmap",
+      "version": "1.8.1",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "instant",
+      "version": "0.1.12",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "ipnet",
+      "version": "2.4.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "itoa",
+      "version": "1.0.1",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "js-sys",
+      "version": "0.3.57",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "lazy_static",
+      "version": "1.4.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "libc",
+      "version": "0.2.123",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "log",
+      "version": "0.4.16",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "matches",
+      "version": "0.1.9",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "memchr",
+      "version": "2.4.1",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "mime",
+      "version": "0.3.16",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "mio",
+      "version": "0.8.2",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "miow",
+      "version": "0.3.7",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "native-tls",
+      "version": "0.2.10",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "ntapi",
+      "version": "0.3.7",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "once_cell",
+      "version": "1.10.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "openssl",
+      "version": "0.10.38",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "openssl-probe",
+      "version": "0.1.5",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "openssl-sys",
+      "version": "0.9.72",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "os_str_bytes",
+      "version": "6.0.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "percent-encoding",
+      "version": "2.1.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "pin-project-lite",
+      "version": "0.2.8",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "pin-utils",
+      "version": "0.1.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "pkg-config",
+      "version": "0.3.25",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "proc-macro2",
+      "version": "1.0.37@git:4445659b0f753a928059244c875a58bb12f791e9",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "proc-macro2",
+      "version": "1.0.37",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "quote",
+      "version": "1.0.18",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "redox_syscall",
+      "version": "0.2.13",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "remove_dir_all",
+      "version": "0.5.3",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "reqwest",
+      "version": "0.11.10",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "ryu",
+      "version": "1.0.9",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "schannel",
+      "version": "0.1.19",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "security-framework",
+      "version": "2.6.1",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "security-framework-sys",
+      "version": "2.6.1",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "serde_json",
+      "version": "1.0.79",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "serde_urlencoded",
+      "version": "0.7.1",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "slab",
+      "version": "0.4.6",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "socket2",
+      "version": "0.4.4",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "syn",
+      "version": "1.0.91",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "tempfile",
+      "version": "3.3.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "termcolor",
+      "version": "1.1.3",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "textwrap",
+      "version": "0.15.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "tinyvec",
+      "version": "1.5.1",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "tinyvec_macros",
+      "version": "0.1.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "tokio",
+      "version": "1.17.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "tokio-native-tls",
+      "version": "0.3.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "tokio-util",
+      "version": "0.7.1",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "tower-service",
+      "version": "0.3.1",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "tracing-attributes",
+      "version": "0.1.20",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "tracing-core",
+      "version": "0.1.25",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "try-lock",
+      "version": "0.2.3",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "unicode-bidi",
+      "version": "0.3.7",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "unicode-normalization",
+      "version": "0.1.19",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "unicode-xid",
+      "version": "0.2.2",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "url",
+      "version": "2.2.2",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "vcpkg",
+      "version": "0.2.15",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "want",
+      "version": "0.3.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "wasi",
+      "version": "0.11.0+wasi-snapshot-preview1",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "wasm-bindgen",
+      "version": "0.2.80",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "wasm-bindgen-backend",
+      "version": "0.2.80",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "wasm-bindgen-futures",
+      "version": "0.4.30",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "wasm-bindgen-macro",
+      "version": "0.2.80",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "wasm-bindgen-macro-support",
+      "version": "0.2.80",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "wasm-bindgen-shared",
+      "version": "0.2.80",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "web-sys",
+      "version": "0.3.57",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "winapi",
+      "version": "0.3.9",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "winapi-i686-pc-windows-gnu",
+      "version": "0.4.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "winapi-util",
+      "version": "0.1.5",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "winapi-x86_64-pc-windows-gnu",
+      "version": "0.4.0",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    },
+    {
+      "name": "winreg",
+      "version": "0.10.1",
+      "missing_criteria": [
+        "safe-to-deploy"
+      ]
+    }
+  ],
+  "suggest": {
+    "suggestions": [
+      {
+        "name": "proc-macro2",
+        "notable_parents": "test-project",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": "1.0.37",
+          "to": "1.0.37@git:4445659b0f753a928059244c875a58bb12f791e9",
+          "diffstat": {
+            "insertions": 63,
+            "deletions": 7,
+            "files_changed": 3
+          }
+        }
+      },
+      {
+        "name": "tinyvec_macros",
+        "notable_parents": "tinyvec",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.1.0",
+          "diffstat": {
+            "insertions": 81,
+            "deletions": 0,
+            "files_changed": 5
+          }
+        }
+      },
+      {
+        "name": "matches",
+        "notable_parents": "url, idna, and form_urlencoded",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.1.9",
+          "diffstat": {
+            "insertions": 210,
+            "deletions": 0,
+            "files_changed": 6
+          }
+        }
+      },
+      {
+        "name": "foreign-types-shared",
+        "notable_parents": "foreign-types",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.1.1",
+          "diffstat": {
+            "insertions": 302,
+            "deletions": 0,
+            "files_changed": 5
+          }
+        }
+      },
+      {
+        "name": "try-lock",
+        "notable_parents": "want",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.2.3",
+          "diffstat": {
+            "insertions": 380,
+            "deletions": 0,
+            "files_changed": 6
+          }
+        }
+      },
+      {
+        "name": "openssl-probe",
+        "notable_parents": "native-tls",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.1.5",
+          "diffstat": {
+            "insertions": 442,
+            "deletions": 0,
+            "files_changed": 9
+          }
+        }
+      },
+      {
+        "name": "tower-service",
+        "notable_parents": "hyper",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.3.1",
+          "diffstat": {
+            "insertions": 501,
+            "deletions": 0,
+            "files_changed": 6
+          }
+        }
+      },
+      {
+        "name": "wasm-bindgen-shared",
+        "notable_parents": "wasm-bindgen-backend and wasm-bindgen-macro-support",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.2.80",
+          "diffstat": {
+            "insertions": 515,
+            "deletions": 0,
+            "files_changed": 7
+          }
+        }
+      },
+      {
+        "name": "pin-utils",
+        "notable_parents": "futures-util",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.1.0",
+          "diffstat": {
+            "insertions": 538,
+            "deletions": 0,
+            "files_changed": 13
+          }
+        }
+      },
+      {
+        "name": "futures-sink",
+        "notable_parents": "h2 and tokio-util",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.3.21",
+          "diffstat": {
+            "insertions": 544,
+            "deletions": 0,
+            "files_changed": 6
+          }
+        }
+      },
+      {
+        "name": "cfg-if",
+        "notable_parents": "log, instant, openssl, and 6 others",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.1.10",
+          "diffstat": {
+            "insertions": 579,
+            "deletions": 0,
+            "files_changed": 9
+          }
+        }
+      },
+      {
+        "name": "foreign-types",
+        "notable_parents": "openssl",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.3.2",
+          "diffstat": {
+            "insertions": 583,
+            "deletions": 0,
+            "files_changed": 6
+          }
+        }
+      },
+      {
+        "name": "remove_dir_all",
+        "notable_parents": "tempfile",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.5.3",
+          "diffstat": {
+            "insertions": 592,
+            "deletions": 0,
+            "files_changed": 7
+          }
+        }
+      },
+      {
+        "name": "instant",
+        "notable_parents": "fastrand",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.1.12",
+          "diffstat": {
+            "insertions": 672,
+            "deletions": 0,
+            "files_changed": 12
+          }
+        }
+      },
+      {
+        "name": "hyper-tls",
+        "notable_parents": "reqwest",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.5.0",
+          "diffstat": {
+            "insertions": 673,
+            "deletions": 0,
+            "files_changed": 11
+          }
+        }
+      },
+      {
+        "name": "form_urlencoded",
+        "notable_parents": "url and serde_urlencoded",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "1.0.1",
+          "diffstat": {
+            "insertions": 689,
+            "deletions": 0,
+            "files_changed": 5
+          }
+        }
+      },
+      {
+        "name": "want",
+        "notable_parents": "hyper",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.3.0",
+          "diffstat": {
+            "insertions": 697,
+            "deletions": 0,
+            "files_changed": 7
+          }
+        }
+      },
+      {
+        "name": "percent-encoding",
+        "notable_parents": "url, reqwest, and form_urlencoded",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "2.1.0",
+          "diffstat": {
+            "insertions": 702,
+            "deletions": 0,
+            "files_changed": 5
+          }
+        }
+      },
+      {
+        "name": "fnv",
+        "notable_parents": "h2 and http",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "1.0.7",
+          "diffstat": {
+            "insertions": 730,
+            "deletions": 0,
+            "files_changed": 8
+          }
+        }
+      },
+      {
+        "name": "itoa",
+        "notable_parents": "http, hyper, serde_json, and 1 other",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "1.0.1",
+          "diffstat": {
+            "insertions": 789,
+            "deletions": 0,
+            "files_changed": 13
+          }
+        }
+      },
+      {
+        "name": "lazy_static",
+        "notable_parents": "reqwest, schannel, and 3 others",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "1.4.0",
+          "diffstat": {
+            "insertions": 876,
+            "deletions": 0,
+            "files_changed": 11
+          }
+        }
+      },
+      {
+        "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",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "1.0.2",
+          "diffstat": {
+            "insertions": 995,
+            "deletions": 0,
+            "files_changed": 10
+          }
+        }
+      },
+      {
+        "name": "tokio-native-tls",
+        "notable_parents": "reqwest and hyper-tls",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.3.0",
+          "diffstat": {
+            "insertions": 1089,
+            "deletions": 0,
+            "files_changed": 16
+          }
+        }
+      },
+      {
+        "name": "winapi-util",
+        "notable_parents": "termcolor",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.1.5",
+          "diffstat": {
+            "insertions": 1095,
+            "deletions": 0,
+            "files_changed": 13
+          }
+        }
+      },
+      {
+        "name": "winapi-i686-pc-windows-gnu",
+        "notable_parents": "winapi",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.4.0",
+          "diffstat": {
+            "insertions": 1132,
+            "deletions": 0,
+            "files_changed": 1391
+          }
+        }
+      },
+      {
+        "name": "winapi-x86_64-pc-windows-gnu",
+        "notable_parents": "winapi",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.4.0",
+          "diffstat": {
+            "insertions": 1167,
+            "deletions": 0,
+            "files_changed": 1420
+          }
+        }
+      },
+      {
+        "name": "futures-core",
+        "notable_parents": "h2, hyper, reqwest, and 3 others",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.3.21",
+          "diffstat": {
+            "insertions": 1175,
+            "deletions": 0,
+            "files_changed": 14
+          }
+        }
+      },
+      {
+        "name": "futures-task",
+        "notable_parents": "futures-util",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.3.21",
+          "diffstat": {
+            "insertions": 1180,
+            "deletions": 0,
+            "files_changed": 14
+          }
+        }
+      },
+      {
+        "name": "http-body",
+        "notable_parents": "hyper and reqwest",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.4.4",
+          "diffstat": {
+            "insertions": 1256,
+            "deletions": 0,
+            "files_changed": 17
+          }
+        }
+      },
+      {
+        "name": "wasm-bindgen-macro",
+        "notable_parents": "wasm-bindgen",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.2.80",
+          "diffstat": {
+            "insertions": 1274,
+            "deletions": 0,
+            "files_changed": 45
+          }
+        }
+      },
+      {
+        "name": "wasm-bindgen-futures",
+        "notable_parents": "reqwest",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.4.30",
+          "diffstat": {
+            "insertions": 1303,
+            "deletions": 0,
+            "files_changed": 12
+          }
+        }
+      },
+      {
+        "name": "fastrand",
+        "notable_parents": "tempfile",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "1.7.0",
+          "diffstat": {
+            "insertions": 1368,
+            "deletions": 0,
+            "files_changed": 10
+          }
+        }
+      },
+      {
+        "name": "mime",
+        "notable_parents": "reqwest",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.3.16",
+          "diffstat": {
+            "insertions": 1715,
+            "deletions": 0,
+            "files_changed": 13
+          }
+        }
+      },
+      {
+        "name": "pkg-config",
+        "notable_parents": "openssl-sys",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.3.25",
+          "diffstat": {
+            "insertions": 1747,
+            "deletions": 0,
+            "files_changed": 14
+          }
+        }
+      },
+      {
+        "name": "core-foundation-sys",
+        "notable_parents": "core-foundation and 2 others",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.8.3",
+          "diffstat": {
+            "insertions": 1965,
+            "deletions": 0,
+            "files_changed": 26
+          }
+        }
+      },
+      {
+        "name": "wasm-bindgen-macro-support",
+        "notable_parents": "wasm-bindgen-macro",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.2.80",
+          "diffstat": {
+            "insertions": 1965,
+            "deletions": 0,
+            "files_changed": 6
+          }
+        }
+      },
+      {
+        "name": "security-framework-sys",
+        "notable_parents": "native-tls and security-framework",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "2.6.1",
+          "diffstat": {
+            "insertions": 2016,
+            "deletions": 0,
+            "files_changed": 27
+          }
+        }
+      },
+      {
+        "name": "unicode-xid",
+        "notable_parents": "syn and proc-macro2",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.2.2",
+          "diffstat": {
+            "insertions": 2044,
+            "deletions": 0,
+            "files_changed": 12
+          }
+        }
+      },
+      {
+        "name": "serde_urlencoded",
+        "notable_parents": "reqwest",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.7.1",
+          "diffstat": {
+            "insertions": 2120,
+            "deletions": 0,
+            "files_changed": 17
+          }
+        }
+      },
+      {
+        "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",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.4.6",
+          "diffstat": {
+            "insertions": 2615,
+            "deletions": 0,
+            "files_changed": 9
+          }
+        }
+      },
+      {
+        "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",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.2.80",
+          "diffstat": {
+            "insertions": 3018,
+            "deletions": 0,
+            "files_changed": 10
+          }
+        }
+      },
+      {
+        "name": "miow",
+        "notable_parents": "mio",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.3.7",
+          "diffstat": {
+            "insertions": 3129,
+            "deletions": 0,
+            "files_changed": 14
+          }
+        }
+      },
+      {
+        "name": "unicode-bidi",
+        "notable_parents": "idna",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.3.7",
+          "diffstat": {
+            "insertions": 3217,
+            "deletions": 0,
+            "files_changed": 20
+          }
+        }
+      },
+      {
+        "name": "wasi",
+        "notable_parents": "mio",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.11.0+wasi-snapshot-preview1",
+          "diffstat": {
+            "insertions": 3302,
+            "deletions": 0,
+            "files_changed": 15
+          }
+        }
+      },
+      {
+        "name": "native-tls",
+        "notable_parents": "reqwest, hyper-tls, and tokio-native-tls",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.2.10",
+          "diffstat": {
+            "insertions": 3715,
+            "deletions": 0,
+            "files_changed": 18
+          }
+        }
+      },
+      {
+        "name": "once_cell",
+        "notable_parents": "openssl",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "1.10.0",
+          "diffstat": {
+            "insertions": 3766,
+            "deletions": 0,
+            "files_changed": 21
+          }
+        }
+      },
+      {
+        "name": "quote",
+        "notable_parents": "syn, tracing-attributes, and 3 others",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "1.0.18",
+          "diffstat": {
+            "insertions": 3838,
+            "deletions": 0,
+            "files_changed": 33
+          }
+        }
+      },
+      {
+        "name": "redox_syscall",
+        "notable_parents": "tempfile",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.2.13",
+          "diffstat": {
+            "insertions": 3890,
+            "deletions": 0,
+            "files_changed": 30
+          }
+        }
+      },
+      {
+        "name": "winreg",
+        "notable_parents": "reqwest",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.10.1",
+          "diffstat": {
+            "insertions": 3920,
+            "deletions": 0,
+            "files_changed": 26
+          }
+        }
+      },
+      {
+        "name": "core-foundation",
+        "notable_parents": "security-framework",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.9.3",
+          "diffstat": {
+            "insertions": 3953,
+            "deletions": 0,
+            "files_changed": 26
+          }
+        }
+      },
+      {
+        "name": "tracing-attributes",
+        "notable_parents": "tracing",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.1.20",
+          "diffstat": {
+            "insertions": 3957,
+            "deletions": 0,
+            "files_changed": 18
+          }
+        }
+      },
+      {
+        "name": "ipnet",
+        "notable_parents": "reqwest",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "2.4.0",
+          "diffstat": {
+            "insertions": 3973,
+            "deletions": 0,
+            "files_changed": 13
+          }
+        }
+      },
+      {
+        "name": "futures-channel",
+        "notable_parents": "hyper",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.3.21",
+          "diffstat": {
+            "insertions": 3992,
+            "deletions": 0,
+            "files_changed": 18
+          }
+        }
+      },
+      {
+        "name": "tempfile",
+        "notable_parents": "native-tls",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "3.3.0",
+          "diffstat": {
+            "insertions": 4059,
+            "deletions": 0,
+            "files_changed": 22
+          }
+        }
+      },
+      {
+        "name": "ryu",
+        "notable_parents": "serde_json and serde_urlencoded",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "1.0.9",
+          "diffstat": {
+            "insertions": 4362,
+            "deletions": 0,
+            "files_changed": 34
+          }
+        }
+      },
+      {
+        "name": "schannel",
+        "notable_parents": "native-tls",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.1.19",
+          "diffstat": {
+            "insertions": 4601,
+            "deletions": 0,
+            "files_changed": 32
+          }
+        }
+      },
+      {
+        "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, native-tls, and 1 other",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.4.16",
+          "diffstat": {
+            "insertions": 5625,
+            "deletions": 0,
+            "files_changed": 19
+          }
+        }
+      },
+      {
+        "name": "proc-macro2",
+        "notable_parents": "syn, quote, test-project, and 3 others",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "1.0.37",
+          "diffstat": {
+            "insertions": 5695,
+            "deletions": 0,
+            "files_changed": 20
+          }
+        }
+      },
+      {
+        "name": "socket2",
+        "notable_parents": "hyper and tokio",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.4.4",
+          "diffstat": {
+            "insertions": 6011,
+            "deletions": 0,
+            "files_changed": 11
+          }
+        }
+      },
+      {
+        "name": "pin-project-lite",
+        "notable_parents": "hyper, tokio, reqwest, and 4 others",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.2.8",
+          "diffstat": {
+            "insertions": 6100,
+            "deletions": 0,
+            "files_changed": 70
+          }
+        }
+      },
+      {
+        "name": "tracing-core",
+        "notable_parents": "tracing",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.1.25",
+          "diffstat": {
+            "insertions": 6156,
+            "deletions": 0,
+            "files_changed": 26
+          }
+        }
+      },
+      {
+        "name": "cc",
+        "notable_parents": "openssl-sys",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "1.0.73",
+          "diffstat": {
+            "insertions": 6554,
+            "deletions": 0,
+            "files_changed": 19
+          }
+        }
+      },
+      {
+        "name": "httparse",
+        "notable_parents": "hyper",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "1.7.0",
+          "diffstat": {
+            "insertions": 7258,
+            "deletions": 0,
+            "files_changed": 18
+          }
+        }
+      },
+      {
+        "name": "memchr",
+        "notable_parents": "tokio and os_str_bytes",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "2.4.1",
+          "diffstat": {
+            "insertions": 8712,
+            "deletions": 0,
+            "files_changed": 46
+          }
+        }
+      },
+      {
+        "name": "bytes",
+        "notable_parents": "h2, http, hyper, tokio, and 4 others",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "1.1.0",
+          "diffstat": {
+            "insertions": 9207,
+            "deletions": 0,
+            "files_changed": 43
+          }
+        }
+      },
+      {
+        "name": "security-framework",
+        "notable_parents": "native-tls",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "2.6.1",
+          "diffstat": {
+            "insertions": 9316,
+            "deletions": 0,
+            "files_changed": 42
+          }
+        }
+      },
+      {
+        "name": "indexmap",
+        "notable_parents": "h2 and 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 and native-tls",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.9.72",
+          "diffstat": {
+            "insertions": 9477,
+            "deletions": 0,
+            "files_changed": 46
+          }
+        }
+      },
+      {
+        "name": "bumpalo",
+        "notable_parents": "wasm-bindgen-backend",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "3.9.1",
+          "diffstat": {
+            "insertions": 10099,
+            "deletions": 0,
+            "files_changed": 17
+          }
+        }
+      },
+      {
+        "name": "mio",
+        "notable_parents": "tokio",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.8.2",
+          "diffstat": {
+            "insertions": 11719,
+            "deletions": 0,
+            "files_changed": 62
+          }
+        }
+      },
+      {
+        "name": "js-sys",
+        "notable_parents": "reqwest, web-sys, and 1 other",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.3.57",
+          "diffstat": {
+            "insertions": 12874,
+            "deletions": 0,
+            "files_changed": 62
+          }
+        }
+      },
+      {
+        "name": "tokio-util",
+        "notable_parents": "h2",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.7.1",
+          "diffstat": {
+            "insertions": 13870,
+            "deletions": 0,
+            "files_changed": 65
+          }
+        }
+      },
+      {
+        "name": "hashbrown",
+        "notable_parents": "indexmap",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.11.2",
+          "diffstat": {
+            "insertions": 14603,
+            "deletions": 0,
+            "files_changed": 31
+          }
+        }
+      },
+      {
+        "name": "url",
+        "notable_parents": "reqwest",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "2.2.2",
+          "diffstat": {
+            "insertions": 16344,
+            "deletions": 0,
+            "files_changed": 15
+          }
+        }
+      },
+      {
+        "name": "tinyvec",
+        "notable_parents": "unicode-normalization",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "1.5.1",
+          "diffstat": {
+            "insertions": 16751,
+            "deletions": 0,
+            "files_changed": 25
+          }
+        }
+      },
+      {
+        "name": "http",
+        "notable_parents": "h2, hyper, reqwest, and http-body",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.2.6",
+          "diffstat": {
+            "insertions": 16819,
+            "deletions": 0,
+            "files_changed": 39
+          }
+        }
+      },
+      {
+        "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, and 1 other",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.2.80",
+          "diffstat": {
+            "insertions": 20977,
+            "deletions": 0,
+            "files_changed": 242
+          }
+        }
+      },
+      {
+        "name": "ntapi",
+        "notable_parents": "mio",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.3.7",
+          "diffstat": {
+            "insertions": 21227,
+            "deletions": 0,
+            "files_changed": 42
+          }
+        }
+      },
+      {
+        "name": "serde_json",
+        "notable_parents": "reqwest and 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 and hyper-tls",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.14.18",
+          "diffstat": {
+            "insertions": 24841,
+            "deletions": 0,
+            "files_changed": 69
+          }
+        }
+      },
+      {
+        "name": "futures-util",
+        "notable_parents": "h2, hyper, and reqwest",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.3.21",
+          "diffstat": {
+            "insertions": 25067,
+            "deletions": 0,
+            "files_changed": 185
+          }
+        }
+      },
+      {
+        "name": "h2",
+        "notable_parents": "hyper and reqwest",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.3.13",
+          "diffstat": {
+            "insertions": 25417,
+            "deletions": 0,
+            "files_changed": 63
+          }
+        }
+      },
+      {
+        "name": "openssl",
+        "notable_parents": "native-tls",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.10.38",
+          "diffstat": {
+            "insertions": 28435,
+            "deletions": 0,
+            "files_changed": 82
+          }
+        }
+      },
+      {
+        "name": "syn",
+        "notable_parents": "tracing-attributes and 2 others",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": "1.0.0",
+          "to": "1.0.91",
+          "diffstat": {
+            "insertions": 21772,
+            "deletions": 6698,
+            "files_changed": 92
+          }
+        }
+      },
+      {
+        "name": "unicode-normalization",
+        "notable_parents": "idna",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.1.19",
+          "diffstat": {
+            "insertions": 28622,
+            "deletions": 0,
+            "files_changed": 24
+          }
+        }
+      },
+      {
+        "name": "idna",
+        "notable_parents": "url",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.2.3",
+          "diffstat": {
+            "insertions": 32532,
+            "deletions": 0,
+            "files_changed": 17
+          }
+        }
+      },
+      {
+        "name": "vcpkg",
+        "notable_parents": "openssl-sys",
+        "suggested_criteria": [
+          "safe-to-deploy"
+        ],
+        "suggested_diff": {
+          "from": null,
+          "to": "0.2.15",
+          "diffstat": {
+            "insertions": 42642,
+            "deletions": 0,
+            "files_changed": 832
+          }
+        }
+      },
+      {
+        "name": "tokio",
+        "notable_parents": "h2, hyper, reqwest, hyper-tls, and 3 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, tokio, and 6 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 and 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": "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": "proc-macro2",
+          "notable_parents": "test-project",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": "1.0.37",
+            "to": "1.0.37@git:4445659b0f753a928059244c875a58bb12f791e9",
+            "diffstat": {
+              "insertions": 63,
+              "deletions": 7,
+              "files_changed": 3
+            }
+          }
+        },
+        {
+          "name": "tinyvec_macros",
+          "notable_parents": "tinyvec",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.1.0",
+            "diffstat": {
+              "insertions": 81,
+              "deletions": 0,
+              "files_changed": 5
+            }
+          }
+        },
+        {
+          "name": "matches",
+          "notable_parents": "url, idna, and form_urlencoded",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.1.9",
+            "diffstat": {
+              "insertions": 210,
+              "deletions": 0,
+              "files_changed": 6
+            }
+          }
+        },
+        {
+          "name": "foreign-types-shared",
+          "notable_parents": "foreign-types",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.1.1",
+            "diffstat": {
+              "insertions": 302,
+              "deletions": 0,
+              "files_changed": 5
+            }
+          }
+        },
+        {
+          "name": "try-lock",
+          "notable_parents": "want",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.2.3",
+            "diffstat": {
+              "insertions": 380,
+              "deletions": 0,
+              "files_changed": 6
+            }
+          }
+        },
+        {
+          "name": "openssl-probe",
+          "notable_parents": "native-tls",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.1.5",
+            "diffstat": {
+              "insertions": 442,
+              "deletions": 0,
+              "files_changed": 9
+            }
+          }
+        },
+        {
+          "name": "tower-service",
+          "notable_parents": "hyper",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.3.1",
+            "diffstat": {
+              "insertions": 501,
+              "deletions": 0,
+              "files_changed": 6
+            }
+          }
+        },
+        {
+          "name": "wasm-bindgen-shared",
+          "notable_parents": "wasm-bindgen-backend and wasm-bindgen-macro-support",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.2.80",
+            "diffstat": {
+              "insertions": 515,
+              "deletions": 0,
+              "files_changed": 7
+            }
+          }
+        },
+        {
+          "name": "pin-utils",
+          "notable_parents": "futures-util",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.1.0",
+            "diffstat": {
+              "insertions": 538,
+              "deletions": 0,
+              "files_changed": 13
+            }
+          }
+        },
+        {
+          "name": "futures-sink",
+          "notable_parents": "h2 and tokio-util",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.3.21",
+            "diffstat": {
+              "insertions": 544,
+              "deletions": 0,
+              "files_changed": 6
+            }
+          }
+        },
+        {
+          "name": "cfg-if",
+          "notable_parents": "log, instant, openssl, and 6 others",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.1.10",
+            "diffstat": {
+              "insertions": 579,
+              "deletions": 0,
+              "files_changed": 9
+            }
+          }
+        },
+        {
+          "name": "foreign-types",
+          "notable_parents": "openssl",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.3.2",
+            "diffstat": {
+              "insertions": 583,
+              "deletions": 0,
+              "files_changed": 6
+            }
+          }
+        },
+        {
+          "name": "remove_dir_all",
+          "notable_parents": "tempfile",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.5.3",
+            "diffstat": {
+              "insertions": 592,
+              "deletions": 0,
+              "files_changed": 7
+            }
+          }
+        },
+        {
+          "name": "instant",
+          "notable_parents": "fastrand",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.1.12",
+            "diffstat": {
+              "insertions": 672,
+              "deletions": 0,
+              "files_changed": 12
+            }
+          }
+        },
+        {
+          "name": "hyper-tls",
+          "notable_parents": "reqwest",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.5.0",
+            "diffstat": {
+              "insertions": 673,
+              "deletions": 0,
+              "files_changed": 11
+            }
+          }
+        },
+        {
+          "name": "form_urlencoded",
+          "notable_parents": "url and serde_urlencoded",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "1.0.1",
+            "diffstat": {
+              "insertions": 689,
+              "deletions": 0,
+              "files_changed": 5
+            }
+          }
+        },
+        {
+          "name": "want",
+          "notable_parents": "hyper",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.3.0",
+            "diffstat": {
+              "insertions": 697,
+              "deletions": 0,
+              "files_changed": 7
+            }
+          }
+        },
+        {
+          "name": "percent-encoding",
+          "notable_parents": "url, reqwest, and form_urlencoded",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "2.1.0",
+            "diffstat": {
+              "insertions": 702,
+              "deletions": 0,
+              "files_changed": 5
+            }
+          }
+        },
+        {
+          "name": "fnv",
+          "notable_parents": "h2 and http",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "1.0.7",
+            "diffstat": {
+              "insertions": 730,
+              "deletions": 0,
+              "files_changed": 8
+            }
+          }
+        },
+        {
+          "name": "itoa",
+          "notable_parents": "http, hyper, serde_json, and 1 other",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "1.0.1",
+            "diffstat": {
+              "insertions": 789,
+              "deletions": 0,
+              "files_changed": 13
+            }
+          }
+        },
+        {
+          "name": "lazy_static",
+          "notable_parents": "reqwest, schannel, and 3 others",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "1.4.0",
+            "diffstat": {
+              "insertions": 876,
+              "deletions": 0,
+              "files_changed": 11
+            }
+          }
+        },
+        {
+          "name": "httpdate",
+          "notable_parents": "hyper",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "1.0.2",
+            "diffstat": {
+              "insertions": 995,
+              "deletions": 0,
+              "files_changed": 10
+            }
+          }
+        },
+        {
+          "name": "tokio-native-tls",
+          "notable_parents": "reqwest and hyper-tls",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.3.0",
+            "diffstat": {
+              "insertions": 1089,
+              "deletions": 0,
+              "files_changed": 16
+            }
+          }
+        },
+        {
+          "name": "winapi-util",
+          "notable_parents": "termcolor",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.1.5",
+            "diffstat": {
+              "insertions": 1095,
+              "deletions": 0,
+              "files_changed": 13
+            }
+          }
+        },
+        {
+          "name": "winapi-i686-pc-windows-gnu",
+          "notable_parents": "winapi",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.4.0",
+            "diffstat": {
+              "insertions": 1132,
+              "deletions": 0,
+              "files_changed": 1391
+            }
+          }
+        },
+        {
+          "name": "winapi-x86_64-pc-windows-gnu",
+          "notable_parents": "winapi",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.4.0",
+            "diffstat": {
+              "insertions": 1167,
+              "deletions": 0,
+              "files_changed": 1420
+            }
+          }
+        },
+        {
+          "name": "futures-core",
+          "notable_parents": "h2, hyper, reqwest, and 3 others",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.3.21",
+            "diffstat": {
+              "insertions": 1175,
+              "deletions": 0,
+              "files_changed": 14
+            }
+          }
+        },
+        {
+          "name": "futures-task",
+          "notable_parents": "futures-util",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.3.21",
+            "diffstat": {
+              "insertions": 1180,
+              "deletions": 0,
+              "files_changed": 14
+            }
+          }
+        },
+        {
+          "name": "http-body",
+          "notable_parents": "hyper and reqwest",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.4.4",
+            "diffstat": {
+              "insertions": 1256,
+              "deletions": 0,
+              "files_changed": 17
+            }
+          }
+        },
+        {
+          "name": "wasm-bindgen-macro",
+          "notable_parents": "wasm-bindgen",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.2.80",
+            "diffstat": {
+              "insertions": 1274,
+              "deletions": 0,
+              "files_changed": 45
+            }
+          }
+        },
+        {
+          "name": "wasm-bindgen-futures",
+          "notable_parents": "reqwest",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.4.30",
+            "diffstat": {
+              "insertions": 1303,
+              "deletions": 0,
+              "files_changed": 12
+            }
+          }
+        },
+        {
+          "name": "fastrand",
+          "notable_parents": "tempfile",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "1.7.0",
+            "diffstat": {
+              "insertions": 1368,
+              "deletions": 0,
+              "files_changed": 10
+            }
+          }
+        },
+        {
+          "name": "mime",
+          "notable_parents": "reqwest",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.3.16",
+            "diffstat": {
+              "insertions": 1715,
+              "deletions": 0,
+              "files_changed": 13
+            }
+          }
+        },
+        {
+          "name": "pkg-config",
+          "notable_parents": "openssl-sys",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.3.25",
+            "diffstat": {
+              "insertions": 1747,
+              "deletions": 0,
+              "files_changed": 14
+            }
+          }
+        },
+        {
+          "name": "core-foundation-sys",
+          "notable_parents": "core-foundation and 2 others",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.8.3",
+            "diffstat": {
+              "insertions": 1965,
+              "deletions": 0,
+              "files_changed": 26
+            }
+          }
+        },
+        {
+          "name": "wasm-bindgen-macro-support",
+          "notable_parents": "wasm-bindgen-macro",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.2.80",
+            "diffstat": {
+              "insertions": 1965,
+              "deletions": 0,
+              "files_changed": 6
+            }
+          }
+        },
+        {
+          "name": "security-framework-sys",
+          "notable_parents": "native-tls and security-framework",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "2.6.1",
+            "diffstat": {
+              "insertions": 2016,
+              "deletions": 0,
+              "files_changed": 27
+            }
+          }
+        },
+        {
+          "name": "unicode-xid",
+          "notable_parents": "syn and proc-macro2",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.2.2",
+            "diffstat": {
+              "insertions": 2044,
+              "deletions": 0,
+              "files_changed": 12
+            }
+          }
+        },
+        {
+          "name": "serde_urlencoded",
+          "notable_parents": "reqwest",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.7.1",
+            "diffstat": {
+              "insertions": 2120,
+              "deletions": 0,
+              "files_changed": 17
+            }
+          }
+        },
+        {
+          "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",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.4.6",
+            "diffstat": {
+              "insertions": 2615,
+              "deletions": 0,
+              "files_changed": 9
+            }
+          }
+        },
+        {
+          "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",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.2.80",
+            "diffstat": {
+              "insertions": 3018,
+              "deletions": 0,
+              "files_changed": 10
+            }
+          }
+        },
+        {
+          "name": "miow",
+          "notable_parents": "mio",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.3.7",
+            "diffstat": {
+              "insertions": 3129,
+              "deletions": 0,
+              "files_changed": 14
+            }
+          }
+        },
+        {
+          "name": "unicode-bidi",
+          "notable_parents": "idna",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.3.7",
+            "diffstat": {
+              "insertions": 3217,
+              "deletions": 0,
+              "files_changed": 20
+            }
+          }
+        },
+        {
+          "name": "wasi",
+          "notable_parents": "mio",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.11.0+wasi-snapshot-preview1",
+            "diffstat": {
+              "insertions": 3302,
+              "deletions": 0,
+              "files_changed": 15
+            }
+          }
+        },
+        {
+          "name": "native-tls",
+          "notable_parents": "reqwest, hyper-tls, and tokio-native-tls",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.2.10",
+            "diffstat": {
+              "insertions": 3715,
+              "deletions": 0,
+              "files_changed": 18
+            }
+          }
+        },
+        {
+          "name": "once_cell",
+          "notable_parents": "openssl",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "1.10.0",
+            "diffstat": {
+              "insertions": 3766,
+              "deletions": 0,
+              "files_changed": 21
+            }
+          }
+        },
+        {
+          "name": "quote",
+          "notable_parents": "syn, tracing-attributes, and 3 others",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "1.0.18",
+            "diffstat": {
+              "insertions": 3838,
+              "deletions": 0,
+              "files_changed": 33
+            }
+          }
+        },
+        {
+          "name": "redox_syscall",
+          "notable_parents": "tempfile",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.2.13",
+            "diffstat": {
+              "insertions": 3890,
+              "deletions": 0,
+              "files_changed": 30
+            }
+          }
+        },
+        {
+          "name": "winreg",
+          "notable_parents": "reqwest",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.10.1",
+            "diffstat": {
+              "insertions": 3920,
+              "deletions": 0,
+              "files_changed": 26
+            }
+          }
+        },
+        {
+          "name": "core-foundation",
+          "notable_parents": "security-framework",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.9.3",
+            "diffstat": {
+              "insertions": 3953,
+              "deletions": 0,
+              "files_changed": 26
+            }
+          }
+        },
+        {
+          "name": "tracing-attributes",
+          "notable_parents": "tracing",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.1.20",
+            "diffstat": {
+              "insertions": 3957,
+              "deletions": 0,
+              "files_changed": 18
+            }
+          }
+        },
+        {
+          "name": "ipnet",
+          "notable_parents": "reqwest",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "2.4.0",
+            "diffstat": {
+              "insertions": 3973,
+              "deletions": 0,
+              "files_changed": 13
+            }
+          }
+        },
+        {
+          "name": "futures-channel",
+          "notable_parents": "hyper",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.3.21",
+            "diffstat": {
+              "insertions": 3992,
+              "deletions": 0,
+              "files_changed": 18
+            }
+          }
+        },
+        {
+          "name": "tempfile",
+          "notable_parents": "native-tls",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "3.3.0",
+            "diffstat": {
+              "insertions": 4059,
+              "deletions": 0,
+              "files_changed": 22
+            }
+          }
+        },
+        {
+          "name": "ryu",
+          "notable_parents": "serde_json and serde_urlencoded",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "1.0.9",
+            "diffstat": {
+              "insertions": 4362,
+              "deletions": 0,
+              "files_changed": 34
+            }
+          }
+        },
+        {
+          "name": "schannel",
+          "notable_parents": "native-tls",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.1.19",
+            "diffstat": {
+              "insertions": 4601,
+              "deletions": 0,
+              "files_changed": 32
+            }
+          }
+        },
+        {
+          "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, native-tls, and 1 other",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.4.16",
+            "diffstat": {
+              "insertions": 5625,
+              "deletions": 0,
+              "files_changed": 19
+            }
+          }
+        },
+        {
+          "name": "proc-macro2",
+          "notable_parents": "syn, quote, test-project, and 3 others",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "1.0.37",
+            "diffstat": {
+              "insertions": 5695,
+              "deletions": 0,
+              "files_changed": 20
+            }
+          }
+        },
+        {
+          "name": "socket2",
+          "notable_parents": "hyper and tokio",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.4.4",
+            "diffstat": {
+              "insertions": 6011,
+              "deletions": 0,
+              "files_changed": 11
+            }
+          }
+        },
+        {
+          "name": "pin-project-lite",
+          "notable_parents": "hyper, tokio, reqwest, and 4 others",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.2.8",
+            "diffstat": {
+              "insertions": 6100,
+              "deletions": 0,
+              "files_changed": 70
+            }
+          }
+        },
+        {
+          "name": "tracing-core",
+          "notable_parents": "tracing",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.1.25",
+            "diffstat": {
+              "insertions": 6156,
+              "deletions": 0,
+              "files_changed": 26
+            }
+          }
+        },
+        {
+          "name": "cc",
+          "notable_parents": "openssl-sys",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "1.0.73",
+            "diffstat": {
+              "insertions": 6554,
+              "deletions": 0,
+              "files_changed": 19
+            }
+          }
+        },
+        {
+          "name": "httparse",
+          "notable_parents": "hyper",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "1.7.0",
+            "diffstat": {
+              "insertions": 7258,
+              "deletions": 0,
+              "files_changed": 18
+            }
+          }
+        },
+        {
+          "name": "memchr",
+          "notable_parents": "tokio and os_str_bytes",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "2.4.1",
+            "diffstat": {
+              "insertions": 8712,
+              "deletions": 0,
+              "files_changed": 46
+            }
+          }
+        },
+        {
+          "name": "bytes",
+          "notable_parents": "h2, http, hyper, tokio, and 4 others",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "1.1.0",
+            "diffstat": {
+              "insertions": 9207,
+              "deletions": 0,
+              "files_changed": 43
+            }
+          }
+        },
+        {
+          "name": "security-framework",
+          "notable_parents": "native-tls",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "2.6.1",
+            "diffstat": {
+              "insertions": 9316,
+              "deletions": 0,
+              "files_changed": 42
+            }
+          }
+        },
+        {
+          "name": "indexmap",
+          "notable_parents": "h2 and 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 and native-tls",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.9.72",
+            "diffstat": {
+              "insertions": 9477,
+              "deletions": 0,
+              "files_changed": 46
+            }
+          }
+        },
+        {
+          "name": "bumpalo",
+          "notable_parents": "wasm-bindgen-backend",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "3.9.1",
+            "diffstat": {
+              "insertions": 10099,
+              "deletions": 0,
+              "files_changed": 17
+            }
+          }
+        },
+        {
+          "name": "mio",
+          "notable_parents": "tokio",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.8.2",
+            "diffstat": {
+              "insertions": 11719,
+              "deletions": 0,
+              "files_changed": 62
+            }
+          }
+        },
+        {
+          "name": "js-sys",
+          "notable_parents": "reqwest, web-sys, and 1 other",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.3.57",
+            "diffstat": {
+              "insertions": 12874,
+              "deletions": 0,
+              "files_changed": 62
+            }
+          }
+        },
+        {
+          "name": "tokio-util",
+          "notable_parents": "h2",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.7.1",
+            "diffstat": {
+              "insertions": 13870,
+              "deletions": 0,
+              "files_changed": 65
+            }
+          }
+        },
+        {
+          "name": "hashbrown",
+          "notable_parents": "indexmap",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.11.2",
+            "diffstat": {
+              "insertions": 14603,
+              "deletions": 0,
+              "files_changed": 31
+            }
+          }
+        },
+        {
+          "name": "url",
+          "notable_parents": "reqwest",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "2.2.2",
+            "diffstat": {
+              "insertions": 16344,
+              "deletions": 0,
+              "files_changed": 15
+            }
+          }
+        },
+        {
+          "name": "tinyvec",
+          "notable_parents": "unicode-normalization",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "1.5.1",
+            "diffstat": {
+              "insertions": 16751,
+              "deletions": 0,
+              "files_changed": 25
+            }
+          }
+        },
+        {
+          "name": "http",
+          "notable_parents": "h2, hyper, reqwest, and http-body",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.2.6",
+            "diffstat": {
+              "insertions": 16819,
+              "deletions": 0,
+              "files_changed": 39
+            }
+          }
+        },
+        {
+          "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, and 1 other",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.2.80",
+            "diffstat": {
+              "insertions": 20977,
+              "deletions": 0,
+              "files_changed": 242
+            }
+          }
+        },
+        {
+          "name": "ntapi",
+          "notable_parents": "mio",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.3.7",
+            "diffstat": {
+              "insertions": 21227,
+              "deletions": 0,
+              "files_changed": 42
+            }
+          }
+        },
+        {
+          "name": "serde_json",
+          "notable_parents": "reqwest and 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 and hyper-tls",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.14.18",
+            "diffstat": {
+              "insertions": 24841,
+              "deletions": 0,
+              "files_changed": 69
+            }
+          }
+        },
+        {
+          "name": "futures-util",
+          "notable_parents": "h2, hyper, and reqwest",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.3.21",
+            "diffstat": {
+              "insertions": 25067,
+              "deletions": 0,
+              "files_changed": 185
+            }
+          }
+        },
+        {
+          "name": "h2",
+          "notable_parents": "hyper and reqwest",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.3.13",
+            "diffstat": {
+              "insertions": 25417,
+              "deletions": 0,
+              "files_changed": 63
+            }
+          }
+        },
+        {
+          "name": "openssl",
+          "notable_parents": "native-tls",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.10.38",
+            "diffstat": {
+              "insertions": 28435,
+              "deletions": 0,
+              "files_changed": 82
+            }
+          }
+        },
+        {
+          "name": "syn",
+          "notable_parents": "tracing-attributes and 2 others",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": "1.0.0",
+            "to": "1.0.91",
+            "diffstat": {
+              "insertions": 21772,
+              "deletions": 6698,
+              "files_changed": 92
+            }
+          }
+        },
+        {
+          "name": "unicode-normalization",
+          "notable_parents": "idna",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.1.19",
+            "diffstat": {
+              "insertions": 28622,
+              "deletions": 0,
+              "files_changed": 24
+            }
+          }
+        },
+        {
+          "name": "idna",
+          "notable_parents": "url",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.2.3",
+            "diffstat": {
+              "insertions": 32532,
+              "deletions": 0,
+              "files_changed": 17
+            }
+          }
+        },
+        {
+          "name": "vcpkg",
+          "notable_parents": "openssl-sys",
+          "suggested_criteria": [
+            "safe-to-deploy"
+          ],
+          "suggested_diff": {
+            "from": null,
+            "to": "0.2.15",
+            "diffstat": {
+              "insertions": 42642,
+              "deletions": 0,
+              "files_changed": 832
+            }
+          }
+        },
+        {
+          "name": "tokio",
+          "notable_parents": "h2, hyper, reqwest, hyper-tls, and 3 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, tokio, and 6 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 and 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": "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
+            }
+          }
+        }
+      ],
+      "safe-to-run": [
+        {
+          "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
+            }
+          }
+        }
+      ]
+    },
+    "total_lines": 1707117
+  }
+}
+stderr:
+
diff --git a/tests/snapshots/test_cli__test-project-suggest.snap b/tests/snapshots/test_cli__test-project-suggest.snap
new file mode 100644
index 0000000..ec70004
--- /dev/null
+++ b/tests/snapshots/test_cli__test-project-suggest.snap
@@ -0,0 +1,120 @@
+---
+source: tests/test-cli.rs
+expression: format_outputs(&output)
+---
+stdout:
+recommended audits for safe-to-deploy:
+    Command                                               Publisher      Used By                                              Audit Size
+    cargo vet diff proc-macro2 1.0.37 1.0.37@git:4445659b0f753a928059244c875a58bb12f791e9
+                                                          UNKNOWN        test-project                                         3 files changed, 63 insertions(+), 7 deletions(-)
+    cargo vet inspect tinyvec_macros 0.1.0                Soveu          tinyvec                                              81 lines
+    cargo vet inspect matches 0.1.9                       SimonSapin     url, idna, and form_urlencoded                       210 lines
+    cargo vet inspect foreign-types-shared 0.1.1          UNKNOWN        foreign-types                                        302 lines
+    cargo vet inspect try-lock 0.2.3                      seanmonstar    want                                                 380 lines
+    cargo vet inspect openssl-probe 0.1.5                 alexcrichton   native-tls                                           442 lines
+    cargo vet inspect tower-service 0.3.1                 davidpdrsn     hyper                                                501 lines
+      NOTE: this project trusts Eliza Weisman (hawkw), who published another version of this crate - consider cargo vet trust tower-service hawkw
+    cargo vet inspect wasm-bindgen-shared 0.2.80          alexcrichton   wasm-bindgen-backend and wasm-bindgen-macro-support  515 lines
+    cargo vet inspect pin-utils 0.1.0                     cramertj       futures-util                                         538 lines
+    cargo vet inspect futures-sink 0.3.21                 taiki-e        h2 and tokio-util                                    544 lines
+    cargo vet inspect cfg-if 0.1.10                       alexcrichton   log, instant, openssl, and 6 others                  579 lines
+    cargo vet inspect foreign-types 0.3.2                 UNKNOWN        openssl                                              583 lines
+    cargo vet inspect remove_dir_all 0.5.3                XAMPPRocky     tempfile                                             592 lines
+    cargo vet inspect instant 0.1.12                      sebcrozet      fastrand                                             672 lines
+    cargo vet inspect hyper-tls 0.5.0                     seanmonstar    reqwest                                              673 lines
+    cargo vet inspect form_urlencoded 1.0.1               Manishearth    url and serde_urlencoded                             689 lines
+    cargo vet inspect want 0.3.0                          seanmonstar    hyper                                                697 lines
+    cargo vet inspect percent-encoding 2.1.0              SimonSapin     url, reqwest, and form_urlencoded                    702 lines
+    cargo vet inspect fnv 1.0.7                           Manishearth    h2 and http                                          730 lines
+    cargo vet inspect itoa 1.0.1                          dtolnay        http, hyper, serde_json, and 1 other                 789 lines
+    cargo vet inspect lazy_static 1.4.0                   KodrAus        reqwest, schannel, and 3 others                      876 lines
+    cargo vet inspect httpdate 1.0.2                      pyfisch        hyper                                                995 lines
+    cargo vet inspect tokio-native-tls 0.3.0              LucioFranco    reqwest and hyper-tls                                1089 lines
+    cargo vet inspect winapi-util 0.1.5                   BurntSushi     termcolor                                            1095 lines
+    cargo vet inspect winapi-i686-pc-windows-gnu 0.4.0    UNKNOWN        winapi                                               1132 lines
+    cargo vet inspect winapi-x86_64-pc-windows-gnu 0.4.0  UNKNOWN        winapi                                               1167 lines
+    cargo vet inspect futures-core 0.3.21                 taiki-e        h2, hyper, reqwest, and 3 others                     1175 lines
+    cargo vet inspect futures-task 0.3.21                 taiki-e        futures-util                                         1180 lines
+    cargo vet inspect http-body 0.4.4                     LucioFranco    hyper and reqwest                                    1256 lines
+    cargo vet inspect wasm-bindgen-macro 0.2.80           alexcrichton   wasm-bindgen                                         1274 lines
+    cargo vet inspect wasm-bindgen-futures 0.4.30         alexcrichton   reqwest                                              1303 lines
+    cargo vet inspect fastrand 1.7.0                      taiki-e        tempfile                                             1368 lines
+    cargo vet inspect mime 0.3.16                         seanmonstar    reqwest                                              1715 lines
+    cargo vet inspect pkg-config 0.3.25                   sdroege        openssl-sys                                          1747 lines
+    cargo vet inspect core-foundation-sys 0.8.3           jdm            core-foundation and 2 others                         1965 lines
+    cargo vet inspect wasm-bindgen-macro-support 0.2.80   alexcrichton   wasm-bindgen-macro                                   1965 lines
+    cargo vet inspect security-framework-sys 2.6.1        kornelski      native-tls and security-framework                    2016 lines
+    cargo vet inspect unicode-xid 0.2.2                   Manishearth    syn and proc-macro2                                  2044 lines
+    cargo vet inspect serde_urlencoded 0.7.1              nox            reqwest                                              2120 lines
+    cargo vet inspect termcolor 1.1.3                     BurntSushi     clap                                                 2578 lines
+    cargo vet inspect slab 0.4.6                          taiki-e        h2                                                   2615 lines
+    cargo vet inspect os_str_bytes 6.0.0                  dylni          clap                                                 2911 lines
+    cargo vet inspect wasm-bindgen-backend 0.2.80         alexcrichton   wasm-bindgen-macro-support                           3018 lines
+    cargo vet inspect miow 0.3.7                          faern          mio                                                  3129 lines
+    cargo vet inspect unicode-bidi 0.3.7                  mbrubeck       idna                                                 3217 lines
+    cargo vet inspect wasi 0.11.0+wasi-snapshot-preview1  alexcrichton   mio                                                  3302 lines
+    cargo vet inspect native-tls 0.2.10                   sfackler       reqwest, hyper-tls, and tokio-native-tls             3715 lines
+    cargo vet inspect once_cell 1.10.0                    matklad        openssl                                              3766 lines
+    cargo vet inspect quote 1.0.18                        dtolnay        syn, tracing-attributes, and 3 others                3838 lines
+    cargo vet inspect redox_syscall 0.2.13                jackpot51      tempfile                                             3890 lines
+    cargo vet inspect winreg 0.10.1                       gentoo90       reqwest                                              3920 lines
+    cargo vet inspect core-foundation 0.9.3               jrmuizel       security-framework                                   3953 lines
+    cargo vet inspect tracing-attributes 0.1.20           hawkw          tracing                                              3957 lines
+      NOTE: this project trusts Eliza Weisman (hawkw) - consider cargo vet trust tracing-attributes or cargo vet trust --all hawkw
+    cargo vet inspect ipnet 2.4.0                         krisprice      reqwest                                              3973 lines
+    cargo vet inspect futures-channel 0.3.21              taiki-e        hyper                                                3992 lines
+    cargo vet inspect tempfile 3.3.0                      Stebalien      native-tls                                           4059 lines
+    cargo vet inspect ryu 1.0.9                           dtolnay        serde_json and serde_urlencoded                      4362 lines
+    cargo vet inspect schannel 0.1.19                     steffengy      native-tls                                           4601 lines
+    cargo vet inspect textwrap 0.15.0                     mgeisler       clap                                                 5196 lines
+    cargo vet inspect log 0.4.16                          KodrAus        mio, want, reqwest, native-tls, and 1 other          5625 lines
+    cargo vet inspect proc-macro2 1.0.37                  dtolnay        syn, quote, test-project, and 3 others               5695 lines
+    cargo vet inspect socket2 0.4.4                       Thomasdezeeuw  hyper and tokio                                      6011 lines
+    cargo vet inspect pin-project-lite 0.2.8              taiki-e        hyper, tokio, reqwest, and 4 others                  6100 lines
+    cargo vet inspect tracing-core 0.1.25                 hawkw          tracing                                              6156 lines
+      NOTE: this project trusts Eliza Weisman (hawkw) - consider cargo vet trust tracing-core or cargo vet trust --all hawkw
+    cargo vet inspect cc 1.0.73                           alexcrichton   openssl-sys                                          6554 lines
+    cargo vet inspect httparse 1.7.0                      seanmonstar    hyper                                                7258 lines
+    cargo vet inspect memchr 2.4.1                        BurntSushi     tokio and os_str_bytes                               8712 lines
+    cargo vet inspect bytes 1.1.0                         Darksonn       h2, http, hyper, tokio, and 4 others                 9207 lines
+    cargo vet inspect security-framework 2.6.1            kornelski      native-tls                                           9316 lines
+    cargo vet inspect indexmap 1.8.1                      cuviper        h2 and clap                                          9445 lines
+    cargo vet inspect openssl-sys 0.9.72                  sfackler       openssl and native-tls                               9477 lines
+    cargo vet inspect bumpalo 3.9.1                       fitzgen        wasm-bindgen-backend                                 10099 lines
+    cargo vet inspect mio 0.8.2                           Thomasdezeeuw  tokio                                                11719 lines
+    cargo vet inspect js-sys 0.3.57                       alexcrichton   reqwest, web-sys, and 1 other                        12874 lines
+    cargo vet inspect tokio-util 0.7.1                    Darksonn       h2                                                   13870 lines
+      NOTE: this project trusts Eliza Weisman (hawkw), who published another version of this crate - consider cargo vet trust tokio-util hawkw
+    cargo vet inspect hashbrown 0.11.2                    Amanieu        indexmap                                             14603 lines
+    cargo vet inspect url 2.2.2                           valenting      reqwest                                              16344 lines
+    cargo vet inspect tinyvec 1.5.1                       Lokathor       unicode-normalization                                16751 lines
+    cargo vet inspect http 0.2.6                          seanmonstar    h2, hyper, reqwest, and http-body                    16819 lines
+    cargo vet inspect reqwest 0.11.10                     seanmonstar    test-project                                         19797 lines
+    cargo vet inspect wasm-bindgen 0.2.80                 alexcrichton   js-sys, reqwest, web-sys, and 1 other                20977 lines
+    cargo vet inspect ntapi 0.3.7                         MSxDOS         mio                                                  21227 lines
+    cargo vet inspect serde_json 1.0.79                   dtolnay        reqwest and test-project                             22848 lines
+    cargo vet inspect hyper 0.14.18                       seanmonstar    reqwest and hyper-tls                                24841 lines
+    cargo vet inspect futures-util 0.3.21                 taiki-e        h2, hyper, and reqwest                               25067 lines
+    cargo vet inspect h2 0.3.13                           seanmonstar    hyper and reqwest                                    25417 lines
+    cargo vet inspect openssl 0.10.38                     sfackler       native-tls                                           28435 lines
+    cargo vet diff syn 1.0.0 1.0.91                       dtolnay        tracing-attributes and 2 others                      92 files changed, 21772 insertions(+), 6698 deletions(-)
+    cargo vet inspect unicode-normalization 0.1.19        Manishearth    idna                                                 28622 lines
+    cargo vet inspect idna 0.2.3                          SimonSapin     url                                                  32532 lines
+    cargo vet inspect vcpkg 0.2.15                        waych          openssl-sys                                          42642 lines
+    cargo vet inspect tokio 1.17.0                        hawkw          h2, hyper, reqwest, hyper-tls, and 3 others          91271 lines
+      NOTE: this project trusts Eliza Weisman (hawkw) - consider cargo vet trust tokio hawkw
+    cargo vet inspect libc 0.2.123                        Amanieu        mio, atty, tokio, openssl, and 8 others              94060 lines
+    cargo vet inspect winapi 0.3.9                        retep998       mio, atty, miow, ntapi, tokio, and 6 others          181323 lines
+    cargo vet inspect web-sys 0.3.57                      alexcrichton   reqwest and wasm-bindgen-futures                     197013 lines
+    cargo vet inspect encoding_rs 0.8.31                  hsivonen       reqwest                                              507245 lines
+
+recommended audits for safe-to-run:
+    Command                              Publisher  Used By  Audit Size
+    cargo vet inspect hermit-abi 0.1.19  stlankes   atty     932 lines
+
+estimated audit backlog: 1707117 lines
+
+Use |cargo vet certify| to record the audits.
+
+stderr:
+
diff --git a/tests/snapshots/test_cli__test-project.snap b/tests/snapshots/test_cli__test-project.snap
new file mode 100644
index 0000000..7aae5b5
--- /dev/null
+++ b/tests/snapshots/test_cli__test-project.snap
@@ -0,0 +1,9 @@
+---
+source: tests/test-cli.rs
+expression: format_outputs(&output)
+---
+stdout:
+Vetting Succeeded (7 fully audited, 2 partially audited, 97 exempted)
+
+stderr:
+
diff --git a/tests/test-cli.rs b/tests/test-cli.rs
new file mode 100644
index 0000000..af1d5f6
--- /dev/null
+++ b/tests/test-cli.rs
@@ -0,0 +1,310 @@
+// These tests largely just check that basic CLI configs still work,
+// and show you how you've changed the output. Yes, a lot of these
+// will randomly churn (especially the help message ones, which
+// contain the latest version), but this is good for two reasons:
+//
+// * You can easily see exactly what you changed
+// * It can prompt you to update any other docs
+//
+// `cargo insta` automates reviewing and updating these snapshots.
+// You can install `cargo insta` with:
+//
+// > cargo install cargo-insta
+//
+// Also note that `cargo test` for an application adds our binary to
+// the env as `CARGO_BIN_EXE_<name>`.
+
+use std::{
+    path::{Path, PathBuf},
+    process::{Command, Output, Stdio},
+};
+
+// NOTE: We filter out the "Blocking: waiting for file lock" lines, as they are
+// printed out non-deterministically when there is file contention.
+fn filter_blocking_lines(stderr: &[u8]) -> String {
+    std::str::from_utf8(stderr)
+        .unwrap()
+        .lines()
+        .filter(|line| !line.starts_with("Blocking: waiting for file lock"))
+        .collect::<Vec<_>>()
+        .join("\n")
+}
+
+fn format_outputs(output: &Output) -> String {
+    let stdout = std::str::from_utf8(&output.stdout).unwrap();
+    let stderr = filter_blocking_lines(&output.stderr);
+    format!("stdout:\n{stdout}\nstderr:\n{stderr}")
+}
+
+fn format_diff_outputs(output: &Output) -> String {
+    // Filter out lines which may contain paths so that the output is portable,
+    // while preserving some of the general format.
+    let stdout = std::str::from_utf8(&output.stdout)
+        .unwrap()
+        .lines()
+        .filter(|line| !line.starts_with("diff --git"))
+        .map(|line| {
+            if let Some(path) = line.strip_prefix("--- ") {
+                return format!(
+                    "--- a/{}",
+                    Path::new(path).file_name().unwrap().to_str().unwrap()
+                );
+            }
+            if let Some(path) = line.strip_prefix("+++ ") {
+                return format!(
+                    "+++ b/{}",
+                    Path::new(path).file_name().unwrap().to_str().unwrap()
+                );
+            }
+            line.to_owned()
+        })
+        .collect::<Vec<_>>()
+        .join("\n");
+    let stderr = filter_blocking_lines(&output.stderr);
+    format!("stdout:\n{stdout}\nstderr:\n{stderr}")
+}
+
+#[test]
+fn test_version() {
+    let bin = env!("CARGO_BIN_EXE_cargo-vet");
+    let output = Command::new(bin)
+        .arg("vet")
+        .arg("-V")
+        .stdout(Stdio::piped())
+        .stderr(Stdio::piped())
+        .output()
+        .unwrap();
+
+    let stdout = String::from_utf8(output.stdout).unwrap();
+    let stderr = String::from_utf8(output.stderr).unwrap();
+
+    assert!(output.status.success(), "{}", stderr);
+    assert_eq!(stderr, "");
+
+    let (name, ver) = stdout.split_once(' ').unwrap();
+    assert_eq!(name, "cargo-vet");
+    let mut ver_parts = ver.trim().split('.');
+    ver_parts.next().unwrap().parse::<u8>().unwrap();
+    ver_parts.next().unwrap().parse::<u8>().unwrap();
+    ver_parts.next().unwrap().parse::<u8>().unwrap();
+    assert!(ver_parts.next().is_none());
+}
+
+#[test]
+fn test_long_help() {
+    let bin = env!("CARGO_BIN_EXE_cargo-vet");
+    let output = Command::new(bin)
+        .arg("vet")
+        .arg("--help")
+        .stdout(Stdio::piped())
+        .stderr(Stdio::piped())
+        .output()
+        .unwrap();
+
+    insta::assert_snapshot!("long-help", format_outputs(&output));
+    assert!(output.status.success(), "{}", output.status);
+}
+
+#[test]
+fn test_short_help() {
+    let bin = env!("CARGO_BIN_EXE_cargo-vet");
+    let output = Command::new(bin)
+        .arg("vet")
+        .arg("-h")
+        .stdout(Stdio::piped())
+        .stderr(Stdio::piped())
+        .output()
+        .unwrap();
+
+    insta::assert_snapshot!("short-help", format_outputs(&output));
+    assert!(output.status.success(), "{}", output.status);
+}
+
+#[test]
+fn test_markdown_help() {
+    let bin = env!("CARGO_BIN_EXE_cargo-vet");
+    let output = Command::new(bin)
+        .arg("vet")
+        .arg("help-markdown")
+        .stdout(Stdio::piped())
+        .stderr(Stdio::piped())
+        .output()
+        .unwrap();
+
+    insta::assert_snapshot!("markdown-help", format_outputs(&output));
+    assert!(output.status.success(), "{}", output.status);
+}
+
+fn test_project_command(subcommand: Option<&str>) -> Command {
+    let project = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
+        .join("tests")
+        .join("test-project");
+    let bin = env!("CARGO_BIN_EXE_cargo-vet");
+    let mut cmd = Command::new(bin);
+    cmd.current_dir(&project).arg("vet");
+    if let Some(sub) = subcommand {
+        cmd.arg(sub);
+    }
+    cmd.arg("--manifest-path")
+        .arg("Cargo.toml")
+        .arg("--cache-dir")
+        .arg("../cache")
+        .arg("--current-time")
+        .arg("2023-01-01 12:00:00Z");
+    cmd
+}
+
+#[test]
+fn test_project() {
+    let output = test_project_command(None)
+        .stdout(Stdio::piped())
+        .stderr(Stdio::piped())
+        .output()
+        .unwrap();
+
+    insta::assert_snapshot!("test-project", format_outputs(&output));
+    assert!(output.status.success(), "{}", output.status);
+}
+
+#[test]
+fn test_project_json() {
+    let output = test_project_command(None)
+        .arg("--output-format=json")
+        .stdout(Stdio::piped())
+        .stderr(Stdio::piped())
+        .output()
+        .unwrap();
+
+    insta::assert_snapshot!("test-project-json", format_outputs(&output));
+    assert!(output.status.success(), "{}", output.status);
+}
+
+#[test]
+fn test_project_suggest() {
+    let output = test_project_command(Some("suggest"))
+        .arg("--no-registry-suggestions")
+        .stdout(Stdio::piped())
+        .stderr(Stdio::piped())
+        .output()
+        .unwrap();
+
+    insta::assert_snapshot!("test-project-suggest", format_outputs(&output));
+    assert!(output.status.success(), "{}", output.status);
+}
+
+#[test]
+fn test_project_suggest_json() {
+    let output = test_project_command(Some("suggest"))
+        .arg("--no-registry-suggestions")
+        .arg("--output-format=json")
+        .stdout(Stdio::piped())
+        .stderr(Stdio::piped())
+        .output()
+        .unwrap();
+
+    insta::assert_snapshot!("test-project-suggest-json", format_outputs(&output));
+    assert!(output.status.success(), "{}", output.status);
+}
+
+#[test]
+fn test_project_dump_graph_full_json() {
+    let output = test_project_command(Some("dump-graph"))
+        .arg("--output-format=json")
+        .arg("--depth=full")
+        .stdout(Stdio::piped())
+        .stderr(Stdio::piped())
+        .output()
+        .unwrap();
+
+    insta::assert_snapshot!("test-project-dump-graph-full-json", format_outputs(&output));
+    assert!(output.status.success(), "{}", output.status);
+}
+
+#[test]
+fn test_project_dump_graph_full() {
+    let output = test_project_command(Some("dump-graph"))
+        .arg("--depth=full")
+        .stdout(Stdio::piped())
+        .stderr(Stdio::piped())
+        .output()
+        .unwrap();
+
+    insta::assert_snapshot!("test-project-dump-graph-full", format_outputs(&output));
+    assert!(output.status.success(), "{}", output.status);
+}
+
+#[test]
+fn test_project_bad_certify_human() {
+    let output = test_project_command(Some("certify"))
+        .arg("asdfsdfs")
+        .stdout(Stdio::piped())
+        .stderr(Stdio::piped())
+        .output()
+        .unwrap();
+
+    insta::assert_snapshot!("test-project-bad-certify-human", format_outputs(&output));
+    assert!(!output.status.success(), "{}", output.status);
+}
+
+#[test]
+fn test_project_bad_certify_json() {
+    let output = test_project_command(Some("certify"))
+        .arg("--output-format=json")
+        .arg("asdfsdfs")
+        .stdout(Stdio::piped())
+        .stderr(Stdio::piped())
+        .output()
+        .unwrap();
+
+    insta::assert_snapshot!("test-project-bad-certify-json", format_outputs(&output));
+    assert!(!output.status.success(), "{}", output.status);
+}
+
+#[test]
+fn test_project_diff_output() {
+    // Test that the diff output from `cargo vet diff` is correctly filtered to
+    // remove files like .cargo_vcs_info.json.
+
+    let mut child = test_project_command(Some("diff"))
+        .arg("--mode")
+        .arg("local")
+        .arg("syn")
+        .arg("1.0.90")
+        .arg("1.0.91")
+        .stdout(Stdio::piped())
+        .stderr(Stdio::piped())
+        .stdin(Stdio::piped())
+        .spawn()
+        .unwrap();
+
+    std::io::Write::write_all(child.stdin.as_mut().unwrap(), b"\n").unwrap();
+
+    let output = child.wait_with_output().unwrap();
+
+    insta::assert_snapshot!("test-project-diff-output", format_diff_outputs(&output));
+    assert!(output.status.success(), "{}", output.status);
+}
+
+#[test]
+fn test_project_diff_output_git() {
+    // Test that the diff output handles git revisions.
+
+    let mut child = test_project_command(Some("diff"))
+        .arg("--mode")
+        .arg("local")
+        .arg("proc-macro2")
+        .arg("1.0.37")
+        .arg("1.0.37@git:4445659b0f753a928059244c875a58bb12f791e9")
+        .stdout(Stdio::piped())
+        .stderr(Stdio::piped())
+        .stdin(Stdio::piped())
+        .spawn()
+        .unwrap();
+
+    std::io::Write::write_all(child.stdin.as_mut().unwrap(), b"\n").unwrap();
+
+    let output = child.wait_with_output().unwrap();
+
+    insta::assert_snapshot!("test-project-diff-output-git", format_diff_outputs(&output));
+    assert!(output.status.success(), "{}", output.status);
+}
diff --git a/tests/test-project/Cargo.lock b/tests/test-project/Cargo.lock
new file mode 100644
index 0000000..f6745a8
--- /dev/null
+++ b/tests/test-project/Cargo.lock
@@ -0,0 +1,989 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "base64"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bumpalo"
+version = "3.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899"
+
+[[package]]
+name = "bytes"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
+
+[[package]]
+name = "cc"
+version = "1.0.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
+
+[[package]]
+name = "cfg-if"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "clap"
+version = "3.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71c47df61d9e16dc010b55dba1952a57d8c215dbb533fd13cdd13369aac73b1c"
+dependencies = [
+ "atty",
+ "bitflags",
+ "indexmap",
+ "os_str_bytes",
+ "strsim",
+ "termcolor",
+ "textwrap",
+]
+
+[[package]]
+name = "core-foundation"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
+
+[[package]]
+name = "encoding_rs"
+version = "0.8.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b"
+dependencies = [
+ "cfg-if 1.0.0",
+]
+
+[[package]]
+name = "fastrand"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf"
+dependencies = [
+ "instant",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
+dependencies = [
+ "matches",
+ "percent-encoding",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3"
+
+[[package]]
+name = "futures-sink"
+version = "0.3.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868"
+
+[[package]]
+name = "futures-task"
+version = "0.3.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a"
+
+[[package]]
+name = "futures-util"
+version = "0.3.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "pin-project-lite",
+ "pin-utils",
+]
+
+[[package]]
+name = "h2"
+version = "0.3.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57"
+dependencies = [
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "futures-util",
+ "http",
+ "indexmap",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "http"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6"
+dependencies = [
+ "bytes",
+ "http",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6330e8a36bd8c859f3fa6d9382911fbb7147ec39807f63b923933a247240b9ba"
+
+[[package]]
+name = "httpdate"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
+
+[[package]]
+name = "hyper"
+version = "0.14.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b26ae0a80afebe130861d90abf98e3814a4f28a4c6ffeb5ab8ebb2be311e0ef2"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "httparse",
+ "httpdate",
+ "itoa",
+ "pin-project-lite",
+ "socket2",
+ "tokio",
+ "tower-service",
+ "tracing",
+ "want",
+]
+
+[[package]]
+name = "hyper-tls"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
+dependencies = [
+ "bytes",
+ "hyper",
+ "native-tls",
+ "tokio",
+ "tokio-native-tls",
+]
+
+[[package]]
+name = "idna"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
+dependencies = [
+ "matches",
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+]
+
+[[package]]
+name = "instant"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
+dependencies = [
+ "cfg-if 1.0.0",
+]
+
+[[package]]
+name = "ipnet"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35e70ee094dc02fd9c13fdad4940090f22dbd6ac7c9e7094a46cf0232a50bc7c"
+
+[[package]]
+name = "itoa"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
+
+[[package]]
+name = "js-sys"
+version = "0.3.57"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.123"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb691a747a7ab48abc15c5b42066eaafde10dc427e3b6ee2a1cf43db04c763bd"
+
+[[package]]
+name = "log"
+version = "0.4.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8"
+dependencies = [
+ "cfg-if 1.0.0",
+]
+
+[[package]]
+name = "matches"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
+
+[[package]]
+name = "memchr"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
+
+[[package]]
+name = "mime"
+version = "0.3.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
+
+[[package]]
+name = "mio"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52da4364ffb0e4fe33a9841a98a3f3014fb964045ce4f7a45a398243c8d6b0c9"
+dependencies = [
+ "libc",
+ "log",
+ "miow",
+ "ntapi",
+ "wasi",
+ "winapi",
+]
+
+[[package]]
+name = "miow"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "native-tls"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9"
+dependencies = [
+ "lazy_static",
+ "libc",
+ "log",
+ "openssl",
+ "openssl-probe",
+ "openssl-sys",
+ "schannel",
+ "security-framework",
+ "security-framework-sys",
+ "tempfile",
+]
+
+[[package]]
+name = "ntapi"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9"
+
+[[package]]
+name = "openssl"
+version = "0.10.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95"
+dependencies = [
+ "bitflags",
+ "cfg-if 1.0.0",
+ "foreign-types",
+ "libc",
+ "once_cell",
+ "openssl-sys",
+]
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.72"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb"
+dependencies = [
+ "autocfg",
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "os_str_bytes"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.37"
+source = "git+https://github.com/dtolnay/proc-macro2?rev=4445659b0f753a928059244c875a58bb12f791e9#4445659b0f753a928059244c875a58bb12f791e9"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
+dependencies = [
+ "proc-macro2 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "remove_dir_all"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "reqwest"
+version = "0.11.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "46a1f7aa4f35e5e8b4160449f51afc758f0ce6454315a9fa7d0d113e958c41eb"
+dependencies = [
+ "base64",
+ "bytes",
+ "encoding_rs",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "hyper",
+ "hyper-tls",
+ "ipnet",
+ "js-sys",
+ "lazy_static",
+ "log",
+ "mime",
+ "native-tls",
+ "percent-encoding",
+ "pin-project-lite",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "tokio",
+ "tokio-native-tls",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "winreg",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
+
+[[package]]
+name = "schannel"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75"
+dependencies = [
+ "lazy_static",
+ "winapi",
+]
+
+[[package]]
+name = "security-framework"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc"
+dependencies = [
+ "bitflags",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.136"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
+
+[[package]]
+name = "serde_json"
+version = "1.0.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32"
+
+[[package]]
+name = "socket2"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
+name = "syn"
+version = "1.0.91"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b683b2b825c8eef438b77c36a06dc262294da3d5a5813fac20da149241dcd44d"
+dependencies = [
+ "proc-macro2 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
+dependencies = [
+ "cfg-if 1.0.0",
+ "fastrand",
+ "libc",
+ "redox_syscall",
+ "remove_dir_all",
+ "winapi",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "test-project"
+version = "0.1.0"
+dependencies = [
+ "cfg-if 0.1.10",
+ "clap",
+ "proc-macro2 1.0.37 (git+https://github.com/dtolnay/proc-macro2?rev=4445659b0f753a928059244c875a58bb12f791e9)",
+ "reqwest",
+ "serde_json",
+ "tokio",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
+
+[[package]]
+name = "tinyvec"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
+
+[[package]]
+name = "tokio"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee"
+dependencies = [
+ "bytes",
+ "libc",
+ "memchr",
+ "mio",
+ "pin-project-lite",
+ "socket2",
+ "winapi",
+]
+
+[[package]]
+name = "tokio-native-tls"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b"
+dependencies = [
+ "native-tls",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0edfdeb067411dba2044da6d1cb2df793dd35add7888d73c16e3381ded401764"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+ "tracing",
+]
+
+[[package]]
+name = "tower-service"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6"
+
+[[package]]
+name = "tracing"
+version = "0.1.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80b9fa4360528139bc96100c160b7ae879f5567f49f1782b0b02035b0358ebf3"
+dependencies = [
+ "cfg-if 1.0.0",
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e65ce065b4b5c53e73bb28912318cb8c9e9ad3921f1d669eb0e68b4c8143a2b"
+dependencies = [
+ "proc-macro2 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6dfce9f3241b150f36e8e54bb561a742d5daa1a47b5dd9a5ce369fd4a4db2210"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
+
+[[package]]
+name = "url"
+version = "2.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "matches",
+ "percent-encoding",
+]
+
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[package]]
+name = "want"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
+dependencies = [
+ "log",
+ "try-lock",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.80"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad"
+dependencies = [
+ "cfg-if 1.0.0",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.80"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4"
+dependencies = [
+ "bumpalo",
+ "lazy_static",
+ "log",
+ "proc-macro2 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f741de44b75e14c35df886aff5f1eb73aa114fa5d4d00dcd37b5e01259bf3b2"
+dependencies = [
+ "cfg-if 1.0.0",
+ "js-sys",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.80"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.80"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b"
+dependencies = [
+ "proc-macro2 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.80"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744"
+
+[[package]]
+name = "web-sys"
+version = "0.3.57"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "winreg"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
+dependencies = [
+ "winapi",
+]
diff --git a/tests/test-project/Cargo.toml b/tests/test-project/Cargo.toml
new file mode 100644
index 0000000..fa8e030
--- /dev/null
+++ b/tests/test-project/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "test-project"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+clap = "3.1.8"
+reqwest = "0.11.10"
+serde_json = "1.0.79"
+tokio = "1.17.0"
+proc-macro2 = { git = "https://github.com/dtolnay/proc-macro2", rev = "4445659b0f753a928059244c875a58bb12f791e9" }
+cfg-if = "0.1.10"
diff --git a/tests/test-project/src/main.rs b/tests/test-project/src/main.rs
new file mode 100644
index 0000000..e7a11a9
--- /dev/null
+++ b/tests/test-project/src/main.rs
@@ -0,0 +1,3 @@
+fn main() {
+    println!("Hello, world!");
+}
diff --git a/tests/test-project/supply-chain/audits.toml b/tests/test-project/supply-chain/audits.toml
new file mode 100644
index 0000000..0cecf2b
--- /dev/null
+++ b/tests/test-project/supply-chain/audits.toml
@@ -0,0 +1,104 @@
+
+# cargo-vet audits file
+
+[criteria.audited]
+description = "super audited"
+implies = "safe-to-deploy"
+
+[criteria.fuzzed]
+description = "fuzzed"
+
+[[wildcard-audits.serde]]
+who = "Person One <personone@example.com>"
+criteria = "safe-to-deploy"
+user-id = 3618 # David Tolnay (dtolnay)
+start = "2022-01-01"
+end = "2022-07-01"
+renew = false
+
+[[audits.atty]]
+criteria = "safe-to-run"
+version = "0.2.14"
+
+[[audits.autocfg]]
+criteria = "safe-to-deploy"
+version = "1.1.0"
+notes = "test for simple absolute version with non-defaults"
+
+[[audits.base64]]
+criteria = "safe-to-deploy"
+version = "0.1.0"
+notes = "test for basic resolution"
+
+[[audits.base64]]
+who = "Person One <personone@example.com>"
+criteria = "safe-to-deploy"
+version = "0.5.0"
+
+[[audits.base64]]
+who = [
+    "Person One <personone@example.com>",
+    "Person Two <persontwo@example.com>",
+]
+criteria = "safe-to-deploy"
+delta = "0.1.0 -> 0.4.0"
+
+[[audits.base64]]
+criteria = "safe-to-deploy"
+delta = "0.2.0 -> 0.14.0"
+notes = "basic resolution"
+
+[[audits.base64]]
+criteria = "safe-to-deploy"
+delta = "0.4.0 -> 0.8.1"
+
+[[audits.base64]]
+criteria = "safe-to-deploy"
+delta = "0.8.1 -> 0.9.0"
+
+[[audits.base64]]
+criteria = "safe-to-deploy"
+delta = "0.9.0 -> 0.13.0"
+
+[[audits.bitflags]]
+criteria = "audited"
+version = "0.1.0"
+notes = "test for unioning criteria from two chains"
+
+[[audits.bitflags]]
+criteria = "fuzzed"
+version = "0.2.0"
+
+[[audits.bitflags]]
+criteria = "audited"
+delta = "0.1.0 -> 1.3.2"
+
+[[audits.bitflags]]
+criteria = "fuzzed"
+delta = "0.2.0 -> 1.3.2"
+
+[[audits.cfg-if]]
+criteria = "safe-to-deploy"
+delta = "0.1.10 -> 1.0.0"
+notes = "test for duplicate suggestions"
+
+[[audits.clap]]
+criteria = "safe-to-deploy"
+version = "3.1.8"
+notes = "test for custom criteria (low-grade and high-grade)"
+
+[[audits.syn]]
+criteria = "safe-to-deploy"
+version = "1.0.0"
+notes = "test for partial delta criteria"
+
+[[audits.unicode-bidi]]
+criteria = "safe-to-deploy"
+delta = "0.2.0 -> 0.3.7"
+notes = "test for delta to unaudited"
+
+[[trusted.tracing]]
+criteria = "safe-to-deploy"
+user-id = 1249 # Eliza Weisman (hawkw)
+start = "2022-01-01"
+end = "2022-07-01"
diff --git a/tests/test-project/supply-chain/config.toml b/tests/test-project/supply-chain/config.toml
new file mode 100644
index 0000000..2c3692b
--- /dev/null
+++ b/tests/test-project/supply-chain/config.toml
@@ -0,0 +1,410 @@
+
+# cargo-vet config file
+
+[cargo-vet]
+version = "0.8"
+
+[policy."clap:3.1.8"]
+dependency-criteria = { atty = "safe-to-run", bitflags = ["audited", "fuzzed"] }
+
+[policy."proc-macro2:1.0.37"]
+
+[policy."proc-macro2:1.0.37@git:4445659b0f753a928059244c875a58bb12f791e9"]
+audit-as-crates-io = true
+
+[policy.test-project]
+audit-as-crates-io = false
+
+[[exemptions.bumpalo]]
+version = "3.9.1"
+criteria = "safe-to-deploy"
+
+[[exemptions.bytes]]
+version = "1.1.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.cc]]
+version = "1.0.73"
+criteria = "safe-to-deploy"
+
+[[exemptions.cfg-if]]
+version = "0.1.10"
+criteria = "safe-to-deploy"
+
+[[exemptions.core-foundation]]
+version = "0.9.3"
+criteria = "safe-to-deploy"
+
+[[exemptions.core-foundation-sys]]
+version = "0.8.3"
+criteria = "safe-to-deploy"
+
+[[exemptions.encoding_rs]]
+version = "0.8.31"
+criteria = "safe-to-deploy"
+
+[[exemptions.fastrand]]
+version = "1.7.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.fnv]]
+version = "1.0.7"
+criteria = "safe-to-deploy"
+
+[[exemptions.foreign-types]]
+version = "0.3.2"
+criteria = "safe-to-deploy"
+
+[[exemptions.foreign-types-shared]]
+version = "0.1.1"
+criteria = "safe-to-deploy"
+
+[[exemptions.form_urlencoded]]
+version = "1.0.1"
+criteria = "safe-to-deploy"
+
+[[exemptions.futures-channel]]
+version = "0.3.21"
+criteria = "safe-to-deploy"
+
+[[exemptions.futures-core]]
+version = "0.3.21"
+criteria = "safe-to-deploy"
+
+[[exemptions.futures-sink]]
+version = "0.3.21"
+criteria = "safe-to-deploy"
+
+[[exemptions.futures-task]]
+version = "0.3.21"
+criteria = "safe-to-deploy"
+
+[[exemptions.futures-util]]
+version = "0.3.21"
+criteria = "safe-to-deploy"
+
+[[exemptions.h2]]
+version = "0.3.13"
+criteria = "safe-to-deploy"
+
+[[exemptions.hashbrown]]
+version = "0.11.2"
+criteria = "safe-to-deploy"
+
+[[exemptions.hermit-abi]]
+version = "0.1.19"
+criteria = "safe-to-run"
+
+[[exemptions.http]]
+version = "0.2.6"
+criteria = "safe-to-deploy"
+
+[[exemptions.http-body]]
+version = "0.4.4"
+criteria = "safe-to-deploy"
+
+[[exemptions.httparse]]
+version = "1.7.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.httpdate]]
+version = "1.0.2"
+criteria = "safe-to-deploy"
+
+[[exemptions.hyper]]
+version = "0.14.18"
+criteria = "safe-to-deploy"
+
+[[exemptions.hyper-tls]]
+version = "0.5.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.idna]]
+version = "0.2.3"
+criteria = "safe-to-deploy"
+
+[[exemptions.indexmap]]
+version = "1.8.1"
+criteria = "safe-to-deploy"
+
+[[exemptions.instant]]
+version = "0.1.12"
+criteria = "safe-to-deploy"
+
+[[exemptions.ipnet]]
+version = "2.4.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.itoa]]
+version = "1.0.1"
+criteria = "safe-to-deploy"
+
+[[exemptions.js-sys]]
+version = "0.3.57"
+criteria = "safe-to-deploy"
+
+[[exemptions.lazy_static]]
+version = "1.4.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.libc]]
+version = "0.2.123"
+criteria = "safe-to-deploy"
+
+[[exemptions.log]]
+version = "0.4.16"
+criteria = "safe-to-deploy"
+
+[[exemptions.matches]]
+version = "0.1.9"
+criteria = "safe-to-deploy"
+
+[[exemptions.memchr]]
+version = "2.4.1"
+criteria = "safe-to-deploy"
+
+[[exemptions.mime]]
+version = "0.3.16"
+criteria = "safe-to-deploy"
+
+[[exemptions.mio]]
+version = "0.8.2"
+criteria = "safe-to-deploy"
+
+[[exemptions.miow]]
+version = "0.3.7"
+criteria = "safe-to-deploy"
+
+[[exemptions.native-tls]]
+version = "0.2.10"
+criteria = "safe-to-deploy"
+
+[[exemptions.ntapi]]
+version = "0.3.7"
+criteria = "safe-to-deploy"
+
+[[exemptions.once_cell]]
+version = "1.10.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.openssl]]
+version = "0.10.38"
+criteria = "safe-to-deploy"
+
+[[exemptions.openssl-probe]]
+version = "0.1.5"
+criteria = "safe-to-deploy"
+
+[[exemptions.openssl-sys]]
+version = "0.9.72"
+criteria = "safe-to-deploy"
+
+[[exemptions.os_str_bytes]]
+version = "6.0.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.percent-encoding]]
+version = "2.1.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.pin-project-lite]]
+version = "0.2.8"
+criteria = "safe-to-deploy"
+
+[[exemptions.pin-utils]]
+version = "0.1.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.pkg-config]]
+version = "0.3.25"
+criteria = "safe-to-deploy"
+
+[[exemptions.proc-macro2]]
+version = "1.0.37"
+criteria = "safe-to-deploy"
+
+[[exemptions.proc-macro2]]
+version = "1.0.37@git:4445659b0f753a928059244c875a58bb12f791e9"
+criteria = "safe-to-deploy"
+
+[[exemptions.quote]]
+version = "1.0.18"
+criteria = "safe-to-deploy"
+
+[[exemptions.redox_syscall]]
+version = "0.2.13"
+criteria = "safe-to-deploy"
+
+[[exemptions.remove_dir_all]]
+version = "0.5.3"
+criteria = "safe-to-deploy"
+
+[[exemptions.reqwest]]
+version = "0.11.10"
+criteria = "safe-to-deploy"
+
+[[exemptions.ryu]]
+version = "1.0.9"
+criteria = "safe-to-deploy"
+
+[[exemptions.schannel]]
+version = "0.1.19"
+criteria = "safe-to-deploy"
+
+[[exemptions.security-framework]]
+version = "2.6.1"
+criteria = "safe-to-deploy"
+
+[[exemptions.security-framework-sys]]
+version = "2.6.1"
+criteria = "safe-to-deploy"
+
+[[exemptions.serde_json]]
+version = "1.0.79"
+criteria = "safe-to-deploy"
+
+[[exemptions.serde_urlencoded]]
+version = "0.7.1"
+criteria = "safe-to-deploy"
+
+[[exemptions.slab]]
+version = "0.4.6"
+criteria = "safe-to-deploy"
+
+[[exemptions.socket2]]
+version = "0.4.4"
+criteria = "safe-to-deploy"
+
+[[exemptions.strsim]]
+version = "0.10.0"
+criteria = "safe-to-deploy"
+suggest = false
+
+[[exemptions.syn]]
+version = "1.0.91"
+criteria = "safe-to-deploy"
+
+[[exemptions.tempfile]]
+version = "3.3.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.termcolor]]
+version = "1.1.3"
+criteria = "safe-to-deploy"
+
+[[exemptions.textwrap]]
+version = "0.15.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.tinyvec]]
+version = "1.5.1"
+criteria = "safe-to-deploy"
+
+[[exemptions.tinyvec_macros]]
+version = "0.1.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.tokio]]
+version = "1.17.0"
+criteria = "safe-to-deploy"
+notes = "this message shouldn't get randomly clobbered"
+
+[[exemptions.tokio-native-tls]]
+version = "0.3.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.tokio-util]]
+version = "0.7.1"
+criteria = "safe-to-deploy"
+
+[[exemptions.tower-service]]
+version = "0.3.1"
+criteria = "safe-to-deploy"
+
+[[exemptions.tracing-attributes]]
+version = "0.1.20"
+criteria = "safe-to-deploy"
+
+[[exemptions.tracing-core]]
+version = "0.1.25"
+criteria = "safe-to-deploy"
+
+[[exemptions.try-lock]]
+version = "0.2.3"
+criteria = "safe-to-deploy"
+
+[[exemptions.unicode-bidi]]
+version = "0.2.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.unicode-normalization]]
+version = "0.1.19"
+criteria = "safe-to-deploy"
+
+[[exemptions.unicode-xid]]
+version = "0.2.2"
+criteria = "safe-to-deploy"
+
+[[exemptions.url]]
+version = "2.2.2"
+criteria = "safe-to-deploy"
+
+[[exemptions.vcpkg]]
+version = "0.2.15"
+criteria = "safe-to-deploy"
+
+[[exemptions.want]]
+version = "0.3.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.wasi]]
+version = "0.11.0+wasi-snapshot-preview1"
+criteria = "safe-to-deploy"
+
+[[exemptions.wasm-bindgen]]
+version = "0.2.80"
+criteria = "safe-to-deploy"
+
+[[exemptions.wasm-bindgen-backend]]
+version = "0.2.80"
+criteria = "safe-to-deploy"
+
+[[exemptions.wasm-bindgen-futures]]
+version = "0.4.30"
+criteria = "safe-to-deploy"
+
+[[exemptions.wasm-bindgen-macro]]
+version = "0.2.80"
+criteria = "safe-to-deploy"
+
+[[exemptions.wasm-bindgen-macro-support]]
+version = "0.2.80"
+criteria = "safe-to-deploy"
+
+[[exemptions.wasm-bindgen-shared]]
+version = "0.2.80"
+criteria = "safe-to-deploy"
+
+[[exemptions.web-sys]]
+version = "0.3.57"
+criteria = "safe-to-deploy"
+
+[[exemptions.winapi]]
+version = "0.3.9"
+criteria = "safe-to-deploy"
+
+[[exemptions.winapi-i686-pc-windows-gnu]]
+version = "0.4.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.winapi-util]]
+version = "0.1.5"
+criteria = "safe-to-deploy"
+
+[[exemptions.winapi-x86_64-pc-windows-gnu]]
+version = "0.4.0"
+criteria = "safe-to-deploy"
+
+[[exemptions.winreg]]
+version = "0.10.1"
+criteria = "safe-to-deploy"
diff --git a/tests/test-project/supply-chain/imports.lock b/tests/test-project/supply-chain/imports.lock
new file mode 100644
index 0000000..b7677cd
--- /dev/null
+++ b/tests/test-project/supply-chain/imports.lock
@@ -0,0 +1,16 @@
+
+# cargo-vet imports lock
+
+[[publisher.serde]]
+version = "1.0.136"
+when = "2022-01-25"
+user-id = 3618
+user-login = "dtolnay"
+user-name = "David Tolnay"
+
+[[publisher.tracing]]
+version = "0.1.33"
+when = "2022-04-09"
+user-id = 1249
+user-login = "hawkw"
+user-name = "Eliza Weisman"