diff --git a/.dockerignore b/.dockerignore index 4137355cec..f63645e591 100644 --- a/.dockerignore +++ b/.dockerignore @@ -10,7 +10,7 @@ /target/ /zerocopy/target/ /anneal/target/ -/anneal/v2/target/ +/anneal/v1/target/ /tools/target/ /exocrate/target/ /hermes/target/ diff --git a/.github/workflows/anneal-release.yml b/.github/workflows/anneal-release.yml index 4e20570434..73eaf60086 100644 --- a/.github/workflows/anneal-release.yml +++ b/.github/workflows/anneal-release.yml @@ -13,7 +13,7 @@ on: branches: - main paths: - - 'anneal/Cargo.toml' + - 'anneal/v1/Cargo.toml' workflow_dispatch: inputs: version: @@ -54,7 +54,7 @@ jobs: id: check run: | set -eo pipefail - cd anneal + cd anneal/v1 CUR_VER=$(cargo metadata -q --format-version 1 | jq -r '.packages[] | select(.name == "cargo-anneal").version') @@ -96,7 +96,7 @@ jobs: CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} run: | set -eo pipefail - cd anneal + cd anneal/v1 ./tools/pre-publish.sh cargo publish --allow-dirty --registry crates-io @@ -150,13 +150,13 @@ jobs: ./ci/release_anneal_version.sh "$VERSION" - python3 anneal/tools/check-release-pr-files.py \ + python3 anneal/v1/tools/check-release-pr-files.py \ --context "Release version bump" \ --include-untracked \ - --allowed anneal/Cargo.lock \ - --allowed anneal/Cargo.toml \ - --allowed anneal/README.md \ - --required anneal/Cargo.toml + --allowed anneal/v1/Cargo.lock \ + --allowed anneal/v1/Cargo.toml \ + --allowed anneal/v1/README.md \ + --required anneal/v1/Cargo.toml git diff --binary > anneal-release-source.patch if [ ! -s anneal-release-source.patch ]; then @@ -284,33 +284,33 @@ jobs: set -eo pipefail ARCHIVE_NAME="anneal-toolchain-${TARGET}.tar.zst" MAX_GITHUB_RELEASE_ASSET_SIZE=2147483647 - mkdir -p anneal/v2/target anneal/release-download-check anneal/release-metadata + mkdir -p anneal/target anneal/v1/release-download-check anneal/v1/release-metadata - nix build ./anneal/v2#omnibus-archive-ci --out-link "anneal/v2/target/${ARCHIVE_NAME}" - cp -L "anneal/v2/target/${ARCHIVE_NAME}" "anneal/${ARCHIVE_NAME}" - ARCHIVE_SIZE="$(python3 -c 'import os, sys; print(os.path.getsize(sys.argv[1]))' "anneal/${ARCHIVE_NAME}")" + nix build ./anneal#omnibus-archive-ci --out-link "anneal/target/${ARCHIVE_NAME}" + cp -L "anneal/target/${ARCHIVE_NAME}" "anneal/v1/${ARCHIVE_NAME}" + ARCHIVE_SIZE="$(python3 -c 'import os, sys; print(os.path.getsize(sys.argv[1]))' "anneal/v1/${ARCHIVE_NAME}")" printf 'Built %s (%s bytes)\n' "$ARCHIVE_NAME" "$ARCHIVE_SIZE" if [ "$ARCHIVE_SIZE" -gt "$MAX_GITHUB_RELEASE_ASSET_SIZE" ]; then echo "::error::${ARCHIVE_NAME} is ${ARCHIVE_SIZE} bytes, which exceeds GitHub's ${MAX_GITHUB_RELEASE_ASSET_SIZE}-byte release asset limit." exit 1 fi - gh release upload "$TAG_NAME" "anneal/${ARCHIVE_NAME}" --repo "$GH_REPO" --clobber + gh release upload "$TAG_NAME" "anneal/v1/${ARCHIVE_NAME}" --repo "$GH_REPO" --clobber gh release download "$TAG_NAME" \ --repo "$GH_REPO" \ --pattern "$ARCHIVE_NAME" \ - --dir anneal/release-download-check \ + --dir anneal/v1/release-download-check \ --clobber - cmp "anneal/${ARCHIVE_NAME}" "anneal/release-download-check/${ARCHIVE_NAME}" + cmp "anneal/v1/${ARCHIVE_NAME}" "anneal/v1/release-download-check/${ARCHIVE_NAME}" url="https://github.com/${GH_REPO}/releases/download/${TAG_NAME}/${ARCHIVE_NAME}" - python3 anneal/tools/collect-release-archive-metadata.py \ - --archive "anneal/release-download-check/${ARCHIVE_NAME}" \ + python3 anneal/v1/tools/collect-release-archive-metadata.py \ + --archive "anneal/v1/release-download-check/${ARCHIVE_NAME}" \ --target "$TARGET" \ --os "$CARGO_OS" \ --arch "$CARGO_ARCH" \ --url "$url" \ - --out "anneal/release-metadata/${TARGET}.json" + --out "anneal/v1/release-metadata/${TARGET}.json" - name: Restore AppArmor restriction if: always() && runner.os == 'Linux' @@ -320,7 +320,7 @@ jobs: uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: anneal-toolchain-metadata-${{ matrix.target }} - path: anneal/release-metadata/${{ matrix.target }}.json + path: anneal/v1/release-metadata/${{ matrix.target }}.json if-no-files-found: error retention-days: 1 @@ -359,7 +359,7 @@ jobs: uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: pattern: anneal-toolchain-metadata-* - path: anneal/release-metadata + path: anneal/v1/release-metadata merge-multiple: true - name: Update Anneal archive metadata @@ -367,27 +367,27 @@ jobs: TAG_NAME: ${{ needs.create-release.outputs.tag_name }} run: | set -eo pipefail - python3 anneal/tools/update-exocrate-metadata.py \ - --cargo-toml anneal/Cargo.toml \ - --metadata-dir anneal/release-metadata \ + python3 anneal/v1/tools/update-exocrate-metadata.py \ + --cargo-toml anneal/v1/Cargo.toml \ + --metadata-dir anneal/v1/release-metadata \ --expected-release-tag "$TAG_NAME" \ --require-all rm -f anneal-release-source.patch - rm -rf anneal/release-metadata + rm -rf anneal/v1/release-metadata - python3 anneal/tools/check-release-pr-files.py \ + python3 anneal/v1/tools/check-release-pr-files.py \ --context "Release workflow" \ --include-untracked \ - --allowed anneal/Cargo.lock \ - --allowed anneal/Cargo.toml \ - --allowed anneal/README.md \ - --required anneal/Cargo.toml + --allowed anneal/v1/Cargo.lock \ + --allowed anneal/v1/Cargo.toml \ + --allowed anneal/v1/README.md \ + --required anneal/v1/Cargo.toml - name: Submit PR id: submit-pr-upstream if: github.repository == 'google/zerocopy' - uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.0 # zizmor: ignore[superfluous-actions] + uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 # zizmor: ignore[superfluous-actions] with: commit-message: "Release Anneal ${{ github.event.inputs.version }}" author: Google PR Creation Bot @@ -396,16 +396,16 @@ jobs: base: ${{ github.event.inputs.branch }} branch: anneal-release-${{ github.event.inputs.version }} add-paths: | - anneal/Cargo.lock - anneal/Cargo.toml - anneal/README.md + anneal/v1/Cargo.lock + anneal/v1/Cargo.toml + anneal/v1/README.md push-to-fork: google-pr-creation-bot/zerocopy token: ${{ secrets.GOOGLE_PR_CREATION_BOT_TOKEN }} # zizmor: ignore[secrets-outside-env] - name: Submit PR (fork test) id: submit-pr-fork if: github.repository != 'google/zerocopy' - uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.0 # zizmor: ignore[superfluous-actions] + uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 # zizmor: ignore[superfluous-actions] with: commit-message: "Release Anneal ${{ github.event.inputs.version }}" author: Google PR Creation Bot @@ -414,9 +414,9 @@ jobs: base: ${{ github.event.inputs.branch }} branch: anneal-release-${{ github.event.inputs.version }} add-paths: | - anneal/Cargo.lock - anneal/Cargo.toml - anneal/README.md + anneal/v1/Cargo.lock + anneal/v1/Cargo.toml + anneal/v1/README.md token: ${{ github.token }} - name: Add labels diff --git a/.github/workflows/anneal.yml b/.github/workflows/anneal.yml index 9b6c95ce40..7c0697b0ab 100644 --- a/.github/workflows/anneal.yml +++ b/.github/workflows/anneal.yml @@ -50,33 +50,33 @@ jobs: set -euo pipefail export PYTHONDONTWRITEBYTECODE=1 python3 -m py_compile \ - anneal/tools/check-release-pr-files.py \ - anneal/tools/test_exocrate_metadata_helpers.py \ - anneal/tools/test_release_pr_files.py \ - anneal/tools/collect-release-archive-metadata.py \ - anneal/tools/update-exocrate-metadata.py \ - anneal/v2/tests/test_prune_lake_cache.py \ - anneal/v2/prune-lake-cache.py \ - anneal/v2/rewrite-lake-vendor.py - python3 -m unittest discover -s anneal/tools -p 'test_*.py' - python3 -m unittest discover -s anneal/v2/tests -p 'test_*.py' - bash anneal/tools/check-release-flow-dry-run.sh + anneal/v1/tools/check-release-pr-files.py \ + anneal/v1/tools/test_exocrate_metadata_helpers.py \ + anneal/v1/tools/test_release_pr_files.py \ + anneal/v1/tools/collect-release-archive-metadata.py \ + anneal/v1/tools/update-exocrate-metadata.py \ + anneal/tests/test_prune_lake_cache.py \ + anneal/prune-lake-cache.py \ + anneal/rewrite-lake-vendor.py + python3 -m unittest discover -s anneal/v1/tools -p 'test_*.py' + python3 -m unittest discover -s anneal/tests -p 'test_*.py' + bash anneal/v1/tools/check-release-flow-dry-run.sh - name: Install Nix uses: DeterminateSystems/determinate-nix-action@d96678350ffd6a456235832eb11e1c491589b7bb # v3.21.8 - name: Check V2 flake evaluation - run: bash anneal/v2/check-flake-eval.sh + run: bash anneal/check-flake-eval.sh anneal_tests: - name: Anneal Tests + name: Anneal V1 Tests runs-on: ubuntu-latest needs: v2_nix_cache permissions: actions: read # Required to download the toolchain artifact. contents: write # Required to push benchmark data to the storage branch env: - ANNEAL_TOOLCHAIN_DIR: ${{ github.workspace }}/anneal/target/anneal-toolchain + ANNEAL_TOOLCHAIN_DIR: ${{ github.workspace }}/anneal/v1/target/anneal-toolchain __ZEROCOPY_LOCAL_DEV: 1 RUSTFLAGS: "" RUSTDOCFLAGS: "" @@ -99,16 +99,16 @@ jobs: uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: anneal-exocrate.tar.zst - path: anneal/v2/target + path: anneal/target # Ensure `llms-full.txt` file is up-to-date. - name: Check doc generation run: cargo run -p doc_gen -- --check - working-directory: anneal + working-directory: anneal/v1 - name: Install Anneal toolchain archive - run: cargo run setup --local-archive v2/target/anneal-exocrate.tar.zst - working-directory: anneal + run: cargo run setup --local-archive ../target/anneal-exocrate.tar.zst + working-directory: anneal/v1 # Run unit tests separately, as they're much less likely to have bugs # during local development, and this makes the GitHub Actions output @@ -116,7 +116,7 @@ jobs: # is due to unit or integration tests). - name: Run unit tests run: cargo test --verbose --bin cargo-anneal - working-directory: anneal + working-directory: anneal/v1 # We duplicate running unit tests since they're very cheap compared to # integration tests, and this way it's easier to be sure that we run all @@ -130,7 +130,7 @@ jobs: duration=$((end - start)) echo "Test Time: $duration seconds" echo "[{\"name\": \"Test Time\", \"unit\": \"seconds\", \"value\": $duration}]" > test_time.json - working-directory: anneal + working-directory: anneal/v1 - name: Combine benchmarks env: @@ -141,7 +141,7 @@ jobs: echo "[{\"name\": \"Total CI Duration (All Steps)\", \"unit\": \"seconds\", \"value\": $total_duration}]" > total_time.json jq -n \ - --slurpfile test anneal/test_time.json \ + --slurpfile test anneal/v1/test_time.json \ --slurpfile total total_time.json \ '[ $test[0][0], @@ -167,14 +167,14 @@ jobs: github-token: ${{ secrets.GITHUB_TOKEN }} verify_examples: - name: Verify example (${{ matrix.example }}) + name: Verify V1 example (${{ matrix.example }}) runs-on: ubuntu-latest needs: v2_nix_cache permissions: actions: read # Required to download the toolchain artifact. contents: read env: - ANNEAL_TOOLCHAIN_DIR: ${{ github.workspace }}/anneal/target/anneal-toolchain + ANNEAL_TOOLCHAIN_DIR: ${{ github.workspace }}/anneal/v1/target/anneal-toolchain __ZEROCOPY_LOCAL_DEV: 1 RUSTFLAGS: "" RUSTDOCFLAGS: "" @@ -215,11 +215,11 @@ jobs: uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: anneal-exocrate.tar.zst - path: anneal/v2/target + path: anneal/target - name: Install Anneal toolchain archive - run: cargo run setup --local-archive v2/target/anneal-exocrate.tar.zst - working-directory: anneal + run: cargo run setup --local-archive ../target/anneal-exocrate.tar.zst + working-directory: anneal/v1 - name: Verify example env: @@ -253,7 +253,7 @@ jobs: exit 1 fi fi - working-directory: anneal + working-directory: anneal/v1 # Build the Nix-produced toolchain archive once and fan out the exact archive # as a workflow artifact. This avoids forcing every downstream matrix runner @@ -283,8 +283,8 @@ jobs: id: restore_anneal_main_nix_cache uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: - path: anneal/v2/target/nix-cache-main - key: anneal-v2-main-nix-cache-v2-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('anneal/v2/flake.nix', 'anneal/v2/flake.lock', 'anneal/v2/rewrite-lake-vendor.py', 'anneal/v2/prune-lake-cache.py') }} + path: anneal/target/nix-cache-main + key: anneal-v2-main-nix-cache-v2-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('anneal/flake.nix', 'anneal/flake.lock', 'anneal/rewrite-lake-vendor.py', 'anneal/prune-lake-cache.py') }} # Pull request caches are scoped by GitHub to the PR merge ref, so they # can speed up repeated pushes to the same PR without becoming visible to @@ -295,8 +295,8 @@ jobs: if: github.event_name == 'pull_request' uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: - path: anneal/v2/target/nix-cache-pr - key: anneal-v2-pr-nix-cache-v1-${{ runner.os }}-${{ runner.arch }}-pr-${{ github.event.pull_request.number }}-${{ hashFiles('anneal/v2/flake.nix', 'anneal/v2/flake.lock', 'anneal/v2/rewrite-lake-vendor.py', 'anneal/v2/prune-lake-cache.py') }} + path: anneal/target/nix-cache-pr + key: anneal-v2-pr-nix-cache-v1-${{ runner.os }}-${{ runner.arch }}-pr-${{ github.event.pull_request.number }}-${{ hashFiles('anneal/flake.nix', 'anneal/flake.lock', 'anneal/rewrite-lake-vendor.py', 'anneal/prune-lake-cache.py') }} - name: Install Nix uses: DeterminateSystems/determinate-nix-action@d96678350ffd6a456235832eb11e1c491589b7bb # v3.21.8 @@ -336,7 +336,7 @@ jobs: exit 1 fi nix build "${nix_args[@]}" .#omnibus-archive-layout-check --no-link - working-directory: anneal/v2 + working-directory: anneal # Re-enable the AppArmor namespace restriction to restore the runner host's default security posture. # `if: always()` ensures this cleanup step runs even if the Nix build fails. @@ -358,13 +358,13 @@ jobs: # wrapper cheap; higher levels add CPU time for negligible size wins. nix copy .#omnibus-archive-ci \ --to "file://$PWD/target/nix-cache-main/?compression=zstd&compression-level=1&trusted=1" - working-directory: anneal/v2 + working-directory: anneal - name: Save Anneal Nix binary cache if: github.event_name == 'push' && github.ref == 'refs/heads/main' && steps.restore_anneal_main_nix_cache.outputs.cache-hit != 'true' uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: - path: anneal/v2/target/nix-cache-main + path: anneal/target/nix-cache-main key: ${{ steps.restore_anneal_main_nix_cache.outputs.cache-primary-key }} - name: Populate Anneal PR Nix binary cache @@ -376,20 +376,20 @@ jobs: # archive itself uses zstd level 6; keep this wrapper at level 1. nix copy .#omnibus-archive-ci \ --to "file://$PWD/target/nix-cache-pr/?compression=zstd&compression-level=1&trusted=1" - working-directory: anneal/v2 + working-directory: anneal - name: Save Anneal PR Nix binary cache if: github.event_name == 'pull_request' && steps.restore_anneal_pr_nix_cache.outputs.cache-hit != 'true' uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: - path: anneal/v2/target/nix-cache-pr + path: anneal/target/nix-cache-pr key: ${{ steps.restore_anneal_pr_nix_cache.outputs.cache-primary-key }} - name: Upload Anneal toolchain archive uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: anneal-exocrate.tar.zst - path: anneal/v2/target/anneal-exocrate.tar.zst + path: anneal/target/anneal-exocrate.tar.zst if-no-files-found: error retention-days: 1 compression-level: 0 @@ -412,10 +412,10 @@ jobs: uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: anneal-exocrate.tar.zst - path: anneal/v2/target + path: anneal/target # FIXME: Pin this nightly to the same Rust date encoded in - # anneal/v2/flake.nix, or derive it from the archive metadata, so v2 CI is + # anneal/flake.nix, or derive it from the archive metadata, so v2 CI is # reproducible instead of following whatever nightly happens to be latest. - name: Install latest nightly Rust uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # zizmor: ignore[superfluous-actions] @@ -424,7 +424,7 @@ jobs: - name: Run V2 tests run: cargo test --workspace --all-features # include, e.g., tests that assume exocrate prebuilt - working-directory: anneal/v2 + working-directory: anneal # Used to signal to branch protections that all other jobs have succeeded. all-jobs-succeed: diff --git a/.vscode/settings.json b/.vscode/settings.json index ed16c0a374..37e5bf41f8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,7 +11,7 @@ "zerocopy/Cargo.toml", "tools/Cargo.toml", "anneal/Cargo.toml", - "anneal/v2/Cargo.toml", + "anneal/v1/Cargo.toml", "exocrate/Cargo.toml" ], // Extra cfgs added to rust-analyzer. diff --git a/anneal/AGENTS.md b/anneal/AGENTS.md index a0142e7794..8f11cd91d3 100644 --- a/anneal/AGENTS.md +++ b/anneal/AGENTS.md @@ -6,81 +6,90 @@ license , at your option. This file may not be copied, modified, or distributed except according to those terms. --> -# Anneal - -> **Note to AI Agents:** This document is intended for agents **developing** the -> Anneal toolchain itself. If you are an agent **using** Anneal, please refer to -> the `llms.txt` and `docs/agent/` directory instead. - -## Basic commands - -1. Run with `cargo run verify`. -2. Run unit tests with `cargo test --bin cargo-anneal`. -3. Run all tests (including integration tests) with `./docker.sh cargo test`. -4. Run a *specific* integration test fixture with `./docker.sh cargo test --test integration fixture_name`. - -## Tips - -1. Integration tests are expensive. Prefer `cargo test --bin cargo-anneal` for quick verification and iteration, - and only run `./docker.sh cargo test --test integration` when you need to verify the integration tests. -2. To see the generated Lean code for a module, use `cargo run expand`. This will run Aeneas and Anneal but skip verification, outputting the generated `.lean` definitions to the terminal. - ```bash - cargo run expand --example abs - ``` -3. To see where intermediate artifacts are placed on disk, run with `RUST_LOG=cargo_anneal=trace` as a fallback: - ```bash - RUST_LOG=cargo_anneal=trace cargo run verify --example abs - ``` - -## Integration Testing - -1. **Updating Expected Output:** Anneal integration tests (stored in `tests/fixtures/`) assert against an `expected.stderr` or `out.txt` file. When you intentionally change the behavior or output of a test, do not edit these files manually. Instead, run the integration test with `BLESS=1` to automatically overwrite the snapshot files: - ```bash - BLESS=1 ./docker.sh cargo test --test integration fixture_name - ``` - -2. **Allowing `sorry`:** While developing and ensuring Aeneas translates Rust correctly, you don't have to write the full Lean proof immediately. You can write `sorry` inside the `proof` block. However, you must pass `--allow-sorry` to Anneal so the verifier doesn't fail immediately on the unimplemented proof. For integration tests, this is done by adding `--allow-sorry` to the `args` array in the fixture's `anneal.toml`: - ```toml - args = ["verify", "--allow-sorry"] - ``` - -## Running Tests in Docker - -Due to the complex toolchains required by Anneal (Rust, Lean 4, Aeneas, and Charon), it is recommended to run integration tests within the provided Docker container. The container handles all system dependencies and provides an isolated environment for the test runner. - -The `docker.sh` script should be used. It handles building and caching the Docker image, mounting compilation cache volumes, mapping local user IDs (so that files are not owned by `root` on your host), and forwarding environment variables. - -Simply prefix your normal Cargo tests with `./docker.sh`: -```bash -./docker.sh cargo test --test integration - -# You can pass standard environment variables. They will be forwarded! -BLESS=1 ./docker.sh cargo test --test integration -``` - -*(Under the hood, this evaluates your path and places you in the same working directory inside the container's bound `/workspace` volume)* - -## Debugging Tips - -1. **Debugging Aeneas vs Anneal Mismatches** - - If you encounter Lean type mismatches (e.g., `Application type mismatch`), it's often a mismatch between what Aeneas generated for the function and what Anneal assumed Aeneas generated in the theorem signature. - - Standard integration tests (`./docker.sh cargo test --test integration`) delete their temporary output directory (`/cache/anneal_target/anneal-test-XXX` inside Docker) upon completion. - - If a test fails and you want to inspect the generated Lean code, use the `ANNEAL_KEEP_TEST_DIR=1` environment variable. - - The test runner will preserve the directory and print its path to stderr: - ```bash - ANNEAL_KEEP_TEST_DIR=1 ./docker.sh cargo test --test integration macro_edge_cases - ``` - - **Note:** Because the test runner operates inside Docker, the printed path will reside within the container's isolated `/cache` volume. To explore it, you must open a shell inside the container: - ```bash - ./docker.sh bash - # Then inside the container, navigate to the path printed to stderr: - cd /cache/anneal_target/anneal-test-XXX - ``` - - Look at `Funs.lean` to see the actual Aeneas parameters, and `[TestName].lean` to see the Anneal theorem signature. - -2. **Caches Need Busting** - - Sometimes `cargo test` fails with bizarre syntax errors that persist despite fixing the code. - - If this happens, clear the integration caches. If you are using `./docker.sh`, the caches reside inside the container's `/cache` volume, so you must clear them from within by running: - ```bash - ./docker.sh rm -rf /cache/anneal_target/anneal_integration_cache - ``` +# Anneal agent guide + +This file governs work in `anneal/`. Work under `anneal/v1/` must also follow +the more specific `v1/AGENTS.md`. + +Anneal is a clean-room, ground-up redesign of the experimental V1 prototype. +V1 is useful evidence, not a default architecture, specification language, or +user interface. Reuse a V1 idea or implementation only after evaluating it +against the current design. + +## Mission + +Anneal is a general verification tool for Rust, with unsafe-code soundness as +its foundational use case. It aims to let ordinary Rust teams verify subtle +properties of real Rust programs while preserving the guarantees expected of +safe Rust. + +Soundness is non-negotiable. Rust compilers and Anneal's source models need not +preserve a program's behavior after undefined behavior, so Anneal must establish +the soundness conditions on which its own model fidelity depends. Never gain +simplicity, coverage, or convenience by weakening such a condition, silently +omitting an operation, or hiding trust. + +## Start here + +`agent_docs/` is the canonical project context and design corpus written for +coding agents. It is not Anneal's user-facing documentation surface. Human +maintainers may read and edit it, but its organization and wording should be +optimized for agent ingestion and judgment. + +In an unfamiliar checkout, first complete the +[agent-corpus preflight](agent_docs/agent-corpus.md). Then follow the canonical +[documentation map](agent_docs/README.md), which owns the reading order, +document authority, and reconciliation procedure for the design canon. + +## Working protocol + +- Evaluate alternatives using the + [design principles](agent_docs/design/principles.md) and satisfy every + applicable + [settled requirement](agent_docs/design/settled-requirements.md). +- Follow accepted decision records. Treat open-question documents, issues, + pull requests, research, experiments, and V1 as evidence rather than settled + authority. +- Do not settle an open design incidentally in implementation. Before a change + commits the project to an answer, obtain explicit agreement from the project + authors and record the accepted decision. Otherwise label the implementation + as an experiment or surface the choice. +- If documents disagree, surface and reconcile the disagreement using the + process in the documentation map instead of silently choosing convenient + text. +- Fail closed relative to the exact claim being reported. Unsupported + semantics, omitted coverage, hidden assumptions, and undischarged obligations + must not produce an unconditional verification claim. +- Determine current behavior from the checked-in code and the + [current-state reference](agent_docs/reference/current-state.md). Open or + stacked pull requests may provide context, but are not authoritative for the + current checkout. +- Prefer existing, maintained Lean, Aeneas, Charon, and Rust mechanisms when + they fit. Flag abstractions or extensions which may belong upstream; decide + ownership case by case, including the maintenance burden on collaborators. +- Prefer robust programmatic interfaces over textual patching and keep the + proof and diagnostic experience usable by ordinary Rust engineers. + +### Applying the design principles + +When settled requirements and accepted decisions do not determine an answer: + +1. state the exact claim each alternative would support; +2. identify added trust, lost coverage, or omitted semantic information; +3. ask whether the result still composes at an established abstraction + boundary; +4. consider usability, maintenance, and the path to broader coverage; +5. prefer reversible experiments while evidence is weak. + +## Documentation maintenance + +- Update the design canon in the same change that alters a settled contract, + trust boundary, or project-wide constraint. +- Give requirements stable identifiers and link implementation decisions back + to them. +- Record meaningful evidence and rejected alternatives, but remove temporary + misunderstandings once terminology has been clarified. +- Keep current-state references synchronized with checked-in behavior. Issues + and pull requests are guides to thinking, not specifications. +- Link to each explanation's canonical owner instead of duplicating it across + agent instructions or corpus pages. diff --git a/anneal/Cargo.lock b/anneal/Cargo.lock index 04bcfa1bdb..cd9c673ac1 100644 --- a/anneal/Cargo.lock +++ b/anneal/Cargo.lock @@ -17,6 +17,18 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.4" @@ -28,14 +40,30 @@ dependencies = [ [[package]] name = "annotate-snippets" -version = "0.11.5" +version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "710e8eae58854cdc1790fcb56cca04d712a17be849eeb81da2a724bf4bae2bc4" +checksum = "f211a51805bc641f3ad5b7664c77d2547af685cc33b4cd8d31964027a46f13f1" dependencies = [ "anstyle", + "memchr", "unicode-width 0.2.2", ] +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse 0.2.7", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + [[package]] name = "anstream" version = "1.0.0" @@ -43,7 +71,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", - "anstyle-parse", + "anstyle-parse 1.0.0", "anstyle-query", "anstyle-wincon", "colorchoice", @@ -57,6 +85,15 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + [[package]] name = "anstyle-parse" version = "1.0.0" @@ -92,11 +129,26 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "ar_archive_writer" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340b" +dependencies = [ + "object", +] + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "assert_cmd" -version = "2.2.0" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a686bbee5efb88a82df0621b236e74d925f470e5445d3220a5648b892ec99c9" +checksum = "2aa3a22042e45de04255c7bf3626e239f450200fd0493c1e382263544b20aea6" dependencies = [ "anstyle", "bstr", @@ -107,11 +159,20 @@ dependencies = [ "wait-timeout", ] +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "backtrace" @@ -143,26 +204,11 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "bit-set" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" -dependencies = [ - "bit-vec", -] - -[[package]] -name = "bit-vec" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" - [[package]] name = "bitflags" -version = "2.11.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "84d7ced0ae9557296835c32bf1b1e02b44c746701f898460fb000d7eaa84f00a" [[package]] name = "block-buffer" @@ -173,6 +219,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "brownstone" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5839ee4f953e811bfdcf223f509cb2c6a3e1447959b0bff459405575bc17f22" +dependencies = [ + "arrayvec", +] + [[package]] name = "bstr" version = "1.12.1" @@ -186,9 +241,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "byteorder" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" @@ -207,16 +268,13 @@ dependencies = [ [[package]] name = "cargo-anneal" -version = "0.1.0-alpha.24" +version = "0.1.0-alpha.22" dependencies = [ "anyhow", - "assert_cmd", - "cargo_metadata 0.23.1", + "cargo_metadata", + "charon", "clap", "clap-cargo", - "console", - "dashmap", - "datatest-stable", "env_logger", "exocrate", "fs2", @@ -224,57 +282,26 @@ dependencies = [ "log", "miette", "pathdiff", - "predicates", - "proc-macro2", - "quote", "rayon", - "regex", "serde", "serde_json", "sha2", - "similar", - "strip-ansi-escapes", - "syn", "tempfile", - "thiserror 2.0.18", - "toml", - "ui_test", + "thiserror", + "toml_const", "walkdir", ] [[package]] name = "cargo-platform" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo-platform" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87a0c0e6148f11f01f32650a2ea02d532b2ad4e81d8bd41e6e565b5adc5e6082" +checksum = "dd0061da739915fae12ea00e16397555ed4371a6bb285431aab930f61b0aa4ba" dependencies = [ "serde", "serde_core", ] -[[package]] -name = "cargo_metadata" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" -dependencies = [ - "camino", - "cargo-platform 0.1.9", - "semver", - "serde", - "serde_json", - "thiserror 1.0.69", -] - [[package]] name = "cargo_metadata" version = "0.23.1" @@ -282,18 +309,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef987d17b0a113becdd19d3d0022d04d7ef41f9efe4f3fb63ac44ba61df3ade9" dependencies = [ "camino", - "cargo-platform 0.3.2", + "cargo-platform", "semver", "serde", "serde_json", - "thiserror 2.0.18", + "thiserror", ] [[package]] name = "cc" -version = "1.2.59" +version = "1.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" +checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" dependencies = [ "find-msvc-tools", "jobserver", @@ -307,11 +334,62 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "charon" +version = "0.1.210" +source = "git+https://github.com/AeneasVerif/charon.git?tag=nightly-2026.06.03#0c91ca1a8e002d6bfa8d8f1f452804fce2f92cf1" +dependencies = [ + "annotate-snippets", + "anstream 0.6.21", + "anyhow", + "assert_cmd", + "clap", + "colored", + "convert_case", + "derive_generic_visitor", + "either", + "env_logger", + "extension-traits", + "fraction", + "hashbrown 0.15.5", + "hax-adt-into", + "index_vec", + "indexmap", + "indoc", + "itertools", + "lazy_static", + "log", + "macros", + "nom", + "nom-supreme", + "num-bigint", + "paste", + "petgraph", + "postcard", + "rustc-hash", + "rustc_version", + "serde", + "serde_json", + "serde_stacker", + "serde_state", + "smallvec", + "stacker", + "strip-ansi-escapes", + "take_mut", + "tempfile", + "toml 0.8.23", + "tracing", + "tracing-subscriber", + "tracing-tree", + "ustr", + "which", +] + [[package]] name = "clap" -version = "4.6.0" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", "clap_derive", @@ -324,7 +402,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "936551935c8258754bb8216aec040957d261f977303754b9bf1a213518388006" dependencies = [ "anstyle", - "cargo_metadata 0.23.1", + "cargo_metadata", "clap", "serde", "serde_json", @@ -336,7 +414,7 @@ version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ - "anstream", + "anstream 1.0.0", "anstyle", "clap_lex", "strsim", @@ -344,14 +422,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.6.0" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -361,30 +439,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] -name = "color-eyre" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5920befb47832a6d61ee3a3a846565cfa39b331331e68a3b1d1116630f2f26d" -dependencies = [ - "backtrace", - "color-spantrace", - "eyre", - "indenter", - "once_cell", - "owo-colors", - "tracing-error", -] - -[[package]] -name = "color-spantrace" +name = "cobs" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8b88ea9df13354b55bc7234ebcce36e6ef896aca2e42a15de9e10edce01b427" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" dependencies = [ - "once_cell", - "owo-colors", - "tracing-core", - "tracing-error", + "thiserror", ] [[package]] @@ -395,19 +455,14 @@ checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "colored" -version = "3.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ - "windows-sys 0.61.2", + "lazy_static", + "windows-sys 0.59.0", ] -[[package]] -name = "comma" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55b672471b4e9f9e95499ea597ff64941a309b2cdbffcc46f2cc5e2d971fd335" - [[package]] name = "console" version = "0.16.3" @@ -420,6 +475,15 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "cpufeatures" version = "0.2.17" @@ -439,13 +503,10 @@ dependencies = [ ] [[package]] -name = "crossbeam-channel" -version = "0.5.15" +name = "critical-section" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" -dependencies = [ - "crossbeam-utils", -] +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" [[package]] name = "crossbeam-deque" @@ -483,29 +544,70 @@ dependencies = [ ] [[package]] -name = "dashmap" -version = "6.1.0" +name = "darling" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ - "cfg-if", - "crossbeam-utils", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core", + "darling_core", + "darling_macro", ] [[package]] -name = "datatest-stable" -version = "0.3.3" +name = "darling_core" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a867d7322eb69cf3a68a5426387a25b45cb3b9c5ee41023ee6cea92e2afadd82" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" dependencies = [ - "camino", - "fancy-regex", - "libtest-mimic", - "walkdir", + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.117", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive_generic_visitor" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21588db07753dafe6460b13faad18bd7e46548ce91ee38f0bc0f7529b3864b82" +dependencies = [ + "derive_generic_visitor_macros", +] + +[[package]] +name = "derive_generic_visitor_macros" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3aed1890235ebcf844a24263ecc395c4e57a970f13bcc9948989f22aa051aa7" +dependencies = [ + "convert_case", + "darling", + "itertools", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] @@ -546,18 +648,22 @@ dependencies = [ ] [[package]] -name = "doc_gen" -version = "0.1.0" -dependencies = [ - "tree-sitter", - "tree-sitter-lean4", -] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] -name = "either" -version = "1.15.0" +name = "embedded-io" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" [[package]] name = "encode_unicode" @@ -575,13 +681,19 @@ dependencies = [ "regex", ] +[[package]] +name = "env_home" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" + [[package]] name = "env_logger" version = "0.11.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" dependencies = [ - "anstream", + "anstream 1.0.0", "anstyle", "env_filter", "jiff", @@ -604,12 +716,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "escape8259" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5692dd7b5a1978a5aeb0ce83b7655c58ca8efdcb79d21036ea249da95afec2c6" - [[package]] name = "exocrate" version = "0.1.0" @@ -626,41 +732,48 @@ dependencies = [ ] [[package]] -name = "eyre" -version = "0.6.12" +name = "ext-trait" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d772df1c1a777963712fb68e014235e80863d6a91a85c4e06ba2d16243a310e5" +dependencies = [ + "ext-trait-proc_macros", +] + +[[package]] +name = "ext-trait-proc_macros" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +checksum = "1ab7934152eaf26aa5aa9f7371408ad5af4c31357073c9e84c3b9d7f11ad639a" dependencies = [ - "indenter", - "once_cell", + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] -name = "fancy-regex" -version = "0.14.0" +name = "extension-traits" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298" +checksum = "a296e5a895621edf9fa8329c83aa1cb69a964643e36cf54d8d7a69b789089537" dependencies = [ - "bit-set", - "regex-automata", - "regex-syntax", + "ext-trait", ] [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "filetime" -version = "0.2.27" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" dependencies = [ "cfg-if", "libc", - "libredox", ] [[package]] @@ -669,6 +782,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + [[package]] name = "flate2" version = "1.1.9" @@ -680,13 +799,10 @@ dependencies = [ ] [[package]] -name = "float-cmp" -version = "0.10.0" +name = "fnv" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" -dependencies = [ - "num-traits", -] +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" @@ -694,6 +810,16 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "fraction" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e076045bb43dac435333ed5f04caf35c7463631d0dae2deb2638d94dd0a5b872" +dependencies = [ + "lazy_static", + "num", +] + [[package]] name = "fs2" version = "0.4.3" @@ -704,6 +830,30 @@ dependencies = [ "winapi", ] +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -757,10 +907,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" [[package]] -name = "hashbrown" -version = "0.14.5" +name = "hash32" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] [[package]] name = "hashbrown" @@ -773,9 +926,35 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.1" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "hax-adt-into" +version = "0.3.5" +source = "git+https://github.com/AeneasVerif/charon.git?tag=nightly-2026.06.03#0c91ca1a8e002d6bfa8d8f1f452804fce2f92cf1" +dependencies = [ + "itertools", + "proc-macro2", + "quote", + "syn 1.0.109", + "tracing", +] + +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version", + "serde", + "spin", + "stable_deref_trait", +] [[package]] name = "heck" @@ -785,9 +964,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "http" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" dependencies = [ "bytes", "itoa", @@ -806,19 +985,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" [[package]] -name = "indenter" -version = "0.3.4" +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indent_write" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" +checksum = "0cfe9645a18782869361d9c8732246be7b410ad4e919d3609ebabdac00ba12c3" + +[[package]] +name = "index_vec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44faf5bb8861a9c72e20d3fb0fdbd59233e43056e2b80475ab0aacdc2e781355" +dependencies = [ + "serde", +] [[package]] name = "indexmap" -version = "2.13.1" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.1", "serde", "serde_core", ] @@ -837,6 +1031,15 @@ dependencies = [ "web-time", ] +[[package]] +name = "indoc" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] + [[package]] name = "is_ci" version = "1.2.0" @@ -849,6 +1052,15 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.18" @@ -857,9 +1069,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jiff" -version = "0.2.23" +version = "0.2.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" +checksum = "4603d3033e49e2b0e31229fcab20a5d40089c607d975cd9c80551dc69eed9102" dependencies = [ "jiff-static", "log", @@ -870,13 +1082,13 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.23" +version = "0.2.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" +checksum = "782d32378dddf207193ac91cefb848ad41abb58195c95168e1291227a0832b47" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -889,12 +1101,20 @@ dependencies = [ "libc", ] +[[package]] +name = "joinery" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72167d68f5fce3b8655487b8038691a3c9984ee769590f93f2a631f4ad64e4f5" + [[package]] name = "js-sys" -version = "0.3.94" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" +checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" dependencies = [ + "cfg-if", + "futures-util", "once_cell", "wasm-bindgen", ] @@ -911,40 +1131,19 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" -[[package]] -name = "levenshtein" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" - [[package]] name = "libc" -version = "0.2.184" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libredox" -version = "0.1.15" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" +checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" dependencies = [ - "bitflags", "libc", - "plain", - "redox_syscall 0.7.3", -] - -[[package]] -name = "libtest-mimic" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14e6ba06f0ade6e504aff834d7c34298e5155c6baca353cc6a4aaff2f9fd7f33" -dependencies = [ - "anstream", - "anstyle", - "clap", - "escape8259", ] [[package]] @@ -964,15 +1163,34 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "113b30b4cd05f7c06868fdb2854f66a7b9fece9a48425351cd532e810d74024f" + +[[package]] +name = "macros" +version = "0.1.0" +source = "git+https://github.com/AeneasVerif/charon.git?tag=nightly-2026.06.03#0c91ca1a8e002d6bfa8d8f1f452804fce2f92cf1" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "matchers" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" [[package]] name = "miette" @@ -1001,9 +1219,15 @@ checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] +[[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.8.9" @@ -1015,10 +1239,106 @@ dependencies = [ ] [[package]] -name = "normalize-line-endings" -version = "0.3.0" +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nom-supreme" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bd3ae6c901f1959588759ff51c95d24b491ecb9ff91aa9c2ef4acc5b1dcab27" +dependencies = [ + "brownstone", + "indent_write", + "joinery", + "memchr", + "nom", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] [[package]] name = "num-traits" @@ -1029,6 +1349,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + [[package]] name = "object" version = "0.37.3" @@ -1062,6 +1391,16 @@ version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + [[package]] name = "parking_lot_core" version = "0.9.12" @@ -1070,11 +1409,17 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.18", + "redox_syscall", "smallvec", "windows-link", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pathdiff" version = "0.2.3" @@ -1087,6 +1432,61 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "petgraph" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +dependencies = [ + "fixedbitset", + "hashbrown 0.15.5", + "indexmap", + "serde", +] + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros", + "phf_shared", + "serde", +] + +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project-lite" version = "0.2.17" @@ -1095,15 +1495,9 @@ checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - -[[package]] -name = "plain" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "portable-atomic" @@ -1113,13 +1507,32 @@ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" dependencies = [ "portable-atomic", ] +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "heapless", + "serde", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "predicates" version = "3.1.4" @@ -1128,10 +1541,7 @@ checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe" dependencies = [ "anstyle", "difflib", - "float-cmp", - "normalize-line-endings", "predicates-core", - "regex", ] [[package]] @@ -1142,21 +1552,12 @@ checksum = "cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144" [[package]] name = "predicates-tree" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2" -dependencies = [ - "predicates-core", - "termtree", -] - -[[package]] -name = "prettydiff" -version = "0.9.0" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac17546d82912e64874e3d5b40681ce32eac4e5834344f51efcf689ff1550a65" +checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2" dependencies = [ - "owo-colors", + "predicates-core", + "termtree", ] [[package]] @@ -1166,7 +1567,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.117", ] [[package]] @@ -1178,6 +1579,16 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "psm" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645dbe486e346d9b5de3ef16ede18c26e6c70ad97418f4874b8b1889d6e761ea" +dependencies = [ + "ar_archive_writer", + "cc", +] + [[package]] name = "quote" version = "1.0.45" @@ -1201,9 +1612,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rayon" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" dependencies = [ "either", "rayon-core", @@ -1228,15 +1639,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "redox_syscall" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" -dependencies = [ - "bitflags", -] - [[package]] name = "redox_users" version = "0.5.2" @@ -1245,7 +1647,7 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.17", "libredox", - "thiserror 2.0.18", + "thiserror", ] [[package]] @@ -1297,6 +1699,12 @@ version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + [[package]] name = "rustc_version" version = "0.4.1" @@ -1306,18 +1714,6 @@ dependencies = [ "semver", ] -[[package]] -name = "rustfix" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82fa69b198d894d84e23afde8e9ab2af4400b2cba20d6bf2b428a8b01c222c5a" -dependencies = [ - "serde", - "serde_json", - "thiserror 1.0.69", - "tracing", -] - [[package]] name = "rustix" version = "1.1.4" @@ -1333,9 +1729,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.37" +version = "0.23.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" dependencies = [ "log", "once_cell", @@ -1348,9 +1744,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.0" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" dependencies = [ "zeroize", ] @@ -1389,9 +1785,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" dependencies = [ "serde", "serde_core", @@ -1424,16 +1820,15 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ - "indexmap", "itoa", "memchr", "serde", @@ -1450,6 +1845,45 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_stacker" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4936375d50c4be7eff22293a9344f8e46f323ed2b3c243e52f89138d9bb0f4a" +dependencies = [ + "serde", + "serde_core", + "stacker", +] + +[[package]] +name = "serde_state" +version = "1.0.0" +source = "git+https://github.com/Nadrieril/serde_state?branch=main#9bffbc1b28fd93533bbd3ca835385f29cb51888f" +dependencies = [ + "serde", + "serde_state_derive", +] + +[[package]] +name = "serde_state_derive" +version = "1.0.0" +source = "git+https://github.com/Nadrieril/serde_state?branch=main#9bffbc1b28fd93533bbd3ca835385f29cb51888f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "sha2" version = "0.10.9" @@ -1478,9 +1912,9 @@ dependencies = [ [[package]] name = "shlex" -version = "1.3.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "simd-adler32" @@ -1489,10 +1923,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] -name = "similar" -version = "2.7.0" +name = "siphasher" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" @@ -1501,21 +1941,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] -name = "spanned" -version = "0.4.1" +name = "spin" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92d4b0c055fde758f086eb4a6e73410247df8a3837fd606d2caeeaf72aa566d" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" dependencies = [ - "anyhow", - "bstr", - "color-eyre", + "lock_api", ] [[package]] -name = "streaming-iterator" -version = "0.1.9" +name = "stable_deref_trait" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "stacker" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "640c8cdd92b6b12f5bcb1803ca3bbf5ab96e5e6b6b96b9ab77dabe9e880b3190" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "windows-sys 0.61.2", +] [[package]] name = "strip-ansi-escapes" @@ -1559,6 +2010,17 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.117" @@ -1570,6 +2032,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "take_mut" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" + [[package]] name = "tar" version = "0.4.46" @@ -1620,33 +2088,13 @@ dependencies = [ "unicode-width 0.2.2", ] -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.18", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "thiserror-impl", ] [[package]] @@ -1657,7 +2105,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1669,6 +2117,39 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "libc", + "num-conv", + "num_threads", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "toml" version = "0.8.23" @@ -1676,11 +2157,50 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", - "serde_spanned", - "toml_datetime", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", "toml_edit", ] +[[package]] +name = "toml" +version = "0.9.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 0.7.15", +] + +[[package]] +name = "toml_const" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389864cf501669b741746e9bc35e66c67f2b80266482ad04c452e2410433f205" +dependencies = [ + "phf", + "toml 0.9.12+spec-1.1.0", + "toml_const_macros", +] + +[[package]] +name = "toml_const_macros" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0f202adfb96027640ad9786a0657775414482cfd840c19e9957ada8cfbbfd" +dependencies = [ + "indexmap", + "proc-macro2", + "quote", + "syn 2.0.117", + "toml 0.9.12+spec-1.1.0", +] + [[package]] name = "toml_datetime" version = "0.6.11" @@ -1690,6 +2210,15 @@ dependencies = [ "serde", ] +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + [[package]] name = "toml_edit" version = "0.22.27" @@ -1698,10 +2227,19 @@ checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", "serde", - "serde_spanned", - "toml_datetime", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", "toml_write", - "winnow", + "winnow 0.7.15", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow 1.0.3", ] [[package]] @@ -1710,6 +2248,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" +[[package]] +name = "toml_writer" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" + [[package]] name = "tracing" version = "0.1.44" @@ -1717,9 +2261,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "tracing-core" version = "0.1.36" @@ -1731,13 +2287,14 @@ dependencies = [ ] [[package]] -name = "tracing-error" -version = "0.2.1" +name = "tracing-log" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ - "tracing", - "tracing-subscriber", + "log", + "once_cell", + "tracing-core", ] [[package]] @@ -1746,72 +2303,35 @@ version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", "sharded-slab", + "smallvec", "thread_local", + "tracing", "tracing-core", + "tracing-log", ] [[package]] -name = "tree-sitter" -version = "0.25.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78f873475d258561b06f1c595d93308a7ed124d9977cb26b148c2084a4a3cc87" -dependencies = [ - "cc", - "regex", - "regex-syntax", - "serde_json", - "streaming-iterator", - "tree-sitter-language", -] - -[[package]] -name = "tree-sitter-language" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "009994f150cc0cd50ff54917d5bc8bffe8cad10ca10d81c34da2ec421ae61782" - -[[package]] -name = "tree-sitter-lean4" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d274c5b559d3a46a0d5d200fc0593fa3fda035304fa794d5d4a760fbf69c2a5e" +name = "tracing-tree" +version = "0.4.0" +source = "git+https://github.com/Nadrieril/tracing-tree#841286bfffd3c2200810244506cd127013dbeff9" dependencies = [ - "cc", - "tree-sitter", + "nu-ansi-term", + "time", + "tracing-core", + "tracing-log", + "tracing-subscriber", ] [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" - -[[package]] -name = "ui_test" -version = "0.30.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ada249620d81f010b9a1472b63a5077ac7c722dd0f4bacf6528b313d0b8c15d8" -dependencies = [ - "annotate-snippets", - "anyhow", - "bstr", - "cargo-platform 0.1.9", - "cargo_metadata 0.18.1", - "color-eyre", - "colored", - "comma", - "crossbeam-channel", - "indicatif", - "levenshtein", - "prettydiff", - "regex", - "rustc_version", - "rustfix", - "serde", - "serde_json", - "spanned", -] +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "unicode-ident" @@ -1827,9 +2347,9 @@ checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" [[package]] name = "unicode-segmentation" -version = "1.13.2" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] name = "unicode-width" @@ -1890,6 +2410,19 @@ dependencies = [ "log", ] +[[package]] +name = "ustr" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18b19e258aa08450f93369cf56dd78063586adf19e92a75b338a800f799a0208" +dependencies = [ + "ahash", + "byteorder", + "lazy_static", + "parking_lot", + "serde", +] + [[package]] name = "utf8-zero" version = "0.8.1" @@ -1950,11 +2483,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.57.1", ] [[package]] @@ -1963,14 +2496,14 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.51.0", ] [[package]] name = "wasm-bindgen" -version = "0.2.117" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" dependencies = [ "cfg-if", "once_cell", @@ -1981,9 +2514,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.117" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1991,22 +2524,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.117" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.117" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" dependencies = [ "unicode-ident", ] @@ -2057,13 +2590,25 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" dependencies = [ "rustls-pki-types", ] +[[package]] +name = "which" +version = "7.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d643ce3fd3e5b54854602a080f34fb10ab75e0b813ee32d00ca2b44fa74762" +dependencies = [ + "either", + "env_home", + "rustix", + "winsafe", +] + [[package]] name = "winapi" version = "0.3.9" @@ -2110,6 +2655,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -2192,6 +2746,18 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" + +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + [[package]] name = "wit-bindgen" version = "0.51.0" @@ -2201,6 +2767,12 @@ dependencies = [ "wit-bindgen-rust-macro", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "wit-bindgen-core" version = "0.51.0" @@ -2222,7 +2794,7 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn", + "syn 2.0.117", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -2238,7 +2810,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -2290,6 +2862,26 @@ dependencies = [ "rustix", ] +[[package]] +name = "zerocopy" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "zeroize" version = "1.8.2" diff --git a/anneal/Cargo.toml b/anneal/Cargo.toml index 6fa3ab88f5..6f5fb09509 100644 --- a/anneal/Cargo.toml +++ b/anneal/Cargo.toml @@ -1,10 +1,14 @@ [workspace] -members = [".", "tools/doc_gen"] +members = ["."] + +[features] +# Enables tests that assume a prebuilt exocrate archive. +exocrate_tests = [] [package] name = "cargo-anneal" edition = "2024" -version = "0.1.0-alpha.24" +version = "0.1.0-alpha.22" description = "Formally verify that your safety comments are correct." categories = [ "development-tools::cargo-plugins", @@ -24,89 +28,42 @@ exclude = [".*", "testdata"] # this crate; `cargo anneal setup` uses this metadata by default when callers # do not provide a local archive. [package.metadata.exocrate.linux.x86_64] -sha256 = "d3d7bbcdfd2645f10e3e64a85b3e848197033992337c969a45576a3d0ec517d9" -url = "https://github.com/google/zerocopy/releases/download/anneal-toolchains-v0.1.0-alpha.24-27079750833-09497849a10d/anneal-toolchain-linux-x86_64.tar.zst" +sha256 = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" +url = "https://example.com/linux-x86_64.tar.zst" [package.metadata.exocrate.macos.x86_64] -sha256 = "1252f15262d11f49795c8ed182a8b47f2ae4e627d90babb2f10636349f686438" -url = "https://github.com/google/zerocopy/releases/download/anneal-toolchains-v0.1.0-alpha.24-27079750833-09497849a10d/anneal-toolchain-macos-x86_64.tar.zst" +sha256 = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" +url = "https://example.com/macos-x86_64.tar.zst" [package.metadata.exocrate.linux.aarch64] -sha256 = "a9c496fcf21670b2306d02722b9afcf375149315e929d53b6a7585d4daa19710" -url = "https://github.com/google/zerocopy/releases/download/anneal-toolchains-v0.1.0-alpha.24-27079750833-09497849a10d/anneal-toolchain-linux-aarch64.tar.zst" +sha256 = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" +url = "https://example.com/linux-aarch64.tar.zst" [package.metadata.exocrate.macos.aarch64] -sha256 = "b4e0a5b420eb441e37564c365f06c215f61a3b825fc3df37293da7c3010d2b81" -url = "https://github.com/google/zerocopy/releases/download/anneal-toolchains-v0.1.0-alpha.24-27079750833-09497849a10d/anneal-toolchain-macos-aarch64.tar.zst" +sha256 = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" +url = "https://example.com/macos-aarch64.tar.zst" [dependencies] +exocrate = { path = "../exocrate" } +toml_const = "1.3.0" +clap = { version = "4.5", features = ["derive"] } +clap-cargo = { version = "0.18.3", features = ["cargo_metadata"] } +env_logger = "0.11" +log = "0.4" anyhow = "1.0.102" cargo_metadata = "0.23.1" -clap = { version = "4.6.0", features = ["derive"] } -clap-cargo = { version = "0.18.3", features = ["cargo_metadata"] } -dashmap = "6.1.0" -env_logger = "0.11.10" -log = "0.4.29" miette = { version = "7.6.0", features = ["derive", "fancy"] } -proc-macro2 = { version = "1.0.106", features = ["span-locations"] } -rayon = "1.11.0" -serde = { version = "1.0.228", features = ["derive"] } -serde_json = "1.0.149" -syn = { version = "2.0.117", features = [ - "full", - "visit", - "extra-traits", - "parsing", - "printing", -] } -quote = "1.0" thiserror = "2.0.18" -walkdir = "2.5.0" +serde_json = "1.0.149" +serde = { version = "1.0.228", features = ["derive"] } indicatif = { version = "0.18.4", features = ["improved_unicode"] } -console = "0.16.3" -exocrate = { path = "../exocrate" } -sha2 = "0.10" fs2 = "0.4" -pathdiff = "0.2.3" -toml = "0.8" -tempfile = "3.27.0" - -[build-dependencies] +walkdir = "2.5.0" sha2 = "0.10" -toml = "0.8" +tempfile = "3.27.0" +rayon = "1.11.0" +# `tag` is `main` branch HEAD on 2026-06-03. +charon_lib = { package = "charon", git = "https://github.com/AeneasVerif/charon.git", tag = "nightly-2026.06.03", default-features = false } [dev-dependencies] -syn = { version = "2.0.117", features = [ - "printing", - "full", - "visit", - "extra-traits", - "parsing", -] } -proc-macro2 = { version = "1.0.106", features = ["span-locations"] } -ui_test = "0.30.4" -assert_cmd = "2.2.0" - -predicates = "3.1.4" - -datatest-stable = "0.3.3" -serde = { version = "1.0", features = ["derive"] } -toml = "0.8" -regex = "1.0" -strip-ansi-escapes = "0.2.1" -similar = "2.7.0" - -[package.metadata.build_rs] - -# The commit hash of the Aeneas repository to use. -# -# FIXME: Add a CI step to verify that this commit exists and matches the -# toolchain version. -aeneas_rev = "42c0e90dacf486f7d3ed5b6cde3a9a81f04915a4" - -# The Lean toolchain version to use. This must match the version of Lean used -# by Aeneas in the `lean-toolchain` file in the commit above. -lean_toolchain = "leanprover/lean4:v4.30.0-rc2" -[[test]] -name = "integration" -harness = false +pathdiff = "0.2" diff --git a/anneal/LICENSE-APACHE b/anneal/LICENSE-APACHE deleted file mode 120000 index 965b606f33..0000000000 --- a/anneal/LICENSE-APACHE +++ /dev/null @@ -1 +0,0 @@ -../LICENSE-APACHE \ No newline at end of file diff --git a/anneal/LICENSE-BSD b/anneal/LICENSE-BSD deleted file mode 120000 index d37ba4277e..0000000000 --- a/anneal/LICENSE-BSD +++ /dev/null @@ -1 +0,0 @@ -../LICENSE-BSD \ No newline at end of file diff --git a/anneal/LICENSE-MIT b/anneal/LICENSE-MIT deleted file mode 120000 index 76219eb72e..0000000000 --- a/anneal/LICENSE-MIT +++ /dev/null @@ -1 +0,0 @@ -../LICENSE-MIT \ No newline at end of file diff --git a/anneal/README.md b/anneal/README.md index 66b7fd2811..f9df49bc9b 100644 --- a/anneal/README.md +++ b/anneal/README.md @@ -1,217 +1,169 @@ -# Anneal - - - -

logo by tinyneonspark

- -> **Note:** Anneal is currently in pre-alpha. You're welcome to use it, but many things -> are broken or unsound, and we will change APIs frequently. - -For safe code, Rust promises that "if it compiles, then it is memory-safe." Anneal promises the same for unsafe code. - -Rust promises that all safe Rust code is free of memory safety bugs. It does this by encoding the requirements for memory safety in the type system, ensuring that if a program type checks, then it is free of memory safety bugs. The lifetime system prevents use-after-free bugs, the ownership system prevents double-free bugs, and so on. **There do not exist type-checked safe Rust programs with memory safety bugs.**[^1] The same is not true of unsafe Rust code. **There do exist type-checked unsafe Rust programs with memory safety bugs.** - -[^1]: Barring compiler and platform bugs, which do exist. - -One way to understand this distinction is that a safe Rust function must be free of memory safety bugs *no matter what value is passed in*. Consider [`usize::strict_div`](https://doc.rust-lang.org/std/primitive.usize.html#method.strict_div): - -```rust -impl usize { - /// # Panics - /// - /// This function will panic if `rhs` is zero. - pub fn strict_div(self, rhs: usize) -> usize; -} -``` - -A safe implementation of `strict_div` would be forced to handle *all possible values* of `self` and `rhs`. `strict_div` must handle the case where `rhs == 0` (division by zero). It could choose to return an arbitrary `usize` or, as it does in practice, panic. But it cannot exhibit a memory safety bug – Rust would prevent such a program from compiling. - -By contrast, consider the unsafe [`usize::unchecked_div_exact`](https://doc.rust-lang.org/std/primitive.usize.html#method.unchecked_div_exact): - -```rust -impl usize { - /// # Safety - /// - /// This results in undefined behavior when `rhs == 0` or `self % rhs != 0`. - pub unsafe fn unchecked_div_exact(self, rhs: usize) -> usize; -} -``` - -Rust doesn't have any way to epxress "a pair of `usize`s such `rhs != 0` and `self % rhs == 0`". Thus, the type signature of `unchecked_div_exact` is *too permissive*. While it prevents some illegal values statically (e.g., the type system will not permit you to pass `-1`), some illegal values will compile just fine, and will result in memory safety bugs at runtime: - -```rust -let res: usize = unsafe { 1usize.unchecked_div_exact(0) }; -``` - -In this view, unsafe Rust differs from safe Rust in that **some type-safe values are still illegal**, while in safe Rust, **all type-safe values are legal** (where "illegal" means "will result in a memory safety bug"). - -As we saw before, Rust ensures that **no type-checked safe Rust programs contain memory safety bugs.** Anneal provides the same promise, but for unsafe Rust code. Just as Rust encodes memory safety requirements in its types, Anneal encodes memory safety requirements in its annotations. Unlike Rust types, Anneal annotations are powerful enough to prevent *any* illegal values from compiling, even in unsafe code. In this sense, Anneal annotations are an *extension* to Rust's type system. - -For example, here's how Anneal would encode the safety precondition of `unchecked_div_exact`, preventing buggy programs from compiling: - -```rust -impl usize { - /// # Safety - /// - /// ```anneal - /// requires(nonzero): rhs != 0 - /// requires(divides): self % rhs == 0 - /// ``` - pub unsafe fn unchecked_div_exact(self, rhs: usize) -> usize; -} - -let res: usize = unsafe { 1usize.unchecked_div_exact(0) }; // ERROR: Cannot prove `rhs != 0` -``` + -| | Rust | Anneal | -|-| ---- | ------ | -| | `cargo check` | `cargo anneal verify` | -| **Types** | Rust types | Rust types + Anneal annotations | -| **Guarantee** | Safe code is free of memory safety bugs | Safe and unsafe code is free of memory safety bugs* | -| **Caveat** | Unsafe code is unchecked | Code outside of the Rust Abstract Machine (AM) is unchecked* | -| **TCB** | Rust toolchain, unsafe code | Rust + Anneal toolchains, code outside of the Rust AM* | - -\* *Anneal cannot verify the soundness of code constructs which exist outside of the Rust Abstract Machine (AM), such as FFI or inline assembly. The programmer must axiomatically assert the behavior of this code, and it remains in the TCB. Note that Rust code which **calls** these constructs (e.g., code which calls a function implemented in C) **can** be verified – Anneal removes this code from the TCB.* - -### Development Philosophy +# Anneal -Anneal is a [formal verification](https://en.wikipedia.org/wiki/Formal_verification) tool. However, it diverges from many formal verification tools in its philosophy for how it should be used by end users. +Anneal is a verification tool for Rust. It helps developers replace informal +reasoning about unsafe code with checked proofs, and it can also verify precise +application-specific promises about program behavior. -While exceptions exist, the most common form of formal verification has historically been *post-hoc verification*, in which verification is applied to a software artifact which already exists and which was not specifically written with verification in mind. This approach comes with a number of assumptions and limitations which we aim to sidestep: -1. Assumes that verification requires distinct expertise which developers lack -2. Verification is applied to a codebase whose structure may not be amenable to verification -3. Results in long iteration cycles, in which feedback from verification is obtained long after a line of code is written -4. Without extra infrastructure (e.g. in CI), software codebase and verification artifacts (specifications and proofs) can drift, requiring periodic efforts to update the verification to "catch up" with recent code changes +> **Status:** Anneal is under active construction and cannot yet verify +> applications. The checked-in executable currently provides only toolchain +> setup infrastructure. The example below illustrates the problem Anneal +> addresses, not a workflow which is currently available. -We believe that all of these can be addressed by thinking of specifications as merely an extension to the type system rather than a parallel artifact. In particular: -1. Developers already have justifications – albeit undocumented – for their belief that their code is correct. Type annotations simply ask them to make these justifications explicit -2. As with any form of static typing, Anneal annotations will shape the codebase and nudge developers towards certain patterns for code organization which are more amenable to verification. This nudging will happen in the developer's inner loop and so will be much more effective than feedback from post-hoc verification -3. We expect `cargo anneal verify` to be just as frequent as `cargo check`, and so iteration cycles are extremely short -4. CI can simply run `cargo anneal verify` to ensure that specifications and proofs never drift or bit rot +## Why Anneal exists -### Human and Agentic Development +Rust's safe APIs promise that ordinary, type-checked use will not cause +undefined behavior—for example, an invalid memory access or a data race. We +call this guarantee soundness: every use which the safe API permits must remain +free from undefined behavior. Safe code can still panic, run forever, or +produce the wrong answer, but a safe API must remain sound even when it uses +unsafe code internally. -Anneal is designed for use by both human engineers and AI coding agents. By providing machine-checked guarantees for safe and `unsafe` code, Anneal eliminates the cognitive burden of manual review and enables the safe acceleration of systems software development. We have [demonstrated](https://drive.google.com/file/d/1areyf438L0izETTHj7PRMnoSHSX4kM29/view?usp=sharing) that Antigravity can author `unsafe` Rust code and prove its soundness using Anneal. +Unsafe Rust lets a programmer perform operations whose soundness the compiler +cannot verify. Each such operation comes with requirements: a pointer might +need to refer to live memory, an index might need to be in bounds, or a value +might need to be initialized. The compiler accepts the operation because the +programmer takes responsibility for meeting those requirements. -Without Anneal: +That reasoning is usually recorded in a comment: ```rust -struct PositiveUsize { - // INVARIANT: x > 0 - x: usize, -} +pub fn get_byte(bytes: &[u8], index: usize) -> u8 { + assert!(index < bytes.len()); -impl PositiveUsize { - /// Creates a new `PositiveUsize` if `x > 0`. - pub fn new(x: usize) -> Option { - if x > 0 { - // SAFETY: We checked that x > 0. - Some(Self { x }) - } else { - None - } - } -} - -impl std::ops::Div for usize { - type Output = usize; - - fn div(self, rhs: PositiveUsize) -> Self::Output { - // SAFETY: The type invariant of `PositiveUsize` guarantees that - // `rhs.x > 0`. This makes division by zero impossible. - unsafe { std::intrinsics::unchecked_div(self, rhs.x) } - } + // SAFETY: `bytes` is a valid slice and `index` is in bounds, so this + // pointer may be dereferenced. + unsafe { *bytes.as_ptr().add(index) } } ``` -With Anneal: +A reviewer can understand this argument, but Rust does not check that the +comment is correct or remains correct after a refactor. If the bounds check is +removed or moved too late, the unsafe operation's requirements may no longer +hold while the comment continues to look reassuring. -```rust -/// ```anneal -/// isValid self := self.val.val > 0 -/// ``` -pub struct PositiveUsize { - pub val: usize, -} - -impl PositiveUsize { - /// Creates a new `PositiveUsize` if `x > 0`. - /// - /// ```anneal - /// ensures: - /// match ret with - /// | none => x.val = 0 - /// | some r => r.val.val = x.val - /// ``` - pub fn new(x: usize) -> Option { - if x > 0 { - Some(Self { val: x }) - } else { - None - } - } -} +Because `get_byte` is a safe function, callers may pass any `usize`. An +out-of-bounds call may panic at the assertion, but it must never cause undefined +behavior. A safe API must not place a hidden safety requirement on its caller. -/// ```anneal -/// proof (h_progress): -/// unfold div_positive -/// rcases h_req with ⟨h_self_val_is_valid, h_rhs_is_valid⟩ -/// have ho := unchecked_div.spec self_val rhs.val { -/// h_a_is_valid := h_self_val_is_valid -/// h_b_is_valid := by verify_is_valid h_b_is_valid _root_.positive_usize.div_positive -/// h_anon := by simp_all [Anneal.IsValid.isValid] -/// } -/// rcases Aeneas.Std.WP.spec_imp_exists ho with ⟨y, h_eq, _⟩ -/// exact ⟨y, h_eq⟩ -/// ``` -fn div_positive(self_val: usize, rhs: PositiveUsize) -> usize { - unsafe { self_val.unchecked_div(rhs.val) } -} - -impl std::ops::Div for usize { - type Output = usize; - - fn div(self, rhs: PositiveUsize) -> usize { - unsafe { unchecked_div(self_val, rhs.val) } - } -} -``` - -## Installation - -Install Anneal and its required toolchains (Charon and Aeneas): - -```bash -cargo install cargo-anneal@0.1.0-alpha.24 -cargo anneal setup -``` - -## Quick Start - -Write a specification for your function using Anneal annotations in a doc comment: - -```rust -/// ```anneal -/// requires: x.val < Usize.max -/// ensures: ret.val = x.val + 1 -/// proof: -/// scalar_tac -/// ``` -pub fn add_one(x: usize) -> usize { - x + 1 -} -``` - -Verify your crate: - -```bash -cargo anneal verify -``` +Anneal turns reasoning like the safety comment into proof obligations checked +by [Lean](https://lean-lang.org/), a language which mechanically checks +mathematical arguments. If Anneal cannot establish an obligation, it reports +the gap and identifies the operation which produced it instead of silently +treating the comment as proof. An undischarged obligation is a verification +failure and a useful diagnostic; by itself, it does not prove that the Rust +code is unsound. -## Usage & Commands +This is intentionally a tiny example; ordinary Rust should simply use +`bytes[index]`. It stands in for implementations which cannot avoid unsafe +operations. It contains no Anneal-specific syntax because the specification +and proof-authoring interface is still being designed. + +## What Anneal adds + +Anneal lets a project describe: + +- the requirements a caller must satisfy before using an unsafe API; +- the promises a function or type makes to its callers; and +- the reasoning which connects ordinary Rust code to those requirements and + promises. + +Anneal checks the Rust safety obligations within the claimed scope, including +the requirements of unsafe operations and the promises on which safe APIs +rely. It follows those promises through function calls, so a verified +implementation can be used through its API without every caller re-examining +its private unsafe code. + +For a safe abstraction, the goal is straightforward: callers can use the safe +API according to the normal Rust type system, with no hidden rule whose +violation could cause undefined behavior. An unsafe API may explicitly give +part of that responsibility to its caller—for example, through a function +declared `unsafe` with documented requirements. + +## What a verification result means + +**Note:** The exact report format and command behavior are still being designed. + +A successful Anneal result identifies the particular code and build it covers +and states which guarantees were checked. At a minimum, it establishes that: + +- every safety-relevant operation within the named coverage satisfies Rust's + requirements; +- the functions and types checked by Anneal keep the promises stated in their + specifications; and +- anything Anneal could not prove, deliberately trusted, or did not cover is + listed rather than silently included in an unconditional claim. + +For example, a result might cover one Cargo target with a particular feature +set and dependency graph. It should say whether generated Rust, dependencies, +and calls into foreign code were checked, trusted, or outside its coverage. + +When Anneal reports that a safe abstraction itself has been verified within a +stated semantic and build envelope, every type-correct safe use which the API +permits within that envelope should be safe—not only the calls which happened +to appear in one test or binary. Any assumptions on that claim must be listed +explicitly. + +Verification does not mean that the program has no bugs. It applies only to +the guarantees and build configuration named in the result. Changing the +target, feature set, dependency, compiler configuration, or generated source +may require checking the program again. + +For promises beyond Rust safety, Anneal checks whether the code satisfies the +specification it was given; it cannot decide whether that specification says +what its author truly wanted. A weak or mistaken specification can describe +the wrong behavior. It cannot, however, erase the Rust safety requirements +which Anneal is responsible for checking. + +## Beyond Rust safety + +Rust safety is only one part of program correctness. A memory-safe function can +still return the wrong answer or panic unexpectedly. Anneal also aims to check +additional promises, for example: + +- a parser returns the value described by its input; +- an operation does not panic when its documented requirements hold; +- a data structure preserves the rules expected by its public API; or +- an implementation follows an application or network protocol. + +Each promise must be stated precisely enough to prove. Anneal should make +common cases approachable while leaving Lean available for specifications and +proofs which need its full expressive power. + +## Incremental adoption + +Adopting formal verification across an existing codebase takes time. Anneal +lets teams begin with their highest-risk code and expand coverage gradually. + +During that process, some unsafe operations may still rely on prose +`// SAFETY:` comments, unfinished proofs, or trusted external behavior. Anneal +reports those portions honestly rather than treating them as machine-checked +facts. This lets a team see what has been proved and what work or trust remains. + +## Under the hood: Lean + +Lean checks Anneal's mathematical arguments. Some specifications or advanced +proofs may be written directly in Lean, while Anneal connects them to the Rust +program and presents useful obligations and diagnostics to Rust developers. + +Lean can check only the statement it receives. A meaningful result therefore +also depends on Anneal connecting that statement to the right Rust program and +reporting any external assumptions. Users should not need to understand the +translation machinery, but they must be able to see the assumptions and gaps +which qualify a result. + +## Repository development -- **`cargo anneal verify`**: Verifies the target crate. -- **`cargo anneal expand`**: Outputs the generated Lean code without running full verification (useful for debugging). -- **`cargo anneal generate`**: Generates the `.lean` files on disk, allowing you to iterate on proofs using standard Lean tooling before copying them back to Rust source. +Coding agents working on Anneal must begin with [`AGENTS.md`](AGENTS.md). It +routes them into `agent_docs/`, an internal corpus written for agent +consumption. That corpus contains design context, current implementation facts, +and operating guidance which are intentionally omitted from this user-facing +introduction. Human maintainers may consult it, but it is not part of Anneal's +user documentation or a stable product interface. diff --git a/anneal/agent_docs/README.md b/anneal/agent_docs/README.md new file mode 100644 index 0000000000..2adce26398 --- /dev/null +++ b/anneal/agent_docs/README.md @@ -0,0 +1,137 @@ + + +# Anneal agent documentation + +This directory is the canonical project context and design corpus written for +coding agents working on the current Anneal redesign. It is not user-facing +documentation or a stable product interface. Human maintainers may inspect, +review, and edit it, but its structure and explanations should optimize agent +comprehension, judgment, and reliable action. + +Agent-oriented organization does not mean prompt-fragment prose. Design pages +should explain rationale declaratively in language human maintainers can audit; +imperative agent procedure belongs in `AGENTS.md` or an explicitly labeled +instruction section. + +This page is the canonical owner of the agent reading order, document +authority, and reconciliation process. Other agent instructions may point here, +but must not maintain a competing copy of that information. + +## Reading order + +Agents should first follow [`AGENTS.md`](../AGENTS.md). In an unfamiliar +checkout, complete the [agent-corpus preflight](agent-corpus.md) before relying +on any local document. + +Before making or reviewing a design change, read the following completely, in +order: + +1. The user-facing [project introduction](../README.md), to understand the + audience and product promise. +2. [Glossary](glossary.md), for stable vocabulary used throughout the canon. +3. [Principles](design/principles.md), for the goals and choice rules behind + design judgment. +4. [Settled requirements](design/settled-requirements.md), for constraints + every acceptable design must meet. +5. [Accepted decisions](design/decisions/README.md), including every decision + relevant to the change. +6. [Verification model](design/verification-model.md), for the intended local + proof and global composition argument. +7. [Result and trust model](design/result-and-trust.md), for the identity and + scope of a claim, its evidence, its residual dependencies, and what must be + reported. +8. [Worked example](design/worked-example.md), as an illustration whose + concrete proof choices are not decisions unless the text cites one. +9. [Open questions](design/open-questions/README.md), including every page + relevant to the task. Candidate approaches are not decisions. +10. [Current state](reference/current-state.md) and the explicitly + non-normative [current priorities](reference/current-priorities.md), for + checked-in behavior and the present engineering frontier. + +For translation, semantics, or proof-infrastructure work, also read +[Aeneas and Charon](reference/aeneas-and-charon.md). Read +[development and CI](reference/development-and-ci.md) before changing commands +or automation. Read [V1 lessons](history/v1-lessons.md) before borrowing from +the prototype, and inspect V1 itself only when the task requires primary +historical evidence. + +The [agent-corpus guide](agent-corpus.md) explains how to discover every +documentation file for a full audit or context-free comprehension test. + +## Document classes + +### Navigation and vocabulary + +This map, the [agent-corpus guide](agent-corpus.md), and the +[glossary](glossary.md) help readers find and interpret the canon. They do not +create design commitments independently of the normative documents and +accepted decisions they describe. + +### Normative design + +The files directly under [`design/`](design/) give the current consolidated +design. Principles guide choices; settled requirements constrain every +acceptable design. The worked example is a teaching companion: its concrete +choices are illustrative, and only the requirements and decisions it cites are +normative. + +### Accepted decisions + +Records under [`design/decisions/`](design/decisions/README.md) capture an +accepted choice, its rationale, and its consequences. A decision changes only +through an explicit amendment or superseding record. Consolidated design files +must be updated when such a change affects their account. + +### Open questions + +Files under [`design/open-questions/`](design/open-questions/README.md) are +design workspaces. Their settled constraints come from the normative documents +and accepted decisions they cite. Candidate approaches, provisional analyses, +and implementation experiments do not settle the question. + +### Reference + +Files under [`reference/`](reference/current-state.md) describe checked-in +code, commands, external tools, and current limitations. They are factual, not +aspirational. The dated +[current-priorities page](reference/current-priorities.md) is non-normative +guidance for the present engineering frontier. References must label intended +future roles and defer to the normative design where appropriate. + +### History and research + +Files under [`history/`](history/v1-lessons.md) preserve evidence, lessons, and +source indexes. V1, issues, pull requests, and research papers may motivate a +decision but do not override the current canon. Volatile source indexes must be +dated. + +## Resolving disagreements + +If normative files disagree with one another, or a consolidated design file +disagrees with an accepted decision, do not silently select one. Check whether +one record explicitly supersedes the other, then surface and repair the +inconsistency. Until it is reconciled, avoid making an irreversible design +choice which depends on the disputed point. + +Do not promote language from an open question, issue, pull request, experiment, +or V1 into a settled claim merely because it is convenient or has been +implemented. Record the decision first or continue to label the implementation +as an experiment. + +## Changing the canon + +A project-wide question becomes settled only with explicit agreement from the +project authors; implementation alone is not ratification. After agreement: + +1. add or update an accepted decision record; +2. update the relevant normative design files; +3. update the status and links of affected open questions; +4. update current-state references if checked-in behavior changed; and +5. preserve significant evidence and genuinely considered alternatives without + retaining obsolete conversational misunderstandings. diff --git a/anneal/agent_docs/agent-corpus.md b/anneal/agent_docs/agent-corpus.md new file mode 100644 index 0000000000..fbe5ebd73f --- /dev/null +++ b/anneal/agent_docs/agent-corpus.md @@ -0,0 +1,93 @@ + + +# Anneal agent corpus + +This page helps a context-free agent find and ingest the current Anneal agent +documentation without confusing a worktree container, another checkout, or +the historical V1 prototype for the intended corpus. The +[documentation map](README.md) is the sole owner of reading order and document +authority. + +## Establish the Anneal root + +Resolve the repository root from the current checkout; never copy an absolute +path from another session. The corpus root is its `anneal/` directory, which +must contain the crate and the documentation entrypoints: + +```bash +repo_root="$(git rev-parse --show-toplevel)" +anneal_root="$repo_root/anneal" + +test -f "$anneal_root/Cargo.toml" +test -f "$anneal_root/AGENTS.md" +test -f "$anneal_root/README.md" +test -f "$anneal_root/agent_docs/README.md" +test -f "$anneal_root/agent_docs/agent-corpus.md" +``` + +These structural checks deliberately avoid exact headings, hashes, and byte +counts. Routine documentation edits should not break discovery. If a marker is +missing, stop and locate the correct worktree instead of substituting a +similarly named directory. + +## Exclude V1 + +`anneal/v1/` is the historical prototype and is **not part of the current +corpus**. Do not recursively ingest it, follow its `AGENTS.md` as current +instructions, or use its README to replace a missing current file. Read +selected V1 documentation or source only when current documentation calls for +historical evidence, and treat it as history rather than authority. The +[V1 lessons](history/v1-lessons.md) page is the normal starting point. + +An `AGENTS.md` above `anneal/` may still impose repository or worktree +procedures. Those instructions apply to repository operations, but do not +replace Anneal's project and design canon. + +## Discover the complete corpus + +For a full documentation audit or a context-free comprehension test, read: + +- `anneal/AGENTS.md`; +- `anneal/README.md`; and +- every Markdown file under `anneal/agent_docs/`, recursively. + +Discover the last category from the filesystem instead of maintaining another +hand-written enumeration: + +```bash +find "$anneal_root/agent_docs" -type f -name '*.md' \ + ! -path '*/design/decisions/0000-template.md' -print | sort +``` + +Follow the order in the [documentation map](README.md). Do not add +`anneal/v1/**/*.md` to the corpus. Source code, issues, pull requests, and +upstream documentation may be necessary evidence for a task, but reading them +alongside the corpus does not give them design authority. + +`design/decisions/0000-template.md` is authoring scaffolding rather than a +decision, so it is deliberately excluded from a comprehension corpus. + +## Completion check + +After reading, a context-free agent should be able to explain, without relying +on V1 or an open pull request: + +- Anneal's product goal, intended users, and why soundness is foundational; +- the principles used to compare designs and the constraints every acceptable + design must satisfy; +- which documents are normative, accepted, open, factual, or historical; +- how an identified Rust subject, a precise claim, checked evidence, and + classified residual dependencies form a reported result; +- how local proofs compose into a global claim and why the source model must be + adequate for that argument; +- what the checked-in executable actually implements today; and +- which unresolved choice the current task must not settle silently. + +If the corpus does not support one of those answers, report the gap rather than +importing an answer from V1, an issue, or a pull request. diff --git a/anneal/agent_docs/design/decisions/0000-template.md b/anneal/agent_docs/design/decisions/0000-template.md new file mode 100644 index 0000000000..655302d089 --- /dev/null +++ b/anneal/agent_docs/design/decisions/0000-template.md @@ -0,0 +1,57 @@ + + +# NNNN: Short decision title + +- **Status:** Proposed +- **Date:** YYYY-MM-DD + +## Context + +Describe the problem, the constraints already settled elsewhere, and why a +decision is needed. Separate observations from assumptions. + +## Decision + +State the decision precisely enough that an implementer can tell whether a +proposal complies with it. Prefer a narrow commitment over settling adjacent +questions without evidence. + +## Rationale + +Explain how this decision follows from Anneal's [principles](../principles.md) +and why the tradeoff is appropriate. + +## Consequences + +List the useful constraints and the costs this decision introduces. Include +effects on soundness, user-visible claims, maintainability, and migration when +they are relevant. + +## Alternatives considered + +Describe credible alternatives and why they were not chosen. Not choosing an +alternative now need not mean rejecting it forever. + +## Deferred questions + +List choices this record intentionally does not make. Link them to the relevant +[open-question document](../open-questions/README.md) where possible. + +## Evidence + +Link primary sources, experiments, V1 lessons, or explicit design conclusions +which support the decision. Mark historical or potentially stale sources as +such. + +## Links + +- [Design principles](../principles.md) +- [Settled requirements](../settled-requirements.md) +- Related decisions and open questions + diff --git a/anneal/agent_docs/design/decisions/0001-v2-is-a-clean-room-redesign.md b/anneal/agent_docs/design/decisions/0001-v2-is-a-clean-room-redesign.md new file mode 100644 index 0000000000..645a94dc10 --- /dev/null +++ b/anneal/agent_docs/design/decisions/0001-v2-is-a-clean-room-redesign.md @@ -0,0 +1,82 @@ + + +# 0001: V2 is a clean-room redesign + +- **Status:** Accepted +- **Date:** 2026-07-17 + +## Context + +Anneal V1 was an early experiment and demonstration. It established that parts +of the idea were useful, but it also embedded provisional choices about syntax, +proof structure, trust, and integration with Aeneas. Treating those choices as +an inherited compatibility surface would turn experiments into constraints +before V2's requirements are understood. + +V1 remains valuable evidence. Some of its code and designs may still be the +best way to implement a V2 requirement. + +## Decision + +Anneal V2 is a clean-room, ground-up rewrite and redesign. The presence of a +concept, interface, or implementation in V1 creates no presumption that V2 will +retain it. + +V2 may borrow or copy from V1 when a fresh evaluation shows that doing so serves +V2's principles and requirements. V2 documentation may refer to V1 to record a +lesson, or after a V1 choice has independently been accepted for V2. In all +other cases, V1 is historical context rather than design authority. + +Here, “clean-room” describes design inheritance; it is not a prohibition on +reusing suitable code. + +## Rationale + +Anneal's purpose requires substantial changes to the prototype. Starting from +the desired assurance and user experience keeps accidental V1 constraints from +outweighing soundness, semantic fidelity, composability, or maintainability. +Retaining V1 as evidence still lets V2 benefit from working code and hard-won +lessons. + +## Consequences + +- V2 proposals must be justified from current requirements and principles, not + merely by matching V1. +- Compatibility with V1 syntax, generated Lean, or workflows is not a default + requirement. +- Copying a V1 component requires checking that its assumptions still hold. +- V1 documentation lives under [`v1/`](../../../v1/) and must not be read as V2 + documentation. +- Lessons from V1 should be distilled into V2 history documents instead of + requiring contributors to reverse-engineer the prototype. + +## Deferred questions + +- Which V1 components, if any, should be reused? +- Will V2 offer migration tools or compatibility syntax for V1 users? +- What specification language, proof surface, and generated-Lean interface + should V2 expose? + +Those questions belong in the relevant +[open design discussions](../open-questions/README.md), not in this record. + +## Evidence + +- The project author explicitly characterized V1 as an early experimental and + demonstration prototype and V2 as a clean-room redesign. +- The [V1 design document](../../../v1/docs/design/design.md) records several + prototype-specific choices which are being reconsidered. +- [V1 lessons](../../history/v1-lessons.md) distills the evidence that should + inform V2 without becoming implicit precedent. + +## Links + +- [Design principles](../principles.md) +- [Settled requirements](../settled-requirements.md) +- [V1 lessons](../../history/v1-lessons.md) diff --git a/anneal/agent_docs/design/decisions/0002-verification-is-artifact-scoped.md b/anneal/agent_docs/design/decisions/0002-verification-is-artifact-scoped.md new file mode 100644 index 0000000000..1c8b02ffff --- /dev/null +++ b/anneal/agent_docs/design/decisions/0002-verification-is-artifact-scoped.md @@ -0,0 +1,94 @@ + + +# 0002: Verification is artifact-scoped + +- **Status:** Accepted +- **Date:** 2026-07-17 + +## Context + +The meaning of Rust source depends on its compilation. Target, enabled features, +`cfg` values, panic strategy, dependency resolution, compiler behavior, and +environmental inputs can all change the program which is compiled. A claim +phrased only as “this library is verified” would therefore leave its subject +ambiguous. + +Anneal may eventually help users verify a matrix of configurations, but a +matrix is a collection of claims and not the initial atomic claim. + +## Decision + +The initial unit of an Anneal source-level verification claim is one Cargo +compilation artifact under a fixed target, configuration, feature set, panic +strategy, dependency graph, and relevant environment. + +A successful verification of one artifact does not imply that another target +or configuration has been verified. A command may eventually orchestrate +several artifacts, but each artifact remains a separately identified claim. + +## Rationale + +Scoping the claim to the program the compiler actually constructs makes it +precise and auditable. It avoids universal claims over code configurations that +Anneal has not analyzed while leaving room for higher-level matrix tooling. + +## Consequences + +- Verification output must identify its compilation subject sufficiently for a + user or auditor to distinguish it from other builds. +- Changing a claim-relevant compilation input produces a different artifact and + requires a new verification claim. +- Cross-target or all-features assurance requires verifying every intended + artifact or separately proving why the claim generalizes. +- Caches must not reuse a result across claim-relevant inputs without a sound + equivalence argument. +- The exact compilation-subject identity, verification-result identity, and + audit-ledger schema remain design work. + +## Alternatives considered + +### Claim verification for the source tree + +A source tree admits many semantically different builds, so this would obscure +which program was analyzed. + +### Require an entire configuration matrix as the atomic unit + +This would make early adoption unnecessarily expensive and still require a +definition of which matrix is complete. + +### Scope only to a crate and target triple + +Features, `cfg`, dependencies, panic behavior, and environmental inputs can +change semantics even when those two values are unchanged. + +## Deferred questions + +- Which inputs must be recorded, normalized, or content-addressed? +- Which host tools, environment variables, compiler flags, and target + specifications can change the compilation subject, and which instead belong + only to verification-result identity or the trust ledger? +- Should `cargo anneal verify` support build matrices, and how should it report + aggregate results? +- How do compilation-subject and verification-result identities appear in the + TCB audit ledger? + +## Evidence + +- The project author selected one fixed Cargo compilation artifact, rather than + a matrix of builds, as the exact initial unit of a source-level claim. +- Rust's compilation model makes target, `cfg`, features, panic strategy, + dependencies, and generated inputs semantically relevant. + +## Links + +- [Generated Rust is input](0003-expanded-generated-rust-is-input.md) +- [The TCB is explicit and shrinkable](0006-the-tcb-is-explicit-and-shrinkable.md) +- [Trust and incremental adoption](../open-questions/trust-and-incremental-adoption.md) +- [Design principles](../principles.md) diff --git a/anneal/agent_docs/design/decisions/0003-expanded-generated-rust-is-input.md b/anneal/agent_docs/design/decisions/0003-expanded-generated-rust-is-input.md new file mode 100644 index 0000000000..c8beccc0ed --- /dev/null +++ b/anneal/agent_docs/design/decisions/0003-expanded-generated-rust-is-input.md @@ -0,0 +1,96 @@ + + +# 0003: Expanded, generated Rust is verification input + +- **Status:** Accepted +- **Date:** 2026-07-17 + +## Context + +Procedural macros and build scripts can generate Rust which becomes part of the +compiled program. Trusting a generator in place of analyzing its output would +exclude real program behavior from Anneal's claim. Requiring generated Rust to +be checked in would also misrepresent normal Cargo builds. + +This decision concerns generated Rust that enters the program seen by Charon. +It does not by itself settle how to treat every side effect of a build script, +foreign libraries selected for linking, or generator execution as a separate +program. + +## Decision + +Rust generated by procedural macros or build scripts and subsequently ingested +by Charon is verification input, just like checked-in Rust source. Its origin +does not exempt it from the obligations that apply to the rest of the compiled +artifact. + +The generator is not trusted as a substitute for checking the generated Rust. +Anneal analyzes the expanded program which participates in the artifact's +semantics. + +## Rationale + +Anneal's claim must track the code which is compiled, not only the files a human +checked into version control. Treating expanded code uniformly improves +semantic fidelity and lets ordinary Rust projects use their existing code +generation practices without adding generated source to the repository. + +## Consequences + +- Anneal must obtain an authoritative representation of generated Rust from the + compiler pipeline. +- Generated code can introduce proof obligations and trusted leaves just as + handwritten code can. +- Diagnostics should retain useful provenance back to a macro invocation or + generator where the toolchain makes that possible. +- The compilation-subject identity must account for inputs that can change + generated code. +- This record does not choose LLBC-only ingestion, a compiler metadata service, + or a source-level annotation scanner. + +## Alternatives considered + +### Trust procedural macros and build scripts + +That would place arbitrary generated program behavior outside the checked +artifact and unnecessarily enlarge the TCB. + +### Analyze only checked-in source + +The analyzed program could then differ from the program rustc compiles. + +### Require generated Rust to be checked in + +This does not match common Rust workflows and would not prove that the checked- +in output is the output used for the verified compilation. + +## Deferred questions + +- Which Charon or rustc interface should expose expanded code, annotations, item + identities, and call metadata? +- How should diagnostics map generated definitions to their source origins? +- Which build-script effects change the compilation subject, and which belong + only to verification-result identity or the trust ledger? +- How should generated native code, linked libraries, and other non-Rust output + be modeled? +- Must generator execution itself be reproducible or verified? + +## Evidence + +- The project author explicitly classified proc-macro- and build-script- + generated Rust as input which Charon ingests, rather than trusted source. +- Charon's compiler-derived program representation already includes code after + Rust compilation has resolved forms that a source-only scanner cannot fully + recover. + +## Links + +- [Verification is artifact-scoped](0002-verification-is-artifact-scoped.md) +- [Aeneas and Charon integration](../open-questions/aeneas-charon-integration.md) +- [Source/model adequacy](../open-questions/source-model-adequacy.md) diff --git a/anneal/agent_docs/design/decisions/0004-invariants-support-all-property-kinds.md b/anneal/agent_docs/design/decisions/0004-invariants-support-all-property-kinds.md new file mode 100644 index 0000000000..487dbad048 --- /dev/null +++ b/anneal/agent_docs/design/decisions/0004-invariants-support-all-property-kinds.md @@ -0,0 +1,94 @@ + + +# 0004: Invariants support all property kinds + +- **Status:** Accepted +- **Date:** 2026-07-17 + +## Context + +V1 explored type invariants under the name `isValid` and trait invariants under +the name `isSafe`. Those names suggest that one mechanism concerns validity and +the other concerns soundness. V2 must support soundness, but users also need +invariants for other properties: panic freedom, protocol correctness, +cryptographic conditions, resource bounds, and domains not designed yet. + +The exact taxonomy and notation for property kinds remain open. + +## Decision + +Both type invariants and trait invariants must support arbitrary property kinds. +Neither mechanism is intrinsically restricted to soundness or to one built-in +notion of safety. + +Soundness remains special and non-negotiable. Generalizing invariant mechanisms +does not permit another selected property to weaken, replace, or bypass the +obligations required for sound Rust. + +## Rationale + +Types and trait bounds are important abstraction boundaries regardless of the +property being composed. A common architecture avoids duplicating an invariant +system for every domain and leaves V2 extensible to properties whose proof +backends arrive later. Preserving soundness as a mandatory foundation keeps +extensibility from weakening the primary assurance. + +## Consequences + +- V2's invariant representation cannot hard-code type invariants as “validity” + or trait invariants as “soundness.” +- Implementations and consumers need a way to identify which property an + invariant establishes or assumes. +- Adding a new property domain must not require replacing the type- or trait- + invariant architecture. +- Initial V2 need not implement specialized proof backends for every possible + property kind; the architecture must leave room for them. +- The V1 names and enforcement mechanisms are not accepted by this decision. + +## Alternatives considered + +### Restrict both invariant forms to soundness + +This would require parallel mechanisms to express the same abstraction pattern +for other correctness properties. + +### Reserve type invariants for validity and trait invariants for soundness + +The distinction follows V1 terminology rather than a fundamental difference in +where property-carrying invariants are useful. + +### Defer extensibility until another property backend exists + +That risks embedding soundness-specific assumptions in interfaces which are +difficult to generalize later. + +## Deferred questions + +- What are property kinds called and how are they declared or selected? +- How are type invariants established, invalidated, opened, and re-established? +- How are trait invariants enforced at implementation sites and made available + through a bound? +- How does Anneal integrate invariant access with Rust's existing `unsafe` + machinery or an Anneal-specific parallel analysis? +- How are dependencies and cycles between property kinds represented? +- What should replace the V1 `isValid` and `isSafe` syntax? + +## Evidence + +- The project author explicitly required both type invariants and trait + invariants to support arbitrary property kinds. +- Experience with V1 showed that its names and partial enforcement did not + express this general architecture; see [V1 lessons](../../history/v1-lessons.md). + +## Links + +- [Contracts and invariants](../open-questions/contracts-and-invariants.md) +- [Property kinds and outcomes](../open-questions/property-kinds-and-outcomes.md) +- [Rust safety integration](../open-questions/rust-safety-integration.md) +- [Design principles](../principles.md) diff --git a/anneal/agent_docs/design/decisions/0005-incremental-adoption-supports-prose-justifications.md b/anneal/agent_docs/design/decisions/0005-incremental-adoption-supports-prose-justifications.md new file mode 100644 index 0000000000..ffe9df39c3 --- /dev/null +++ b/anneal/agent_docs/design/decisions/0005-incremental-adoption-supports-prose-justifications.md @@ -0,0 +1,99 @@ + + +# 0005: Incremental adoption supports prose justifications + +- **Status:** Accepted +- **Date:** 2026-07-17 + +## Context + +Real Rust codebases already justify unsafe operations with prose such as +`// SAFETY:` comments. Requiring every relevant obligation to be formalized and +proved before Anneal provides any value would make adoption all-or-nothing. +Conversely, silently treating prose as a machine proof would make Anneal's +assurance misleading. + +The relationship between Anneal property tracking and Rust's existing `unsafe` +machinery remains an open design question, but incremental adoption is a +requirement under either architecture. + +## Decision + +Anneal must support an incremental-adoption mechanism in which selected formal +proof obligations may temporarily be represented by prose safety +justifications. + +Every such substitution is an explicit, auditable trust assumption. Anneal +must not report or consume it as if it were a completed machine-checked proof, +and a user must be able to discover it when evaluating the resulting assurance. + +This record requires the capability; it does not decide its syntax, which +obligations may use it, or the success semantics of a command that encounters +one. + +## Rationale + +Incremental adoption lets users formalize high-value portions of an existing +codebase without first rewriting every safety comment. Making the remaining +human reasoning explicit preserves honest claims and provides a measurable path +toward a fully formal result. + +## Consequences + +- Anneal needs a durable association between a prose justification and the + obligation it stands in for. +- Trust reports must expose which obligations rely on prose rather than a + completed proof; how the final ledger categorizes those assumptions remains + open. +- Results containing prose justifications are conditional on their correctness. +- Tooling should make it possible to find and reduce the remaining prose + obligations over time. +- Integration with familiar Rust comments is possible but not mandated by this + record. + +## Alternatives considered + +### Require complete formalization before running Anneal + +This gives a simple final state but does not support the required path for +adopting Anneal in an existing codebase. + +### Treat every unproved obligation as an unrestricted axiom + +This loses the connection to the relevant Rust operation and obscures the +different reasons that facts remain trusted. + +### Silently skip obligations without proofs + +That would make success impossible to interpret and would violate the need for +an auditable trust boundary. + +## Deferred questions + +- What syntax associates prose with an Anneal obligation? +- Which obligations may use prose and under which command or policy? +- Does a result with prose assumptions exit successfully as “conditionally + checked,” or use another status distinct from “verified”? +- How are review, ownership, expiry, and migration of prose assumptions tracked? +- How should this mechanism build on Rust `unsafe` blocks, safety comments, + lints, and possible future language features? + +## Evidence + +- The project author required some form of prose safety justification to stand + in for selected Anneal proofs during incremental adoption. +- Existing Rust practice already locates human safety arguments near unsafe + operations, providing a migration source even though prose is not itself a + proof. + +## Links + +- [The TCB is explicit and shrinkable](0006-the-tcb-is-explicit-and-shrinkable.md) +- [Trust and incremental adoption](../open-questions/trust-and-incremental-adoption.md) +- [Rust safety integration](../open-questions/rust-safety-integration.md) diff --git a/anneal/agent_docs/design/decisions/0006-the-tcb-is-explicit-and-shrinkable.md b/anneal/agent_docs/design/decisions/0006-the-tcb-is-explicit-and-shrinkable.md new file mode 100644 index 0000000000..83a9c2fed4 --- /dev/null +++ b/anneal/agent_docs/design/decisions/0006-the-tcb-is-explicit-and-shrinkable.md @@ -0,0 +1,122 @@ + + +# 0006: The TCB is explicit and shrinkable + +- **Status:** Accepted +- **Date:** 2026-07-17 + +## Context + +Anneal cannot initially prove every layer on which a Rust execution depends. +Primitive unsafe operations, compiler intrinsics, FFI, assembly, the +verification toolchain, and hardware all introduce facts that may have to be +trusted. Hiding those assumptions behind a successful command would make the +result impossible to audit. + +At the same time, treating today's body-unmodeled operations as a permanent +semantic boundary would prevent future work from replacing them with formal +models. For example, a formal ISA model could eventually make some assembly +transparent. + +## Decision + +Anneal V2 will reduce unproved operational semantics to a small, explicit set +of trusted leaves and will expose the assumptions on which a result depends. +The TCB is an auditable boundary, not an implicit promise. + +Initially, trusted semantic leaves are expected to include some unsafe standard +library functions and intrinsics, raw-pointer dereferences, FFI, and inline or +external assembly. Approved axioms and prose justifications must remain visible +as unproved assumptions. Incomplete proofs must remain visible as outstanding +obligations; whether a particular command mode blocks on or conditionally +admits them remains open. None may masquerade as verified facts. + +The architecture must permit a trusted leaf or platform assumption to be +replaced by a more detailed formal model later. A fully adopted codebase should +reserve axioms for genuinely external semantics, although the boundary of +“external” can shrink as new models become available. + +This decision does not claim that Anneal's complete practical TCB is initially +small. The compiler, translators, proof checker, platforms, and hardware remain +part of the end-to-end TCB until independently justified. “Small” applies first +to the explicit set of trusted program-semantic leaves. + +## Rationale + +A small and inspectable trusted semantic leaf set, together with an explicit +account of the broader end-to-end TCB, lets a user understand exactly what an +Anneal result entitles them to believe. Making the boundary replaceable +supports stronger assurance over time without requiring the initial +implementation to formalize the Rust compiler, every ISA, and every foreign +system before it is useful. + +## Consequences + +- Verification results are relative to the correctness of their recorded + trusted leaves and platform assumptions. +- Anneal needs a TCB audit ledger which includes axioms and unfinished + obligations, not merely a binary success indicator. +- The ledger must ultimately account for relevant Anneal, Aeneas, Charon, + rustc, LLVM, host-hardware, and target-hardware assumptions; its exact schema + is not fixed here. +- Standard-library support may use the same axiom-authoring machinery exposed + for genuinely external user semantics. +- Replacing an axiom with a proof or a leaf with a model must reduce, rather + than merely relocate invisibly, the reported trust boundary. +- Anneal authors are responsible for the adequacy of built-in soundness + preconditions on axiomatic unsafe leaves. + +## Alternatives considered + +### Prove every leaf before V2 can be useful + +This would postpone practical verification until numerous independent compiler, +library, platform, and hardware models exist. + +### Trust arbitrary body-unmodeled functions without reporting them + +This would verify callers only relative to hidden assumptions and invite users +to overstate the result. + +### Make the initial leaf boundary permanent + +That would prevent stronger future models from reducing the TCB and would make +implementation expedience part of Anneal's long-term assurance definition. + +## Deferred questions + +- What is the exact TCB audit-ledger schema and output format? +- Which assumptions cause `cargo anneal verify` to fail, succeed conditionally, + or succeed with warnings? +- Which initial leaves are axiomatized by Anneal, Aeneas, a platform package, or + a user? +- Who authors and distributes FFI contracts: library consumers, upstream + library authors, or both? +- How are toolchain, host, target, and hardware identities represented? +- Which formal Rust, allocator, ISA, concurrency, and foreign-language models + should replace trusted leaves first? + +## Evidence + +- The project author defined V2's long-term direction as reducing the TCB to a + small explicit collection of trusted leaves while preserving the option to + model those leaves later. +- The project author identified the TCB audit log as an important user-facing + capability and required axioms and not-yet-proven specifications to appear in + it. +- The requested trust inventory includes Anneal's dependencies and both host + and target hardware, even though its exact schema remains open. + +## Links + +- [Verification is artifact-scoped](0002-verification-is-artifact-scoped.md) +- [Incremental adoption supports prose](0005-incremental-adoption-supports-prose-justifications.md) +- [Trust and incremental adoption](../open-questions/trust-and-incremental-adoption.md) +- [Source/model adequacy](../open-questions/source-model-adequacy.md) +- [Verification result and trust](../result-and-trust.md) diff --git a/anneal/agent_docs/design/decisions/README.md b/anneal/agent_docs/design/decisions/README.md new file mode 100644 index 0000000000..a231e49d52 --- /dev/null +++ b/anneal/agent_docs/design/decisions/README.md @@ -0,0 +1,60 @@ + + +# Design decisions + +This directory records Anneal V2 design decisions whose alternatives matter to +future work. A decision record says what was decided, why that choice follows +from Anneal's principles, and which questions it deliberately leaves open. + +Decision records complement, rather than replace, the other design documents: + +- [Principles](../principles.md) supplies reusable rules for evaluating design + alternatives. +- [Settled requirements](../settled-requirements.md) collects the constraints + that an acceptable design must satisfy. +- [Open questions](../open-questions/README.md) tracks design spaces in which no + choice has yet been made. + +An accepted record is normative until it is superseded. Historical evidence, +the V1 implementation, issues, and pull requests do not override it. If new +evidence changes a decision, add a new record that names the record it +supersedes instead of silently rewriting the old rationale. Small corrections +which do not change the decision may be made in place. + +## Statuses + +- **Proposed:** available for discussion, but not binding. +- **Accepted:** a current project commitment. +- **Rejected:** considered and deliberately not adopted. +- **Superseded:** retained as history, with a link to its replacement. + +## Authoring a record + +Copy [the template](0000-template.md), allocate the next four-digit number, and +keep the decision as narrow as the available evidence permits. In particular: + +- distinguish required outcomes from possible implementations; +- derive the rationale from project principles rather than precedent alone; +- list costs and constraints as well as benefits; +- move unresolved choices to **Deferred questions**; and +- link the evidence that made the decision possible. + +Do not turn a promising direction, an implementation trend, or a V1 design into +an accepted decision without explicit project agreement. + +## Accepted decisions + +| Record | Decision | +| --- | --- | +| [0001](0001-v2-is-a-clean-room-redesign.md) | V2 is a clean-room, ground-up redesign. | +| [0002](0002-verification-is-artifact-scoped.md) | A verification claim initially covers one fixed compilation artifact. | +| [0003](0003-expanded-generated-rust-is-input.md) | Expanded, generated Rust is verification input, not trusted source. | +| [0004](0004-invariants-support-all-property-kinds.md) | Type and trait invariants support arbitrary property kinds. | +| [0005](0005-incremental-adoption-supports-prose-justifications.md) | Incremental adoption supports explicit, audited prose justifications. | +| [0006](0006-the-tcb-is-explicit-and-shrinkable.md) | The trusted computing base is explicit, auditable, and shrinkable. | diff --git a/anneal/agent_docs/design/open-questions/README.md b/anneal/agent_docs/design/open-questions/README.md new file mode 100644 index 0000000000..ca5e098588 --- /dev/null +++ b/anneal/agent_docs/design/open-questions/README.md @@ -0,0 +1,76 @@ + + +# Open design questions + +This directory records consequential Anneal V2 design questions that have not +yet been decided. It exists to preserve the reasoning behind candidate designs +without turning the current implementation, Anneal V1, an issue, or a passing +conversation into policy by accident. + +An open question is not a blank slate. Each document begins with a compact +table linking the requirements and accepted decisions that constrain it, then +states only the topic-specific implications needed to evaluate candidates. +Those tables are indexes, not independent normative summaries. Agents and +humans may explore the remaining design space, but must not silently relax the +linked constraints. Conversely, candidates and current leanings are evidence +and working hypotheses, not commitments. + +The normative [project principles](../principles.md), +[settled requirements](../settled-requirements.md), and +[accepted decisions](../decisions/README.md) take precedence over this ledger. +Anneal V1 is a source of experience, not a default architecture for V2. When a +choice is accepted, record it in a decision record and update or close the +corresponding question here rather than merely changing an implementation. + +## Index + +| Area | Central question | +| --- | --- | +| [Property kinds and outcomes](property-kinds-and-outcomes.md) | How should Anneal represent distinct guarantees, execution outcomes, dependencies, and user selection without baking in a premature taxonomy? | +| [Rust safety integration](rust-safety-integration.md) | Which obligations should reuse Rust's `unsafe` machinery, and which require a richer Anneal-owned tracking system? | +| [Contracts and invariants](contracts-and-invariants.md) | How should function contracts, type invariants, and trait invariants be stated, enforced, and made available to callers? | +| [Source/model adequacy](source-model-adequacy.md) | How can proofs about a model that assumes soundness establish the soundness of the source program itself? | +| [Memory, resources, and effects](memory-resources-and-effects.md) | Where can unsafe implementations close to simple functional models, and where must resource or effect semantics remain visible? | +| [Trust and incremental adoption](trust-and-incremental-adoption.md) | How should axioms, incomplete proofs, prose justifications, and the audit ledger affect the meaning of verification? | +| [Aeneas and Charon integration](aeneas-charon-integration.md) | What information and machinery should Anneal consume, extend upstream, or own itself? | +| [Proof authoring and user experience](proof-authoring-and-user-experience.md) | What proof surface can eventually serve ordinary Rust engineers, formal-methods specialists, and agents without sacrificing rigor? | + +These areas intentionally overlap. A proposed answer should follow the related +links and account for its consequences elsewhere. In particular, choices about +property kinds affect Rust integration, contracts, trust reporting, and the +command-line interface; choices about source adequacy affect the Aeneas/Charon +boundary and the memory model. + +## How to update this ledger + +This section is the shared procedure for every page in this directory; +individual pages should add only topic-specific criteria and experiments. + +When adding evidence or a candidate design: + +1. State which settled constraints it preserves. +2. Distinguish observed facts from inferences and preferences. +3. Explain the user-visible and trusted-computing-base consequences. +4. Record dependencies on Rust, Lean, Aeneas, Charon, compilers, or hardware. +5. Prefer a small experiment or counterexample over confidence based only on + architectural taste. + +When deciding a question: + +1. Derive the decision from the project's principles and concrete evidence. +2. Add a decision record with alternatives and consequences. +3. Update every affected document, including settled requirements and current + state where appropriate. +4. Leave enough historical context here to explain what was learned, while + clearly marking the question or sub-question as resolved. + +Temporary disagreements about terminology should not be preserved as design +questions after the underlying meaning is understood. This ledger tracks real +choices that could change Anneal's guarantees, architecture, or user +experience—not conversational cleanup. diff --git a/anneal/agent_docs/design/open-questions/aeneas-charon-integration.md b/anneal/agent_docs/design/open-questions/aeneas-charon-integration.md new file mode 100644 index 0000000000..0df6ba07ba --- /dev/null +++ b/anneal/agent_docs/design/open-questions/aeneas-charon-integration.md @@ -0,0 +1,151 @@ + + +# Aeneas and Charon integration + +**Status:** Open design discussion. + +Anneal is intended to orchestrate and extend Charon's compiler extraction and +Aeneas's Lean translation. V2 must decide which interfaces to consume, which +capabilities to add upstream, and which temporary adaptations Anneal should +own. These are engineering choices governed by semantic fidelity, +maintainability, and user experience rather than a categorical ownership rule. + +## Settled constraints + +| Authority | Constraint on this question | +| --- | --- | +| [ANNEAL-REQ-001](../settled-requirements.md#anneal-req-001-v2-is-a-clean-room-redesign), [decision 0001](../decisions/0001-v2-is-a-clean-room-redesign.md) | V1 integration techniques carry no presumption into V2. | +| [ANNEAL-REQ-017](../settled-requirements.md#anneal-req-017-initial-claims-are-artifact-scoped), [decision 0002](../decisions/0002-verification-is-artifact-scoped.md) | Integration must identify one compiler-resolved compilation subject. | +| [ANNEAL-REQ-018](../settled-requirements.md#anneal-req-018-generated-rust-is-analyzed-input), [decision 0003](../decisions/0003-expanded-generated-rust-is-input.md) | Expanded generated Rust which enters the artifact is analyzed input. | +| [ANNEAL-REQ-019](../settled-requirements.md#anneal-req-019-coverage-adequacy-is-enforced) | Every potentially invalid operation in the reported coverage envelope must be reconciled with complete obligation coverage. | +| [ANNEAL-REQ-021](../settled-requirements.md#anneal-req-021-results-carry-an-audit-ledger) | Results must record the relevant translator, compiler, and proof-tool identities and gaps. | +| [ANNEAL-REQ-025](../settled-requirements.md#anneal-req-025-existing-proof-machinery-is-preferred), [ANNEAL-REQ-026](../settled-requirements.md#anneal-req-026-upstream-evolution-is-in-scope) | Suitable maintained machinery is preferred, and upstream changes are available when they are the best engineering choice. | + +Topic-specific implications: + +- The compiler-resolved artifact must remain authoritative. Any source scanner, + annotation index, generated proof, or sidecar manifest must reconcile with + it rather than silently creating a shadow view of the Rust program. +- No requirement categorically assigns a capability upstream or downstream. + Ownership depends on semantic fidelity, interface robustness, maintenance + burden, and user experience. +- Direct LLBC consumption, structured compiler metadata, Lean syntax + extensions, and downstream adapters all remain candidates. Fragile textual + rewriting is evidence that an interface may be missing, not a prohibited + implementation technique. + +V1's duplicate progress and conditional-correctness proofs are evidence in +favor of experimenting with Aeneas's WP specifications and `step`/`step*` +tactics. They do not settle the proof architecture. + +## Information boundary + +LLBC is a natural semantic input because Charon has already resolved much of +Rust's syntax and types. Anneal annotations also need to bind source intent to +the exact items, fields, operations, and calls that the compiler verifies. +Depending only on a parallel `syn` parse risks creating a shadow view that +disagrees after macro expansion, `cfg` processing, name resolution, or +desugaring. + +One candidate is a compiler-resolved annotation or index mode in Charon. It +could expose expanded annotations, stable-within-artifact item identifiers, +signatures, source provenance, monomorphized or resolved call metadata, unsafe +operations, field accesses, and links to LLBC declarations. A source scanner +could remain a bootstrap or authoring aid but would have to reconcile exactly +against this authoritative index. + +This candidate has not been accepted. Its motivation is to avoid silent skew, +support generated code, find every obligation, improve diagnostics, and reduce +dependence on generated Lean names. We still need to determine which facts are +already in LLBC, which should be added to Charon, and which Anneal can derive +reliably. + +## Questions to resolve + +### What should Charon expose? + +- How are annotations preserved through expansion and lowering? +- Which identifiers remain stable enough for proof artifacts, incremental + builds, and diagnostics? +- Can fields, unsafe operations, drop glue, trait dispatch, closures, and + compiler-generated items be indexed uniformly? +- Does Anneal need MIR-level facts that LLBC intentionally omits? +- How are source spans represented for macro-generated and desugared code? +- Can Charon report unsupported or erased constructs so Anneal fails closed? +- At what compiler phase must extraction occur to avoid transformations that + assume undefined behavior cannot happen? + +The last question is part of the adequacy problem described in +[source/model adequacy](source-model-adequacy.md). + +### What should Aeneas expose or own? + +- Can unsafe leaves be represented within Aeneas's WP semantics with explicit + guards and resource effects? +- Which distinctions among return, panic, unwind, abort, divergence, undefined + operation, and modeling failure should Aeneas preserve? +- How can multiple property obligations share symbolic execution through + `step` and `step*` without forcing every domain into the same logic? +- What changes are needed for separation-logic objects or capabilities whose + use must remain affine or linear? +- Should Aeneas emit structured proof metadata alongside Lean definitions? +- Can generated definitions expose stable theorem interfaces rather than + implementation-specific names? +- How does unsafe and separation-logic work available or proposed in the + relevant Aeneas revision relate to an Anneal-owned short-term pointer model? + +Generic resource machinery may belong in Lean libraries, Rust-specific +semantics in Aeneas, and orchestration in Anneal, but that division is only a +working possibility. See [memory, resources, and effects](memory-resources-and-effects.md). + +### How does Anneal attach its contracts? + +Candidate techniques include: + +- extend LLBC with elaborated contracts and invariant declarations; +- provide Aeneas a structured sidecar manifest keyed by Charon identifiers; +- extend Aeneas lowering so proposition-valued contract arguments appear in + generated functions; +- generate sidecar WP theorems for every indexed call; +- inject stable Lean macros or syntax that elaborate against generated + declarations; and +- validate generated Lean against an independent obligation manifest. + +The choice should be based on completeness, auditability, compatibility with +Aeneas tactics, diagnostic quality, and maintenance cost. “No textual +post-processing ever” is not a principle, but fragile search-and-replace over +generated code is strong evidence that an upstream interface is missing. + +### How are versions and downstream adaptations managed? + +- Does Anneal track released Aeneas/Charon versions, pinned revisions, or a + maintained fork? +- What compatibility contract can upstream realistically offer? +- How do proof artifacts record the exact translator and standard-library + versions they depend on? +- When should experimental work live downstream before an upstream API is + proposed? +- Who owns tests demonstrating that an upstream change preserves Anneal's + semantic assumptions? + +## Evaluation criteria + +A durable integration must make the compiler-resolved artifact authoritative, +detect annotation or coverage skew, preserve all semantics needed for claimed +properties, and give proof authors stable interfaces. It should reuse upstream +machinery, avoid imposing Anneal-only complexity on all Aeneas users, and leave +a credible path for temporary downstream work to mature upstream. + +Useful experiments include a proc-macro-generated unsafe call with an +annotation, a generic trait call resolved after monomorphization, an invariant +on a compiler-generated field access path, shared WP execution for two property +kinds, and a resource assertion that Lean cannot duplicate. + +The effect on ordinary users is discussed in +[proof authoring and user experience](proof-authoring-and-user-experience.md). diff --git a/anneal/agent_docs/design/open-questions/contracts-and-invariants.md b/anneal/agent_docs/design/open-questions/contracts-and-invariants.md new file mode 100644 index 0000000000..58733f32c4 --- /dev/null +++ b/anneal/agent_docs/design/open-questions/contracts-and-invariants.md @@ -0,0 +1,176 @@ + + +# Contracts and invariants + +**Status:** Open design discussion. + +Function contracts, type invariants, and trait invariants are the principal +units by which local proofs become reusable facts. Their semantic roles are +durable, but their syntax, enforcement mechanisms, and relationship to Rust's +types remain open for V2. + +## Settled constraints + +| Authority | Constraint on this question | +| --- | --- | +| [ANNEAL-REQ-005](../settled-requirements.md#anneal-req-005-soundness-is-foundational), [ANNEAL-REQ-006](../settled-requirements.md#anneal-req-006-soundness-specifications-produce-adequate-obligations) | User contracts cannot weaken or erase Rust soundness obligations. | +| [ANNEAL-REQ-009](../settled-requirements.md#anneal-req-009-local-contract-obligations), [ANNEAL-REQ-010](../settled-requirements.md#anneal-req-010-local-results-compose-globally) | Item contracts must support local proof and reusable global composition. | +| [ANNEAL-REQ-012](../settled-requirements.md#anneal-req-012-property-dependencies) | Contracts and invariants must express dependencies among property kinds, including cycles treated soundly. | +| [ANNEAL-REQ-014](../settled-requirements.md#anneal-req-014-type-invariants-support-arbitrary-property-kinds), [ANNEAL-REQ-015](../settled-requirements.md#anneal-req-015-trait-invariants-support-arbitrary-property-kinds), [decision 0004](../decisions/0004-invariants-support-all-property-kinds.md) | Both type and trait invariants support arbitrary property kinds, with their respective establishment and use obligations enforced. | +| [ANNEAL-REQ-016](../settled-requirements.md#anneal-req-016-contracts-distinguish-semantic-roles) | The model must distinguish soundness obligations from ordinary functional preconditions and other property roles. | +| [ANNEAL-REQ-019](../settled-requirements.md#anneal-req-019-coverage-adequacy-is-enforced) | Whatever contract encoding is chosen must not omit a potentially invalid operation within the reported coverage envelope. | + +Topic-specific implications: + +- A safe API cannot make undefined behavior depend on an extra unchecked + obligation of its safe caller. It may still state conditional functional or + other guarantees without turning their conditions into soundness + preconditions. +- Built-in soundness contracts are answerable to Rust's semantics. A + user-defined correctness axiom records the user's intended property; Anneal + can enforce and report it but cannot supply its real-world intent. +- V1's `isValid`, `isSafe`, and indentation-sensitive proof syntax are neither + enforcement mechanisms nor syntax commitments for V2. + +## Function contracts + +### Safety contracts versus general contracts + +Rust's source syntax does not distinguish a soundness precondition from a +precondition for panic freedom, a functional result, or a protocol. V2 needs a +semantic distinction even if the surface syntax is shared. + +Questions include: + +- Does every clause name its property kind explicitly, inherit one from its + declaration, or elaborate to a typed Lean-level obligation? +- How does a safe function express “if `x > 0`, the result has property P” + without suggesting that `x <= 0` may cause undefined behavior? +- Can a single precondition support multiple postconditions or property kinds? +- How are frame conditions, exceptional postconditions, trace conditions, and + resource transfer represented? +- Are contracts attached to source functions, monomorphized instances, trait + methods, function values, or compiler-resolved items with quantified + generics? +- Which contract facts become part of a separately compiled crate's + verification interface? + +The surface should make an accidental weakening of soundness obligations hard +to overlook while remaining usable for general correctness claims. + +### Contracts as types or sidecar theorems + +One candidate lowers a Rust function with Anneal preconditions to a Lean +function whose proposition-valued arguments must be supplied at every call. +This closely resembles an extension to the type system and makes an omitted +proof ill-typed. Another keeps Aeneas's ordinary function model and proves +sidecar theorems showing that every call satisfies the relevant contract. The +latter aligns more directly with existing Aeneas WP machinery. + +This is primarily a representation and enforcement choice. Either is +acceptable only if Anneal can establish complete coverage of all applicable +calls. The deeper circularity in using a soundness-dependent model is treated +in [source/model adequacy](source-model-adequacy.md). + +Open details include higher-order calls, recursion, dynamic dispatch, trait +resolution, closures, foreign calls, and how proof obligations survive +monomorphization or separate compilation. + +## Type invariants + +A type invariant describes facts that valid uses of a value may rely on. It may +support soundness—for example, initialized storage or a valid pointer +relationship—or another property such as a cryptographic representation +invariant or resource bound. + +Questions include: + +- Is an invariant a proposition over an abstract value, a resource assertion, + a family indexed by property kind, or an interface that can provide multiple + forms of evidence? +- At which points must it hold: all observable safe states, function + boundaries, borrow boundaries, suspension points, panic edges, or some + declared subset? +- How is an invariant opened temporarily, and what capability guarantees that + it is restored before control escapes through return, panic, cancellation, + or destruction? +- Which reads require proof that the invariant holds, and which writes require + proof that it is re-established? +- How do interior mutability, aliasing, pinning, drop, partial initialization, + unions, and concurrency change the proof rules? +- Can a type carry several invariants with dependencies between property kinds? + +Enforcement might build on Rust unsafe fields or use Anneal-specific analysis; +see [Rust safety integration](rust-safety-integration.md). Resource-bearing +invariants cannot be flattened into freely duplicable propositions; see +[memory, resources, and effects](memory-resources-and-effects.md). + +## Trait invariants + +The durable idea behind V1's `isSafe` is that an implementation supplies an +invariant promised by the trait, and generic code consumes that invariant from +a known bound. V2 must make both halves enforceable. + +Questions include: + +- Does the trait declare one invariant per property kind, one structured family + of invariants, or named obligations with explicit dependencies? +- Which implementations require a proof: unsafe implementations only, all + implementations of an annotated trait, or a set selected by the property? +- How is evidence made available in a generic context with `T: Trait`? +- How are associated types, constants, generic associated types, supertraits, + negative reasoning, specialization, and trait objects handled? +- Does dynamic dispatch carry a proof dictionary, rely on a sidecar theorem for + the vtable, or elaborate through another mechanism? +- How do auto traits and compiler-generated implementations participate? +- Can an implementation assume other property-kind obligations while proving + its own, and how are cycles certified? + +The answer must prevent an implementation from entering verified code without +its declared obligations while avoiding repeated proofs at every generic use. + +## Specification adequacy and evolution + +Lean can prove a weak or vacuous proposition. For non-axiomatic soundness +specifications, Anneal must connect generated obligations to modeled primitive +semantics strongly enough to guarantee adequacy; human review alone is not the +end state. How it does so is open. + +For user-defined properties, specification intent ultimately comes from the +user. Anneal can still help by detecting unused clauses, vacuous implications, +inconsistent assumptions, or contracts that do not cover executions, but such +checks do not create an external ground truth. + +Contracts also need an evolution story: + +- What changes are compatible for downstream proofs? +- Can implementation details and Aeneas-generated names remain hidden behind a + stable verification interface? +- How are property-kind dependencies versioned? +- Can automated proof repair absorb small Rust refactorings without concealing + a semantic contract change? + +## Evaluation criteria and examples + +A viable design must preserve safe Rust's soundness promise, enforce every +declared implementation obligation, expose exactly the facts a caller may use, +respect resource semantics, and compose across generics and crates. + +Representative experiments should cover: + +- a safe API around a raw-pointer representation; +- a safe function with a conditional functional guarantee but no caller + soundness obligation; +- a type whose field invariant is temporarily broken and restored; +- an unsafe trait with generic and dynamically dispatched consumers; +- a trait invariant for a property other than soundness; and +- mutually dependent type, trait, and callee properties. + +Proof surface and diagnostics are discussed in +[proof authoring and user experience](proof-authoring-and-user-experience.md). diff --git a/anneal/agent_docs/design/open-questions/memory-resources-and-effects.md b/anneal/agent_docs/design/open-questions/memory-resources-and-effects.md new file mode 100644 index 0000000000..618f709c90 --- /dev/null +++ b/anneal/agent_docs/design/open-questions/memory-resources-and-effects.md @@ -0,0 +1,159 @@ + + +# Memory, resources, and effects + +**Status:** Open design discussion. + +Aeneas obtains a simple functional model for a large subset of Rust by using +the borrow checker's discipline. Unsafe Rust, interior mutation, allocators, +concurrency, I/O, and similar features can require spatial, temporal, or +effectful reasoning. Anneal's central opportunity is to combine the simple +functional domain with enough resource-aware machinery to verify unsafe code +without imposing a full heap logic on every proof. + +## Settled constraints + +| Authority | Constraint on this question | +| --- | --- | +| [ANNEAL-REQ-003](../settled-requirements.md#anneal-req-003-general-rust-use-cases) | The architecture must accommodate Rust domains whose essential memory, concurrency, or external-effect semantics cannot be erased. | +| [ANNEAL-REQ-005](../settled-requirements.md#anneal-req-005-soundness-is-foundational) | Simplicity cannot weaken the soundness foundation of a source-level claim. | +| [ANNEAL-REQ-007](../settled-requirements.md#anneal-req-007-the-memory-model-is-conservative) | Any Anneal-owned memory model must conservatively formalize the normative Rust guarantees it claims. | +| [ANNEAL-REQ-008](../settled-requirements.md#anneal-req-008-resource-semantics-are-preserved) | Ownership, provenance, initialization, permissions, and protocol resources must retain their usage discipline in Lean. | +| [ANNEAL-REQ-010](../settled-requirements.md#anneal-req-010-local-results-compose-globally) | Resource and effect reasoning must support abstraction boundaries whose established contracts are reusable by callers. | +| [ANNEAL-REQ-011](../settled-requirements.md#anneal-req-011-long-running-and-exceptional-behavior) | The model must support invariant-preserving infinite execution and sound panic, unwind, and recovery paths. | +| [ANNEAL-REQ-025](../settled-requirements.md#anneal-req-025-existing-proof-machinery-is-preferred), [ANNEAL-REQ-026](../settled-requirements.md#anneal-req-026-upstream-evolution-is-in-scope) | Suitable maintained machinery is preferred, but Anneal, Aeneas, Charon, Lean libraries, and Rust may evolve where needed. | + +Topic-specific implications: + +- Prefer the simplest abstraction that preserves every fact needed for the + claim. Pure value contracts are desirable when faithful, but purity is not + an independent objective. +- Operationally global state or history may be packaged behind local + capabilities, shared invariants, or abstract interfaces only if the proof + rules preserve its resource discipline. +- Robust contextual refinement at a safe boundary does not require literal + representation as a pure value transformer. Effectful, world-indexed, or + protocol interfaces remain available when a simpler boundary would erase + claim-relevant behavior. + +## Locality and abstraction closure + +Safe encapsulation demonstrates that unsafe implementation details can often +be hidden behind a safe interface: clients should not need to reason about the +entire program to use a well-designed abstraction soundly. Separation logic +recovers a related locality by splitting global state into composable resources. +Anneal should exploit this symmetry, but must not assume every abstraction can +be represented as a pure function on values. + +Open questions include: + +- What semantic condition shows that an unsafe implementation refines its safe + interface in every safe context? +- Which effects can close behind an existential representation invariant, and + which must remain in the public contract? +- Can allocator state, provenance history, concurrency protocols, and device + state be represented by local capabilities or shared invariants? +- When a fact is operationally global, what proof rules let a client use a + localized view without making the proof unsound? +- How are abstraction boundaries nested and composed across crates? + +The objective is not purity for its own sake. It is the simplest interface that +is faithful, compositional, understandable, and resilient to implementation +change. + +## Questions to resolve + +### What resource logic is required? + +- Which assertions must be affine, linear, persistent, fractional, or otherwise + controlled? +- How are allocation, initialization, validity, provenance, aliasing, + permissions, and lifetime represented? +- Which rules are generic separation-logic infrastructure and which encode + Rust-specific semantics? +- Can Aeneas's existing state and WP machinery host these assertions, or is a + new monad or logic required? +- How are resource assertions transferred through Aeneas's forward/backward + treatment of borrows? +- How do resource obligations appear in ordinary Lean goals and tactics without + letting users duplicate or discard them illegally? + +The design should reuse maintained Lean and Aeneas abstractions wherever they +fit. Reinvention remains possible when an existing abstraction cannot express +the needed guarantee or would impose unacceptable complexity. + +### Which effects remain visible? + +Representative domains include: + +- allocators and address reuse; +- I/O and interaction with an external environment; +- atomics, locks, threads, and scheduling; +- nondeterminism and randomness; +- panic, unwind cleanup, cancellation, and abort; +- volatile or memory-mapped operations; +- FFI and inline or out-of-line assembly; and +- long-running services and reactive protocols. + +For each, V2 must decide whether a boundary exposes a state transformer, an +effect trace, a WP, a protocol capability, a nondeterministic relation, or a +simpler derived contract. The answer may vary by property kind. A functional +result theorem may hide scheduling details that a deadlock theorem must retain. + +### How conservative is the memory model? + +Rust's operational guarantees are still largely written in prose and continue +to evolve. V2 must determine: + +- which normative documents and team decisions ground each axiom; +- how ambiguity is handled without accidentally claiming more than Rust + guarantees; +- when to ask the Rust project to clarify or change the reference; +- how target layout, ABI, compiler, and hardware assumptions enter the model; +- how provenance and aliasing rules are versioned; and +- how proofs remain useful as the model is refined. + +The authors' direct involvement with Rust's operational-semantics work makes +upstream clarification possible. It does not remove the need to expose current +assumptions in Anneal's trust ledger. + +### Where should the implementation live? + +Candidate arrangements include: + +- Aeneas owns general unsafe or separation-logic semantics and Anneal supplies + annotations and orchestration; +- Anneal temporarily owns a conservative pointer/layout layer that later moves + upstream; +- Lean libraries own generic resource machinery while Aeneas owns Rust + instantiations; and +- multiple semantic backends coexist behind a common contract interface. + +Changes should be upstream when they form reusable Aeneas or Charon +functionality and when the maintenance burden is acceptable. Shipping a sound +and auditable Anneal remains more important than an aesthetically perfect +ownership boundary. + +## Evaluation criteria + +A candidate must preserve resource usage rules, support modular composition, +state its relationship to normative Rust, and make exceptional and infinite +executions sound. It should keep simple safe-code proofs simple and reveal +complex machinery only where the claimed behavior needs it. It should also +allow the trusted model to shrink or become more precise without rewriting +unrelated user proofs. + +Useful experiments include a raw-pointer container that closes to a pure safe +API, an allocator whose capability must remain visible, a lock with a shared +invariant, panic during partially initialized construction, and an I/O protocol +whose functional return value alone is inadequate. + +See also [property kinds and outcomes](property-kinds-and-outcomes.md), +[contracts and invariants](contracts-and-invariants.md), and +[Aeneas and Charon integration](aeneas-charon-integration.md). diff --git a/anneal/agent_docs/design/open-questions/proof-authoring-and-user-experience.md b/anneal/agent_docs/design/open-questions/proof-authoring-and-user-experience.md new file mode 100644 index 0000000000..c8efa210fa --- /dev/null +++ b/anneal/agent_docs/design/open-questions/proof-authoring-and-user-experience.md @@ -0,0 +1,177 @@ + + +# Proof authoring and user experience + +**Status:** Open design discussion. + +Anneal's eventual audience includes ordinary Rust teams, formal-methods +specialists, and coding agents. The aspiration is that even early-career Rust +engineers can participate, potentially with AI assistance, while the resulting +claims remain rigorous and reviewable. The right source syntax, proof workflow, +and division of labor are matters for experimentation. + +## Settled constraints + +| Authority | Constraint on this question | +| --- | --- | +| [ANNEAL-REQ-001](../settled-requirements.md#anneal-req-001-v2-is-a-clean-room-redesign), [decision 0001](../decisions/0001-v2-is-a-clean-room-redesign.md) | V1's literate proof syntax is evidence, not a product commitment. | +| [ANNEAL-REQ-005](../settled-requirements.md#anneal-req-005-soundness-is-foundational), [ANNEAL-REQ-021](../settled-requirements.md#anneal-req-021-results-carry-an-audit-ledger) | A smoother interface cannot weaken the formal claim or conceal its dependencies. | +| [ANNEAL-REQ-010](../settled-requirements.md#anneal-req-010-local-results-compose-globally) | Established abstraction contracts must be reusable without reopening private bodies. | +| [ANNEAL-REQ-022](../settled-requirements.md#anneal-req-022-incremental-adoption-supports-prose), [decision 0005](../decisions/0005-incremental-adoption-supports-prose-justifications.md) | The workflow must accommodate explicit, audited prose justifications during incremental adoption. | +| [ANNEAL-REQ-024](../settled-requirements.md#anneal-req-024-incompleteness-is-distinguishable-from-external-trust) | UX and diagnostics must not collapse unfinished proofs into intentional external trust. | +| [ANNEAL-REQ-025](../settled-requirements.md#anneal-req-025-existing-proof-machinery-is-preferred) | Suitable Lean and Aeneas machinery should be reused rather than rebuilt without a concrete benefit. | +| [ANNEAL-REQ-027](../settled-requirements.md#anneal-req-027-ordinary-rust-engineers-are-a-target-audience) | The eventual workflow must serve ordinary Rust engineers without sacrificing meaning or auditability. | + +Topic-specific implications: + +- Machine checking, not trust in a human- or AI-generated explanation, + establishes a formal claim. Assistance can change who writes a proof, but + not what counts as evidence. +- Proof interfaces should avoid needless dependence on generated names and + internal WP structure. That preference does not require hiding Lean or + Aeneas from expert users. +- Humans and agents should work from the same contracts, principles, and + result semantics even when their authoring interfaces differ. + +## Who authors what? + +Possible workflows include: + +- Rust engineers write contracts and proof-relevant explanations while agents + synthesize Lean proofs; +- agents propose both code and specifications, with humans reviewing intent and + Lean checking proof correctness; +- formal-methods specialists publish reusable contracts, tactics, and models + consumed by ordinary teams; +- library authors ship verified interfaces that downstream users consume + without seeing implementation proofs; and +- humans write all proof material directly for especially sensitive code. + +V2 should not assume one universal workflow. It should identify which artifacts +require human judgment—especially the intended meaning of user-defined +specifications—and which can be generated or checked mechanically. + +Open questions include: + +- What must a Rust author understand about Lean, LLBC, Aeneas WP semantics, and + memory resources? +- Which proof steps are stable enough for agents to regenerate automatically? +- How does review distinguish a semantic specification change from proof repair + after a refactor? +- Can organizations require human approval of axioms or soundness contracts + while automating ordinary proofs? +- How are proof ownership and maintenance responsibility recorded? + +## Surface syntax and artifact layout + +Candidate locations include Rust attributes, doc comments, inline comments, +adjacent Lean files, generated proof modules with checked-in overlays, or a +combination. Each has different consequences for macro expansion, IDE support, +source control, Rust documentation, and proof reuse. + +Questions to resolve: + +- How are property kinds, preconditions, postconditions, type invariants, trait + invariants, exceptional behavior, and resource transfer written? +- Which parts use Rust-like syntax and which expose Lean terms? +- Can specifications remain near Rust while larger proofs live in ordinary Lean + modules? +- How does a proof refer to compiler-resolved items without depending on + unstable generated names? +- Are proofs checked in, generated on demand, cached as artifacts, or all three + under different policies? +- How are macro-generated items annotated or given contracts? +- Can source formatting and rustdoc ignore Anneal content cleanly? +- What is the migration path if syntax evolves rapidly during pre-alpha work? + +The compiler binding needed for these choices is discussed in +[Aeneas and Charon integration](aeneas-charon-integration.md). + +## Obligation presentation and diagnostics + +`cargo anneal verify` should help a Rust engineer understand what must be true +at the relevant source location. It remains open how much of the underlying +Lean goal and Aeneas state is exposed. + +Useful diagnostic capabilities may include: + +- a Rust-level explanation of the violated callee precondition; +- the property kind and transitive dependency that generated it; +- the exact compiler-resolved operation, including generated code provenance; +- relevant assumptions, type or trait invariants, and owned resources; +- the normal, panic, unwind, or other path on which the obligation occurs; +- a minimal Lean goal for expert debugging; +- an audit distinction among failed, unsupported, incomplete, axiomatic, and + prose-justified obligations; and +- machine-readable output for IDEs and agents. + +Questions include whether Anneal should synthesize counterexamples, symbolic +traces, suggested invariants, or proof skeletons; how it avoids presenting an +unsoundly simplified explanation; and how diagnostics remain stable when +Aeneas internals change. + +## Verification loop + +The desired workflow should feel proportionate to ordinary Rust development: +fast feedback for local changes, a stronger reproducible check in CI, and a +clear audit artifact for review or release. Design work is needed for: + +- incremental extraction and Lean recompilation; +- caching keyed by the exact Cargo artifact and toolchain; +- checking selected property kinds and their dependencies; +- distinguishing quick local checking from release assurance without + overloading “verified”; +- editing support that navigates between Rust obligations and Lean proofs; +- proof repair when generated definitions change; and +- reducing irrelevant churn in error messages and audit logs. + +The command success policy is part of +[trust and incremental adoption](trust-and-incremental-adoption.md), while the +semantics of selected properties are covered in +[property kinds and outcomes](property-kinds-and-outcomes.md). + +## Abstraction and leakage + +Some Aeneas concepts will inevitably be useful to proof authors. The open +question is which become stable Anneal concepts and which remain expert escape +hatches. + +Candidate layers include: + +1. Rust-oriented contracts and generated obligations for common proofs. +2. Reusable Anneal tactics and libraries that expose selected WP and resource + concepts. +3. Full Lean and Aeneas access for specialists and novel domains. + +This layering is only a candidate. It must not cause the convenient layer to +hide assumptions or make advanced properties impossible. Likewise, forcing all +users to manipulate generated forward/backward functions or raw separation +logic would undermine the broad-adoption goal if stable abstractions can avoid +it. + +## Evaluation criteria and experiments + +Evaluate workflows by semantic clarity, error discoverability, proof stability, +auditability, adoption cost, and the ability of both humans and agents to make +safe changes. Raw proof length or the absence of visible Lean is not by itself a +measure of usability. + +Experiments should include: + +- a new Rust graduate repairing a failed call-site proof with guided tooling; +- an agent updating a proof after a small implementation refactor without + changing the contract; +- a specialist adding a new resource-aware property domain; +- migration from `// SAFETY:` prose to a formal proof; +- review of a PR that changes both a contract and its proof; and +- downstream use of a verified generic library without dependence on generated + Aeneas names. + +The governing contract and invariant questions are recorded in +[contracts and invariants](contracts-and-invariants.md). diff --git a/anneal/agent_docs/design/open-questions/property-kinds-and-outcomes.md b/anneal/agent_docs/design/open-questions/property-kinds-and-outcomes.md new file mode 100644 index 0000000000..0c14ee4b3e --- /dev/null +++ b/anneal/agent_docs/design/open-questions/property-kinds-and-outcomes.md @@ -0,0 +1,164 @@ + + +# Property kinds and execution outcomes + +**Status:** Open design discussion. + +Anneal must reason about Rust soundness and also support properties such as +panic freedom, deadlock freedom, cryptographic correctness, protocol +correctness, and resource bounds. Rust's present `safe`/`unsafe` distinction +does not identify these different kinds of obligation. V2 needs an extensible +model, but adopting a taxonomy too early would constrain domains that have not +yet been explored. + +## Settled constraints + +| Authority | Constraint on this question | +| --- | --- | +| [ANNEAL-REQ-002](../settled-requirements.md#anneal-req-002-general-properties), [ANNEAL-REQ-004](../settled-requirements.md#anneal-req-004-extensibility-before-breadth) | The architecture must admit arbitrary user-defined property domains without requiring all specialized backends initially. | +| [ANNEAL-REQ-005](../settled-requirements.md#anneal-req-005-soundness-is-foundational) | Soundness is a mandatory semantic foundation, not an optional peer of user-selected properties. | +| [ANNEAL-REQ-009](../settled-requirements.md#anneal-req-009-local-contract-obligations), [ANNEAL-REQ-010](../settled-requirements.md#anneal-req-010-local-results-compose-globally) | Property evidence must support local proof and global composition. | +| [ANNEAL-REQ-011](../settled-requirements.md#anneal-req-011-long-running-and-exceptional-behavior) | Verification cannot equate success with normal termination; it must express invariant-preserving divergence and sound panic, unwind, and recovery. | +| [ANNEAL-REQ-012](../settled-requirements.md#anneal-req-012-property-dependencies) | Property kinds may depend on one another, including through cycles that require a sound joint treatment. | +| [ANNEAL-REQ-013](../settled-requirements.md#anneal-req-013-property-selection) | Users must eventually be able to select which non-foundational properties are enforced. | +| [ANNEAL-REQ-014](../settled-requirements.md#anneal-req-014-type-invariants-support-arbitrary-property-kinds), [ANNEAL-REQ-015](../settled-requirements.md#anneal-req-015-trait-invariants-support-arbitrary-property-kinds), [decision 0004](../decisions/0004-invariants-support-all-property-kinds.md) | Both invariant forms must carry arbitrary property kinds. | + +Topic-specific implications: + +- Selecting or defining another property cannot substitute for the soundness + obligations needed to interpret its theorem as a claim about Rust. +- A common execution model may share evidence among properties, but no + requirement forces domains with different semantics into identical proof + judgments. +- The taxonomy, dependency algorithm, cycle treatment, and command/reporting + vocabulary all remain open. + +## Taxonomy remains open + +One proposal distinguishes: + +- semantic outcomes and effects, such as return, panic or unwind, abort, + divergence, undefined behavior, and model or tool failure; +- standard assurance policies, such as soundness, panic freedom, termination, + partial correctness, and total correctness; and +- extensible user property domains, such as functional, relational, trace, + protocol, quantitative, or probabilistic properties. + +This proposal has deliberately been neither accepted nor rejected. These terms +are useful for exploring the space, but must not be exposed as a stable API or +used to infer settled semantics. Among other issues, a property like deadlock +freedom may concern traces, progress, concurrency resources, and termination at +once; “panic freedom” may need to distinguish panic, abort, and unwind; and the +boundary between a policy and a user domain may not survive real examples. + +## Questions to resolve + +### What is first-class? + +- Which concepts, beyond soundness, should Anneal itself understand? +- Should panic, unwind, abort, nontermination, and ordinary return be modeled as + outcomes, effects, predicates over traces, or some combination? +- Which concepts need dedicated compiler extraction or proof machinery, and + which can be libraries defined over a smaller semantic core? +- Can one representation cover safety properties, liveness properties, + hyperproperties, probabilistic claims, and quantitative bounds without + pretending that their proof rules are identical? + +Initial V2 is expected to supply an extensible architecture rather than +complete support for deadlock freedom, hyperproperties, quantitative bounds, +or cryptographic reasoning. + +### How are execution paths combined? + +V1 split “progress” from conditional correctness. Experience suggests that the +split duplicated symbolic-execution work and fit poorly with Aeneas's WP +specifications and `step`/`step*` tactics. The current direction is to build on +Aeneas's combined machinery and share symbolic execution across obligations. +It remains open: + +- how path-specific obligations are attached to normal return, panic, unwind, + abort, and unbounded execution; +- whether different property kinds consume a common execution certificate or + require domain-specific proof judgments; +- how cleanup during unwinding is modeled well enough to establish soundness; + and +- how partial or total correctness is requested without making soundness + depend on termination. + +The Aeneas revision examined during this design discussion represented several +exceptional situations through a coarse failure channel. A bounds check, +arithmetic overflow, explicit panic, undefined operation, unsupported model +feature, and tool failure can have very different implications. Before relying +on this observation, recheck the Aeneas revision pinned by the current +toolchain. We still need to determine which cases it combines, which +distinctions survive LLBC lowering, and whether to refine the semantic result +type, attach metadata, or express the distinctions in WP predicates. This is an +investigation, not a claim about the correct Aeneas API. + +### How do dependencies compose? + +- How are dependencies between property kinds declared and discovered? +- Should selecting a property automatically select its transitive dependency + closure? +- If a dependency is excluded, does verification fail, succeed relative to an + audited assumption, or vary by command profile? +- How should mutually dependent properties be proven? Candidate approaches + include an explicit joint certificate for each strongly connected component, + inductive closure, and coinductive closure for appropriate liveness claims. +- How are dependencies represented through generic functions, trait bounds, + dynamic dispatch, and separately verified crates? + +These choices interact with the meaning of an incomplete proof and with trust +reporting; see [trust and incremental adoption](trust-and-incremental-adoption.md). + +### What does the user select and see? + +- Are property kinds named globally, scoped to crates, or identified by Lean + propositions or Rust declarations? +- Can projects define new kinds with reusable proof rules, or only attach + arbitrary propositions to built-in mechanisms? +- Does a command select desired top-level guarantees, obligations to check, or + both? +- How should output distinguish “not requested,” “proved,” “assumed,” “blocked + by unsupported semantics,” and “not applicable”? +- Which guarantees belong in machine-readable artifacts so downstream crates + can rely on them? + +## Evaluation criteria + +An acceptable design must: + +1. Make it impossible to interpret another selected property as a + substitute for soundness. +2. Give panic, divergence, and recovery faithful meanings rather than treating + them all as failed verification or silently discarding their paths. +3. Preserve compositional reasoning across functions and crates. +4. Avoid performing essentially the same symbolic execution independently for + every property where evidence can safely be shared. +5. Allow specialized proof systems where sharing would erase a domain's + semantics. +6. Produce an audit result whose dependencies and assumptions a user can + understand. +7. Be testable on realistic examples before the vocabulary becomes stable. + +## Useful experiments + +- A server loop that maintains an invariant forever without promising + termination. +- A destructor that runs during unwinding and whose unsafe operations must + remain sound. +- A function whose soundness depends on a callee's cryptographic or protocol + property. +- A lock abstraction with mutually dependent ownership and deadlock claims. +- One implementation checked for soundness, panic freedom, and a functional + result property using shared symbolic execution. + +Related questions include [Rust safety integration](rust-safety-integration.md), +[memory, resources, and effects](memory-resources-and-effects.md), and +[proof authoring](proof-authoring-and-user-experience.md). diff --git a/anneal/agent_docs/design/open-questions/rust-safety-integration.md b/anneal/agent_docs/design/open-questions/rust-safety-integration.md new file mode 100644 index 0000000000..5009d291c4 --- /dev/null +++ b/anneal/agent_docs/design/open-questions/rust-safety-integration.md @@ -0,0 +1,165 @@ + + +# Integration with Rust's safety machinery + +**Status:** Open design discussion. + +Rust has a familiar, compiler-enforced vocabulary for `unsafe fn`, `unsafe` +blocks, and unsafe traits. As of this page's 2026-07-17 review, unsafe fields +were under development upstream; that status is volatile and must be rechecked +before it becomes an implementation premise. Rust also has ecosystem +conventions and lints around `# Safety` documentation and `// SAFETY:` comments. +That machinery tracks only one undifferentiated notion of safety, while Anneal +must support multiple property kinds and stronger proof obligations. V2 must +decide where to extend Rust's system and where to create an Anneal-owned system +alongside it. + +## Settled constraints + +| Authority | Constraint on this question | +| --- | --- | +| [ANNEAL-REQ-001](../settled-requirements.md#anneal-req-001-v2-is-a-clean-room-redesign), [decision 0001](../decisions/0001-v2-is-a-clean-room-redesign.md) | V1 syntax and its mapping to Rust `unsafe` are evidence, not defaults. | +| [ANNEAL-REQ-005](../settled-requirements.md#anneal-req-005-soundness-is-foundational) | Soundness remains mandatory under every integration strategy. | +| [ANNEAL-REQ-014](../settled-requirements.md#anneal-req-014-type-invariants-support-arbitrary-property-kinds), [ANNEAL-REQ-015](../settled-requirements.md#anneal-req-015-trait-invariants-support-arbitrary-property-kinds), [decision 0004](../decisions/0004-invariants-support-all-property-kinds.md) | Type and trait invariants are required and must support arbitrary property kinds. | +| [ANNEAL-REQ-016](../settled-requirements.md#anneal-req-016-contracts-distinguish-semantic-roles) | Rust's single safety axis cannot erase distinctions among Anneal obligation roles. | +| [ANNEAL-REQ-022](../settled-requirements.md#anneal-req-022-incremental-adoption-supports-prose), [decision 0005](../decisions/0005-incremental-adoption-supports-prose-justifications.md) | Selected formal obligations may be replaced temporarily by explicit, audited prose justifications. | +| [ANNEAL-REQ-026](../settled-requirements.md#anneal-req-026-upstream-evolution-is-in-scope) | Rust language and specification evolution is in long-term scope. | + +Topic-specific implications: + +- A safe Rust API must remain sound for every type-checked safe use, even when + its implementation uses unsafe code or Anneal tracks additional properties. +- Type invariants cannot be omitted because V1's mechanism was unsound, and V2 + need not wait for unsafe fields: Rust support and Anneal-specific analysis + are both candidates. +- Rust's existing unsafe boundaries offer familiar enforcement and migration + points, while arbitrary property kinds may require Anneal-owned scopes. The + balance between those systems remains the question being decided. + +## The central choice + +At one end of the design space, Anneal could map formal soundness obligations +closely onto Rust's existing unsafe boundaries. This would reuse compiler +checks, programmer expectations, documentation conventions, Clippy lints, and +incremental workflows. A Rust `unsafe` block could be the place where either a +formal proof or an audited prose justification discharges an obligation. + +At the other end, Anneal could maintain a richer parallel system. It could +track separately named property kinds, require proof around operations Rust +does not mark unsafe, and support type invariants before unsafe fields +stabilize. This avoids forcing concepts such as panic freedom or cryptographic +correctness into Rust's single `unsafe` bit. + +A hybrid is plausible: use Rust boundaries as authoritative evidence for some +soundness obligations while layering general property tracking and additional +invariant-sensitive operations on compiler-resolved program facts. The exact +division, reconciliation rules, and user vocabulary remain open. + +## Questions to resolve + +### Which Rust constructs carry Anneal meaning? + +- Does every `unsafe` operation generate an Anneal soundness obligation, or do + obligations attach only to unsafe leaves with modeled semantic guards? +- Is an `unsafe` block evidence of the intended proof scope, a syntactic place + to write a proof, or merely a Rust type-checking construct? +- How are unsafe functions whose callers provide prose `# Safety` contracts + related to machine-readable Anneal contracts? +- Can an operation be safe under Rust's soundness axis but guarded under another + Anneal property kind? If so, what source construct marks it? +- How should unsafe traits and future language features interact with Anneal's + trait invariants? + +Rust's `unsafe` syntax identifies responsibility boundaries; it does not by +itself fully state the semantic preconditions that make an operation sound. +Any design that reuses the syntax still needs an adequate specification of +those preconditions. See [source/model adequacy](source-model-adequacy.md). + +### How are property kinds spelled? + +A hypothetical Rust feature such as `unsafe(soundness)` or +`unsafe(panic_freedom)` illustrates the desired distinction, but does not exist. +V2 must decide whether annotations: + +- name property kinds independently of Rust syntax; +- decorate existing unsafe functions, blocks, traits, and fields; +- introduce Anneal-specific proof scopes for operations that Rust considers + safe; or +- combine these approaches and require exact reconciliation. + +The answer must work for user-defined kinds without requiring a Rust language +change for each new domain. It must also avoid implying that a caller may +violate the soundness of a safe Rust API merely because no Anneal property was +selected. + +### How are type invariants protected? + +V1's `isValid` concept demonstrated the usefulness of type invariants but did +not prevent safe code from mutating fields without re-establishing them. V2 +needs an enforcement boundary. Candidate mechanisms include: + +- Rust unsafe fields, once their exact semantics are usable; +- Anneal analysis that marks reads, writes, borrows, moves, construction, and + destruction of invariant-carrying representation as proof-requiring; +- module or constructor boundaries; +- explicit invariant-opening and invariant-closing operations; +- refinement wrappers whose representation cannot be accessed directly; and +- combinations of these for different kinds of invariant. + +The design must account for pattern matching, field projection, mutable and +shared borrowing, interior mutability, destructuring assignment, unions, +layout operations, drop, and code generated by macros. It must say when an +invariant may be temporarily broken, who owns the capability to restore it, +and how resource-sensitive facts are consumed. See +[contracts and invariants](contracts-and-invariants.md) and +[memory, resources, and effects](memory-resources-and-effects.md). + +### How does prose coexist with proofs? + +Some version of prose-based discharge is required for incremental adoption. +Open choices include: + +- whether an existing `// SAFETY:` comment is enough or must carry an explicit + Anneal marker; +- whether comments discharge individual call-site obligations, whole unsafe + blocks, or selected subtrees; +- which property kinds may use prose; +- whether prose uses an axiom-like or shared admission mechanism, a dedicated + status or provenance record, or another representation, while reports still + distinguish it from axiomatic semantics, incomplete proofs, and checked + evidence; +- whether Anneal checks that every Rust unsafe boundary has either proof or + prose, even when only part of a crate is formally adopted; and +- how later tooling helps replace prose with proofs without changing the Rust + code's structure. + +Prose can mark a trust gap; it cannot silently supply a machine-checked theorem. +The command and ledger semantics are discussed in +[trust and incremental adoption](trust-and-incremental-adoption.md). + +## Evaluation criteria + +Compare candidate integrations by asking: + +1. Does safe Rust retain its unconditional soundness promise? +2. Can the system express independent and dependent property kinds without + abusing Rust's single safety axis? +3. Can every invariant-sensitive operation be found in expanded, + compiler-resolved code, including generated code? +4. Is responsibility visible and familiar enough for Rust engineers to review? +5. Can a project adopt Anneal gradually without disguising trust gaps? +6. Does the design continue to work if Rust gains unsafe fields or richer + safety annotations? +7. Can the implementation fail closed when Rust and Anneal views disagree? + +Useful prototypes should include an invariant-carrying type before unsafe +fields, a crate mixing formal proofs and `// SAFETY:` comments, and a +property other than soundness attached to an operation Rust considers safe. The +compiler metadata needed for those experiments is covered in +[Aeneas and Charon integration](aeneas-charon-integration.md). diff --git a/anneal/agent_docs/design/open-questions/source-model-adequacy.md b/anneal/agent_docs/design/open-questions/source-model-adequacy.md new file mode 100644 index 0000000000..8e907826bd --- /dev/null +++ b/anneal/agent_docs/design/open-questions/source-model-adequacy.md @@ -0,0 +1,168 @@ + + +# Source/model adequacy + +**Status:** Open design discussion. + +Anneal's intended verification pipeline uses Aeneas and Lean to prove that +unsafe operations satisfy their preconditions, yet the correspondence between +Rust and the generated model is only promised for sound Rust programs. An +unsound Rust program may be lowered to a Lean definition that bears no useful +relationship to its source. V2 must close this apparent circularity rather +than merely assume that successful Lean proofs describe the original program. + +## Settled constraints + +| Authority | Constraint on this question | +| --- | --- | +| [ANNEAL-REQ-005](../settled-requirements.md#anneal-req-005-soundness-is-foundational), [ANNEAL-REQ-006](../settled-requirements.md#anneal-req-006-soundness-specifications-produce-adequate-obligations) | A source-level soundness result requires adequate, non-erasable obligations for all supported Rust safety conditions. | +| [ANNEAL-REQ-007](../settled-requirements.md#anneal-req-007-the-memory-model-is-conservative) | Trusted unsafe-leaf guards must conservatively reflect the normative Rust guarantees being claimed. | +| [ANNEAL-REQ-017](../settled-requirements.md#anneal-req-017-initial-claims-are-artifact-scoped), [decision 0002](../decisions/0002-verification-is-artifact-scoped.md) | The initial theorem concerns one fixed Cargo compilation artifact, not every build configuration. | +| [ANNEAL-REQ-018](../settled-requirements.md#anneal-req-018-generated-rust-is-analyzed-input), [decision 0003](../decisions/0003-expanded-generated-rust-is-input.md) | Expanded generated Rust ingested by Charon is analyzed input rather than trusted merely because a generator produced it. | +| [ANNEAL-REQ-019](../settled-requirements.md#anneal-req-019-coverage-adequacy-is-enforced) | Every potentially invalid source operation in the reported coverage envelope must receive the obligations needed to justify the proof model. | +| [ANNEAL-REQ-020](../settled-requirements.md#anneal-req-020-the-tcb-is-explicit-and-shrinkable), [ANNEAL-REQ-021](../settled-requirements.md#anneal-req-021-results-carry-an-audit-ledger), [decision 0006](../decisions/0006-the-tcb-is-explicit-and-shrinkable.md) | Unproved correspondence, toolchain, coverage, and primitive-semantic assumptions remain explicit and replaceable. | +| [ANNEAL-REQ-026](../settled-requirements.md#anneal-req-026-upstream-evolution-is-in-scope) | Rust, Charon, Aeneas, and Anneal may all change to support a defensible adequacy argument. | + +Topic-specific implications: + +- A Lean theorem about an inadequate or incompletely covered model is not a + theorem about the Rust artifact. Model and tool failures therefore cannot be + reported as successful source verification. +- Proposition-valued function arguments and sidecar call-site theorems are + alternative obligation encodings. Neither is sufficient without complete + coverage and a semantic argument connecting discharged guards to source + validity. +- The exact adequacy theorem, extraction point, coverage unit, and division + between proof and trusted translation remain open. + +## The required semantic connection + +We need a precise theorem connecting source executions, extracted semantics, +and generated proof obligations. Its exact statement is open. Candidate proof +families include: + +- an execution-prefix argument showing correspondence while validity + conditions hold and ruling out a first invalid operation by proving every + required guard; +- a forward or backward simulation maintained under an explicit source + validity invariant; and +- per-artifact translation validation that checks the extracted operations and + obligations against a separately justified semantic relation. + +These are sketches, may overlap, and are neither exhaustive nor accepted. Any +formulation must account for nondeterminism, concurrency, unwind and abort +behavior, infinite executions, provenance, external effects, compiler +transformations, and whatever relation Aeneas actually proves between LLBC and +Lean. + +The important distinction is between the semantic argument and the Lean API +used to present an obligation. Proposition-valued parameters make unguarded +model calls ill-typed; sidecar WP theorems may reuse Aeneas machinery more +directly. Either presentation still requires a theorem explaining why proving +the obligations rules out undefined source behavior. + +## Questions to resolve + +### Where is the authoritative program captured? + +- At which rustc phase must Charon extract the program so operations are still + present and their semantics have not already been changed by optimizations + that assume away undefined behavior? +- Are MIR and LLBC sufficiently expressive to identify every relevant unsafe + primitive and path, including drop glue, compiler-generated shims, intrinsics, + unwinding, and monomorphized calls? +- What compiler transformations occur before extraction, and what assumptions + do they make? +- Which source annotations survive expansion and lowering, and how are they + reconciled with compiler-resolved items? + +[Issue #3041](https://github.com/google/zerocopy/issues/3041) raises the risk +that extraction after UB-exploiting optimization could erase exactly the +operation Anneal needs to guard. This remains an investigation; V2 must not +assume the current extraction point is adequate without evidence. + +### What counts as complete coverage? + +- How are direct calls, trait dispatch, function pointers, closures, virtual + calls, recursion, drop glue, statics, and compiler intrinsics enumerated? +- Is proof coverage stated over syntactic call sites, semantic operations, + reachable monomorphized instances, or another unit? +- How are unreachable code and dead branches justified? +- What happens when Charon or Aeneas omits an unsupported construct? +- How does separate compilation carry verified contracts without assuming that + dependency code was built with different features or targets? +- How does Anneal prove that its annotation index and the LLBC artifact describe + the same items? + +Coverage should be machine-checkable and appear in the audit artifact where it +depends on assumptions. See +[Aeneas and Charon integration](aeneas-charon-integration.md) and +[trust and incremental adoption](trust-and-incremental-adoption.md). + +### What semantics do leaf guards express? + +Raw pointer dereference, pointer arithmetic, reads and writes, allocation, +deallocation, intrinsics, inline assembly, and FFI have different semantic +foundations. For each leaf V2 must determine: + +- the Rust operation being modeled; +- the precondition sufficient to rule out its undefined behaviors; +- the result and effects available to subsequent proofs; +- resource ownership or provenance consumed and produced; +- panic, unwind, abort, and divergence behavior where applicable; and +- which facts are proven, derived from Rust's specification, or trusted. + +An explicit set of assumptions is not automatically a conservative model of +normative Rust guarantees. The relationship must itself be documented and, +where possible, mechanized. Resource-bearing guards are discussed in +[memory, resources, and effects](memory-resources-and-effects.md). + +### What must be proved about the toolchain? + +- Is the adequacy theorem end-to-end, or a composition of rustc-to-LLBC, + LLBC-to-Lean, Anneal instrumentation, Lean kernel, and code-generation claims? +- Which translations are formally verified, validated per artifact, tested, or + trusted? +- Can Anneal independently validate that generated Lean contains all expected + obligations without brittle text rewriting? +- Which host compiler, LLVM, linker, and target hardware assumptions affect the + final claim rather than only tool availability? + +These questions do not require V2 to verify its entire toolchain immediately. +They do require an honest boundary and a path to shrink it. + +## Candidate approaches + +Candidate work can be combined: + +- extend Charon with compiler-resolved annotations and an operation index; +- validate LLBC against an independently generated obligation manifest; +- extend Aeneas's WP semantics with guarded unsafe leaves; +- make guards proposition-valued inputs to selected generated Lean functions; +- generate sidecar theorems and have Anneal verify one theorem per indexed + operation; +- prove an adequacy or trace-simulation theorem connecting complete guards to + source validity; and +- add fail-closed coverage checks for every unsupported or unmatched item. + +Textual patches to generated Lean are less desirable than programmatic +interfaces, but the criterion is robustness and semantic auditability, not a +categorical ban on any implementation technique. + +## Evaluation criteria + +A successful approach must state exactly which source artifact it covers, +identify every operation that can invalidate the source/model relationship, +make missing obligations detectable, preserve outcome and resource semantics, +and expose its trusted assumptions. It should also let V2 reuse Aeneas's WP +machinery rather than rebuilding it without need. + +The result should survive adversarial examples: an invalid operation optimized +away, an unsafe action in generated drop glue, a trait call resolved only after +monomorphization, and a panic path that runs unsafe cleanup. diff --git a/anneal/agent_docs/design/open-questions/trust-and-incremental-adoption.md b/anneal/agent_docs/design/open-questions/trust-and-incremental-adoption.md new file mode 100644 index 0000000000..efbc6d3ed4 --- /dev/null +++ b/anneal/agent_docs/design/open-questions/trust-and-incremental-adoption.md @@ -0,0 +1,163 @@ + + +# Trust and incremental adoption + +**Status:** Open design discussion. + +Anneal must be useful before an entire codebase has been modeled, while making +it impossible to confuse a partially trusted result with an end-to-end proof. +It must also support genuinely external semantics that may remain axiomatic for +the foreseeable future. V2 therefore needs first-class accounting for trust, +incompleteness, and adoption state. + +## Settled constraints + +| Authority | Constraint on this question | +| --- | --- | +| [ANNEAL-REQ-006](../settled-requirements.md#anneal-req-006-soundness-specifications-produce-adequate-obligations) | Non-axiomatic soundness specifications require machine-enforced adequacy rather than human intent alone. | +| [ANNEAL-REQ-017](../settled-requirements.md#anneal-req-017-initial-claims-are-artifact-scoped), [decision 0002](../decisions/0002-verification-is-artifact-scoped.md) | Trust and incompleteness qualify a result about one precisely identified compilation subject. | +| [ANNEAL-REQ-020](../settled-requirements.md#anneal-req-020-the-tcb-is-explicit-and-shrinkable), [decision 0006](../decisions/0006-the-tcb-is-explicit-and-shrinkable.md) | Trusted semantic leaves and the broader end-to-end TCB must be explicit and replaceable by deeper models. | +| [ANNEAL-REQ-021](../settled-requirements.md#anneal-req-021-results-carry-an-audit-ledger) | Every result must expose its exact claim, dependencies, trust, incompleteness, coverage, toolchain, environment, and hardware assumptions. | +| [ANNEAL-REQ-022](../settled-requirements.md#anneal-req-022-incremental-adoption-supports-prose), [decision 0005](../decisions/0005-incremental-adoption-supports-prose-justifications.md) | Selected obligations may temporarily rely on prose, but that reliance is an explicit residual dependency rather than a completed proof. | +| [ANNEAL-REQ-023](../settled-requirements.md#anneal-req-023-axioms-cover-genuine-external-semantics) | Fully adopted code should reserve axioms for genuine semantic boundaries; standard-library primitives and FFI remain important expected uses. | +| [ANNEAL-REQ-024](../settled-requirements.md#anneal-req-024-incompleteness-is-distinguishable-from-external-trust) | An intended but unfinished proof must remain distinguishable from an intentionally axiomatic specification. | + +Topic-specific implications: + +- A result containing residual dependencies must identify their kind, origin, + and affected claim. When a dependency has a known path to stronger evidence, + the ledger should expose it; a successful exit code cannot erase the + dependency. +- The meaning of a user-defined property comes from the user; any unproved + proposition assumed about it remains a residual dependency. Anneal enforces + and reports that assumption but cannot validate the intended meaning. This + differs from responsibility for the adequacy of primitive soundness guards. +- Command statuses, policy profiles, ledger schema, and authorship or + distribution of external specifications remain open. + +## Distinct evidence classifications and roles + +Not every residual dependency is trusted, and not all missing evidence means +the same thing. At minimum, the design must preserve distinctions among: + +- an axiom representing genuinely external semantics; +- a primitive-semantic assumption, with its actual origin and owner; +- a specification whose proof is intended but not complete; +- a call site temporarily justified by a prose `// SAFETY:` explanation; +- source or generated code intentionally outside the adoption scope; +- a construct modeled only through an opaque specification; +- an unsupported, skipped, or unmodeled construct; +- an admitted Lean theorem or equivalent escape hatch; +- a compiler, translator, kernel, runtime, or hardware assumption; and +- a model/tool failure that prevents any defensible claim. + +These may share implementation machinery, but users need to know each item's +status, role, owner, effect on the claim, and any known path to stronger +evidence. An approved semantic assumption may condition a claim; an incomplete +proof remains an unresolved proof obligation; a coverage or tool failure may +instead block the claim. The canonical definitions are in +[result and trust](../result-and-trust.md#what-trust-means). + +## Questions to resolve + +### What does command success mean? + +Candidate policies include: + +- `cargo anneal verify` fails on every incomplete proof but permits explicitly + approved axioms with a ledger; +- a separate incremental profile succeeds with a clearly conditional status; +- one command returns structured assurance grades and uses configurable exit + policy; or +- distinct commands separate end-to-end verification from partial checking. + +The design must avoid a state in which users or CI interpret “success” as a +stronger claim than the artifact supports. Open details include: + +- whether unrequested property kinds appear as gaps; +- whether a deliberately excluded dependency is an error or audited + assumption; +- how policy changes affect reproducibility; +- whether CI can impose a budget or allowlist for trust gaps; and +- what downstream crates may rely on from a partially verified dependency. + +### How are incomplete proofs represented? + +An explicit “specification present, proof incomplete” status may be preferable +to encoding incremental work as an axiom. It is analogous in purpose to Lean's +`sorry`, but V2 need not expose that mechanism directly. + +Questions include: + +- Is incompleteness attached to a theorem, obligation, source region, property + kind, or artifact? +- Does it preserve the intended specification for callers while marking its + proof as incomplete rather than established? +- Can a project prohibit new gaps while grandfathering existing ones? +- Does each gap have an owner, rationale, expiration, or issue link? +- How are transitive gaps summarized without losing their origin? +- Can a proof later replace the gap without changing the public verification + interface? + +### How do prose justifications work? + +Prose support is required, and a prose justification must remain +distinguishable from checked evidence. Its representation and binding semantics +are open: it could be a specialized admitted obligation associated with a Rust +unsafe boundary, a separate evidence status or provenance field, or another +form with Rust-oriented diagnostics. + +We need to decide: + +- which comments Anneal recognizes and how they bind to exact compiler-resolved + operations; +- whether prose is allowed only for soundness or for arbitrary property kinds; +- whether the text is included verbatim or hashed in the ledger; +- whether ordinary Rust linting conventions are reused; +- what happens when code moves and a comment no longer binds unambiguously; and +- how the tool guides conversion from prose to a machine proof. + +This intersects with [Rust safety integration](rust-safety-integration.md). + +### Who authors and distributes external specifications? + +For FFI and platform APIs, the specification might be authored by: + +- each Rust consumer; +- the C, C++, assembly, or platform library author; +- an Anneal standard-library or ecosystem package; or +- a third-party verification authority. + +V2 needs identities, versioning, target constraints, and a way to distinguish a +widely reviewed specification from a local assertion without turning social +trust into a misleading proof claim. Specifications may eventually be backed +by proofs in another system or by a formal ISA model. + +### What exactly is in the ledger? + +The canonical minimum inventory is defined in +[result and trust](../result-and-trust.md#audit-ledger). The remaining questions +concern representation and policy: exact field schema and granularity, stable +identities, normalization and reproducibility, serialization, signing, diff +format, retention and privacy, and the relationship between a machine-readable +artifact and a useful human audit view. + +## Evaluation criteria + +An acceptable design must be fail-closed about unsupported semantics, preserve +the difference between proof and trust, support useful incremental adoption, +and make transitive assumptions visible. It should reward shrinking the TCB, +allow external models to replace axioms, and produce stable artifacts suitable +for code review and CI policy. + +Useful experiments include a crate with one prose-justified call, a dependency +with an incomplete functional proof, an FFI binding with target-specific +axioms, and the same code verified under two compiler or hardware assumptions. +The source coverage needed to populate the ledger is discussed in +[source/model adequacy](source-model-adequacy.md). diff --git a/anneal/agent_docs/design/principles.md b/anneal/agent_docs/design/principles.md new file mode 100644 index 0000000000..b21f97b1a0 --- /dev/null +++ b/anneal/agent_docs/design/principles.md @@ -0,0 +1,144 @@ + + +# Design principles + +This page states Anneal's goals and the choice rules used when +[settled requirements](settled-requirements.md) and +[accepted decisions](decisions/README.md) do not determine an answer. Technical +mechanisms belong in the linked design documents; agent procedure belongs in +[`AGENTS.md`](../../AGENTS.md). + +## Goals and scope + +Anneal's long-term scope includes arbitrarily subtle correctness properties and +arbitrary Rust codebases. That scope guides the architecture; it does not +require the current executable or first milestone to support every Rust +construct or property domain. + +Unsafe Rust leaves soundness obligations which Rust's type system does not +discharge. Giving them rigorous, practical treatment is Anneal's foundational +use case, especially in unsafe-heavy systems code. Anneal must nevertheless +support functional correctness, protocols, panic freedom, resource bounds, and +other user-defined properties in the same framework. + +Anneal is intended for ordinary Rust organizations. Specialists and AI agents +may assist, but Rust engineers must be able to understand, debug, review, and +incrementally adopt the result. + +## Protect soundness and the meaning of every reported claim + +A theorem about modeled behavior supports the corresponding Rust claim only +insofar as the covered Rust behavior remains defined. Correspondence over an +execution prefix may help prove this condition, but soundness is still a +prerequisite for interpreting a model theorem as a fact about the Rust artifact. + +Anneal must account for every operation which can cause undefined behavior +under the supported Rust semantics and generate adequate obligations for it. A +weak, incomplete, or vacuous user specification cannot remove those +obligations. Unsupported semantics, missing coverage, and insufficient evidence +must narrow, condition, or prevent the reported claim instead of disappearing +behind a successful proof check. + +For a user-defined property, Anneal can enforce a precise definition but cannot +infer whether it captures the user's intent. A specification claiming +correspondence with Rust or another external semantics is different: its +adequacy is a claim-relevant dependency which must be justified or reported. +See the [verification model](verification-model.md) and +[result and trust](result-and-trust.md). + +## Prove abstraction boundaries once, then compose them + +An implementation becomes reusable through an abstraction boundary only after +it has been proved to satisfy the declared interface. That interface must +account for every claim-relevant interaction with the context, including +capabilities or resources exchanged across it. For soundness, a reusable safe +API must remain sound under every type-correct safe use admitted by that API +within the stated semantic and compilation envelope. + +Clients may then use the established guarantees without reopening the private +implementation. They may interact through declared mutation, allocation, I/O, +nondeterminism, or other effects, but neither side may impose undeclared +requirements on the other. This interface-relative isolation—not absence of +interaction—is the locality Anneal seeks. See +[contextual refinement](verification-model.md#contextual-refinement). + +## Introduce only the semantic machinery the claim requires + +A pure value-level contract is preferred when it fully describes the +abstraction boundary. Resource, provenance, initialization, ownership, +concurrency, protocol, or effect semantics are required whenever ignoring them +could invalidate the claim. + +For example, a freely reusable Lean fact cannot by itself authorize exclusive +mutation: two consumers could each cite it to justify access. Whether enforced +by a resource proposition, a state- or world-indexed weakest precondition, a +monadic discipline, or another mechanism, the proof interface must retain the +permission's usage rules. + +This yields a hybrid model without making “hybrid” a goal. The boundary between +functional translation and richer semantics evolves with Aeneas and Charon. +Revision-sensitive facts belong in the +[Aeneas and Charon reference](../reference/aeneas-and-charon.md); unresolved +architecture belongs in +[memory, resources, and effects](open-questions/memory-resources-and-effects.md). + +## Reuse general proof machinery after deriving the right Rust obligation + +Rust semantics determine the obligations. Once an obligation becomes a general +proof problem, Anneal favors proof abstractions maintained by Lean and +Aeneas—and compiler-resolved interfaces maintained by Charon—over parallel +special-purpose mechanisms. + +For example, `index < slice.len()` is one arithmetic sub-obligation among the +guards for `slice.get_unchecked(index)`. Aeneas's weakest-precondition +machinery and Lean's arithmetic support can derive that inequality from a +preceding bounds check; a separate Anneal bounds logic would duplicate rather +than improve the semantics. Other unsafe operations may additionally require +provenance, liveness, initialization, authority, or similar conditions which +need richer machinery; a concrete mismatch of that kind justifies extending +the underlying abstraction. + +This example does not decide how obligations are encoded. Interface and +upstreaming choices remain in the +[Aeneas and Charon integration discussion](open-questions/aeneas-charon-integration.md). + +## Expose and reduce every trusted dependency + +A dependency is trusted for a claim when the claim relies on its correctness +but the included checked evidence does not establish it at the claimed semantic +endpoint. Translation into Lean does not itself remove trust. + +Semantic assumptions, extraction and correspondence, tool correctness, +pipeline integration, and execution-platform assumptions play different roles +and must remain distinguishable. An incomplete proof is an unresolved +obligation, not an approved semantic boundary; coverage gaps and tool failures +may narrow or block a claim rather than become assumptions. + +Trusted dependencies must be visible and replaceable by stronger evidence. +Moving one into a helper or upstream component does not reduce trust unless the +end-to-end dependency is removed. Definitions, classifications, and audit +requirements live in [result and trust](result-and-trust.md). + +## Make partial adoption useful without calling it complete + +Anneal must support incremental adoption, including selected prose +`// SAFETY:` justifications and a distinct representation of unfinished +proofs. Neither may silently become checked evidence. Results must identify what +was checked, what remains, which claim the evidence supports, and what would +strengthen it. + +Among designs preserving those distinctions, this principle favors stable +proof interfaces, source-linked diagnostics, resilience to small changes, and +feedback useful to ordinary Rust engineers. + +## Relationship among the principles + +Soundness and claim integrity constrain acceptable designs. Among those +designs, simplicity, reuse, trust reduction, coverage, maintenance, and user +experience require case-specific judgment rather than a permanent total order. diff --git a/anneal/agent_docs/design/result-and-trust.md b/anneal/agent_docs/design/result-and-trust.md new file mode 100644 index 0000000000..5b62158340 --- /dev/null +++ b/anneal/agent_docs/design/result-and-trust.md @@ -0,0 +1,374 @@ + + +# Result and trust + +This document defines what identifies an Anneal result, which dimensions its +claim must state, how checked evidence differs from residual dependencies, and +what users must be able to audit. The +[verification model](verification-model.md) explains how local obligations +compose to produce the result. + +Anneal does not eliminate trust by translating Rust into Lean. It must instead +report exactly which claim its evidence supports and expose every dependency +needed to connect that evidence to the claim. The exact serialization, command +profiles, and exit-status policy remain open; see +[trust and incremental adoption](open-questions/trust-and-incremental-adoption.md). + +## What trust means + +A dependency is trusted for a reported claim when that claim relies on the +dependency's correctness but the result's included checked evidence does not +establish it at the claimed semantic endpoint. Trust is therefore relative to +a particular result and claim, not an intrinsic label on a component. A +compiler may be irrelevant to a theorem stated only about a Lean model but +essential to a claim about the binary it produces. + +Different trusted dependencies play different roles. A semantic assumption +says what an operation or environment means. Tool trust says that a component +handled its inputs and semantics correctly. Integration or correspondence +trust says that the components, identities, and artifacts are connected to the +same compilation subject correctly. Execution-substrate trust connects a Rust +or binary claim to the platform and hardware on which it runs. + +An incomplete proof and an unchecked admission have distinct statuses, but +neither includes checked evidence for the required argument. An incomplete +proof records unresolved proof work; an unchecked admission is an escape hatch. +Neither becomes an intentional semantic boundary merely because Lean can +represent both using axiom-like declarations. Likewise, a coverage gap or tool +failure may narrow or block a claim instead of becoming an assumption the claim +is permitted to rely on. **Residual dependency** is therefore broader than +**trusted dependency**. + +## Compilation-subject and result identity + +Anneal's initial subject is one concrete Cargo compilation artifact. Here, +“compilation artifact” means the compiler-resolved Rust program and semantic +configuration selected for verification, not the native executable, library +archive, or other file emitted by Cargo. “Compilation subject” is the preferred +phrase when that distinction matters. + +The compilation subject changes whenever an input can change the Rust program +presented to Charon or its source-level semantics. Rust emitted by procedural +macros or build machinery and then ingested by Charon is part of that subject. +It is analyzed like checked-in Rust rather than trusted merely because a +generator produced it. This does not make execution of the generator itself +part of the proved Rust program, nor does it verify every output that the +generator could produce. + +A verification result has a separate identity. It combines the compilation +subject with a precise claim, checked evidence, residual dependencies, and the +models and tools that produced or checked them. Changing a proof, property +selection, translator, semantics, or trusted specification can therefore +produce a different result about the same compilation subject. + +Classify an input by what it can change: + +- an input that changes the compiler-resolved Rust program or its source + semantics participates in compilation-subject identity; +- an input that changes extraction, modeling, obligations, evidence, selected + claim, or assumptions participates in verification-result identity; and +- an input that changes only the relationship between the Rust subject and a + binary execution qualifies that execution claim. + +One component may have several roles. A rustc revision, for example, can +change the compilation subject and also remain part of the end-to-end TCB. +Classify the actual dependency rather than permanently assigning each tool to +one bucket. + +Verification across targets, features, panic strategies, dependency versions, +or other build-matrix cells initially produces multiple subject-specific +results. Tooling may group and deduplicate them, but must identify which cells +were checked, omitted, or assumed. A prior result must not be silently +generalized to a related subject. + +## Claim dimensions + +“Verified” is incomplete unless the result states independent dimensions of +the claim: + +### Property and guarantee + +The result identifies each asserted property, the guarantee established for +it, and every dependency on another property or assumption. Rust soundness is +foundational; selecting another property does not silently remove the +soundness basis needed to interpret the model as Rust. + +### Behavior + +The result identifies which source behaviors the guarantee covers. Normal +return, panic and unwind, abort, and divergence can support different +obligations. A soundness result does not imply panic freedom or termination +unless those guarantees are also stated and proved. + +### Client and coverage envelope + +The result identifies the items, paths, call sites, generic instances, and +clients covered. Proving all concrete uses in one compilation subject is +different from proving contextual refinement for every type-correct safe use +admitted by an API within a stated envelope. Omitted or unsupported coverage +may not be inferred to have been checked. + +### Semantic endpoint + +The result identifies the semantic object about which it makes a claim. A +Lean-kernel theorem concerns declarations in a Lean environment. A claim about +the Rust compilation subject additionally depends on adequate extraction, +translation, obligation generation, and Rust semantics. A claim about a +compiled binary executing on target hardware additionally depends on the +compiler, linker, ABI, platform, and hardware connection. + +These endpoints and the other dimensions are not one linear hierarchy. A +result can strengthen its client quantification without extending to target +hardware, or add panic freedom without changing its semantic endpoint. Every +reported result must state the actual combination rather than present one +ambiguous assurance grade. + +### Conditions + +The claim is relative to every explicit semantic, environmental, and trusted +dependency that remains after proof checking. A condition can make a result +useful without making it unconditional. Its type and consequences must remain +visible. + +## Evidence and residual dependencies + +Every evidence-graph leaf, proof obligation, and coverage boundary needs enough +classification for users and tools to distinguish its evidence, its role in +the claim, and its coverage or modeling disposition. These dimensions are +independent even if the eventual report schema represents them differently. + +### Evidence classification + +The following distinctions must remain observable. They need not become +mutually exclusive values in one status enum: the eventual schema may use +statuses, subtypes, provenance fields, or several of these. + +- **Checked evidence:** Lean or another accepted checker established the + obligation relative to that checker's environment and assumptions. +- **Approved assumption:** an explicit premise is permitted to condition the + reported claim without evidence establishing it in this result. Its role, + origin, owner, and effect on the claim must be recorded. A specialized form, + such as a prose justification, retains that more specific provenance. +- **Incomplete proof:** a formal obligation is intentionally recorded as work + whose checked evidence has not yet been completed. +- **Unchecked admission or escape hatch:** an admitted theorem, unclassified + axiom, or equivalent bypass supplies no ordinary checked evidence. It must be + detected and reported separately from intentional incompleteness and approved + semantic boundaries. +- **Prose justification:** a human safety argument is relied upon for + incremental adoption in place of a machine-checked proof. Whether the schema + represents it as a specialized admission, a distinct status, or another form + remains open, but the result must preserve the distinction. +- **Tool or model failure:** verification could not establish the requested + result. This is not a source-program outcome and supplies no evidence that + the source property holds or fails. + +### Coverage and modeling disposition + +- **Body or path selected for analysis:** the named implementation body or path + participates in the evidence graph. Its operations still require supported + semantics and adequate obligations. +- **Specification-only or opaque body:** clients use a specification without + analyzing the implementation body at this endpoint. This can be a legitimate + trusted semantic leaf rather than a coverage gap; the specification still + needs its own evidence classification and role in the claim. +- **Outside the selected adoption or coverage scope:** a named item, path, or + generated input is deliberately excluded from the claimed envelope. +- **In scope but uncovered or unreconciled:** an item, path, call, or operation + belongs to the claimed envelope but is absent from or not connected to the + evidence graph despite having supported semantics. This is a coverage + adequacy failure, not an intentional scope boundary. +- **Semantics represented:** the checked model assigns semantics and generates + obligations for the operations relevant to the claim. Whether that + representation and those obligations are adequate is classified separately + as a claim-relevant dependency. +- **Unsupported or unmodeled:** a relevant construct or semantics is not + represented by the checked model. The result must identify the claim this + prevents, narrows, or conditions. + +Coverage and modeling are themselves distinct: selecting a body does not prove +that every operation in it is modeled. A result may therefore need more than +one of these classifications. + +### Role in the claim + +- **Program-proof obligation:** the result must establish a primitive guard, + callee precondition, invariant, contract guarantee, or property-dependency + edge for the analyzed program. Incompleteness, prose, or an admission changes + its evidence treatment, not this role. +- **Program-semantic dependency:** the result relies on the specified meaning + of a primitive Rust or standard-library operation, FFI, assembly, an ISA, a + platform interface, or another part of the modeled program. Its origin and + owner must be recorded. Anneal is responsible for the adequacy of + specifications it provides and for exposing the adequacy dependency of any + specification it imports. +- **User-property dependency:** a user-authored proposition is used without + proof. Merely defining the intended meaning of a user property is part of the + claim, not a residual dependency; assuming that the program satisfies it is. + Anneal cannot infer whether the definition captures the user's intent. +- **Correspondence or integration dependency:** the result relies on source + extraction, translation, obligation coverage, identity reconciliation, or + composition connecting the same compilation subject correctly across stages. +- **Tool-implementation dependency:** the result relies on Anneal, Charon, + Aeneas, Lean, rustc, LLVM, a linker, or another tool behaving correctly. +- **Execution-substrate dependency:** the result relies on an ABI, allocator, + operating system, firmware, external service, foreign library, hardware + behavior, or another condition not established by the program proof. + +The final names and machine representation are open. Their semantic +distinctions are not. In particular, an approved semantic assumption, an +unfinished proof, an unchecked admission, a prose argument, an opaque body, a +coverage gap, and a tool failure support different conclusions. One item can +carry classifications in several dimensions, and its role alone does not +determine whether it allows, conditions, narrows, or blocks a result. + +An undischarged obligation means that Anneal has not established the requested +claim. It is a useful diagnostic about missing evidence, not by itself a proof +that the Rust source violates the property. + +## Two trust boundaries + +Evidence classification and role do not replace the need to distinguish the +scope of a trust boundary. Anneal must distinguish: + +- the **trusted semantic leaf boundary**: program operations whose behavior or + soundness requirements are specified axiomatically instead of proved from a + deeper model; and +- the **end-to-end TCB**: every implementation and assumption needed to connect + checked evidence to the claimed Rust or target execution. + +The initial semantic leaf boundary may include raw-pointer operations, unsafe +standard-library functions, intrinsics, assembly, FFI, and platform operations +whose behavior is exposed through axiomatic specifications rather than derived +from a deeper implementation model. Anneal's standard library will likely use +the same axiom facility to specify some of these leaves. Anneal's authors are +responsible for the adequacy of Anneal-provided soundness specifications. + +The end-to-end TCB is much broader. Depending on the semantic endpoint, it can +include Anneal's obligation generation, Charon's extraction, Aeneas's +translation and semantics, Lean's kernel and proof environment, rustc, LLVM, +linking, the interpretation of Rust and target specifications, operating +systems or firmware, foreign libraries, and host and target hardware. + +A small program-semantic leaf boundary does not make this broader TCB small. +The audit ledger must make clear which boundary a change actually shrinks. + +## Audit ledger + +Every result must expose enough information to reproduce its subject, interpret +its claim, inspect its evidence, and audit its residual dependencies. At +minimum, the ledger records: + +### Claim and scope + +- the property guarantees asserted and their dependencies; +- covered behaviors and exceptional outcomes; +- the client, item, path, call-site, generic-instance, and other coverage + boundaries; and +- the semantic endpoint and every condition needed to reach it. + +### Evidence and gaps + +- each obligation and dependency, with its evidence classification, role, and + any applicable coverage or modeling disposition; +- every trusted semantic leaf and axiom, including origin, owner, and affected + properties; +- every incomplete or admitted proof and every prose justification, with a + source location where applicable; +- every specification-only or opaque body, with the corresponding + specification and evidence classification; +- every skipped, unsupported, or unmodeled item or operation; +- coverage gaps and the claims they prevent or condition; and +- dependencies among proofs, properties, specifications, and assumptions. + +### Compilation subject + +- workspace, package, Cargo target, target kind, profile, source revision or + other source identity; +- dependency resolution and enabled features; +- target triple or specification, `cfg` values, panic strategy, overflow + behavior, and other source-semantics-relevant compiler options; +- generated Rust, proc-macro expansion, build outputs, and included files that + form the compiler-resolved subject; and +- relevant environment variables, build-script directives, ABI, and other + inputs or assumptions affecting that subject. + +### Verification evidence and toolchain + +- Anneal, Aeneas, and Charon versions or revisions, options, models, + obligations, and downstream semantic patches; +- rustc, the Rust standard library, LLVM, linking tools, and relevant compiler + options; +- the LLBC, Lean declarations, checked proof artifacts, Lean, Lake, Mathlib, + and other proof-library versions needed to identify or reproduce the + evidence; and +- proof, extraction, translation, and reporting configuration not already + captured by the compilation subject. + +### Execution substrate + +- operating-system, firmware, external-service, foreign-library, and platform + assumptions relevant to the claim; +- the host hardware or trusted execution environment on which verification ran + to the degree needed to reproduce or assess the result; and +- the target hardware model or concrete implementation assumed by any binary + execution claim. + +If a result stops at a model or Rust-subject claim, the ledger states that +binary and target-hardware execution are outside the claim rather than implying +they were verified. Host hardware running the checker may nevertheless remain +in the practical TCB. + +Recording a field does not necessarily make it part of a future canonical +content identifier. Defining normalization, semantic equivalence, provenance, +signatures, storage formats, and the useful granularity of hardware identity +remains open. The ledger may not omit a claim-relevant dependency merely +because its canonical encoding is unsettled. + +## Fail-closed reporting + +“Fail closed” means Anneal must never silently report a claim stronger than its +evidence supports. It does not predetermine one command name or exit-code +policy. + +An approved external axiom may be compatible with a successful result whose +claim is explicitly relative to that axiom. An explicitly named incremental +mode may report a weaker conditional result with incomplete proofs or prose +justifications. Whatever policy is chosen: + +- every successful exit has a documented meaning; +- conditional or incomplete evidence is not labeled as an unconditional + verification result; +- omitted coverage remains visible and qualifies the claim; +- property dependencies cannot be disabled without being discharged or + reported as conditions; +- model and tool failures are not treated as source behaviors; and +- users can compare results and ledgers as evidence or trust changes. + +The distinction between a strictly verified mode and conditionally checked +profiles remains open. Anneal must settle that user-facing policy explicitly +rather than allowing implementation accidents to define it. + +## Reducing trust over time + +Stable abstraction contracts should be separable from the current depth of +their implementation model. This permits, for example: + +- a primitive-operation axiom to be replaced by a proved Rust operational + model; +- an assembly axiom to be replaced by an ISA-level proof; +- an FFI specification to be connected to a verified foreign implementation; +- a compiler assumption to be replaced by translation validation or verified + compilation; or +- a hardware assumption to be connected to a formal hardware model. + +The replacement reduces trust only if it supports the same or a stronger +end-to-end claim and removes the old dependency from the ledger. Moving an +assumption into generated code, a helper whose body is not modeled, or another +project without proving it does not shrink the TCB. diff --git a/anneal/agent_docs/design/settled-requirements.md b/anneal/agent_docs/design/settled-requirements.md new file mode 100644 index 0000000000..f09e9edc1c --- /dev/null +++ b/anneal/agent_docs/design/settled-requirements.md @@ -0,0 +1,274 @@ + + +# Settled requirements + +These requirements record constraints that have been unambiguously accepted +for Anneal V2. They deliberately avoid choosing among implementations still +under discussion. Stable identifiers allow code, decisions, and open questions +to refer to the requirement they serve. They are an atomic registry, not a +second explanation of the design philosophy: use the +[design principles](principles.md) to understand why these constraints matter +and the linked design documents for their canonical elaboration. + +## Project scope + +### ANNEAL-REQ-001: V2 is a clean-room redesign + +V2 must be designed from its goals and current evidence rather than inheriting +V1 by default. V1 code and ideas may be reused deliberately. Their existence is +not evidence that V2 should preserve them. + +See [decision 0001](decisions/0001-v2-is-a-clean-room-redesign.md). + +### ANNEAL-REQ-002: General properties + +The long-term architecture must support arbitrarily complex, user-defined +correctness properties. It must not encode an assumption that soundness is the +only property worth verifying. + +### ANNEAL-REQ-003: General Rust use cases + +The long-term architecture must be capable of covering arbitrary Rust +codebases and use cases. Initial milestones may support a strict subset, but +the architecture must not require unsafe-heavy domains to abandon their +essential memory, concurrency, or external-effect semantics. + +### ANNEAL-REQ-004: Extensibility before breadth + +Initial V2 need not ship specialized backends for deadlock freedom, +hyperproperties, cryptographic correctness, quantitative bounds, +probabilistic properties, or every other anticipated domain. It must leave a +path to add such domains without redesigning the core composition model. + +## Soundness and semantic fidelity + +### ANNEAL-REQ-005: Soundness is foundational + +Anneal must treat Rust soundness as a non-optional foundation of a faithful +verification result. Selecting another property kind must not silently disable +the soundness assumptions on which source-to-model correspondence depends. + +### ANNEAL-REQ-006: Soundness specifications produce adequate obligations + +For every non-axiomatic source of Rust soundness requirements, Anneal must +derive obligations sufficient for the supported semantics. In particular, the +specification of a primitive operation must impose the actual conditions +needed for its sound use. A weak, incomplete, or vacuous user contract must not +be able to erase those conditions. + +This is **obligation adequacy**: it concerns whether the generated obligation +is the right one. The separate coverage requirement +[ANNEAL-REQ-019](#anneal-req-019-coverage-adequacy-is-enforced) concerns +whether every relevant operation actually receives and discharges such an +obligation. How Anneal establishes either property is open; see +[source/model adequacy](open-questions/source-model-adequacy.md). + +### ANNEAL-REQ-007: The memory model is conservative + +Any Anneal-owned abstraction of Rust memory and unsafe operations must be a +conservative formalization of the normative guarantees Anneal claims to +support, not merely an undocumented set of convenient assumptions. Ambiguous +prose in Rust's specifications must be surfaced, resolved with the relevant +Rust teams where possible, and recorded as an assumption until resolved. + +### ANNEAL-REQ-008: Resource semantics are preserved + +Lifting separation-logic assertions, ownership, provenance, initialization, or +protocol state into Lean must preserve their usage discipline. Such resources +must not become freely reusable ordinary facts when that would invalidate the +reported claim. In particular, simplifying their use may not compromise +soundness. + +## Local and global verification + +### ANNEAL-REQ-009: Local contract obligations + +Locally, verification must establish that an item's preconditions imply its +applicable guarantees and that every invocation satisfies the required +preconditions or properties of its callees. + +The exact treatment of normal return, panic, unwind, abort, and divergence is +open; this requirement does not imply that every verified item terminates +normally. + +### ANNEAL-REQ-010: Local results compose globally + +Local obligations must compose into a whole-artifact guarantee relative to an +explicit set of trusted leaves and environmental assumptions. A caller must not +need to re-verify the private body of a callee whose contract has been +established. + +### ANNEAL-REQ-011: Long-running and exceptional behavior + +The verification model must be able to express and justify: + +- a server that executes without bound while preserving invariants; +- code that remains sound while panicking; +- unwind cleanup relevant to soundness; and +- code that catches a panic and continues execution. + +Anneal must not equate successful verification with normal termination in all +cases. + +### ANNEAL-REQ-012: Property dependencies + +Property kinds may depend on one another. All supported properties ultimately +depend on soundness, and a soundness obligation may itself rely on a declared +guarantee of another property kind. For example, a callee's functional +guarantee that an index is in bounds may discharge a caller's raw-pointer +soundness obligation. The architecture must not isolate each property kind in +a universe that cannot express these dependencies. + +“Ultimately depend on soundness” describes the semantic basis needed to +interpret model theorems as claims about Rust; it does not impose an acyclic +proof order in which soundness is always established first. Mutually dependent +obligations must be discharged jointly or by another sound treatment of +cycles. The representation and proof mechanism remain open. + +### ANNEAL-REQ-013: Property selection + +Users must eventually be able to choose which non-foundational property kinds +are enforced by a `cargo anneal verify` invocation. Dependency closure, +reporting, command modes, and handling of excluded dependencies remain open. + +## Contracts and invariants + +### ANNEAL-REQ-014: Type invariants support arbitrary property kinds + +V2 must support invariants associated with types, and those invariants must not +be limited to soundness. The V1 `isValid` mechanism is not an acceptable +implementation because safe field mutation could bypass invariant +re-establishment. + +V2 may build on Rust unsafe fields, perform its own field-access and mutation +analysis, or use another sound mechanism. It need not wait for unsafe fields to +stabilize. + +### ANNEAL-REQ-015: Trait invariants support arbitrary property kinds + +V2 must support invariants associated with traits for arbitrary property +kinds. It must enforce the invariant at the implementation site and make the +established invariant available where the corresponding trait bound is known. + +The V1 spelling `isSafe` is not a commitment to syntax or to a soundness-only +meaning. + +### ANNEAL-REQ-016: Contracts distinguish semantic roles + +The architecture must be capable of distinguishing a caller obligation needed +for Rust soundness from an ordinary functional-domain precondition and from +obligations of other property kinds. Rust's single `unsafe` axis does not erase +these semantic differences, even if V2 initially maps several of them onto +existing Rust syntax. + +## Artifact and translation boundary + +### ANNEAL-REQ-017: Initial claims are artifact-scoped + +The initial unit of verification is one Cargo compilation artifact with fixed +target, features, `cfg` values, dependencies, panic strategy, generated code, +and relevant environment. A claim across a build matrix requires multiple +artifact claims or future quantified support. + +See [decision 0002](decisions/0002-verification-is-artifact-scoped.md). +The subject and result identities are elaborated in +[result and trust](result-and-trust.md). + +### ANNEAL-REQ-018: Generated Rust is analyzed input + +Rust code produced by proc macros and build machinery and subsequently ingested +by Charon must be treated as part of the artifact being analyzed, rather than +trusted merely because a generator produced it. This does not by itself settle +how to model every non-Rust effect of a build script. + +See [decision 0003](decisions/0003-expanded-generated-rust-is-input.md). + +### ANNEAL-REQ-019: Coverage adequacy is enforced + +Anneal must ensure that every potentially invalid source operation in the +reported coverage envelope is guarded by the obligations needed to justify +the model used for its proof. An operation, path, or relevant generated item +must not disappear from coverage merely because the obligation generator, +translator, or proof interface failed to represent it. + +This is **coverage adequacy**: it concerns complete application of obligations +whose semantic content is governed by +[ANNEAL-REQ-006](#anneal-req-006-soundness-specifications-produce-adequate-obligations). +The encoding and enforcement mechanism remain open; see +[source/model adequacy](open-questions/source-model-adequacy.md). + +## Trust and adoption + +### ANNEAL-REQ-020: The TCB is explicit and shrinkable + +V2 may rely on a small set of trusted leaves, but they must be explicit, +auditable, and replaceable by deeper models over time. Near-term examples may +include unsafe standard-library leaves, intrinsics, raw-pointer operations, +FFI, and assembly. + +See [decision 0006](decisions/0006-the-tcb-is-explicit-and-shrinkable.md). +The program-semantic leaf boundary and the broader end-to-end TCB are +distinguished in [result and trust](result-and-trust.md). + +### ANNEAL-REQ-021: Results carry an audit ledger + +Users must be able to determine what remains trusted or incomplete. The ledger +must identify the exact subject and claim, checked evidence, residual +dependencies, coverage limits, relevant toolchain, and host and target +assumptions. The canonical minimum inventory is maintained in +[result and trust](result-and-trust.md#audit-ledger); the exact schema and +presentation remain open. + +### ANNEAL-REQ-022: Incremental adoption supports prose + +Some form of existing prose safety justification must be able to stand in for +selected Anneal proofs during incremental adoption. Such a justification is +not a machine-checked proof and must be identifiable in the audit ledger. + +See [decision 0005](decisions/0005-incremental-adoption-supports-prose-justifications.md). +Its evidence classification is defined in +[result and trust](result-and-trust.md#evidence-and-residual-dependencies). + +### ANNEAL-REQ-023: Axioms cover genuine external semantics + +In a fully adopted codebase, axioms should be reserved for genuine semantic +boundaries. Anneal's own standard library will likely use the same facility to +specify primitive unsafe operations, and FFI will likely require user- or +library-authored axioms for the foreseeable future. Who should author and +distribute FFI specifications remains open. + +### ANNEAL-REQ-024: Incompleteness is distinguishable from external trust + +The design must be able to report a proof that is not yet completed separately +from a specification intentionally taken as axiomatic. It is open whether this +uses a first-class incomplete-proof mechanism, command profiles, Lean `sorry`, +or another representation. + +## Ecosystem and usability + +### ANNEAL-REQ-025: Existing proof machinery is preferred + +V2 should build on suitable abstractions maintained by Lean, Aeneas, and their +standard libraries, including WP specifications and tactics, unless a concrete +benefit justifies replacement. This is a strong engineering preference rather +than a prohibition on new machinery. + +### ANNEAL-REQ-026: Upstream evolution is in scope + +Changes or additions to Aeneas and Charon are in scope. In the longer term, +Rust language and specification changes are also in scope. Short-term +implementation constraints must not be documented as permanent project +philosophy. + +### ANNEAL-REQ-027: Ordinary Rust engineers are a target audience + +The eventual workflow must be usable in normal Rust organizations, including +by engineers without formal-methods specialization. Human and AI assistance, +diagnostics, stable proof interfaces, and gradual adoption may all contribute; +none may weaken the meaning or auditability of a successful result. diff --git a/anneal/agent_docs/design/verification-model.md b/anneal/agent_docs/design/verification-model.md new file mode 100644 index 0000000000..44bfdffb5a --- /dev/null +++ b/anneal/agent_docs/design/verification-model.md @@ -0,0 +1,239 @@ + + +# Verification model + +This document describes how Anneal derives a verification result. It does not +choose an annotation syntax, outcome taxonomy, Lean encoding, command mode, or +report format. Subject and result identity, evidence classifications, trust +boundaries, and the canonical audit-ledger contents are defined in +[result and trust](result-and-trust.md). + +The high-level derivation is: + +```text +identified Rust subject ++ requested claim dimensions ++ evaluated evidence graph with classified leaves += reported result stating the established claim and remaining gaps +``` + +Each term matters. Evidence about an unidentified program is not reproducible. +A theorem without a precise requested claim invites overinterpretation. Local +proofs without an evaluated evidence graph do not establish a global result. +Each leaf must be classified as checked evidence, an admissible assumption, a +coverage limit, or a blocking gap or failure; otherwise an axiom, omitted +operation, or unfinished proof disappears into hidden trust. The reported +result states which claim was actually established, any explicit conditions +on it, and why each residual dependency remains. + +The [worked byte-buffer example](worked-example.md) illustrates this model +without ratifying a concrete proof architecture. + +## Subject and claim + +Anneal initially verifies one concrete Cargo compilation subject. Generated +Rust ingested by Charon is part of that subject rather than trusted because a +proc macro or build process produced it. The exact target, features, `cfg` +values, dependencies, panic strategy, generated inputs, and relevant +environment delimit the program to which the result applies. + +A claim about that subject has several independent dimensions: + +- the properties and guarantees asserted, including their dependencies; +- the source behaviors to which those guarantees apply, such as normal return, + panic and unwind, abort, or divergence; +- the client and coverage envelope, from concrete calls in one artifact to + every type-correct safe use admitted by an API within a stated envelope; and +- the semantic endpoint, from a theorem about a Lean model through a claim + about the Rust artifact to execution of a compiled binary on target + hardware; and +- the explicit conditions on which the claim depends. + +These dimensions must not be collapsed into one ladder of “verification +levels.” A source-level soundness result need not establish panic freedom. A +whole-artifact proof of concrete calls need not justify every separately +compiled client. A theorem checked by Lean need not establish a hardware +execution claim without the correspondence and toolchain dependencies needed +to connect them. + +The reporting definition and identity of each dimension are maintained in +[result and trust](result-and-trust.md#claim-dimensions). This document +describes the proof structure from which they are derived. + +## Local obligations + +For an item `f` and a selected property `P`, the schematic local claim is: + +1. assume `f`'s applicable preconditions, invariants, capabilities, + environmental conditions, and already established interface guarantees; +2. show that every call and primitive operation made by `f` satisfies the + required preconditions and property dependencies of the invoked operation; + and +3. show that every behavior covered by the claim establishes the applicable + guarantee of `f`'s contract. + +For a normal-return functional contract, this specializes to the familiar +statement that preconditions imply postconditions. It does not imply that all +claims require normal termination. A soundness claim may permit divergence or +a safe panic while requiring every executed prefix and relevant unwind cleanup +to remain sound. + +The same shape applies when an interface carries resources or effects, but its +assumptions and guarantees are not necessarily freely reusable propositions. +Ownership, provenance, initialization, permissions, protocol state, or an +opened invariant may need to be consumed, transferred, framed, or +re-established. The local proof interface must retain those rules. Treating a +resource as an ordinary duplicable Lean fact would change the claim rather than +simplify its proof. + +Type and trait invariants participate in these local interfaces. A type +invariant must be established and preserved at every relevant operation; a +trait invariant must be established by an implementation and made available +where its trait bound is known. Their concrete enforcement and syntax remain +open in [contracts and invariants](open-questions/contracts-and-invariants.md). + +## Global closure + +Local results form an evidence graph: + +- nodes establish item contracts, invariants, primitive specifications, or + deeper semantic facts; +- call edges require callers to establish callees' preconditions; +- property-dependency edges allow one guarantee to discharge an obligation of + another property; and +- leaf edges end either in checked evidence or in an explicitly classified + residual dependency. + +For example, a callee's functional guarantee that an index is below a buffer +length may discharge the soundness precondition of a caller's raw-pointer +access. This does not make soundness optional; the functional theorem is an +edge in the proof that the pointer access remains sound. + +An artifact-level claim follows only when every relevant non-leaf node +satisfies its local contract, every required edge is discharged, coverage is +adequate, and each remaining assumption is admissible for and included in the +conditions of that claim. Mutually dependent properties may require joint +reasoning; the graph model does not impose an artificial proof order in which +every soundness theorem must be completed before any other property can +contribute to it. + +For Rust soundness, the conditions assigned to primitive unsafe leaves must be +adequate. Anneal is responsible for conditions it provides and must report the +adequacy dependency of any specification it imports. If the conditions are +sufficient for the supported Rust semantics and every use establishes them, +the local results compose into an artifact-level soundness claim relative to +the recorded semantic and environmental dependencies. + +Classification alone does not discharge a residual dependency. Its status +determines whether Anneal can establish the requested claim relative to an +admissible assumption, must narrow or condition the claim, or cannot establish +a substantive claim at all. A tool failure supplies no evidence, and some +coverage gaps prevent rather than merely qualify a claim. Command policy may +decide which established conditional results count as success; it may not turn +a missing evidentiary connection into an assumption. See [evidence and residual +dependencies](result-and-trust.md#evidence-and-residual-dependencies). + +## Adequacy closes the source/model loop + +Anneal intends to use a model that is promised to correspond to sound Rust in +order to prove that the Rust subject is sound. It cannot resolve this apparent +cycle by assuming soundness unchecked. + +A source-level soundness result therefore needs both complete, adequate guards +for potentially invalid source operations and a justified correspondence from +the guarded source execution to the proof model. It must not assume the very +whole-program soundness that the guards are intended to establish. The exact +correspondence theorem and proof decomposition remain open. + +One candidate is a guarded-prefix argument: establish correspondence before a +first invalid source operation, identify and guard every operation that could +be that first invalid operation, and use checked evidence for those guards to +rule such an operation out. A trace simulation, translation validation, or +another construction may establish the same required connection instead. See +[source/model adequacy](open-questions/source-model-adequacy.md). + +Whichever proof family is chosen, two different adequacy requirements are +involved: + +- **Obligation adequacy** means that each generated guard expresses the right + Rust validity conditions. Primitive semantics and other non-axiomatic + sources of Rust requirements must determine those conditions; a user + contract cannot weaken them away (ANNEAL-REQ-006 in the + [settled requirements](settled-requirements.md)). +- **Coverage adequacy** means that every potentially invalid operation in + scope actually receives such a guard and that missing operations, paths, or + generated items prevent an unconditional result + ([ANNEAL-REQ-019](settled-requirements.md#anneal-req-019-coverage-adequacy-is-enforced)). + +How the guards and correspondence evidence are encoded, proved, and divided +among rustc, Charon, Aeneas, and Anneal remains open. The semantic requirements +do not choose between Lean proof arguments, sidecar theorems, or another +enforcement mechanism. + +Specification adequacy differs for a property whose meaning is supplied by the +user. Anneal can check that the declared definition is propagated and +enforced, but cannot infer whether it expresses the user's intended business, +cryptographic, or protocol requirement. If a specification claims +correspondence with Rust, an FFI implementation, an ISA, or another external +semantics, its adequacy remains an explicit trust dependency. + +## Contextual refinement + +Whole-artifact composition and reusable abstraction verification make +different claims. + +A whole-artifact result may cover only the concrete call sites, generic +instances, and reachable behaviors in one compilation subject. This is useful, +but it does not by itself prove a library safe for every future downstream +client. + +A reusable safe-API soundness claim requires robust safety: the implementation +must remain sound under every type-correct safe use admitted by the API within +a stated semantic and compilation envelope. More generally, contextual +refinement requires every implementation behavior visible to a context in the +quantified envelope to be permitted by the declared interface. Clients in that +envelope may then rely on the interface without reopening the private +implementation. Rust's underlying requirement is broad—an API presented as +safe must be sound for every type-correct safe use—even when Anneal can +initially report only a narrower artifact result. + +Every interaction relevant to the claim must be mediated by the interface and +by the capabilities or resources transferred across it. The implementation may +not silently invalidate context-owned resources or impose caller obligations +which the interface does not declare. This is not literal absence of +interaction, and the relevant boundary need not be a lexical scope. Allocators, +I/O, atomics, locks, nondeterminism, and other domains may expose deliberate +effects through effectful or resource-aware interfaces. + +The abstraction boundary may still be a pure value-level contract when that is +faithful. Contextual refinement requires it to preserve every observation and +discipline relevant to the claim; it does not require all unsafe +implementations to masquerade as pure functions. + +How Anneal exports and checks quantified contracts across separate compilation +remains open. Until it can establish that stronger claim, it must report the +narrower client envelope rather than silently generalize one artifact's +evidence. + +## Behavior remains part of the claim + +Anneal must support programs that return, panic safely, unwind through +soundness-relevant cleanup, catch a panic and continue, or execute indefinitely +while preserving invariants on every finite prefix. Verification therefore +cannot always mean normal termination. + +The proof model must distinguish enough source behavior to justify the +selected claim. It does not yet decide whether soundness, panic freedom, +termination, and other distinctions are built-in property kinds, standard +policies, effects in a weakest-precondition model, or user-defined predicates. +See [property kinds and outcomes](open-questions/property-kinds-and-outcomes.md). + +A model or tool failure is not a source-program behavior. It may create an +unsupported or incomplete result, but may never be interpreted as evidence +that the source satisfies a property. diff --git a/anneal/agent_docs/design/worked-example.md b/anneal/agent_docs/design/worked-example.md new file mode 100644 index 0000000000..0be3657f36 --- /dev/null +++ b/anneal/agent_docs/design/worked-example.md @@ -0,0 +1,226 @@ + + +# Worked example: an unsafe-backed byte buffer + +This schematic example connects the concepts in the +[verification model](verification-model.md) and +[result and trust model](result-and-trust.md). It is a teaching example, not a +normative design: `ByteBuffer`, the primitive leaves, predicates, proof +decomposition, outcome names, and result presentation below are all +illustrative. Names such as `BufferInvariant`, `owns`, and +`AbstractSequence` are explanatory notation rather than proposed Anneal or Lean +syntax. Open choices remain governed by the +[open-question ledger](open-questions/README.md). + +## The example abstraction + +Imagine a crate with this safe interface: + +```rust +pub struct ByteBuffer { /* private raw-pointer representation */ } + +impl ByteBuffer { + pub fn replace(&mut self, index: usize, value: u8) -> u8; +} +``` + +Internally, `ByteBuffer` owns a heap allocation and records a raw pointer, +length, and capacity. A schematic implementation of `replace` is: + +1. if `index >= len`, panic before touching the raw allocation; +2. otherwise call an unsafe primitive that replaces the byte at + `ptr + index` and returns the previous byte; and +3. return the previous byte. + +This resembles an implementation using `ptr::replace`, but the example does +not assert that `ptr::replace` should be Anneal's actual semantic leaf. + +## The trusted primitive leaf + +Suppose the Anneal standard library currently treats a primitive operation +called `replace_byte` as a trusted leaf. Its safety guard requires that: + +- the pointer identifies a live allocation with suitable provenance; +- the selected address is in bounds and properly aligned; +- the selected byte is initialized; and +- the caller holds exclusive authority to write that byte. + +The leaf's illustrative resource transition is: + +```text +owns(allocation, bytes) * index < length(bytes) + -- replace_byte(index, value) --> +owns(allocation, bytes[index := value]) * old = bytes[index] +``` + +Here `*` is only mnemonic notation for resource composition. Crucially, +`owns(allocation, bytes)` is consumed and replaced by the operation. It is not +an ordinary hypothesis that a proof may duplicate and use for two writes. The +exact resource logic or monadic machinery needed to enforce this discipline is +an [open design question](open-questions/memory-resources-and-effects.md). + +The primitive specification is trusted only at this boundary. Anneal must +still generate and check the guard at each call, and the ledger must identify +the specification as trusted. A future lower-level memory model could prove +the same leaf contract and remove that trust without changing `ByteBuffer`'s +public contract. + +## The type invariant + +For an abstract byte sequence `xs`, imagine an internal invariant with two +components: + +```text +BufferInvariant(buffer, xs) := + there is one live allocation described by buffer.ptr/len/capacity + * buffer has the exclusive owns(allocation, bytes) capability + * buffer.len = length(xs) + * the initialized prefix of bytes equals xs +``` + +The ownership component is soundness-relevant and resource-bearing. The +sequence relation supports an illustrative user-defined property, +`AbstractSequence`. A final design might represent those components together +or separately; this example does not decide that question. + +Holding a mutable reference to a valid `ByteBuffer` allows its implementation +to open the invariant according to controlled rules. It does not give arbitrary +proof code two copies of `owns`. Before control leaves the method normally or +by unwinding through relevant cleanup, the invariant must be available in the +state required at that boundary. + +Constructors must establish the invariant. Mutating methods must transform and +restore it. Destruction must consume the allocation capability exactly once. +Checking only `replace` while leaving construction, other field mutation, or +destruction unguarded would not establish the abstraction's soundness. + +## Contracts at the safe boundary + +The safe method has **no caller obligation whose violation permits undefined +behavior**. Every type-correct safe caller may pass any `usize`. In particular, +`index < len` is not a Rust safety precondition of `replace`. + +The API can nevertheless have ordinary outcome and functional contracts. One +illustrative contract says: + +- if `index` is out of bounds, the method panics without changing the abstract + sequence; +- if the method returns normally, the return value is the old byte at + `index`, and the new abstract sequence differs only by storing `value` at + that index; and +- throughout either behavior, the method remains sound. + +A caller seeking a no-panic result could prove the ordinary condition +`index < len`. Failure to prove that condition means the no-panic claim is +unavailable; it must not turn the safe call into undefined behavior. Whether +Anneal presents no-panic as a built-in policy, a property kind, or some other +construct remains open. + +## Local proof obligations + +For this example, a single symbolic execution could produce the following +obligations. That shared execution is illustrative rather than a commitment to +the final proof architecture. + +### Entry + +- Obtain `BufferInvariant(buffer, xs)` under the rules for a mutable borrow. +- Retain exactly one allocation capability while the invariant is open. + +### Out-of-bounds branch + +- Show that no raw-memory primitive is invoked. +- Show that the buffer state still represents `xs`. +- Under the artifact's unwind strategy, show that cleanup observes all + invariants and capabilities needed for soundness. +- Establish the contract's panic behavior. No normal-return postcondition is + claimed on this branch. + +### In-bounds branch + +- Derive every `replace_byte` guard from the branch condition and the opened + invariant; a user-written postcondition cannot substitute for a missing + provenance, initialization, or ownership fact. +- Apply the leaf's resource transition once. +- Re-establish `BufferInvariant(buffer, xs[index := value])` with the returned + allocation capability. +- Establish that the returned byte equals `xs[index]`. + +### Exit + +- Restore the invariant before returning control to safe code. +- Record dependencies between the user-defined `AbstractSequence` result and + the foundational soundness proof. + +Whether these facts are enforced through propositional arguments in translated +Lean definitions, sidecar WP theorems, or another complete mechanism remains +[unresolved](open-questions/source-model-adequacy.md). The requirement is that +Anneal cannot omit the primitive guard or a relevant control-flow branch. + +## Panic and unwind + +Because the bounds check occurs before the invariant is opened for mutation, +the panic branch has an unchanged buffer. With an unwind panic strategy, the +model must nevertheless follow cleanup and destruction far enough to prove +that resources remain valid and are consumed correctly. A panic is a source +behavior, not a verifier failure. + +Thus this method may satisfy soundness for every index while failing a blanket +no-panic claim. If a particular artifact uses `panic=abort`, the artifact and +claim differ; a proof for the unwind build is not automatically a proof for the +abort build. The example says nothing about whether outcome distinctions +become first-class axes in the final interface. + +Long-running behavior does not arise in this method. A server loop would use +the same general compositional idea but prove invariant preservation over each +finite execution prefix rather than require normal termination. + +## Global composition + +Assume that the artifact also proves: + +- every constructor establishes `BufferInvariant`; +- every other safe operation preserves it for all type-correct uses; +- destruction consumes its allocation exactly once; +- every call to a trusted memory or allocation leaf establishes that leaf's + guard; and +- no relevant item or control-flow edge is skipped. + +The local results then compose. Safe clients can use `ByteBuffer` without +examining its raw-pointer implementation or proving `index < len` for +soundness. The artifact is sound relative to the recorded primitive +specifications, translation adequacy, toolchain, and environmental assumptions. +If `AbstractSequence` is selected, clients may also use the proved sequence +contract without reopening the representation. + +This composition does not prove that a trusted leaf specification matches +Rust, that a user chose a useful abstract sequence contract, or that an +unchecked artifact variant has the same behavior. Those are different trust, +specification-adequacy, and artifact-identity questions. + +## An illustrative result record + +One fictional result for this example might be summarized as follows. The +record is intentionally compact; it demonstrates how the subject, claim, +evidence, and residual dependencies fit together rather than proposing a +schema. + +| Part | Illustrative contents | +| --- | --- | +| Compilation subject | `byte-buffer 0.1.0` at revision `abc123`; target `x86_64-unknown-linux-gnu`; feature `checked-replace`; compiler-resolved `cfg`; locked dependencies; `panic=unwind`; expanded generated Rust; relevant environment inputs | +| Claim | Soundness for the covered artifact, plus `AbstractSequence` for normal returns from `replace`; no claim over other targets, features, panic strategies, dependency resolutions, or downstream clients | +| Checked evidence | Constructors establish `BufferInvariant`; relevant methods preserve it; destruction consumes the allocation once; every covered memory-leaf call establishes its guard; `replace` proves its sequence contract | +| Residual dependencies | Trusted allocation, deallocation, and `replace_byte` specifications; no user axioms, incomplete proofs, prose justifications, or unsupported coverage in this fictional run | +| Provenance | Exact Anneal, Charon, Aeneas, rustc, LLVM, Lean, and proof-library identities and options, plus relevant host and target platform assumptions | + +If an incremental run relied only on prose for the `replace_byte` call, that +fact would move into the residual dependencies rather than checked evidence. +The eventual command-status policy remains open, but the conditional +dependency could not disappear from the reported result. See the canonical +[result and trust model](result-and-trust.md). diff --git a/anneal/agent_docs/glossary.md b/anneal/agent_docs/glossary.md new file mode 100644 index 0000000000..612b85cfec --- /dev/null +++ b/anneal/agent_docs/glossary.md @@ -0,0 +1,247 @@ + + +# Glossary + +This glossary gives short definitions for vocabulary stable enough to use +throughout the Anneal design. Proposed taxonomies remain in +[open design questions](design/open-questions/README.md) until accepted. + +## Assurance and properties + +**Soundness** + +: A guarantee that the covered Rust behavior has no undefined behavior. A safe + abstraction is sound only when every type-correct safe use admitted by its API + within the stated semantic and compilation envelope preserves that guarantee; + checking selected clients supports a narrower artifact claim. Soundness is + also a prerequisite for Anneal's promised source-to-model correspondence. + +**Unsafe (Rust)** + +: Rust syntax which marks an operation as carrying obligations the compiler + does not check, or marks a region in which the programmer accepts + responsibility for such obligations. The presence of `unsafe` code does not + by itself mean that the program is unsound. + +**Unsound** + +: Capable of causing Rust undefined behavior in a use which the relevant API + presents as permitted. An implementation may use `unsafe` operations and + still be sound when it establishes every required condition. + +**Property** + +: A proposition Anneal is asked to establish about a program or its behavior. + Examples include soundness, panic freedom, functional correctness, protocol + conformance, and resource bounds. + +**Property kind** + +: A class of obligations which may have its own contracts, invariants, + dependencies, and selection policy. Soundness is necessarily special; which + other classes receive first-class treatment remains open. + +**User-defined correctness property** + +: A property's intended meaning as supplied by the user rather than fixed by + Rust. Anneal can check a precise specification, but cannot decide whether it + expresses what the user actually wanted. + +**Specification adequacy** + +: Whether a specification is strong and faithful enough to justify the claim + made from it. In particular, a weak user contract must not erase a Rust + soundness requirement which Anneal is responsible for generating. + +## Claims, contracts, and composition + +**Claim** + +: The precise statement a verification result supports. It identifies the + Rust subject, properties, relevant behavior, coverage, and assumptions; “this + program is verified” without those qualifications is not a complete claim. + +**Contract** + +: The requirements under which an item may be used and the guarantees it + provides. A contract may mention multiple properties or execution outcomes; + its final source syntax remains open. + +**Precondition / caller obligation** + +: A requirement which must hold before an operation. At a call site, the caller + must establish each selected precondition—for example, that a pointer refers + to live memory. Rust safety requirements and ordinary functional assumptions + may require different source-language treatment even when Lean represents + both as propositions. + +**Postcondition** + +: A guarantee established after an operation for a relevant outcome. Which + outcomes carry which guarantees is part of the open property/outcome design. + +**Local guarantee** + +: What is proved about one item: under its preconditions, it establishes its + postconditions and meets the preconditions of the operations it invokes. + +**Global guarantee** + +: A claim obtained by composing local guarantees across the covered program, + relative to its explicitly reported trusted leaves and environmental + assumptions. + +**Robust safety** + +: The implementation remains sound under every type-correct safe use admitted + by its API within an explicitly stated semantic and compilation envelope. + This is the soundness-specific instance of the broader + contextual-refinement requirement. A result covering only selected clients + or calls is an artifact-level claim, not robust safety. + +**Contextual refinement** + +: Every implementation behavior visible to a context in the claim's quantified + envelope is permitted by the declared interface, including its stated effects + and transferred resources. Clients can therefore use the interface without + inspecting the private body. Checking the calls present in one compiled + program is narrower than proving this property for every client in such an + envelope; a result must say which claim it establishes. + +## Invariants and resources + +**Invariant** + +: A condition attached to a program abstraction which its rules require at + specified boundaries. Those rules determine when the condition may be + assumed, opened, consumed, and re-established. + +**Type invariant** + +: An invariant associated with values or storage of a type. Type invariants may + express arbitrary property kinds, and their enforcement must cover every + operation which can make the invariant relevant. + +**Trait invariant** + +: An invariant, of any property kind, which each relevant implementation of a + trait must establish and code with the corresponding trait bound may use. + +**Resource proposition** + +: A proposition whose proof carries ownership, permission, or another usage + discipline, so it cannot necessarily be copied or discarded like an + ordinary fact. Exclusive ownership and protocol state are common examples. + +**Capability** + +: Locally held authority or evidence which permits an operation, potentially + packaging facts about a heap, allocator, concurrency protocol, or external + environment. + +## Models, subjects, and evidence + +**Compilation subject (compilation artifact)** + +: One compiler-resolved Rust program with fixed target, features, `cfg` values, + dependencies, panic strategy, generated Rust, and relevant environment. It is + not a matrix of builds or merely Cargo's emitted native binary. + +**Verification result** + +: A reported claim about one compilation subject together with the checked + evidence and classified dependencies which support it. Proof-tool versions, + proofs, or assumptions can change the result without changing the Rust + subject. + +**Evidence graph** + +: The connected local contracts, proofs, call obligations, translation + evidence, and coverage evidence from which a result is composed. This is a + conceptual dependency graph; its concrete serialization remains open. + +**Residual dependency** + +: A dependency, gap, or failure which remains after Anneal evaluates the + evidence graph. It may condition or narrow a claim, or prevent Anneal from + establishing one. Examples include a trusted semantic leaf, an incomplete + proof, a prose justification, unsupported coverage, a toolchain dependency, + or an environmental assumption. Its status, role, and effect on the claim + must be explicit. + +**Source model** + +: The Lean-level semantics used to reason about the compiled Rust program. + Anneal must justify that this model is faithful for the claim it reports. + +**LLBC** + +: Charon's compiler-resolved, structured Rust intermediate representation. + Aeneas consumes LLBC, and current Anneal design work also trends toward + consuming it directly; the long-term integration boundary remains open. + +**Weakest-precondition specification (WP specification)** + +: A specification of what must hold before an operation so that a desired + continuation property holds afterward. Aeneas provides WP machinery and + tactics such as `step` and `step*` which Anneal should reuse when appropriate. + +## Trust and incompleteness + +**Trust** + +: A claim-relative reliance on a dependency whose correctness is not + established by checked evidence included in the result at the claimed + semantic endpoint. Trust can concern program semantics, correspondence and + integration, tool implementations, or the execution substrate. Not every + residual dependency is accepted as trust; some gaps and failures block a + claim instead. + +**Trusted computing base (TCB)** + +: Everything whose correctness must be assumed for a result to imply its + claim. This can include semantic leaves, translators, proof checking, + compilers, semantic assumptions, and relevant host and target hardware. + +**Trusted leaf** + +: An operation or external component whose specification is assumed rather + than proved from a body visible to the current verification. A deeper model + may replace that assumption later. + +**Axiom** + +: A proposition intentionally admitted without proof in the current model, + such as a specification of genuine external semantics or an Anneal-provided + primitive leaf. It must remain distinguishable in the result from an + unfinished proof or prose justification. Whether those categories share an + underlying Lean mechanism does not change their different meanings. + +**Incomplete proof** + +: A declared obligation whose proof has not yet been completed. It is different + from a claim that a proposition is inherently axiomatic. + +**Prose safety justification** + +: A human explanation, commonly a Rust `// SAFETY:` comment, accepted + temporarily in place of a selected formal proof during incremental adoption. + It is not a machine-checked theorem and must remain visible in the result. + +**Audit ledger** + +: The human- and machine-readable projection of the identities, residual + dependencies, coverage gaps, and trust assumptions which qualify a result. + Its exact schema remains open; its existence is required. + +**Fail closed** + +: Refuse to report a claim stronger than the available evidence supports. A + separately identified conditional or incremental result may still be useful + when every assumption and incomplete obligation is explicit. diff --git a/anneal/agent_docs/history/research-index.md b/anneal/agent_docs/history/research-index.md new file mode 100644 index 0000000000..1d213e1a5c --- /dev/null +++ b/anneal/agent_docs/history/research-index.md @@ -0,0 +1,184 @@ + + +# Anneal Research Index + +**Snapshot date: 2026-07-17.** + +Issues and pull requests record experiments, incomplete arguments, and views +which may already have changed. They are evidence and prompts for further +thinking, never normative design. Before relying on one, check its current +status, stack position, discussion, and diff. Normative constraints live in +[`agent_docs/design/`](../design/). + +## Coordinating issue + +[Tracking issue #3016](https://github.com/google/zerocopy/issues/3016) is the +best entry point into the work. At this snapshot it organizes work around: + +- making the implementation and infrastructure suitable for external + contribution; +- manually formalizing Rust from generated Lean in order to discover the + abstractions automation should eventually produce; +- separation-logic support in Aeneas; +- user-facing annotation and proof syntax; and +- internal code generation and organization. + +Its milestone and workstream wording is a planning snapshot, not a contract. +Use [principles](../design/principles.md), +[settled requirements](../design/settled-requirements.md), and the +[open-question index](../design/open-questions/README.md) to interpret it. + +## Open V2 and cross-cutting pull requests + +The following PRs were returned as open by a repository query on the snapshot +date. Many are GHerrit-managed stacked changes, so adjacent PRs can overlap or +be rebased together. Open status is not evidence that a change is absent from +this checkout: an open PR may be a stack ancestor or the current branch. +Inspect the tree and history to determine what is present, and use +[the current implementation state](../reference/current-state.md) for the +checked-in behavior rather than treating PR descriptions as authoritative. + +### Current orchestration and toolchain stack + +- [#3474: Compile merged util module at stack base](https://github.com/google/zerocopy/pull/3474) +- [#3463: Include Cargo in the exocrate Rust toolchain](https://github.com/google/zerocopy/pull/3463) +- [#3400: Add exocrate toolchain setup and `Toolchain` resolver](https://github.com/google/zerocopy/pull/3400) +- [#3401: Add Cargo workspace and target resolution](https://github.com/google/zerocopy/pull/3401) +- [#3402: Map workspace packages to `AnnealArtifact`s](https://github.com/google/zerocopy/pull/3402) +- [#3418: Add a pinned `charon_lib` dependency](https://github.com/google/zerocopy/pull/3418) +- [#3404: Add the Charon execution engine, `generate` CLI, and integration tests](https://github.com/google/zerocopy/pull/3404) +- [#3405: Chase out-of-tree dependencies for generation](https://github.com/google/zerocopy/pull/3405) +- [#3485: Prime Lake traces in one server traversal](https://github.com/google/zerocopy/pull/3485) +- [#3486: Audit cached Lean artifacts against clean builds](https://github.com/google/zerocopy/pull/3486) +- [#3403: Map compiler diagnostics back to Rust source](https://github.com/google/zerocopy/pull/3403) +- [#3436: Add an exocrate install-fixup hook](https://github.com/google/zerocopy/pull/3436) + +These changes are useful evidence about Cargo artifact identity, Charon +integration, diagnostics, cache portability, and the cost of dependency +chasing. They do not settle the long-term Anneal/Aeneas/Charon boundary. + +### Earlier or parallel V2 infrastructure + +- [#3361: Introduce Nix-based toolchain management](https://github.com/google/zerocopy/pull/3361) +- [#3362: Initial `setup` implementation](https://github.com/google/zerocopy/pull/3362) +- [#3378: Pass manifest and lockfile paths explicitly through exocrate](https://github.com/google/zerocopy/pull/3378) +- [#3487: Reorganize Anneal V1 and V2](https://github.com/google/zerocopy/pull/3487) + +Some effects of these proposals may already exist through different merged +commits. Read the current tree before assuming an open PR remains the intended +route. + +## Other open Anneal PRs: primarily V1 evidence + +These PRs were also open at the snapshot date. Most target the prototype or +pre-date the clean V2 boundary. They belong in historical research, not in the +V2 implementation plan by default. + +### Annotation, naming, and verification experiments + +- [#3478: Preserve module paths in generated function names](https://github.com/google/zerocopy/pull/3478) +- [#3477: Validate `isSafe` trait invariants while parsing](https://github.com/google/zerocopy/pull/3477) +- [#3321: Replace annotation syntax with verbatim Lean](https://github.com/google/zerocopy/pull/3321) +- [#3320: Translate and verify integration tests for the new syntax](https://github.com/google/zerocopy/pull/3320) +- [#3261: Add Lake package-management files](https://github.com/google/zerocopy/pull/3261) + +### Build, cache, and release experiments + +- [#3339: Remove redundant integration-test harness code](https://github.com/google/zerocopy/pull/3339) +- [#3338: Avoid copying Lean dependencies into the build directory](https://github.com/google/zerocopy/pull/3338) +- [#3337: Update integration tests for Nix](https://github.com/google/zerocopy/pull/3337) +- [#3336: Replace Docker with Nix](https://github.com/google/zerocopy/pull/3336) +- [#3335: WIP experimental changes](https://github.com/google/zerocopy/pull/3335) +- [#3334: WIP Docker-to-Nix conversion](https://github.com/google/zerocopy/pull/3334) +- [#3327: Download and install artifacts in parallel](https://github.com/google/zerocopy/pull/3327) +- [#3298: Adopt the Lake artifact cache and share workspace packages](https://github.com/google/zerocopy/pull/3298) +- [#3285: Overhaul release artifact and PR generation](https://github.com/google/zerocopy/pull/3285) +- [#3258: Roll prebuilts](https://github.com/google/zerocopy/pull/3258) +- [#3255: Reuse prebuilt Lean artifacts in generated workspaces](https://github.com/google/zerocopy/pull/3255) + +The repeated approaches are themselves evidence: setup latency, hermeticity, +Lake cache relocation, release provenance, and clean-build equivalence are +important engineering problems. No abandoned mechanism should be revived +solely because several old PRs explored it. + +## Issue map by research question + +The tracking discussion and repository issue search surfaced the following +threads. Open or closed status does not determine whether the underlying +question is settled. + +### Semantic coverage and artifact identity + +- [#3041: What about unreachable code?](https://github.com/google/zerocopy/issues/3041) + asks whether MIR elimination before Charon can hide code or produce a + misleading coverage claim. This is the concrete form of the question about + extraction before optimizations which assume or erase behavior. +- [#3017: Support `#[cfg]` in verification targets](https://github.com/google/zerocopy/issues/3017) + motivates making one resolved compilation artifact the source of truth. +- [#3350: Imported Rust types get unqualified names in generated specs](https://github.com/google/zerocopy/issues/3350) + illustrates the danger of a source-derived shadow naming system. + +### Trust, axioms, and proof completeness + +- [#3206: Ban axioms in non-axiom annotations](https://github.com/google/zerocopy/issues/3206) + is evidence for syntactic and semantic auditing of the Lean environment. +- [#3110: Visit all annotations and reject invalid locations](https://github.com/google/zerocopy/issues/3110) + concerns fail-closed coverage rather than only parsing supported locations. + +### Proof authoring and syntax + +- [#3201: Interactive proofs tracking](https://github.com/google/zerocopy/issues/3201) +- [#3062: Write Anneal specifications in Rust](https://github.com/google/zerocopy/issues/3062) +- [#3218: Make an entire annotation one Lean AST](https://github.com/google/zerocopy/issues/3218) +- [#3090: Pretty-print generated Lean](https://github.com/google/zerocopy/issues/3090) +- [#3086: Parse attributes with `chumsky`](https://github.com/google/zerocopy/issues/3086) +- [#3057: Support non-indented comments](https://github.com/google/zerocopy/issues/3057) + +These record syntax and tooling experiments. V2 has not selected a surface +language or proof location. + +### Hermeticity, packaging, and contributor workflow + +- [#3331: Manage all toolchain dependencies hermetically](https://github.com/google/zerocopy/issues/3331) +- [#3259: Build `Anneal.lean` independently in CI and locally](https://github.com/google/zerocopy/issues/3259) +- [#3256: Minimize dependencies of `Anneal.lean` and generated Lean](https://github.com/google/zerocopy/issues/3256) +- [#3266: Sanitize Cargo values used in Docker image creation](https://github.com/google/zerocopy/issues/3266) +- [#3420: Use standard base directories instead of polluting home directories](https://github.com/google/zerocopy/issues/3420) +- [#3060: Embed assets in the Anneal binary](https://github.com/google/zerocopy/issues/3060) + +Later Nix/exocrate work answers parts of these issues through different +mechanisms. Re-evaluate the underlying requirement rather than assuming the +issue's proposed implementation is still current. + +## Upstream reading + +- [Charon repository and documentation](https://github.com/AeneasVerif/charon) +- [Aeneas repository and documentation](https://github.com/AeneasVerif/aeneas) +- [Lean reference manual](https://lean-lang.org/doc/reference/latest/) +- [Rust Reference: behavior considered undefined](https://doc.rust-lang.org/reference/behavior-considered-undefined.html) +- [Rust Unsafe Code Guidelines reference](https://rust-lang.github.io/unsafe-code-guidelines/) +- [RustBelt project and papers](https://plv.mpi-sws.org/rustbelt/) + +Read the pinned Aeneas and Charon revisions used by `flake.nix`, not only their +latest default branches. Anneal collaborates with both projects, so upstream +changes are possible; current upstream behavior remains distinct from a +proposed extension. The concise Anneal-specific division of responsibility is +in [Aeneas and Charon](../reference/aeneas-and-charon.md). + +## Maintaining this index + +When refreshing the snapshot: + +1. record the new date; +2. query current issue and PR state rather than copying this list; +3. preserve historically useful links under an explicitly historical heading; +4. summarize what question a source informs, not what decision it allegedly + dictates; and +5. move a conclusion into normative documentation only after the project has + actually accepted it. diff --git a/anneal/agent_docs/history/v1-lessons.md b/anneal/agent_docs/history/v1-lessons.md new file mode 100644 index 0000000000..d5cddc906e --- /dev/null +++ b/anneal/agent_docs/history/v1-lessons.md @@ -0,0 +1,196 @@ + + +# Lessons from Anneal V1 + +Anneal V1 is the experimental prototype in `anneal/v1/`. V2 is a clean-room, +ground-up redesign. V1 supplies observations, examples, and failure modes; it +does not supply defaults. Copy V1 code or concepts only after showing that they +serve V2's principles and current requirements. + +This page deliberately distills lessons rather than preserving every +terminological clarification made while discussing V1. + +## What the prototype demonstrated + +### A useful end-to-end workflow is possible + +V1 connected Rust source, Charon, Aeneas, generated Lean, user-authored +contracts, and the Lean checker. It demonstrated that a Cargo-facing tool can +keep formal obligations close to Rust code, automate the toolchain, and map +many failures back into a workflow recognizable to Rust engineers. + +That validates the product direction, not V1's particular annotation grammar, +source scanner, generated theorem shapes, or directory layout. + +### Aeneas's functional model is a powerful default + +For supported Rust, Aeneas turns disciplined borrowing and mutation into +mathematical value transformations. V1 showed the ergonomic payoff of using +that model for ordinary control flow surrounding unsafe leaves. V2 should +build on maintained Aeneas and Lean abstractions where they remain faithful, +including weakest-precondition specifications and existing tactics. + +The lesson is not that every behavior must be forced into a pure transformer. +Resource and effect semantics must remain explicit whenever erasing them +could undermine soundness. + +### Local contracts can support global reasoning + +V1's precondition/postcondition structure exercised the intended composition: +a caller proves a callee's requirements, and a callee proves what it returns +or preserves. This remains the core local shape from which artifact-wide +soundness and user-defined properties should be assembled. + +V1 did not prove the complete adequacy theorem connecting arbitrary unsafe +Rust execution to its Lean model. That gap must remain visible rather than +being hidden by successful Lean compilation. + +## Designs which should **not** be inherited + +### Orthogonal progress and correctness duplicated work + +V1 split proofs into progress and correctness branches. The correctness branch +was conditional on successful execution, while the progress branch established +that execution succeeded. In practice: + +- the two proofs often repeated the same symbolic execution; +- the split did not compose naturally with Aeneas's WP specifications or its + `step` and `step*` tactics; and +- conditional correctness could be vacuous when progress was absent. + +V2 is leaning toward a shared Aeneas-style symbolic execution with obligations +for the relevant outcomes. The exact outcome/property architecture is still +open; V1 is evidence against making the orthogonal split the default, not a +decision that every verified function must terminate normally. + +Sound infinite execution, sound panic and unwind, and recovery after panic are +required use cases. + +### `isValid` was not enforced at mutation boundaries + +V1 attached an `isValid` predicate to a type and injected it into function +contracts, but safe Rust code could access or mutate invariant-carrying fields +without re-establishing the predicate. The mechanism was therefore knowingly +unsound and eventually required an explicit opt-in flag. + +V2 still requires type invariants for arbitrary property kinds. It must also +enforce opening and re-establishing them at every operation which can violate +them. Rust's proposed unsafe-fields feature could provide the language-level +boundary; Anneal-specific field access and mutation analysis is also in scope. +Waiting for upstream stabilization is not the only option. + +### `isSafe` expressed an architecture V1 did not enforce + +The durable idea behind V1's `isSafe` was that an unsafe trait implementation +establishes an invariant and generic code with the trait bound can consume it. +V1 documentation and implementation did not consistently enforce both halves. + +V2 requires trait invariants, like type invariants, to support arbitrary +property kinds. It must verify the invariant at every applicable +implementation site and expose exactly the corresponding assumption wherever +the trait bound is known. The final name and syntax are open. + +### Generated-Lean coupling was brittle + +V1 generated theorem signatures and proof scaffolding by predicting details +of Aeneas output: item names, tuple shapes, mutable-borrow returns, and special +treatment of `Unit` and `Never`. Small changes in Aeneas could require matching +Anneal changes or produce opaque Lean type errors. + +V2 should prefer compiler-resolved identities and maintained Aeneas interfaces +over textual surgery and a parallel source-derived ABI. This does not mandate +one interface: LLBC fields, library APIs, CLI modes, Lean syntax extensions, +and robust downstream adapters remain case-by-case options. + +### The source scanner could disagree with the compiler + +Parsing doc comments independently of rustc made it easy to lose the effects +of macro expansion, `cfg`, name resolution, aliases, and Cargo target +selection. A scanner can remain useful for source presentation, but semantic +claims must reconcile exactly with the compiler-selected artifact. + +### V1 annotation syntax is an experiment + +Indentation-sensitive Lean blocks in Rust documentation comments proved that +literate verification can work. They also exposed parser complexity, +formatting constraints, and leakage of Aeneas-generated names and proof +machinery. V2 is not committed to doc comments, indentation sensitivity, raw +Lean, Rust-like specifications, proof placement, or any V1 keyword. + +The audience requirement survives: ordinary Rust teams, including relatively +new engineers and engineers assisted by agents, must eventually be able to use +Anneal. Syntax should be judged by semantic clarity, diagnostics, +evolvability, and workflow—not resemblance to the prototype. + +## Trust-boundary lessons + +### `unsafe(axiom)` verified callers relative to a leaf + +V1's `unsafe(axiom)` made a function body opaque and trusted the user's +behavioral specification. It did not verify that body. This was useful for +composition and incremental coverage, but it left both the opaque +implementation and the specification in the TCB. + +V2's production objective is a small, explicit, shrinkable collection of +trusted leaves. Near-term leaves are expected to include some standard-library +unsafe operations and intrinsics, FFI, assembly, and raw-pointer operations. +Future formal ISA, foreign-language, or operational models may remove +individual leaves from the TCB. + +The syntax used for axioms is likely also needed by an Anneal standard library. +User-authored axioms will likely remain necessary for FFI for the foreseeable +future. It is open whether a foreign library author or each Rust consumer +normally owns those specifications. + +### Incomplete adoption needs a first-class status + +V1 supported `sorry`-based development and axiomatic boundaries, but a +production result needs to distinguish trusted external semantics from work +which is merely unfinished. Incremental adoption must also permit some +existing prose `// SAFETY:` justifications in place of formal proofs. + +Every such boundary must appear in an audit ledger. Whether an incomplete run +uses a separate command, profile, exit status, or result label remains open. + +### Specification adequacy cannot be delegated away + +Lean will prove a weak or vacuous specification. For Rust soundness, Anneal +must know or generate the required primitive safety obligations and ensure +that non-axiomatic specifications are adequate for them. Human review alone +is not an acceptable substitute for this mechanical guarantee. + +User-defined correctness properties have a different ground truth. Anneal can +ensure that a declared axiom is used and upheld, but cannot decide whether the +user chose the intended cryptographic, protocol, quantitative, or functional +property. + +## Product and reporting lessons + +- Rust's single `unsafe` axis is too coarse to express all property kinds. + Anneal must support dependencies among soundness and arbitrary additional + properties, while the degree of integration with Rust's existing unsafe + machinery remains open. +- Soundness is special and non-negotiable. Panic freedom, termination, + deadlock freedom, cryptographic correctness, resource bounds, and other + properties must not be silently conflated with it. +- A verification result needs an audit ledger. V1's opacity, axioms, `sorry`, + unsupported code, and toolchain assumptions were too easy to understand + only by inspecting implementation details. +- Diagnostics are part of semantic usability. When generated names or tuple + encodings leak into errors, users cannot reliably repair or review proofs. +- Incremental adoption is a core use case, but a partially checked result must + never masquerade as an unconditional one. + +## How to use V1 now + +Use `anneal/v1/` to reproduce experiments, inspect examples, and understand +why a V2 question exists. When citing it in a V2 design discussion, state the +observation rather than treating the V1 mechanism as precedent. If evidence +from V1 conflicts with [V2 principles](../design/principles.md) or +[settled requirements](../design/settled-requirements.md), V2 wins. diff --git a/anneal/agent_docs/reference/aeneas-and-charon.md b/anneal/agent_docs/reference/aeneas-and-charon.md new file mode 100644 index 0000000000..d58363fb5b --- /dev/null +++ b/anneal/agent_docs/reference/aeneas-and-charon.md @@ -0,0 +1,83 @@ + + +# Aeneas and Charon + +Anneal is being developed in collaboration with the +[Charon](https://github.com/AeneasVerif/charon) and +[Aeneas](https://github.com/AeneasVerif/aeneas) projects. This page records the +revision-sensitive facts coding agents need when working on that integration; +it does not assign future responsibilities among the projects. + +Upstream documentation and source are authoritative for a pinned release. This +page was last reviewed on 2026-07-17, when `anneal/flake.nix` selected Aeneas +release `nightly-2026.06.03`. Recheck the current pin before relying on a +supported construct, result type, or API described here. + +## Charon: compiler-integrated extraction + +Charon runs with the Rust compiler and exports Rust programs into LLBC (Low +Level Borrow Calculus), an intermediate representation designed for formal +reasoning. In the reviewed toolchain it provides compiler-resolved program +information after macro expansion and conditional compilation, including +resolved types, control flow, and source spans. + +Charon is not a verifier. Successful extraction means that it produced LLBC +for the selected compilation; it does not prove that the source is sound or +that a later Lean model is adequate. + +LLBC currently serves as Aeneas's semantic input. It can also provide +compiler-authoritative facts against which Anneal may reconcile annotations, +contracts, operations, calls, and diagnostics. Whether ordinary LLBC is +sufficient or another compiler-resolved interface is needed remains open. + +## Aeneas: functional translation and proof support + +Aeneas consumes LLBC and translates supported Rust into definitions in proof +assistants, including Lean. For a useful subset of Rust whose mutation follows +ordinary borrowing, this produces pure functional transformations rather than +an explicit whole-program heap model. Mutable-borrow translations carry the +information needed to reconstruct the final borrowed value. + +In the revision reviewed for this page, the Aeneas Lean library includes +weakest-precondition specifications and tactics such as `step` and `step*`. +Their exact interfaces and supported semantic envelope are revision-sensitive +and must be checked against the current pin. + +Aeneas does not by itself establish adequacy for every Rust program. Anneal's +questions about unsupported operations, resource-sensitive semantics, +exceptional execution, and the source-to-model correspondence are tracked in +the design documents linked below. + +## Checked-in Anneal integration + +The checked-in Anneal executable does not yet translate applications or verify +contracts. It currently packages and installs the pinned Aeneas distribution, +including the Aeneas and Charon executables and the Aeneas Lean libraries, and +constructs a Lean workspace which imports those libraries. See the +[current state](current-state.md) for the authoritative implementation summary. + +Anneal obtains the distribution from Aeneas's published +`nightly-2026.06.03` archive through `anneal/flake.nix`. The archive contains +Charon as part of the bundled toolchain; the checked-in setup does not maintain +a separate Charon pin. + +## Design questions owned elsewhere + +This reference intentionally does not decide what Charon should expose, what +semantics Aeneas should own, whether Anneal should extend either project, or +which downstream adaptations are acceptable. Those choices are governed by +the [project principles](../design/principles.md) and tracked in: + +- [Aeneas and Charon integration](../design/open-questions/aeneas-charon-integration.md); +- [source/model adequacy](../design/open-questions/source-model-adequacy.md); +- [memory, resources, and effects](../design/open-questions/memory-resources-and-effects.md); and +- [proof authoring and user experience](../design/open-questions/proof-authoring-and-user-experience.md). + +Experience from Anneal's earlier Aeneas integration is recorded separately in +[V1 lessons](../history/v1-lessons.md). diff --git a/anneal/agent_docs/reference/current-priorities.md b/anneal/agent_docs/reference/current-priorities.md new file mode 100644 index 0000000000..e8131d263f --- /dev/null +++ b/anneal/agent_docs/reference/current-priorities.md @@ -0,0 +1,108 @@ + + +# Current priorities + +**Snapshot date: 2026-07-17. This page is volatile and non-normative.** + +This page helps an agent apply Anneal's long-term principles to the early +implementation. It describes a useful near-term optimization lens, not an +accepted roadmap or a new ordering among the project's values. An explicit +task from the project authors, the [normative design](../README.md#normative-design), +and accepted decisions take precedence. + +## Starting point + +Read the dated [current implementation state](current-state.md) before planning +from this page. Establish what exists from the checked-in code and tests rather +than repeating a capability inventory here. + +Open and stacked pull requests explore nearby pipeline stages. They are useful +evidence about active engineering and integration pressure, but neither their +existence nor their implementation choices make them authoritative. Check the +current tree, tests, and history before claiming a capability has landed. The +[research index](../history/research-index.md) is only a dated navigation aid. + +## Near-term optimization lens + +Absent more specific direction, prefer work that turns the setup-only +foundation into a dependable, inspectable pipeline one reviewable stage at a +time. In practice, high-leverage work tends to have several of these +properties: + +- It makes the pinned Rust, Charon, Aeneas, Lean, and supporting artifacts + reproducible, installable, testable, or diagnosable. +- It connects a concrete Cargo compilation artifact to the next pipeline stage + through an explicit, machine-readable interface. +- It preserves compilation-subject identity and verification provenance rather + than relying on ambient state or an unreported default. +- It has a focused test that demonstrates the capability on the checked-in + branch and fails clearly when a dependency or stage is unavailable. +- It improves failure reporting and makes unsupported coverage or trust visible + rather than treating absence as success. +- It uses maintained Charon, Aeneas, Lean, Cargo, or rustc interfaces where + they fit, while keeping an upstream change in scope when that is the cleaner + long-term boundary. +- It is narrow enough to land without prematurely fixing an open semantic, + annotation, proof-authoring, or command-policy design. + +This lens favors durable enabling infrastructure over speculative completion +of the entire verifier. It does not make infrastructure more important than +soundness: any stage that begins making semantic claims must satisfy the +[settled requirements](../design/settled-requirements.md) and preserve the +[verification-result and trust model](../design/result-and-trust.md). + +## Working near the pipeline frontier + +Nearby work may involve Cargo target and artifact resolution, Charon execution, +LLBC transport, Aeneas generation, dependency discovery, Lake workspaces, +diagnostic mapping, or toolchain fixups. Treat that list as a description of +the engineering neighborhood, not a prescribed sequence or component boundary. + +For each change: + +1. Establish what is already present in this checkout; do not plan against a + sibling worktree or an open PR by accident. +2. Name the concrete input, output, and failure modes of the stage being added. +3. Preserve enough identity and provenance to relate its output to the fixed + [compilation subject and verification result](../design/result-and-trust.md). +4. Test the stage independently where practical and test the newly connected + boundary end to end. +5. Update current-state references only after the capability is checked in. +6. If the implementation would choose an answer recorded as open, pause for + explicit agreement or frame the work as a reversible experiment. + +In particular, pipeline plumbing must not incidentally settle the source of +annotation metadata, the Anneal/Aeneas/Charon ownership boundary, the unsafe +memory model, proof arguments versus sidecar theorems, or the final property +and outcome taxonomy. Those questions and their settled constraints live in +the [open-question index](../design/open-questions/README.md). + +## What not to optimize for yet + +Do not infer that the project currently wants: + +- a broad but unaudited claim of Rust coverage; +- compatibility with V1 syntax or architecture for its own sake; +- a polished `verify` success mode before its claim and trust reporting are + defined; +- local textual patches when a robust programmatic boundary is available; or +- premature generalization across target, feature, or configuration matrices + when the settled initial unit is one fixed compilation artifact. + +These cautions do not forbid experiments. They require experiments to be +identified as such and prevent temporary scaffolding from silently becoming a +project-wide decision. + +## Refreshing this page + +Revalidate this snapshot whenever the checked-in executable gains a pipeline +stage or project authors state a new near-term priority. Keep volatile work +sequencing here, current facts in the other `reference/` pages, and lasting +constraints or decisions in `design/`. Remove priorities that no longer help +choose work; do not preserve them as historical authority. diff --git a/anneal/agent_docs/reference/current-state.md b/anneal/agent_docs/reference/current-state.md new file mode 100644 index 0000000000..221704335a --- /dev/null +++ b/anneal/agent_docs/reference/current-state.md @@ -0,0 +1,157 @@ + + +# Current implementation state + +**Snapshot date: 2026-07-17.** + +This page describes the code checked into `anneal/`. It is a factual snapshot, +not a promise about the final architecture. Inspect the current tree and tests +when a detail matters; update this page when implementation changes make it +stale. + +## Status in one sentence + +Anneal currently constructs and installs its pinned external toolchain, but it +does not yet discover a Rust compilation subject, generate or check application +proofs, or produce a verification result. + +No invocation of the checked-in executable currently proves that an application +is safe or correct. + +## Repository boundary + +- `anneal/` contains the current clean-room implementation. +- `anneal/v1/` contains the historical prototype and has separate instructions. +- `exocrate/` installs and locates the versioned external toolchain archive. +- `.github/workflows/anneal.yml` contains jobs for both generations; passing a + V1 job does not establish a V2 capability. + +V1 remains useful implementation and design evidence. Its interfaces and +behavior are not defaults for the current implementation. + +## Implemented command path + +The compiled command currently supports `cargo-anneal setup`, including the +Cargo-plugin spelling `cargo anneal setup`. It selects either a local archive +provided with `--local-archive` or platform metadata from `Cargo.toml`, asks +`exocrate` to install or locate the archive, and reports the installation +directory. + +The checked-in remote archive URLs and hashes are placeholders. Development +and CI therefore use a locally constructed archive. `__ANNEAL_LOCAL_DEV` +selects a repository-local development installation; it is not a stable user +interface. + +`src/util.rs` contains helpers intended for later orchestration, but it is not +currently part of the compiled production command path. The presence of a +helper or an implementation in an open or sibling branch is not evidence that +the checked-in command exposes that capability. + +## Toolchain infrastructure + +`flake.nix` pins and packages a coupled Rust, Charon, Aeneas, Lean, Mathlib, and +supporting-tool environment for Linux and macOS on x86-64 and AArch64. It builds +Aeneas's Lean library, vendors Lake dependencies, prunes unused cache material, +normalizes selected paths and timestamps, and produces the omnibus archive used +by setup and CI. + +Supporting scripts rewrite vendored Lake metadata, prune the Mathlib closure, +derive coupled upstream versions and hashes, and check that release-relevant +flake outputs evaluate for every declared host system. These are substantial +toolchain and reproducibility capabilities; they are not an application +verification pipeline. + +## CI coverage + +The V2 CI path builds and layout-checks the omnibus archive, passes that exact +archive to the Rust test job, runs archive-dependent installation and Lake +cache tests, runs support-script tests, and evaluates the flake. Persistent Nix +caches written by pull requests are isolated from the cache populated by +trusted `main` builds. + +See [Development and CI](development-and-ci.md) for current commands, fixtures, +and workflow details. That page, the affected manifests, and the workflow files +are the closest sources of truth for validation. + +## Verification stages not yet implemented + +The checked-in executable does not yet: + +- select and identify a Cargo compilation subject; +- invoke Charon or consume LLBC; +- invoke Aeneas or generate a Lean model; +- resolve Anneal specifications or invariants; +- generate and track proof obligations; +- invoke Lean to check an application proof; +- map proof failures back to Rust source; +- compute property dependencies or coverage; or +- emit a scoped result and trust ledger. + +The intended high-level flow remains: + +```text +Cargo/rustc compilation subject + | + v + Charon extraction --> LLBC + | | + | v + | Aeneas Lean model + | | + +--------> Anneal contracts and obligations + | + v + Lean verification + | + v + scoped result and trust ledger +``` + +This diagram describes responsibilities, not settled APIs. Annotation +transport, unsafe-memory semantics, the Anneal/Aeneas/Charon boundary, proof +encoding, property representation, and command policy remain open. See the +[open-question index](../design/open-questions/README.md). + +## Current reporting and trust gaps + +Anneal does not yet produce a compilation-subject manifest, canonical result +identity, proof-coverage report, or audit ledger. It therefore does not yet +report residual trust in Charon, Aeneas, Anneal, Lean, rustc, LLVM, external +semantics, or host and target hardware. + +The toolchain archive pins many proof inputs, but the Rust toolchain used to +compile Anneal itself is not yet derived from the archive's pinned Rust +toolchain in CI. Placeholder download metadata also leaves the published +installation path incomplete. + +These are implementation limitations, not permission for a future verifier to +omit trust or coverage. The required result semantics live in +[Verification result and trust](../design/result-and-trust.md). + +## Using other sources + +Open issues and pull requests provide evidence about active work and design +pressure, but they do not describe the capabilities of this checkout. Before +claiming that a stage exists, inspect the checked-in implementation and tests. +Before treating an implementation choice as intended architecture, consult the +normative design documents and accepted decisions. + +The dated [current priorities](current-priorities.md) page is non-normative and +may help select near-term work. The [research index](../history/research-index.md) +is a navigation aid for issues and pull requests, not a capability ledger. + +## Updating this page + +Update this page when behavior lands, not when a pull request opens. Keep: + +- implemented facts here; +- commands and CI details in [Development and CI](development-and-ci.md); +- intended guarantees in `agent_docs/design/`; +- unresolved alternatives in `agent_docs/design/open-questions/`; and +- volatile work sequencing in [current priorities](current-priorities.md). diff --git a/anneal/agent_docs/reference/development-and-ci.md b/anneal/agent_docs/reference/development-and-ci.md new file mode 100644 index 0000000000..4c9c914706 --- /dev/null +++ b/anneal/agent_docs/reference/development-and-ci.md @@ -0,0 +1,146 @@ + + +# Development and CI + +Run V2 commands from `anneal/` or pass `--manifest-path anneal/Cargo.toml` +from the repository root. Commands rooted at `anneal/v1/` operate on the V1 +prototype. + +This page records the current workflow. It is not a stable CLI specification. + +## Fast local checks + +From the repository root: + +```bash +cargo test --locked --manifest-path anneal/Cargo.toml +PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover \ + -s anneal/tests -p 'test_*.py' +cargo fmt --manifest-path anneal/Cargo.toml --all -- --check +``` + +The repository-wide formatting entry point is: + +```bash +./ci/check_fmt.sh +``` + +That script intentionally formats V2, V1, `exocrate`, the workspace tools, +and Zerocopy. Use the narrower Cargo command while iterating on a V2-only +change; use the repository entry point before handing off a broad change. + +## Running setup locally + +The checked-in remote archive metadata is placeholder data. Exercise setup +with an archive built locally: + +```bash +mkdir -p anneal/target +nix build ./anneal#omnibus-archive-ci \ + --out-link anneal/target/anneal-exocrate.tar.zst +cargo run --manifest-path anneal/Cargo.toml -- \ + setup --local-archive anneal/target/anneal-exocrate.tar.zst +``` + +The archive argument is interpreted from the command's current directory. +When developing inside `anneal/`, use +`target/anneal-exocrate.tar.zst` instead. + +`__ANNEAL_LOCAL_DEV=1` selects the local-development installation location. +It is an internal switch and may change. + +## Archive-dependent Rust tests + +Tests behind the `exocrate_tests` feature expect this exact path relative to +`anneal/`: + +```text +target/anneal-exocrate.tar.zst +``` + +After building the archive, run: + +```bash +cargo test --locked --manifest-path anneal/Cargo.toml --all-features +``` + +These tests install the archive and build a small generated Lean workspace to +ensure the read-only, precompiled Lake cache can be reused. A missing archive +is a test-fixture failure, not evidence about Anneal's proof design. + +## Nix checks + +Evaluate every release-relevant flake package for every supported system: + +```bash +bash anneal/check-flake-eval.sh +``` + +Build and validate the archive for the current system: + +```bash +nix build ./anneal#omnibus-archive-ci +nix build ./anneal#omnibus-archive-layout-check --no-link +``` + +Evaluation across four systems is not a cross-platform build. CI's archive +builder builds for its current runner and separately checks that the package +graph evaluates for all declared systems. + +When rolling Aeneas, Rust, or Lean inputs, use +`anneal/chase-aeneas-versions.sh` as an aid. It downloads upstream artifacts +and computes coupled hashes. Review every resulting version and hash; Mathlib +cache output may require a subsequent fixed-output hash refresh. + +## CI topology + +`.github/workflows/anneal.yml` contains both generations: + +- `static_checks` runs V1 and V2 support-script tests and the V2 flake + evaluation check. +- `anneal_tests` and `verify_examples` are V1 jobs rooted at `anneal/v1/`. +- `v2_nix_cache` builds the V2 omnibus archive, checks its layout, and uploads + it for this workflow run. +- `v2` downloads that exact archive and runs + `cargo test --workspace --all-features` in `anneal/`. + +The Nix caches have a trust boundary. A cache saved by a trusted `main` build +may be reused by pull requests; a pull request writes only its PR-scoped +cache. Preserve that separation when changing cache keys or Actions steps. +The workflow artifact, not the persistent cache, is the cross-job handoff for +the current run. + +The release workflow currently publishes the V1 crate from `anneal/v1/` while +using V2's Nix flake to build omnibus toolchain archives. This mixed state is +intentional during the transition. Do not "simplify" paths without first +understanding which generation owns the crate and which owns the archive. + +## Platform-specific concerns + +- The flake declares Linux and macOS on x86-64 and AArch64. +- Linux builds use an FHS environment for downloaded Lean tooling. +- Ubuntu 24.04 CI temporarily adjusts an AppArmor user-namespace setting for + the sandboxed Nix build and restores it in an `always()` step. Preserve the + cleanup path. +- Lake metadata and trace files can contain build-machine absolute paths. + Archive construction deliberately rewrites them and makes the installed + Aeneas tree read-only. +- Archive size is checked against GitHub's release-asset limit. + +## Scope discipline + +Open stacked PRs contain scanner, Cargo resolution, Charon, generation, and +diagnostic work. When developing on a branch below those changes, use the +files actually present in the checkout as the source of truth. Do not add +tests or documentation for an API which only exists in another stack unless +the change explicitly depends on that stack. + +When a V2 change touches shared CI or release machinery, also run the relevant +V1 support tests. Otherwise, V1's large integration suite is not the default +validation target for V2 implementation work. diff --git a/anneal/v2/chase-aeneas-versions.sh b/anneal/chase-aeneas-versions.sh similarity index 100% rename from anneal/v2/chase-aeneas-versions.sh rename to anneal/chase-aeneas-versions.sh diff --git a/anneal/v2/check-flake-eval.sh b/anneal/check-flake-eval.sh similarity index 98% rename from anneal/v2/check-flake-eval.sh rename to anneal/check-flake-eval.sh index 22d71f04dd..5deef6e2ae 100644 --- a/anneal/v2/check-flake-eval.sh +++ b/anneal/check-flake-eval.sh @@ -11,7 +11,7 @@ set -euo pipefail ROOT="$(git rev-parse --show-toplevel)" -FLAKE="$ROOT/anneal/v2" +FLAKE="$ROOT/anneal" systems=( aarch64-darwin diff --git a/anneal/v2/flake.lock b/anneal/flake.lock similarity index 100% rename from anneal/v2/flake.lock rename to anneal/flake.lock diff --git a/anneal/v2/flake.nix b/anneal/flake.nix similarity index 100% rename from anneal/v2/flake.nix rename to anneal/flake.nix diff --git a/anneal/v2/prune-lake-cache.py b/anneal/prune-lake-cache.py similarity index 100% rename from anneal/v2/prune-lake-cache.py rename to anneal/prune-lake-cache.py diff --git a/anneal/v2/rewrite-lake-vendor.py b/anneal/rewrite-lake-vendor.py similarity index 100% rename from anneal/v2/rewrite-lake-vendor.py rename to anneal/rewrite-lake-vendor.py diff --git a/anneal/src/main.rs b/anneal/src/main.rs index 3d363b977d..b947700606 100644 --- a/anneal/src/main.rs +++ b/anneal/src/main.rs @@ -1,21 +1,25 @@ -mod aeneas; -mod charon; -mod diagnostics; -mod errors; -mod generate; -mod parse; +// Copyright 2026 The Fuchsia Authors +// +// Licensed under the 2-Clause BSD License , Apache License, Version 2.0 +// , or the MIT +// license , at your option. +// This file may not be copied, modified, or distributed except according to +// those terms. + +use clap::Parser as _; + +#[allow(dead_code)] mod resolve; +#[allow(dead_code)] mod scanner; -mod validate; - +#[allow(dead_code)] mod setup; -mod ui_test_shim; +#[allow(dead_code)] mod util; -use clap::Parser; - -/// Anneal: A Literate Verification Toolchain -#[derive(Parser, Debug)] +/// Anneal +#[derive(clap::Parser, Debug)] #[command(name = "cargo-anneal", version, about, long_about = None)] struct Cli { #[command(subcommand)] @@ -24,46 +28,61 @@ struct Cli { #[derive(clap::Subcommand, Debug)] enum Commands { - /// Verify a crate - Verify(resolve::Args), /// Setup Anneal dependencies - Setup(resolve::SetupArgs), - /// Expand a crate's Lean output - Expand(ExpandArgs), - /// Generate Lean workspace and print paths without building - Generate(resolve::Args), - #[command(hide = true)] - ToolchainPath, + Setup(SetupArgs), } -#[derive(clap::ValueEnum, Clone, Debug, Default, PartialEq)] -pub enum EmitFormat { - #[default] - All, - Anneal, - Aeneas, +#[derive(clap::Parser, Debug)] +pub struct SetupArgs { + /// Path to a local dependency archive to use instead of downloading. + #[arg(long, value_name = "path-to-local-archive")] + pub local_archive: Option, } -#[derive(Parser, Debug)] -pub struct ExpandArgs { - #[command(flatten)] - pub resolve_args: resolve::Args, +exocrate::config! { + const CONFIG: Config = Config { + rel_dir_path: [".anneal", "toolchain"], + versioned_files: &["../Cargo.toml", "../Cargo.lock"], + }; +} - /// Which tool's generated Lean code to output - #[arg(long, default_value_t, value_enum)] - pub emit: EmitFormat, +exocrate::parse_remote_archive! { + const REMOTE: RemoteArchive = "Cargo.toml" [ + (linux, x86_64), + (macos, x86_64), + (linux, aarch64), + (macos, aarch64), + ]; } -fn main() -> anyhow::Result<()> { +fn setup_installation_dir(args: SetupArgs) -> std::path::PathBuf { + let location = if std::env::var("__ANNEAL_LOCAL_DEV").is_ok() { + exocrate::Location::LocalDev + } else { + exocrate::Location::UserGlobal + }; + let source = match args.local_archive { + Some(local_archive) => exocrate::Source::Local(local_archive), + None => exocrate::Source::Remote(REMOTE), + }; + + let (installation_dir, _) = CONFIG + .resolve_installation_dir_or_install(location, source) + // FIXME: Implement unified error reporting (e.g., via `anyhow`). + .expect("failed to resolve-or-install dependencies"); + installation_dir +} + +fn setup(args: SetupArgs) { + let installation_dir = setup_installation_dir(args); + log::info!("anneal toolchain is installed at {:?}", installation_dir); +} + +fn main() { // Suppressing timestamps removes a source of nondeterminism that is // difficult to work around in integration tests. env_logger::builder().format_timestamp(None).init(); - if std::env::var("ANNEAL_UI_TEST_MODE").is_ok() { - ui_test_shim::run(); - return Ok(()); - } - let mut args_iter = std::env::args_os().peekable(); let bin_name = args_iter.next().unwrap_or_else(|| "cargo-anneal".into()); // If we're being run as a cargo plugin, the second argument will be "anneal". @@ -73,112 +92,251 @@ fn main() -> anyhow::Result<()> { let args = Cli::parse_from(std::iter::once(bin_name).chain(args_iter)); match args.command { - Commands::Verify(resolve_args) => { - prepare_and_run(&resolve_args, |locked_roots, packages| { - aeneas::verify_lean_workspace(locked_roots, packages) - })?; + Commands::Setup(args) => setup(args), + } +} + +#[cfg(test)] +mod tests { + #[cfg(feature = "exocrate_tests")] + mod exocrate_tests { + use std::{ + fs, io, + path::{Path, PathBuf}, + process::Command, + sync::OnceLock, + }; + + use serde_json::{Value, json}; + + const LOCAL_ARCHIVE: &str = "target/anneal-exocrate.tar.zst"; + static INSTALLATION_DIR: OnceLock = OnceLock::new(); + + #[test] + fn test_setup() { + install_local_archive(); } - Commands::Generate(resolve_args) => { - prepare_and_run(&resolve_args, |locked_roots, packages| { - aeneas::generate_lean_workspace(locked_roots, packages)?; - let lean_root = locked_roots.lean_root(); - let toolchain = setup::Toolchain::resolve()?; - println!("Lean workspace generated at: {}", lean_root.display()); - println!(); - println!("To manually build and experiment:"); - println!(" 1. cd {}", lean_root.display()); - println!( - " 2. LAKE_CACHE_DIR={} {} --keep-toolchain build", - toolchain.cache_dir().display(), - toolchain.lean_bin().join("lake").display() - ); - Ok(()) - })?; + + #[test] + fn test_archive_lake_cache_reuse() { + let installation_dir = install_local_archive(); + let temp = tempfile::Builder::new() + .prefix("anneal-v2-archive-cache-reuse-") + .tempdir() + .expect("failed to create archive cache reuse tempdir"); + assert_archive_lake_cache_reuse(&installation_dir, temp.path()) + .expect("archive Lake cache reuse test failed"); + } + + fn install_local_archive() -> PathBuf { + // ASSUMPTION: The CI dependency builder downloads the Nix-built + // archive artifact to this path before running v2 tests. + INSTALLATION_DIR + .get_or_init(|| { + super::super::setup_installation_dir(super::super::SetupArgs { + local_archive: Some(LOCAL_ARCHIVE.into()), + }) + }) + .clone() + } + + fn assert_archive_lake_cache_reuse( + toolchain_root: &Path, + temp_root: &Path, + ) -> Result<(), Box> { + let aeneas_root = toolchain_root.join("aeneas"); + let aeneas_lean = aeneas_root.join("backends/lean"); + let lean_root = toolchain_root.join("lean"); + let workspace = temp_root.join("generated-workspace"); + + assert_no_write_bits(&aeneas_root)?; + + fs::create_dir_all(workspace.join("generated"))?; + fs::copy(aeneas_lean.join("lean-toolchain"), workspace.join("lean-toolchain"))?; + fs::write(workspace.join("generated/Generated.lean"), "import Aeneas\n")?; + fs::write( + workspace.join("lakefile.lean"), + format!( + r#"import Lake +open Lake DSL + +require aeneas from "{}" + +package anneal_verification + +@[default_target] +lean_lib Generated where + srcDir := "generated" + roots := #[`Generated] +"#, + lake_string(&aeneas_lean) + ), + )?; + write_relative_archive_manifest(&workspace, &aeneas_lean)?; + + // The Nix archive must support fresh generated workspaces without + // reconfiguring packages or rebuilding read-only Lake artifacts. + run_lake_archive_command( + &workspace, + &lean_root, + &["--keep-toolchain", "--old", "build", "Generated"], + )?; + run_lake_archive_command( + &workspace, + &lean_root, + &["--keep-toolchain", "env", "lean", "--json", "generated/Generated.lean"], + )?; + + Ok(()) } - Commands::Setup(args) => { - setup::run_setup(setup::SetupArgs { local_archive: args.local_archive })?; + + fn assert_no_write_bits(root: &Path) -> Result<(), Box> { + let metadata = fs::symlink_metadata(root)?; + if metadata.file_type().is_symlink() { + return Ok(()); + } + if has_write_bits(&metadata.permissions()) { + panic!("archive path should be read-only: {}", root.display()); + } + if metadata.is_dir() { + for entry in fs::read_dir(root)? { + assert_no_write_bits(&entry?.path())?; + } + } + Ok(()) + } + + #[cfg(unix)] + fn has_write_bits(permissions: &fs::Permissions) -> bool { + use std::os::unix::fs::PermissionsExt as _; + permissions.mode() & 0o222 != 0 } - Commands::ToolchainPath => { - let toolchain = setup::Toolchain::resolve()?; - println!("{}", toolchain.bin_dir().display()); + + #[cfg(not(unix))] + fn has_write_bits(permissions: &fs::Permissions) -> bool { + !permissions.readonly() } - Commands::Expand(expand_args) => { - prepare_and_run(&expand_args.resolve_args, |locked_roots, packages| { - let lean_generated_root = locked_roots.lean_generated_root(); - - for artifact in packages { - if artifact.start_from.is_empty() { - continue; - } - - let slug = artifact.artifact_slug(); - let output_dir = lean_generated_root.join(&slug); - - let emit_all = expand_args.emit == EmitFormat::All; - let emit_anneal = emit_all || expand_args.emit == EmitFormat::Anneal; - let emit_aeneas = emit_all || expand_args.emit == EmitFormat::Aeneas; - - println!("=== Lean expansion for: {} ===", artifact.name.target_name); - - if emit_aeneas { - println!("--- Aeneas ---"); - // Read Aeneas outputs from disk - let types_path = output_dir.join("Types.lean"); - let types_ext_path = output_dir.join("TypesExternal.lean"); - let funs_path = output_dir.join("Funs.lean"); - let funs_ext_path = output_dir.join("FunsExternal.lean"); - - if types_path.exists() { - println!("{}", std::fs::read_to_string(&types_path)?); - } - if types_ext_path.exists() { - println!("{}", std::fs::read_to_string(&types_ext_path)?); - } - if funs_path.exists() { - println!("{}", std::fs::read_to_string(&funs_path)?); - } - if funs_ext_path.exists() { - println!("{}", std::fs::read_to_string(&funs_ext_path)?); - } - } - - if emit_anneal { - println!("--- Anneal ---"); - let generated = generate::generate_artifact(artifact); - println!("{}", generated.code); - } + + fn write_relative_archive_manifest( + workspace: &Path, + aeneas_lean: &Path, + ) -> Result<(), Box> { + let aeneas_lean = fs::canonicalize(aeneas_lean)?; + let workspace = fs::canonicalize(workspace)?; + let manifest_path = aeneas_lean.join("lake-manifest.json"); + let manifest: Value = serde_json::from_reader(fs::File::open(&manifest_path)?)?; + let aeneas_packages = + manifest.get("packages").and_then(Value::as_array).ok_or_else(|| { + invalid_data(format!( + "Aeneas Lake manifest {} is missing packages", + manifest_path.display() + )) + })?; + + let aeneas_dir = relative_manifest_string(&aeneas_lean, &workspace)?; + let mut packages = vec![json!({ + "type": "path", + "name": "aeneas", + "dir": aeneas_dir, + "inherited": false, + })]; + + for entry in aeneas_packages { + let mut entry = entry.as_object().cloned().ok_or_else(|| { + invalid_data("Aeneas Lake manifest package entry is not an object") + })?; + let package_type = entry.get("type").and_then(Value::as_str).ok_or_else(|| { + invalid_data("Aeneas Lake manifest package entry is missing type") + })?; + if package_type != "path" { + return Err(invalid_data(format!( + "Aeneas Lake manifest package entry is {package_type:?}, not a path dependency" + )) + .into()); } + let package_dir = entry.get("dir").and_then(Value::as_str).ok_or_else(|| { + invalid_data("Aeneas Lake manifest package entry is missing dir") + })?; + let package_dir = Path::new(package_dir); + let package_dir = if package_dir.is_absolute() { + package_dir.to_path_buf() + } else { + aeneas_lean.join(package_dir) + }; + let package_dir = fs::canonicalize(package_dir)?; + entry.insert( + "dir".to_string(), + json!(relative_manifest_string(&package_dir, &workspace)?), + ); + entry.insert("inherited".to_string(), json!(true)); + packages.push(Value::Object(entry)); + } - Ok(()) + let manifest = json!({ + "version": "1.2.0", + "packagesDir": ".lake/packages", + "packages": packages, + "name": "anneal_verification", + "lakeDir": ".lake", + "fixedToolchain": false, + }); + fs::write( + workspace.join("lake-manifest.json"), + format!("{}\n", serde_json::to_string_pretty(&manifest)?), + )?; + Ok(()) + } + + fn relative_manifest_string( + path: &Path, + base: &Path, + ) -> Result> { + let path = pathdiff::diff_paths(path, base).ok_or_else(|| { + invalid_data(format!( + "failed to compute relative path from {} to {}", + base.display(), + path.display() + )) })?; + Ok(path.to_string_lossy().into_owned()) } - } - Ok(()) -} + fn lake_string(path: &Path) -> String { + path.to_string_lossy().replace('\\', "\\\\").replace('"', "\\\"") + } -fn prepare_and_run(resolve_args: &resolve::Args, f: F) -> anyhow::Result> -where - F: FnOnce(&resolve::LockedRoots, &[scanner::AnnealArtifact]) -> anyhow::Result, -{ - let roots = resolve::resolve_roots(resolve_args)?; - - let packages = scanner::scan_workspace(&roots)?; - if packages.is_empty() { - log::warn!( - "No Anneal annotations (/// ```lean ...) found in the selected targets. Nothing to verify." - ); - return Ok(None); - } + fn run_lake_archive_command( + workspace: &Path, + lean_root: &Path, + args: &[&str], + ) -> Result<(), Box> { + let lean_bin = lean_root.join("bin"); + let mut cmd = Command::new(lean_bin.join("lake")); + cmd.args(args).current_dir(workspace).env_clear(); + + let lib_var = + if cfg!(target_os = "macos") { "DYLD_LIBRARY_PATH" } else { "LD_LIBRARY_PATH" }; + cmd.env( + lib_var, + std::env::join_paths([lean_root.join("lib"), lean_root.join("lib/lean")])?, + ); - let locked_roots = roots.lock_run_root()?; - validate::validate_artifacts( - &packages, - resolve_args.allow_sorry, - resolve_args.unsound_allow_is_valid, - )?; - charon::run_charon(resolve_args, &locked_roots, &packages)?; - aeneas::run_aeneas(&locked_roots, &packages, resolve_args)?; + let output = cmd.output()?; + if !output.status.success() { + return Err(io::Error::other(format!( + "lake {:?} failed with status {}\nstdout:\n{}\nstderr:\n{}", + args, + output.status, + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + )) + .into()); + } + Ok(()) + } - Ok(Some(f(&locked_roots, &packages)?)) + fn invalid_data(message: impl Into) -> io::Error { + io::Error::new(io::ErrorKind::InvalidData, message.into()) + } + } } diff --git a/anneal/src/resolve.rs b/anneal/src/resolve.rs index 9a44f65296..a87fa5cde0 100644 --- a/anneal/src/resolve.rs +++ b/anneal/src/resolve.rs @@ -1,13 +1,30 @@ -use std::{env, fs, path::PathBuf}; - -use anyhow::{Context, Result, anyhow}; -use cargo_metadata::{Metadata, MetadataCommand, Package, PackageName, Target, TargetKind}; -use clap::Parser; -use sha2::{Digest, Sha256}; - -use crate::util::DirLock; - -#[derive(Parser, Debug)] +// Copyright 2026 The Fuchsia Authors +// +// Licensed under the 2-Clause BSD License , Apache License, Version 2.0 +// , or the MIT +// license , at your option. +// This file may not be copied, modified, or distributed except according to +// those terms. + +//! Cargo package and target resolution for Anneal. +//! +//! Cargo selectors can identify several compilation artifacts depending on +//! package layout: default selection may include libraries and binaries, +//! `--tests` can build test harnesses plus supporting targets, and a library +//! target can expose several crate types such as `rlib` and `cdylib`. +//! +//! This module resolves that selection into one explicit Anneal artifact per +//! package target and target kind. Downstream stages process those artifacts +//! independently, assigning each its own LLBC path and Charon configuration. +//! This prevents selected rustc units from sharing a `--dest-file`, avoids +//! collisions between workspace packages with the same Rust crate name, and +//! permits per-target Charon options such as `--start-from` and `--opaque`. + +use anyhow::Context as _; +use sha2::Digest as _; + +#[derive(clap::Parser, Debug)] pub struct Args { #[command(flatten)] pub manifest: clap_cargo::Manifest, @@ -18,60 +35,40 @@ pub struct Args { #[command(flatten)] pub features: clap_cargo::Features, - /// Verify the library target + /// Verify the library target. #[arg(long)] pub lib: bool, - /// Verify specific binary targets + /// Verify specific binary targets. #[arg(long)] pub bin: Vec, - /// Verify all binary targets + /// Verify all binary targets. #[arg(long)] pub bins: bool, - /// Verify specific example targets + /// Verify specific example targets. #[arg(long)] pub example: Vec, - /// Verify all example targets + /// Verify all example targets. #[arg(long)] pub examples: bool, - /// Verify specific test targets + /// Verify specific test targets. #[arg(long)] pub test: Vec, - /// Verify all test targets + /// Verify all test targets. #[arg(long)] pub tests: bool, - /// Allow `sorry` in proofs and inject `sorry` for missing proofs - #[arg(long)] - pub allow_sorry: bool, - - /// Allow use of `isValid` annotations + /// Permit Lean proof admissions (`sorry`, `admit`, or `sorryAx`). /// - /// `isValid` annotations are currently unsound. In particular, Rust does - /// not yet support annotating a field with `unsafe`, denoting that it - /// carries an invariant. Thus, `isValid` annotations are effectively - /// advisory – any code which does not have a Anneal annotation can modify - /// invariant-carrying fields without needing to use an `unsafe` block. - /// Without an `unsafe` block, Anneal has no way of knowing that an - /// operation needs to be analyzed for soundness. - /// - /// Once the `syn` parser supports parsing `unsafe` fields (which are - /// already supported in a nightly Rust feature), Anneal will require that - /// `isValid` is only used on `unsafe` fields. + /// Without this explicit opt-in, generated Lean treats warnings as errors + /// so every declaration which semantically depends on an admission fails. #[arg(long)] - pub unsound_allow_is_valid: bool, -} - -#[derive(Parser, Debug)] -pub struct SetupArgs { - /// Install dependencies from a locally built exocrate archive. - #[arg(long, value_name = "path-to-local-archive")] - pub local_archive: Option, + pub allow_sorry: bool, } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] @@ -97,23 +94,21 @@ pub enum AnnealTargetKind { Test, } -// We map `cargo_metadata::TargetKind` to our own `AnnealTargetKind` to -// strictly validate supported target types and simplify downstream logic. -// While `cargo_metadata` is exhaustive, we only care about a subset of -// targets relevant to verification. - impl AnnealTargetKind { pub fn is_lib(&self) -> bool { use AnnealTargetKind::*; - matches!(self, Lib | RLib | ProcMacro | CDyLib | DyLib | StaticLib) + match self { + Lib | RLib | ProcMacro | CDyLib | DyLib | StaticLib => true, + Bin | Example | Test => false, + } } } -impl TryFrom<&TargetKind> for AnnealTargetKind { +impl std::convert::TryFrom<&cargo_metadata::TargetKind> for AnnealTargetKind { type Error = (); - fn try_from(kind: &TargetKind) -> Result { - use TargetKind::*; + fn try_from(kind: &cargo_metadata::TargetKind) -> anyhow::Result { + use cargo_metadata::TargetKind::*; match kind { Lib => Ok(Self::Lib), RLib => Ok(Self::RLib), @@ -132,8 +127,15 @@ impl TryFrom<&TargetKind> for AnnealTargetKind { #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct AnnealTargetName { - pub package_name: PackageName, + /// The Cargo package that owns this artifact. + pub package_name: cargo_metadata::PackageName, + /// The Cargo target name. pub target_name: String, + /// The target kind compiled for this artifact. + /// + /// This is part of the identity because one Cargo target can expose + /// multiple crate types, and because later Charon and Lean outputs must not + /// rely on target names alone. pub kind: AnnealTargetKind, } @@ -147,34 +149,32 @@ pub struct AnnealTargetName { pub struct AnnealTarget { pub name: AnnealTargetName, pub kind: AnnealTargetKind, - /// Path to the main source file for this target. - pub src_path: PathBuf, + /// Path to the `Cargo.toml` for this target. - pub manifest_path: PathBuf, + pub manifest_path: std::path::PathBuf, } #[derive(Debug)] pub struct Roots { - pub workspace: PathBuf, // E.g., `target/anneal`. - anneal_global_root: PathBuf, + anneal_global_root: std::path::PathBuf, // E.g., `target/anneal/`. - anneal_run_root: PathBuf, + anneal_run_root: std::path::PathBuf, pub roots: Vec, } impl Roots { - pub fn lock_run_root(&self) -> Result> { - let lock = DirLock::lock_exclusive(self.anneal_run_root.clone())?; - Ok(LockedRoots { roots: self, anneal_run_root: lock }) + pub fn lock_run_root(&self) -> anyhow::Result> { + let lock = crate::util::DirLock::lock_exclusive(self.anneal_run_root.clone())?; + Ok(LockedRoots { roots: self, anneal_run_root: lock, llbc_override: None }) } - pub fn cargo_target_dir(&self) -> PathBuf { + pub fn cargo_target_dir(&self) -> std::path::PathBuf { self.anneal_global_root.join("cargo_target") } } -/// A wrapper around `Roots` that proves the build lock is held. +/// A wrapper around [`crate::resolve::Roots`] that proves the build lock is held. /// /// This struct is the *only* way to access paths within the Anneal build /// directory (e.g., LLBC output, Lean generation). This enforces that all @@ -182,38 +182,37 @@ impl Roots { pub struct LockedRoots<'a> { roots: &'a Roots, anneal_run_root: crate::util::DirLock, + pub llbc_override: Option, } impl<'a> LockedRoots<'a> { - pub fn llbc_root(&self) -> PathBuf { - self.anneal_run_root.path.join("llbc") - } - - pub fn lean_root(&self) -> PathBuf { - self.anneal_run_root.path.join("lean") - } - - pub fn lean_generated_root(&self) -> PathBuf { - self.lean_root().join("generated") + pub fn llbc_root(&self) -> std::path::PathBuf { + if let Some(ref over) = self.llbc_override { + over.clone() + } else { + self.anneal_run_root.path.join("llbc") + } } // We expose the Cargo target directory for convenience, as it is used // by downstream tools like Charon to coordinate dependency artifacts. - pub fn cargo_target_dir(&self) -> PathBuf { + pub fn cargo_target_dir(&self) -> std::path::PathBuf { self.roots.cargo_target_dir() } - - pub fn workspace(&self) -> &PathBuf { - &self.roots.workspace - } } /// Resolves all verification roots. /// /// Each entry represents a distinct compilation artifact to be verified. -pub fn resolve_roots(args: &Args) -> Result { +/// Keeping this artifact list explicit is deliberate: later Charon invocation +/// code should not have to rediscover which files a Cargo flag happened to +/// produce for a particular workspace shape. +pub fn resolve_roots(args: &Args, toolchain: &crate::setup::Toolchain) -> anyhow::Result { log::trace!("resolve_roots({:?})", args); - let mut cmd = MetadataCommand::new(); + let mut cmd = cargo_metadata::MetadataCommand::new(); + cmd.cargo_path(crate::setup::Tool::Cargo.path(toolchain)) + .env("RUSTC", crate::setup::Tool::Rustc.path(toolchain)) + .env(crate::setup::rust_library_path_env_var(), toolchain.rust_lib()); if let Some(path) = &args.manifest.manifest_path { cmd.manifest_path(path); @@ -228,18 +227,19 @@ pub fn resolve_roots(args: &Args) -> Result { args.features.forward_metadata(&mut cmd); let metadata = cmd.exec().context("Failed to run 'cargo metadata'")?; + // We enforce that all local dependencies are contained within the workspace + // root. This is a temporary limitation to simplify the verification model + // and ensure a "hermetic-like" boundary for analysis. It prevents issues + // where experimental or local forks of dependencies might be picked up + // unpredictably, or where Charon might struggle to locate source files + // outside the standard project structure. + check_for_external_deps(&metadata)?; + let selected_packages = resolve_packages(&metadata, &args.workspace, args.manifest.manifest_path.as_deref())?; - check_selected_packages_in_workspace(&metadata, &selected_packages)?; let (anneal_global_root, anneal_run_root) = resolve_run_roots(&metadata); - let mut roots = Roots { - workspace: metadata.workspace_root.as_std_path().to_owned(), - // cargo_target_dir: metadata.target_directory.as_std_path().to_owned(), - anneal_global_root, - anneal_run_root, - roots: Vec::new(), - }; + let mut roots = Roots { anneal_global_root, anneal_run_root, roots: Vec::new() }; for package in selected_packages { log::trace!("Scanning package: {}", package.name); @@ -262,7 +262,6 @@ pub fn resolve_roots(args: &Args) -> Result { // reference for the rest of the pipeline. This avoids ambiguity // if the CWD changes or if we're working with complex workspace // structures. - src_path: target.src_path.as_std_path().to_owned(), manifest_path: package.manifest_path.as_std_path().to_owned(), })); } @@ -270,26 +269,22 @@ pub fn resolve_roots(args: &Args) -> Result { Ok(roots) } -fn resolve_run_roots(metadata: &Metadata) -> (PathBuf, PathBuf) { +fn resolve_run_roots( + metadata: &cargo_metadata::Metadata, +) -> (std::path::PathBuf, std::path::PathBuf) { log::trace!("resolve_run_root"); log::debug!("workspace_root: {:?}", metadata.workspace_root.as_std_path()); // NOTE: Automatically handles `CARGO_TARGET_DIR` env var. let target_dir = metadata.target_directory.as_std_path(); let anneal_global = target_dir.join("anneal"); - // Used by integration tests to ensure deterministic shadow dir names. - if let Ok(name) = std::env::var("ANNEAL_TEST_DIR_NAME") { - let run_root = anneal_global.join(name); - return (anneal_global, run_root); - } - // Hash the path to the workspace root to avoid collisions between different // workspaces using the same target directory. We use SHA-256 (truncated to // 64 bits) for stable hashing across Rust versions. This ensures that the // build directory name remains consistent for the same workspace root, // avoiding unnecessary cache invalidation. let workspace_root_hash = { - let mut hasher = Sha256::new(); + let mut hasher = sha2::Sha256::new(); hasher.update(b"anneal_build_salt"); hasher.update(metadata.workspace_root.as_str().as_bytes()); let result = hasher.finalize(); @@ -304,13 +299,13 @@ fn resolve_run_roots(metadata: &Metadata) -> (PathBuf, PathBuf) { /// Resolves which packages to process based on workspace flags and CWD. fn resolve_packages<'a>( - metadata: &'a Metadata, + metadata: &'a cargo_metadata::Metadata, args: &clap_cargo::Workspace, manifest_path: Option<&std::path::Path>, -) -> Result> { +) -> anyhow::Result> { log::trace!("resolve_packages(workspace: {}, all: {})", args.workspace, args.all); let mut packages = if !args.package.is_empty() { - // Resolve explicitly selected packages (-p / --package) + // Resolve explicitly selected packages (-p / --package). args.package .iter() .map(|name| { @@ -318,9 +313,9 @@ fn resolve_packages<'a>( .packages .iter() .find(|p| p.name == *name) - .ok_or_else(|| anyhow!("Package '{}' not found in workspace", name)) + .ok_or_else(|| anyhow::anyhow!("Package '{}' not found in workspace", name)) }) - .collect::>>()? + .collect::>>()? } else if args.workspace || args.all { // Resolve entire workspace (--workspace / --all). This explicitly // selects all workspace members, ignoring any packages that might be @@ -337,7 +332,7 @@ fn resolve_packages<'a>( let cwd = { let cwd_candidate = manifest_path .map(|p| p.to_path_buf()) - .unwrap_or_else(|| env::current_dir().unwrap_or_default()) + .unwrap_or_else(|| std::env::current_dir().unwrap_or_default()) .canonicalize() .context("Failed to canonicalize CWD")?; @@ -350,7 +345,7 @@ fn resolve_packages<'a>( } }; - // Find the package whose manifest directory is an ancestor of CWD + // Find the package whose manifest directory is an ancestor of CWD. let current_pkg = metadata.packages.iter().find(|p| { let manifest_dir = p.manifest_path.parent().unwrap(); cwd.starts_with(manifest_dir) @@ -368,14 +363,14 @@ fn resolve_packages<'a>( .filter_map(|id| metadata.packages.iter().find(|p| &p.id == id)) .collect() } else { - return Err(anyhow!( + return Err(anyhow::anyhow!( "Could not determine package from current directory. Please use -p or --workspace." )); } } }; - // Filter out excluded packages (--exclude) + // Filter out excluded packages (--exclude). if !args.exclude.is_empty() { packages.retain(|p| !args.exclude.contains(&p.name)); } @@ -383,8 +378,9 @@ fn resolve_packages<'a>( Ok(packages) } -/// Flattening Resolver: -/// Returns a list of (Target, TargetKind) pairs. +/// Resolves the Cargo targets selected from one package. +/// +/// Returns a list of `(Target, TargetKind)` pairs. /// If a target is defined as `crate-type = ["rlib", "cdylib"]`, and both are /// requested, this returns two entries, allowing them to be verified /// independently. @@ -392,12 +388,12 @@ fn resolve_packages<'a>( /// This flattening is critical because different crate types may be compiled /// with different flags or conditional compilation options (although the /// current scanner is CFG-agnostic, future improvements might respect this). -/// Verifying them independently ensures we cover all intended compilation -/// modes. +/// Verifying them independently gives later stages a separate artifact identity +/// and output path for every intended compilation mode. fn resolve_targets<'a>( - package: &'a Package, + package: &'a cargo_metadata::Package, args: &Args, -) -> Result> { +) -> anyhow::Result> { log::trace!("resolve_targets({})", package.name); let default_mode = !args.lib && args.bin.is_empty() @@ -439,38 +435,36 @@ fn resolve_targets<'a>( Ok(selected_artifacts) } -// TODO: Eventually, we'll want to support selected packages outside the Cargo -// workspace root by analyzing them in-place or teaching downstream stages how -// to map those source files. - -/// Ensures every selected verification root is contained within the workspace root. -fn check_selected_packages_in_workspace(metadata: &Metadata, packages: &[&Package]) -> Result<()> { - log::trace!("check_selected_packages_in_workspace"); - // Canonicalize workspace root to handle symlinks correctly - let workspace_root = fs::canonicalize(&metadata.workspace_root) +/// Scans the package graph to ensure all local dependencies are contained +/// within the workspace root. Returns an error if an external path dependency +/// is found. +pub fn check_for_external_deps(metadata: &cargo_metadata::Metadata) -> anyhow::Result<()> { + log::trace!("check_for_external_deps"); + // Canonicalize workspace root to handle symlinks correctly. + let workspace_root = std::fs::canonicalize(&metadata.workspace_root) .context("Failed to canonicalize workspace root")?; - for pkg in packages { - let pkg_path = pkg.manifest_path.as_std_path(); - - // Canonicalize the package path for comparison - let canonical_pkg_path = fs::canonicalize(pkg_path) - .with_context(|| format!("Failed to canonicalize path for package {}", pkg.name))?; - - // We only constrain the packages Anneal will scan as verification - // roots. Cargo metadata may also include build-only local path - // dependencies from the Anneal implementation itself, such as the - // repository-root `exocrate` helper crate when running - // `cargo run verify`. Rejecting those would make Anneal's own package - // layout constrain user verification. - if !canonical_pkg_path.starts_with(&workspace_root) { - anyhow::bail!( - "Unsupported external package: '{}' at {:?}.\n\ - Anneal currently only supports verifying packages contained \ - within the workspace root.", - pkg.name, - pkg_path - ); + for pkg in &metadata.packages { + // We only care about packages that are "local" (source is None). + // If source is Some(...), it's from crates.io or git, which is fine + // (handled by Cargo). + if pkg.source.is_none() { + let pkg_path = pkg.manifest_path.as_std_path(); + + // Canonicalize the package path for comparison. + let canonical_pkg_path = std::fs::canonicalize(pkg_path) + .with_context(|| format!("Failed to canonicalize path for package {}", pkg.name))?; + + // Check if the package lives outside the workspace tree. + if !canonical_pkg_path.starts_with(&workspace_root) { + anyhow::bail!( + "Unsupported external dependency: '{}' at {:?}.\n\ + Anneal currently only supports verifying workspaces where all local \ + dependencies are contained within the workspace root.", + pkg.name, + pkg_path + ); + } } } diff --git a/anneal/src/scanner.rs b/anneal/src/scanner.rs index 44b66417a8..7b4884c9ac 100644 --- a/anneal/src/scanner.rs +++ b/anneal/src/scanner.rs @@ -1,59 +1,51 @@ -use std::{ - collections::{HashMap, HashSet}, - ffi::OsStr, - path::{Path, PathBuf}, - sync::mpsc::{self, Sender}, -}; - -use anyhow::Result; -use sha2::{Digest as _, Sha256}; - -use crate::{ - parse::{self, ParsedLeanItem}, - resolve::{AnnealTargetKind, AnnealTargetName, LockedRoots, Roots}, -}; - -#[derive(Clone)] -struct ScannerContext { - err_tx: Sender, - // `ParsedLeanItem`s must have their `module_path` field updated to be - // relative to the crate root. - item_tx: Sender<(AnnealTargetName, ParsedLeanItem, String)>, - name: AnnealTargetName, - current_prefix: Vec, -} - -/// A scanned artifact containing all the necessary information to generate -/// a Lean specification. +// Copyright 2026 The Fuchsia Authors +// +// Licensed under the 2-Clause BSD License , Apache License, Version 2.0 +// , or the MIT +// license , at your option. +// This file may not be copied, modified, or distributed except according to +// those terms. + +use sha2::Digest as _; + +/// Represents a compilation target (artifact) that needs to be processed. /// -/// This represents a single Rust target (library, binary, etc.) and includes -/// the list of discovered Anneal items and the calculated entry points for -/// Charon. +/// Charon compiles the entire target to generate LLBC files. The generated +/// LLBC is the source of truth for processing Anneal annotations that affect +/// Aeneas code generation. +#[derive(Clone, Debug)] pub struct AnnealArtifact { - pub name: AnnealTargetName, - pub target_kind: AnnealTargetKind, + pub name: crate::resolve::AnnealTargetName, + pub target_kind: crate::resolve::AnnealTargetKind, /// The path to the crate's `Cargo.toml`. - pub manifest_path: PathBuf, - pub items: Vec>, - // NOTE: We store `start_from` as a `HashSet` rather than a `Vec` as an - // optimization: when we encounter items which we can't name (which carry - // Anneal annotations), we add their parent module to the list of - // entrypoints. If there are multiple items in the same module, this can - // lead to duplication in the list of entrypoints. Storing them in a - // `HashSet` avoids us having to de-dup later. - pub start_from: HashSet, + pub manifest_path: std::path::PathBuf, +} + +impl From<&crate::resolve::AnnealTarget> for AnnealArtifact { + fn from(target: &crate::resolve::AnnealTarget) -> Self { + Self { + name: target.name.clone(), + target_kind: target.kind, + manifest_path: target.manifest_path.clone(), + } + } } impl AnnealArtifact { - /// Returns a unique, Lean-compatible "slug" for this artifact that matches - /// the name that Aeneas will expect for the corresponding Lean module. + /// Returns a unique, Lean-compatible artifact slug. /// - /// Guarantees uniqueness based on manifest path even if multiple packages - /// have the same name. The slug is guaranteed to be a valid Lean - /// identifier (no hyphens). + /// Charon uses the slug as the file stem for the target's emitted LLBC + /// file. Later Aeneas/Lean stages can reuse the same Lean-compatible stem + /// when associating generated Lean code with this artifact. The slug is + /// guaranteed to be a valid Lean identifier (no hyphens), and is unique + /// based on the manifest path, target name, and target kind. pub fn artifact_slug(&self) -> String { fn hash(data: &[u8]) -> u64 { - let mut hasher = Sha256::new(); + // Use SHA-256 not for security but rather stability; Rust's + // `DefaultHasher` doesn't guarantee stability even across runs of + // the same binary. + let mut hasher = sha2::Sha256::new(); hasher.update(data); let result = hasher.finalize(); let mut bytes = [0u8; 8]; @@ -61,25 +53,11 @@ impl AnnealArtifact { u64::from_le_bytes(bytes) } - // Double-hash to make sure we can distinguish between e.g. - // (manifest_path, target_name) = ("abc", "def") and ("ab", "cdef"), - // which would hash identically if we just hashed their concatenation. - // - // Use SHA-256 not for security but rather stability – Rust's - // `DefaultHasher` doesn't guarantee stability even across runs of the - // same binary. - // - // `ANNEAL_HASH_WITH_REMOVED_PREFIX` allows our integration test - // framework to strip the randomized sandbox prefix from the manifest - // path before hashing, ensuring deterministic hashes even when running - // in a sandboxed environment. - let mut manifest_path_to_hash = self.manifest_path.as_path(); - if let Ok(prefix) = std::env::var("ANNEAL_HASH_WITH_REMOVED_PREFIX") { - if let Ok(stripped) = self.manifest_path.strip_prefix(&prefix) { - manifest_path_to_hash = stripped; - } - } - let h0 = hash(manifest_path_to_hash.as_os_str().as_encoded_bytes()); + // Compute `hash([hash(manifest_path), hash(target_name), hash(target_kind)])` to + // distinguish between e.g. (manifest_path, target_name) = ("abc", "def") and + // ("ab", "cdef"), which would hash identically if we just hashed their + // concatenation. + let h0 = hash(self.manifest_path.as_os_str().as_encoded_bytes()); let h1 = hash(self.name.target_name.as_bytes()); let h2 = hash(&[self.target_kind as u8]); let hashes = [h0, h1, h2]; @@ -104,7 +82,8 @@ impl AnnealArtifact { let pkg = to_pascal(self.name.package_name.as_str()); let target = to_pascal(&self.name.target_name); - // We use the hash to ensure uniqueness. + // We use the hash to ensure uniqueness even if our prior `to_pascal` + // mapping caused a collision. format!("{}{}{:08x}", pkg, target, h) } @@ -113,392 +92,11 @@ impl AnnealArtifact { format!("{}.llbc", self.artifact_slug()) } - /// Returns the name of the `.lean` spec file to use for this artifact. - pub fn lean_spec_file_name(&self) -> String { - format!("{}.lean", self.artifact_slug()) - } - /// Returns the absolute path to the .llbc file. /// - /// This method requires `LockedRoots` to ensure that the caller holds the + /// This method requires [`crate::resolve::LockedRoots`] to ensure that the caller holds the /// build lock before accessing the build artifact path. - pub fn llbc_path(&self, roots: &LockedRoots) -> PathBuf { + pub fn llbc_path(&self, roots: &crate::resolve::LockedRoots) -> std::path::PathBuf { roots.llbc_root().join(self.llbc_file_name()) } - - /// Returns true if this artifact contains items that should result in a `Funs.lean` file. - pub fn has_functions(&self) -> bool { - self.items.iter().any(|i| { - matches!( - i.item, - crate::parse::ParsedItem::Function(_) | crate::parse::ParsedItem::Impl(_) - ) - }) - } - - /// Returns true if this artifact contains items that should result in a `Types.lean` file. - pub fn has_types(&self) -> bool { - self.items.iter().any(|i| { - matches!(i.item, crate::parse::ParsedItem::Type(_) | crate::parse::ParsedItem::Trait(_)) - }) - } -} - -/// Scans the workspace to identify Anneal entry points (`/// ```lean` blocks) -/// and collects targets for verification. -pub fn scan_workspace(roots: &Roots) -> Result> { - log::trace!("scan_workspace({:?})", roots); - - let (err_tx, err_rx) = mpsc::channel::(); - let (item_tx, item_rx) = - mpsc::channel::<(AnnealTargetName, ParsedLeanItem, String)>(); - - let monitor_handle = std::thread::spawn(move || { - let mut error_count = 0; - for err in err_rx { - if error_count == 0 { - eprintln!("\n=== Anneal Verification Failed ==="); - } - error_count += 1; - // Use eprintln! to print immediately to stderr - // `miette::Report` natively formats with rich diagnostic spans via `{:?}`. - eprintln!("\n[Anneal Error] {:?}", err); - } - error_count - }); - - rayon::scope(|s| { - for target in &roots.roots { - let ctx = ScannerContext { - err_tx: err_tx.clone(), - item_tx: item_tx.clone(), - name: target.name.clone(), - current_prefix: vec!["crate".to_string()], - }; - s.spawn(move |s| { - process_file_recursive( - s, - &target.src_path, - ctx, - false, // Initial call is top-level, so not inside block - Vec::new(), - ) - }); - } - }); - - // Inform the monitor thread that no more errors will be sent, causing it to - // exit. - drop(err_tx); - - let (mut entry_points, mut start_from_map) = { - drop(item_tx); - let mut entry_points = - HashMap::>>::new(); - let mut start_from_map = HashMap::>::new(); - for (target, item, sf_str) in item_rx { - start_from_map.entry(target.clone()).or_default().insert(sf_str); - entry_points.entry(target).or_default().push(item); - } - (entry_points, start_from_map) - }; - - // Wait for the monitor to finish flushing the errors. - let count = monitor_handle.join().expect("Error monitor thread panicked"); - - if count > 0 { - return Err(anyhow::anyhow!("Aborting due to {} previous errors.", count)); - } - - Ok(roots - .roots - .iter() - .filter_map(|target| { - Some(AnnealArtifact { - name: target.name.clone(), - target_kind: target.kind, - manifest_path: target.manifest_path.clone(), - items: entry_points.remove(&target.name)?, - start_from: start_from_map.remove(&target.name).unwrap_or_default(), - }) - }) - .collect()) -} - -// NOTE: It might be tempting to try to deduplicate files to avoid re-processing -// a file that is reachable via multiple paths. However, this is incorrect, as -// this only happens if the file is named in multiple `#[path]` attributes, in -// which case it logically constitutes a distinct module each time it is -// referenced. -fn process_file_recursive<'a>( - scope: &rayon::Scope<'a>, - src_path: &Path, - ctx: ScannerContext, - inside_block: bool, - mut ancestors: Vec, -) { - log::trace!("process_file_recursive(src_path: {:?})", src_path); - - // Canonicalize the path to ensure we don't process the same file multiple - // times (e.g. via symlinks or different relative paths). - let src_path = match std::fs::canonicalize(src_path) { - Ok(p) => p, - Err(e) => { - // It is valid for a module to be declared but not exist (e.g., if - // it is cfg-gated for another platform). In strict Rust, we would - // check the cfg attributes, but since we are just scanning, it is - // safe to warn and return. - // - // Note: The scanner is currently CFG-agnostic. It does not evaluate - // `#[cfg(...)]` attributes, so it may attempt to scan files that - // are disabled for the current target (e.g., Windows-specific code - // on Linux). This is a known limitation that may cause build failures - // in Charon if it tries to verify non-existent items. - log::debug!("Skipping unreachable or missing file {:?}: {}", src_path, e); - return; - } - }; - - // Cycle detection: prevent infinite recursion on symlinks or recursive mod - // declarations, while still allowing identical files to be mounted in - // different branches of the tree. A cycle would represent an invalid crate - // anyway (i.e., an invalid set of `#[path]` attributes). - // - // Note: This check is purely path-based. It does not inspect the content of - // the file. - if ancestors.contains(&src_path) { - return; - } - ancestors.push(src_path.clone()); - - let result = parse::read_file_and_scan_compilation_unit( - &src_path, - inside_block, - |_src, res| match res { - Ok(mut item) => { - item.module_path.splice(0..0, ctx.current_prefix.clone()); - - let unreliable = match &item.item { - crate::parse::ParsedItem::Impl(_) => true, - crate::parse::ParsedItem::Function(f) => { - matches!( - f.item, - crate::parse::FunctionItem::Impl(..) - | crate::parse::FunctionItem::Trait(_) - | crate::parse::FunctionItem::Foreign(_) - ) - } - _ => false, - }; - let module_path = item.module_path.join("::"); - let start_from_str = if unreliable { - // For items where we cannot reliably determine the fully qualified - // name (e.g., items inside `impl` blocks or `trait` definitions), - // we must fall back to using the containing module as the - // entry point. This forces Charon to analyze the entire module, - // which is less efficient but ensures we don't miss the item. - module_path - } else { - format!("{}::{}", module_path, item.item.name().unwrap()) - }; - - use crate::parse::hkd::LiftToSafe; - // Before sending the parsed item across the thread boundary - // (from the `rayon` worker back to the main thread), we must - // "lift" the AST nodes. This internally drops the rich, - // non-`Send` `syn` trees stored in the `Local` mode, - // transforming them into the lightweight, `Send`-safe `Safe` - // equivalents (e.g., `SafeType` and `SafeSignature`). - ctx.item_tx.send((ctx.name.clone(), item.lift(), start_from_str)).unwrap(); - } - Err(e) => { - let _ = ctx.err_tx.send(miette::Report::new(e)); - } - }, - ); - - // After scanning the current file, we look for declared submodules that - // were not loaded inline (i.e., `mod foo;` instead of `mod foo { ... }`). - // `read_file_and_scan_compilation_unit` returns a list of these "unloaded" - // modules so we can resolve their paths and recurse. - let (_, unloaded_modules) = match result { - Ok(res) => res, - Err(e) => { - let _ = ctx.err_tx.send(miette::miette!("{:#}", e)); - return; - } - }; - - // Determine the directory to search for child modules in. - // - For `mod.rs`, `lib.rs`, `main.rs`, children are in the parent directory. - // e.g. `src/lib.rs` -> `mod foo` -> `src/foo.rs` - // - For `my_mod.rs`, children are in a sibling directory of the same name. - // e.g. `src/my_mod.rs` -> `mod sub` -> `src/my_mod/sub.rs` - let file_stem = src_path.file_stem().and_then(OsStr::to_str).unwrap_or(""); - let base_dir = if matches!(file_stem, "mod" | "lib" | "main") { - // If we are in `mod.rs`, `lib.rs`, or `main.rs`, then `mod foo;` - // looks for `foo.rs` or `foo/mod.rs` in the *same* directory. - // - // e.g. `src/lib.rs` -> `mod foo` -> `src/foo.rs` - src_path.parent().unwrap_or(&src_path).to_path_buf() - } else { - // If we are in `src/foo.rs`, then `mod bar;` looks for - // `src/foo/bar.rs` or `src/foo/bar/mod.rs`. - // - // e.g. src/foo.rs -> src/foo/ - src_path.with_extension("") - }; - - // Resolve and queue child modules for processing. - for module in unloaded_modules { - if let Some(mod_path) = - resolve_module_path(&base_dir, &module.name, module.path_attr.as_deref()) - { - // Spawn new tasks for discovered modules. - let mut ctx_clone = ctx.clone(); - ctx_clone.current_prefix.push(module.name); - - // TODO: Can we do something more efficient than cloning here? Maybe - // a linked list? - let ancestors = ancestors.clone(); - scope.spawn(move |s| { - process_file_recursive(s, &mod_path, ctx_clone, module.inside_block, ancestors) - }) - } else { - // This is an expected condition – it shows up when modules are - // conditionally compiled. Instead of implementing conditional - // compilation ourselves, we can just let rustc error later if - // this is actually an error. - // - // Example: `#[cfg(windows)] mod win_only;` on Linux. `win_only.rs` - // might not exist at all, or might verify successfully but be - // ignored by rustc. We just skip it here. - log::debug!("Could not resolve module '{}' in {:?}", module.name, src_path); - } - } -} - -/// Resolves a module name to a file path, checking standard Rust locations. -/// -/// * `base_dir`: The directory containing the parent file. -/// * `mod_name`: The name of the module (e.g., "foo"). -/// * `path_attr`: The optional `#[path = "..."]` attribute string. -fn resolve_module_path( - base_dir: &Path, - mod_name: &str, - path_attr: Option<&str>, -) -> Option { - log::trace!( - "resolve_module_path(base_dir: {:?}, mod_name: {:?}, path_attr: {:?})", - base_dir, - mod_name, - path_attr - ); - - // If `#[path = "..."]` is present, it overrides the standard lookup logic. - // The path is always relative to the current module's directory. - if let Some(custom_path) = path_attr { - let p = base_dir.join(custom_path); - if p.exists() { - return Some(p); - } - return None; - } - - // Standard lookup: `foo.rs` - let inline = base_dir.join(format!("{}.rs", mod_name)); - if inline.exists() { - return Some(inline); - } - - // Standard lookup: `foo/mod.rs` - let nested = base_dir.join(mod_name).join("mod.rs"); - if nested.exists() { - return Some(nested); - } - - None -} - -#[cfg(test)] -mod tests { - use std::path::PathBuf; - - use cargo_metadata::PackageName; - - use super::*; - use crate::resolve::{AnnealTargetKind, AnnealTargetName}; - - #[test] - fn test_llbc_file_name_collision() { - let name_lib = AnnealTargetName { - package_name: PackageName::new("pkg".to_string()), - target_name: "name".to_string(), - kind: AnnealTargetKind::Lib, - }; - - let artifact_lib = AnnealArtifact { - name: name_lib.clone(), - target_kind: AnnealTargetKind::Lib, - manifest_path: PathBuf::from("Cargo.toml"), - start_from: std::collections::HashSet::new(), - items: vec![], - }; - - let name_bin = AnnealTargetName { - package_name: PackageName::new("pkg".to_string()), - target_name: "name".to_string(), - kind: AnnealTargetKind::Bin, - }; - - let artifact_bin = AnnealArtifact { - name: name_bin, - target_kind: AnnealTargetKind::Bin, - manifest_path: PathBuf::from("Cargo.toml"), - start_from: std::collections::HashSet::new(), - items: vec![], - }; - - let artifact_workspace_collision = AnnealArtifact { - name: name_lib.clone(), - target_kind: AnnealTargetKind::Lib, - // A different manifest but identical package/target semantics - manifest_path: PathBuf::from("crates/other/Cargo.toml"), - start_from: std::collections::HashSet::new(), - items: vec![], - }; - - // The file names must be distinct because of the trailing hash. - assert_ne!(artifact_lib.llbc_file_name(), artifact_bin.llbc_file_name()); - assert!(artifact_lib.llbc_file_name().starts_with("PkgName")); - assert!(artifact_bin.llbc_file_name().starts_with("PkgName")); - - // Distinct `manifest_path`s must prevent collisions even when the - // package name and target semantics are otherwise identical. - assert_ne!(artifact_lib.llbc_file_name(), artifact_workspace_collision.llbc_file_name()); - assert!(artifact_workspace_collision.llbc_file_name().starts_with("PkgName")); - } - - #[test] - fn test_lean_spec_file_name_uses_slug() { - let name = AnnealTargetName { - package_name: PackageName::new("pkg-foo".to_string()), - target_name: "name-bar".to_string(), - kind: AnnealTargetKind::Lib, - }; - - let artifact = AnnealArtifact { - name, - target_kind: AnnealTargetKind::Lib, - manifest_path: PathBuf::from("Cargo.toml"), - start_from: std::collections::HashSet::new(), - items: vec![], - }; - - // Slug should be PascalCase: PkgFoo_NameBar_ - // Spec file should be slug + .lean - let spec_name = artifact.lean_spec_file_name(); - assert!(spec_name.starts_with("PkgFooNameBar")); - assert!(spec_name.ends_with(".lean")); - } } diff --git a/anneal/src/setup.rs b/anneal/src/setup.rs index b6d25ade59..9e17911f4d 100644 --- a/anneal/src/setup.rs +++ b/anneal/src/setup.rs @@ -1,213 +1,154 @@ -//! Subcommand for installing Anneal dependencies. - -use std::{path::PathBuf, process::Command}; +// Copyright 2026 The Fuchsia Authors +// +// Licensed under the 2-Clause BSD License , Apache License, Version 2.0 +// , or the MIT +// license , at your option. +// This file may not be copied, modified, or distributed except according to +// those terms. use anyhow::Context as _; pub struct SetupArgs { - pub local_archive: Option, + pub local_archive: Option, } -pub const CONFIG: exocrate::Config = exocrate::Config { - rel_dir_path: &["anneal", "toolchain"], - version_slug: env!("ANNEAL_EXOCRATE_VERSION_SLUG"), -}; +exocrate::config! { + pub const CONFIG: Config = Config { + rel_dir_path: [".anneal", "toolchain"], + versioned_files: &["../Cargo.toml", "../Cargo.lock"], + }; +} + +exocrate::parse_remote_archive! { + pub const REMOTE: RemoteArchive = "Cargo.toml" [ + (linux, x86_64), + (macos, x86_64), + (linux, aarch64), + (macos, aarch64), + ]; +} -#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Tool { + Cargo, Charon, - #[allow(dead_code)] - CharonDriver, - Aeneas, + Rustc, } impl Tool { pub fn name(&self) -> &'static str { match self { + Self::Cargo => "cargo", Self::Charon => "charon", - Self::CharonDriver => "charon-driver", - Self::Aeneas => "aeneas", + Self::Rustc => "rustc", } } - pub fn path(&self, toolchain: &Toolchain) -> PathBuf { + pub fn path(&self, toolchain: &Toolchain) -> std::path::PathBuf { match self { - Self::Charon | Self::CharonDriver | Self::Aeneas => { - toolchain.aeneas_bin_dir().join(self.name()) - } + Self::Cargo | Self::Rustc => toolchain.rust_bin().join(self.name()), + Self::Charon => toolchain.aeneas_bin_dir().join(self.name()), } } } const AENEAS_DIR: &str = "aeneas"; -const AENEAS_BACKENDS_DIR: &str = "backends"; -const AENEAS_LEAN_DIR: &str = "lean"; -const BIN_DIR: &str = "bin"; -const LIB_DIR: &str = "lib"; -const LEAN_SYSROOT: &str = "lean"; const RUST_SYSROOT: &str = "rust"; +const AENEAS_BIN_DIR: &str = "bin"; +const RUST_BIN_DIR: &str = "bin"; +const RUST_LIB_DIR: &str = "lib"; pub struct Toolchain { - pub root: PathBuf, + root: std::path::PathBuf, } impl Toolchain { pub fn resolve() -> anyhow::Result { + let location = resolve_location(); let root = CONFIG - .resolve_installation_dir(location()) + .resolve_installation_dir(location) .context("Toolchain not installed. Please run 'cargo anneal setup' first.")?; Ok(Self { root }) } - pub fn bin_dir(&self) -> PathBuf { - self.aeneas_bin_dir() - } - - pub fn cache_dir(&self) -> PathBuf { - self.root.join("lake-cache") - } - - pub fn aeneas_root(&self) -> PathBuf { - self.root.join(AENEAS_DIR) + #[cfg(all(test, feature = "exocrate_tests"))] + pub fn root(&self) -> &std::path::Path { + &self.root } - pub fn aeneas_bin_dir(&self) -> PathBuf { - self.aeneas_root().join(BIN_DIR) + pub fn aeneas_bin_dir(&self) -> std::path::PathBuf { + self.root.join(AENEAS_DIR).join(AENEAS_BIN_DIR) } - pub fn aeneas_lean_dir(&self) -> PathBuf { - self.aeneas_root().join(AENEAS_BACKENDS_DIR).join(AENEAS_LEAN_DIR) - } - - pub fn rust_sysroot(&self) -> PathBuf { + pub fn rust_sysroot(&self) -> std::path::PathBuf { self.root.join(RUST_SYSROOT) } - pub fn rust_bin(&self) -> PathBuf { - self.rust_sysroot().join(BIN_DIR) + pub fn rust_bin(&self) -> std::path::PathBuf { + self.rust_sysroot().join(RUST_BIN_DIR) } - pub fn rust_lib(&self) -> PathBuf { - self.rust_sysroot().join(LIB_DIR) + pub fn rust_lib(&self) -> std::path::PathBuf { + self.rust_sysroot().join(RUST_LIB_DIR) } - pub fn lean_sysroot(&self) -> PathBuf { - self.root.join(LEAN_SYSROOT) + pub fn command(&self, tool: Tool) -> anyhow::Result { + let mut cmd = std::process::Command::new(tool.path(self)); + cmd.env_clear(); + match tool { + Tool::Cargo | Tool::Rustc => {} + Tool::Charon => { + // The archive supplies Rust tools, but not host build tools + // such as the linker. Keep the caller's `PATH` after our Rust + // bin directory so Cargo builds use the managed Rust toolchain + // while still finding those host tools. + cmd.env("CHARON_TOOLCHAIN_IS_IN_PATH", "1") + .env("PATH", prepend_current_path(self.rust_bin())?) + .env(rust_library_path_env_var(), self.rust_lib()); + } + } + Ok(cmd) } +} - pub fn lean_bin(&self) -> PathBuf { - self.lean_sysroot().join(BIN_DIR) +fn prepend_current_path(path: std::path::PathBuf) -> anyhow::Result { + let mut paths = vec![path]; + if let Some(current_path) = std::env::var_os("PATH") { + paths.extend(std::env::split_paths(¤t_path)); } + std::env::join_paths(paths).context("failed to construct PATH for tool command") +} - pub fn command(&self, tool: Tool) -> Command { - if std::env::var("ANNEAL_USE_PATH_FOR_TOOLS").is_ok() { - Command::new(tool.name()) - } else { - Command::new(tool.path(self)) - } - } +/// Returns the platform library search path variable used by Rust tools. +pub(crate) fn rust_library_path_env_var() -> &'static str { + if cfg!(target_os = "macos") { "DYLD_LIBRARY_PATH" } else { "LD_LIBRARY_PATH" } } pub fn run_setup(args: SetupArgs) -> anyhow::Result<()> { - let local_archive = args - .local_archive - .or_else(|| std::env::var_os("ANNEAL_SETUP_LOCAL_ARCHIVE").map(PathBuf::from)); - let source = match local_archive { + let location = resolve_location(); + let source = match args.local_archive { Some(local_archive) => exocrate::Source::Local(local_archive), - None => exocrate::Source::Remote(remote_archive()), + None => exocrate::Source::Remote(REMOTE), }; - let installation_dir = CONFIG - .resolve_installation_dir_or_install(location(), source) + let (installation_dir, status) = CONFIG + .resolve_installation_dir_or_install(location, source) .context("failed to resolve-or-install dependencies")?; - log::info!("anneal toolchain is installed at {:?}", installation_dir); + match status { + exocrate::ResolvedOrInstalled::ResolvedExisting => { + log::warn!("anneal toolchain was already installed at {:?}", installation_dir); + } + exocrate::ResolvedOrInstalled::NewlyInstalled => { + log::info!("anneal toolchain freshly installed at {:?}", installation_dir); + } + } Ok(()) } -fn location() -> exocrate::Location { - if let Some(dir) = std::env::var_os("ANNEAL_TOOLCHAIN_DIR") { - exocrate::Location::Custom(PathBuf::from(dir)) - } else if std::env::var("__ZEROCOPY_LOCAL_DEV").is_ok() - || std::env::var("__ANNEAL_LOCAL_DEV").is_ok() - { +fn resolve_location() -> exocrate::Location { + if std::env::var("__ANNEAL_LOCAL_DEV").is_ok() { exocrate::Location::LocalDev } else { exocrate::Location::UserGlobal } } - -fn remote_archive() -> exocrate::RemoteArchive { - match (std::env::consts::OS, std::env::consts::ARCH) { - ("linux", "x86_64") => remote_archive_for( - env!("ANNEAL_EXOCRATE_LINUX_X86_64_URL"), - env!("ANNEAL_EXOCRATE_LINUX_X86_64_SHA256"), - ), - ("macos", "x86_64") => remote_archive_for( - env!("ANNEAL_EXOCRATE_MACOS_X86_64_URL"), - env!("ANNEAL_EXOCRATE_MACOS_X86_64_SHA256"), - ), - ("linux", "aarch64") => remote_archive_for( - env!("ANNEAL_EXOCRATE_LINUX_AARCH64_URL"), - env!("ANNEAL_EXOCRATE_LINUX_AARCH64_SHA256"), - ), - ("macos", "aarch64") => remote_archive_for( - env!("ANNEAL_EXOCRATE_MACOS_AARCH64_URL"), - env!("ANNEAL_EXOCRATE_MACOS_AARCH64_SHA256"), - ), - (os, arch) => panic!("unsupported platform: {os}-{arch}"), - } -} - -fn remote_archive_for(url: &'static str, sha256: &'static str) -> exocrate::RemoteArchive { - exocrate::RemoteArchive { - url, - sha256: decode_hex(sha256).expect("package.metadata.exocrate sha256 must be valid hex"), - } -} - -fn decode_hex(s: &str) -> Option<[u8; 32]> { - let bytes = s.as_bytes(); - if bytes.len() != 64 { - return None; - } - let mut res = [0u8; 32]; - for i in 0..32 { - let h_nib = decode_nibble(bytes[i * 2])?; - let l_nib = decode_nibble(bytes[i * 2 + 1])?; - res[i] = (h_nib << 4) | l_nib; - } - Some(res) -} - -fn decode_nibble(c: u8) -> Option { - match c { - b'0'..=b'9' => Some(c - b'0'), - b'a'..=b'f' => Some(c - b'a' + 10), - b'A'..=b'F' => Some(c - b'A' + 10), - _ => None, - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn tool_paths_use_omnibus_layout() { - let toolchain = Toolchain { root: PathBuf::from("/tmp/toolchain") }; - - assert_eq!(toolchain.bin_dir(), PathBuf::from("/tmp/toolchain/aeneas/bin")); - assert_eq!( - toolchain.aeneas_lean_dir(), - PathBuf::from("/tmp/toolchain/aeneas/backends/lean") - ); - assert_eq!( - Tool::Charon.path(&toolchain), - PathBuf::from("/tmp/toolchain/aeneas/bin/charon") - ); - assert_eq!( - Tool::Aeneas.path(&toolchain), - PathBuf::from("/tmp/toolchain/aeneas/bin/aeneas") - ); - } -} diff --git a/anneal/src/util.rs b/anneal/src/util.rs index fe085b37dc..602e1585d1 100644 --- a/anneal/src/util.rs +++ b/anneal/src/util.rs @@ -1,16 +1,25 @@ -use std::path::PathBuf; +// Copyright 2026 The Fuchsia Authors +// +// Licensed under the 2-Clause BSD License , Apache License, Version 2.0 +// , or the MIT +// license , at your option. +// This file may not be copied, modified, or distributed except according to +// those terms. -use anyhow::{Context, Result}; -use fs2::FileExt; +use std::io::BufRead as _; + +use anyhow::Context as _; +use fs2::FileExt as _; /// Represents an active, exclusive lock on a directory. /// /// This struct guarantees that the process holds an OS-level file lock /// guarding the specified directory. -pub struct DirLock { +pub(crate) struct DirLock { /// The path to the directory being guarded. - pub path: PathBuf, - // Kept alive to hold the flock + pub(crate) path: std::path::PathBuf, + // Kept alive to hold the flock. _file: std::fs::File, } @@ -22,17 +31,29 @@ impl DirLock { /// the directory itself to avoid platform-specific issues with /// directory locking and to ensure the lock file persists even if /// the directory is cleaned. - pub fn lock_exclusive(path: PathBuf) -> Result { + pub(crate) fn lock_exclusive(path: std::path::PathBuf) -> anyhow::Result { let file = Self::open_lock_file(&path)?; file.lock_exclusive() .with_context(|| format!("Failed to acquire exclusive lock on {:?}", path))?; Ok(Self { path, _file: file }) } - fn open_lock_file(path: &std::path::Path) -> Result { + /// Acquires a shared lock on the specified directory. + /// + /// Multiple processes can hold shared locks simultaneously, but an + /// exclusive lock will block until all shared locks are released. + #[cfg(any(test, feature = "exocrate_tests"))] + pub(crate) fn lock_shared(path: std::path::PathBuf) -> anyhow::Result { + let file = Self::open_lock_file(&path)?; + file.lock_shared() + .with_context(|| format!("Failed to acquire shared lock on {:?}", path))?; + Ok(Self { path, _file: file }) + } + + fn open_lock_file(path: &std::path::Path) -> anyhow::Result { let lock_path = path.join(".lock"); - // Ensure the directory exists + // Ensure the directory exists. if let Some(parent) = lock_path.parent() { std::fs::create_dir_all(parent).with_context(|| { format!("Failed to create directory for lock file: {:?}", parent) @@ -57,3 +78,206 @@ impl DirLock { } } } + +pub(crate) struct ProcessOutput { + pub status: std::process::ExitStatus, + pub stderr_lines: Vec, +} + +/// Spawns a child process, drains its stderr in a background thread, and processes +/// its stdout line-by-line in the main thread while showing a progress spinner. +pub(crate) fn run_command_with_progress( + mut cmd: std::process::Command, + pb: Option, + mut process_stdout_line: F, +) -> anyhow::Result +where + F: FnMut(&str, Option<&indicatif::ProgressBar>) -> anyhow::Result<()>, +{ + cmd.stdout(std::process::Stdio::piped()); + cmd.stderr(std::process::Stdio::piped()); + + let mut child = cmd.spawn().context("Failed to spawn child process")?; + + let stderr_buffer = std::sync::Arc::new(std::sync::Mutex::new(Vec::new())); + let stderr_buffer_clone = std::sync::Arc::clone(&stderr_buffer); + + let mut stderr_thread = None; + if let Some(stderr) = child.stderr.take() { + stderr_thread = Some(std::thread::spawn(move || { + let reader = std::io::BufReader::new(stderr); + for line in reader.lines().map_while(Result::ok) { + stderr_buffer_clone.lock().unwrap().push(line); + } + })); + } + + if let Some(ref p) = pb { + p.enable_steady_tick(std::time::Duration::from_millis(100)); + } + + if let Some(stdout) = child.stdout.take() { + let reader = std::io::BufReader::new(stdout); + for line in reader.lines().map_while(Result::ok) { + process_stdout_line(&line, pb.as_ref())?; + if let Some(ref p) = pb { + p.tick(); + } + } + } + + if let Some(ref p) = pb { + p.finish_and_clear(); + } + + let status = child.wait().context("Failed to wait for child process")?; + + if let Some(thread) = stderr_thread { + let _ = thread.join(); + } + + let stderr_lines = std::sync::Arc::try_unwrap(stderr_buffer).unwrap().into_inner().unwrap(); + + Ok(ProcessOutput { status, stderr_lines }) +} + +/// Performs a lock test action according to the `role` of the current actor. Actors may: +/// +/// - Obtain an exclusive or shared lock for `lock_dir`, +/// - Log actions in `log_file`, +/// - Wait for a signal from `sig_file`. +/// +/// Individual tests compose multiple role-based actions and verify the resulting action log. +#[cfg(feature = "exocrate_tests")] +pub(crate) fn run_test_lock_helper( + role: &str, + lock_dir: &std::path::Path, + log_file: &std::path::Path, + sig_file: &std::path::Path, +) -> anyhow::Result<()> { + use std::io::Write as _; + + let append_log = |msg: &str| -> anyhow::Result<()> { + let mut file = std::fs::OpenOptions::new().create(true).append(true).open(log_file)?; + writeln!(file, "{}", msg)?; + Ok(()) + }; + + let wait_for_sig = || -> anyhow::Result<()> { + let start = std::time::Instant::now(); + while !sig_file.exists() { + if start.elapsed() > std::time::Duration::from_secs(3) { + anyhow::bail!("Timeout waiting for signal file {:?}", sig_file); + } + std::thread::sleep(std::time::Duration::from_millis(50)); + } + Ok(()) + }; + + match role { + "reader-a" => { + let _lock = DirLock::lock_shared(lock_dir.to_path_buf())?; + append_log("SHARED_START_A")?; + wait_for_sig()?; + append_log("SHARED_END_A")?; + } + "reader-b" => { + let _lock = DirLock::lock_shared(lock_dir.to_path_buf())?; + append_log("SHARED_START_B")?; + std::fs::write(sig_file, "")?; + append_log("SHARED_END_B")?; + } + "writer-a" => { + let _lock = DirLock::lock_exclusive(lock_dir.to_path_buf())?; + append_log("EXCLUSIVE_START_A")?; + wait_for_sig()?; + append_log("EXCLUSIVE_END_A")?; + } + "reader-exclusion" => { + std::fs::write(sig_file, "")?; + let _lock = DirLock::lock_shared(lock_dir.to_path_buf())?; + append_log("SHARED_START_B")?; + append_log("SHARED_END_B")?; + } + _ => anyhow::bail!("Unknown test-lock-helper role: {}", role), + } + + Ok(()) +} + +#[cfg(test)] +#[macro_export] +macro_rules! workspace_fixture { + ($dir:expr, { $($path:expr => $content:expr),* $(,)? }) => {{ + let root = $dir.path(); + $( + let file_path = root.join($path); + if let Some(parent) = file_path.parent() { + std::fs::create_dir_all(parent).unwrap(); + } + std::fs::write(&file_path, $content).unwrap(); + )* + }}; +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_dir_lock_exclusive_mutual_exclusion() { + let temp_dir = tempfile::tempdir().unwrap(); + let lock_path = temp_dir.path().to_path_buf(); + + let barrier = std::sync::Arc::new(std::sync::Barrier::new(2)); + let barrier_clone = std::sync::Arc::clone(&barrier); + let lock_path_clone = lock_path.clone(); + + let lock_released = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)); + let lock_released_clone = std::sync::Arc::clone(&lock_released); + + // Thread A acquires the lock. + let thread_a = std::thread::spawn(move || { + let _lock = DirLock::lock_exclusive(lock_path_clone).expect("Failed to lock exclusive"); + barrier_clone.wait(); // Signal Thread B that A holds the lock. + + // Simulate brief work holding the lock. + std::thread::sleep(std::time::Duration::from_millis(100)); + lock_released_clone.store(true, std::sync::atomic::Ordering::Relaxed); + // _lock drops here, releasing the lock. + }); + + // Thread B waits for Thread A to acquire the lock, then tries to acquire it itself. + let thread_b = std::thread::spawn(move || { + barrier.wait(); // Wait for Thread A to acquire lock. + + // Attempt to acquire lock. This should block until Thread A releases it. + let _lock = DirLock::lock_exclusive(lock_path).expect("Failed to lock exclusive in B"); + + // Assert that B only successfully locked the directory AFTER A released it. + assert!( + lock_released.load(std::sync::atomic::Ordering::Relaxed), + "Thread B acquired lock before Thread A released it!" + ); + }); + + thread_a.join().unwrap(); + thread_b.join().unwrap(); + } + + #[test] + fn test_dir_lock_shared_coexistence() { + let temp_dir = tempfile::tempdir().unwrap(); + let lock_path = temp_dir.path().to_path_buf(); + + // Thread A acquires shared lock. + let lock_a = DirLock::lock_shared(lock_path.clone()).expect("Failed to lock shared"); + + // Thread B should be able to acquire shared lock immediately without blocking. + let lock_b = DirLock::lock_shared(lock_path).expect("Failed to lock shared concurrently"); + + // Both locks are held. + drop(lock_a); + drop(lock_b); + } +} diff --git a/anneal/v2/tests/test_prune_lake_cache.py b/anneal/tests/test_prune_lake_cache.py similarity index 100% rename from anneal/v2/tests/test_prune_lake_cache.py rename to anneal/tests/test_prune_lake_cache.py diff --git a/anneal/v2/tests/test_rewrite_lake_vendor.py b/anneal/tests/test_rewrite_lake_vendor.py similarity index 100% rename from anneal/v2/tests/test_rewrite_lake_vendor.py rename to anneal/tests/test_rewrite_lake_vendor.py diff --git a/anneal/.gitattributes b/anneal/v1/.gitattributes similarity index 100% rename from anneal/.gitattributes rename to anneal/v1/.gitattributes diff --git a/anneal/.gitignore b/anneal/v1/.gitignore similarity index 100% rename from anneal/.gitignore rename to anneal/v1/.gitignore diff --git a/anneal/v1/AGENTS.md b/anneal/v1/AGENTS.md new file mode 100644 index 0000000000..18854514c3 --- /dev/null +++ b/anneal/v1/AGENTS.md @@ -0,0 +1,92 @@ + + +# Anneal V1 agent guide + +> **Historical prototype:** This file applies only to `anneal/v1/`. Anneal V2 +> is the clean-room redesign in the parent directory. V1 code, syntax, and +> architecture are not V2 defaults. Read the [V2 agent guide](../AGENTS.md) and +> [distilled V1 lessons](../agent_docs/history/v1-lessons.md) before carrying an +> idea from this subtree into V2. + +> **Note to AI Agents:** This document is intended for agents **developing the +> V1 prototype** itself. If you are an agent **using V1**, please refer to the +> `llms.txt` and `docs/agent/` directory instead. + +## Basic commands + +1. Run with `cargo run verify`. +2. Run unit tests with `cargo test --bin cargo-anneal`. +3. Run all tests (including integration tests) with `./docker.sh cargo test`. +4. Run a *specific* integration test fixture with `./docker.sh cargo test --test integration fixture_name`. + +## Tips + +1. Integration tests are expensive. Prefer `cargo test --bin cargo-anneal` for quick verification and iteration, + and only run `./docker.sh cargo test --test integration` when you need to verify the integration tests. +2. To see the generated Lean code for a module, use `cargo run expand`. This will run Aeneas and Anneal but skip verification, outputting the generated `.lean` definitions to the terminal. + ```bash + cargo run expand --example abs + ``` +3. To see where intermediate artifacts are placed on disk, run with `RUST_LOG=cargo_anneal=trace` as a fallback: + ```bash + RUST_LOG=cargo_anneal=trace cargo run verify --example abs + ``` + +## Integration Testing + +1. **Updating Expected Output:** Anneal integration tests (stored in `tests/fixtures/`) assert against an `expected.stderr` or `out.txt` file. When you intentionally change the behavior or output of a test, do not edit these files manually. Instead, run the integration test with `BLESS=1` to automatically overwrite the snapshot files: + ```bash + BLESS=1 ./docker.sh cargo test --test integration fixture_name + ``` + +2. **Allowing `sorry`:** While developing and ensuring Aeneas translates Rust correctly, you don't have to write the full Lean proof immediately. You can write `sorry` inside the `proof` block. However, you must pass `--allow-sorry` to Anneal so the verifier doesn't fail immediately on the unimplemented proof. For integration tests, this is done by adding `--allow-sorry` to the `args` array in the fixture's `anneal.toml`: + ```toml + args = ["verify", "--allow-sorry"] + ``` + +## Running Tests in Docker + +Due to the complex toolchains required by Anneal (Rust, Lean 4, Aeneas, and Charon), it is recommended to run integration tests within the provided Docker container. The container handles all system dependencies and provides an isolated environment for the test runner. + +The `docker.sh` script should be used. It handles building and caching the Docker image, mounting compilation cache volumes, mapping local user IDs (so that files are not owned by `root` on your host), and forwarding environment variables. + +Simply prefix your normal Cargo tests with `./docker.sh`: +```bash +./docker.sh cargo test --test integration + +# You can pass standard environment variables. They will be forwarded! +BLESS=1 ./docker.sh cargo test --test integration +``` + +*(Under the hood, this evaluates your path and places you in the same working directory inside the container's bound `/workspace` volume)* + +## Debugging Tips + +1. **Debugging Aeneas vs Anneal Mismatches** + - If you encounter Lean type mismatches (e.g., `Application type mismatch`), it's often a mismatch between what Aeneas generated for the function and what Anneal assumed Aeneas generated in the theorem signature. + - Standard integration tests (`./docker.sh cargo test --test integration`) delete their temporary output directory (`/cache/anneal_target/anneal-test-XXX` inside Docker) upon completion. + - If a test fails and you want to inspect the generated Lean code, use the `ANNEAL_KEEP_TEST_DIR=1` environment variable. + - The test runner will preserve the directory and print its path to stderr: + ```bash + ANNEAL_KEEP_TEST_DIR=1 ./docker.sh cargo test --test integration macro_edge_cases + ``` + - **Note:** Because the test runner operates inside Docker, the printed path will reside within the container's isolated `/cache` volume. To explore it, you must open a shell inside the container: + ```bash + ./docker.sh bash + # Then inside the container, navigate to the path printed to stderr: + cd /cache/anneal_target/anneal-test-XXX + ``` + - Look at `Funs.lean` to see the actual Aeneas parameters, and `[TestName].lean` to see the Anneal theorem signature. + +2. **Caches Need Busting** + - Sometimes `cargo test` fails with bizarre syntax errors that persist despite fixing the code. + - If this happens, clear the integration caches. If you are using `./docker.sh`, the caches reside inside the container's `/cache` volume, so you must clear them from within by running: + ```bash + ./docker.sh rm -rf /cache/anneal_target/anneal_integration_cache + ``` diff --git a/anneal/v2/Cargo.lock b/anneal/v1/Cargo.lock similarity index 68% rename from anneal/v2/Cargo.lock rename to anneal/v1/Cargo.lock index cd9c673ac1..04bcfa1bdb 100644 --- a/anneal/v2/Cargo.lock +++ b/anneal/v1/Cargo.lock @@ -17,18 +17,6 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - [[package]] name = "aho-corasick" version = "1.1.4" @@ -40,30 +28,14 @@ dependencies = [ [[package]] name = "annotate-snippets" -version = "0.12.16" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f211a51805bc641f3ad5b7664c77d2547af685cc33b4cd8d31964027a46f13f1" +checksum = "710e8eae58854cdc1790fcb56cca04d712a17be849eeb81da2a724bf4bae2bc4" dependencies = [ "anstyle", - "memchr", "unicode-width 0.2.2", ] -[[package]] -name = "anstream" -version = "0.6.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" -dependencies = [ - "anstyle", - "anstyle-parse 0.2.7", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - [[package]] name = "anstream" version = "1.0.0" @@ -71,7 +43,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", - "anstyle-parse 1.0.0", + "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", @@ -85,15 +57,6 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" -[[package]] -name = "anstyle-parse" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" -dependencies = [ - "utf8parse", -] - [[package]] name = "anstyle-parse" version = "1.0.0" @@ -129,26 +92,11 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" -[[package]] -name = "ar_archive_writer" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340b" -dependencies = [ - "object", -] - -[[package]] -name = "arrayvec" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" - [[package]] name = "assert_cmd" -version = "2.2.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aa3a22042e45de04255c7bf3626e239f450200fd0493c1e382263544b20aea6" +checksum = "9a686bbee5efb88a82df0621b236e74d925f470e5445d3220a5648b892ec99c9" dependencies = [ "anstyle", "bstr", @@ -159,20 +107,11 @@ dependencies = [ "wait-timeout", ] -[[package]] -name = "atomic-polyfill" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" -dependencies = [ - "critical-section", -] - [[package]] name = "autocfg" -version = "1.5.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "backtrace" @@ -204,11 +143,26 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bitflags" -version = "2.12.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84d7ced0ae9557296835c32bf1b1e02b44c746701f898460fb000d7eaa84f00a" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "block-buffer" @@ -219,15 +173,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "brownstone" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5839ee4f953e811bfdcf223f509cb2c6a3e1447959b0bff459405575bc17f22" -dependencies = [ - "arrayvec", -] - [[package]] name = "bstr" version = "1.12.1" @@ -241,15 +186,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" - -[[package]] -name = "byteorder" -version = "1.5.0" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "bytes" @@ -268,13 +207,16 @@ dependencies = [ [[package]] name = "cargo-anneal" -version = "0.1.0-alpha.22" +version = "0.1.0-alpha.24" dependencies = [ "anyhow", - "cargo_metadata", - "charon", + "assert_cmd", + "cargo_metadata 0.23.1", "clap", "clap-cargo", + "console", + "dashmap", + "datatest-stable", "env_logger", "exocrate", "fs2", @@ -282,26 +224,57 @@ dependencies = [ "log", "miette", "pathdiff", + "predicates", + "proc-macro2", + "quote", "rayon", + "regex", "serde", "serde_json", "sha2", + "similar", + "strip-ansi-escapes", + "syn", "tempfile", - "thiserror", - "toml_const", + "thiserror 2.0.18", + "toml", + "ui_test", "walkdir", ] [[package]] name = "cargo-platform" -version = "0.3.3" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0061da739915fae12ea00e16397555ed4371a6bb285431aab930f61b0aa4ba" +checksum = "87a0c0e6148f11f01f32650a2ea02d532b2ad4e81d8bd41e6e565b5adc5e6082" dependencies = [ "serde", "serde_core", ] +[[package]] +name = "cargo_metadata" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +dependencies = [ + "camino", + "cargo-platform 0.1.9", + "semver", + "serde", + "serde_json", + "thiserror 1.0.69", +] + [[package]] name = "cargo_metadata" version = "0.23.1" @@ -309,18 +282,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef987d17b0a113becdd19d3d0022d04d7ef41f9efe4f3fb63ac44ba61df3ade9" dependencies = [ "camino", - "cargo-platform", + "cargo-platform 0.3.2", "semver", "serde", "serde_json", - "thiserror", + "thiserror 2.0.18", ] [[package]] name = "cc" -version = "1.2.63" +version = "1.2.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" +checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" dependencies = [ "find-msvc-tools", "jobserver", @@ -334,62 +307,11 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" -[[package]] -name = "charon" -version = "0.1.210" -source = "git+https://github.com/AeneasVerif/charon.git?tag=nightly-2026.06.03#0c91ca1a8e002d6bfa8d8f1f452804fce2f92cf1" -dependencies = [ - "annotate-snippets", - "anstream 0.6.21", - "anyhow", - "assert_cmd", - "clap", - "colored", - "convert_case", - "derive_generic_visitor", - "either", - "env_logger", - "extension-traits", - "fraction", - "hashbrown 0.15.5", - "hax-adt-into", - "index_vec", - "indexmap", - "indoc", - "itertools", - "lazy_static", - "log", - "macros", - "nom", - "nom-supreme", - "num-bigint", - "paste", - "petgraph", - "postcard", - "rustc-hash", - "rustc_version", - "serde", - "serde_json", - "serde_stacker", - "serde_state", - "smallvec", - "stacker", - "strip-ansi-escapes", - "take_mut", - "tempfile", - "toml 0.8.23", - "tracing", - "tracing-subscriber", - "tracing-tree", - "ustr", - "which", -] - [[package]] name = "clap" -version = "4.6.1" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" dependencies = [ "clap_builder", "clap_derive", @@ -402,7 +324,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "936551935c8258754bb8216aec040957d261f977303754b9bf1a213518388006" dependencies = [ "anstyle", - "cargo_metadata", + "cargo_metadata 0.23.1", "clap", "serde", "serde_json", @@ -414,7 +336,7 @@ version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ - "anstream 1.0.0", + "anstream", "anstyle", "clap_lex", "strsim", @@ -422,14 +344,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.6.1" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -439,12 +361,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] -name = "cobs" +name = "color-eyre" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5920befb47832a6d61ee3a3a846565cfa39b331331e68a3b1d1116630f2f26d" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +checksum = "b8b88ea9df13354b55bc7234ebcce36e6ef896aca2e42a15de9e10edce01b427" dependencies = [ - "thiserror", + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", ] [[package]] @@ -455,14 +395,19 @@ checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "colored" -version = "2.2.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" +checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "lazy_static", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] +[[package]] +name = "comma" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55b672471b4e9f9e95499ea597ff64941a309b2cdbffcc46f2cc5e2d971fd335" + [[package]] name = "console" version = "0.16.3" @@ -475,15 +420,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "convert_case" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "cpufeatures" version = "0.2.17" @@ -503,10 +439,13 @@ dependencies = [ ] [[package]] -name = "critical-section" -version = "1.2.0" +name = "crossbeam-channel" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] [[package]] name = "crossbeam-deque" @@ -544,70 +483,29 @@ dependencies = [ ] [[package]] -name = "darling" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.117", -] - -[[package]] -name = "darling_macro" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" -dependencies = [ - "darling_core", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "deranged" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" -dependencies = [ - "powerfmt", -] - -[[package]] -name = "derive_generic_visitor" -version = "0.2.0" +name = "dashmap" +version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21588db07753dafe6460b13faad18bd7e46548ce91ee38f0bc0f7529b3864b82" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" dependencies = [ - "derive_generic_visitor_macros", + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", ] [[package]] -name = "derive_generic_visitor_macros" -version = "0.2.0" +name = "datatest-stable" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3aed1890235ebcf844a24263ecc395c4e57a970f13bcc9948989f22aa051aa7" +checksum = "a867d7322eb69cf3a68a5426387a25b45cb3b9c5ee41023ee6cea92e2afadd82" dependencies = [ - "convert_case", - "darling", - "itertools", - "proc-macro2", - "quote", - "syn 2.0.117", + "camino", + "fancy-regex", + "libtest-mimic", + "walkdir", ] [[package]] @@ -648,22 +546,18 @@ dependencies = [ ] [[package]] -name = "either" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" - -[[package]] -name = "embedded-io" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" +name = "doc_gen" +version = "0.1.0" +dependencies = [ + "tree-sitter", + "tree-sitter-lean4", +] [[package]] -name = "embedded-io" -version = "0.6.1" +name = "either" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "encode_unicode" @@ -681,19 +575,13 @@ dependencies = [ "regex", ] -[[package]] -name = "env_home" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" - [[package]] name = "env_logger" version = "0.11.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" dependencies = [ - "anstream 1.0.0", + "anstream", "anstyle", "env_filter", "jiff", @@ -716,6 +604,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "escape8259" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5692dd7b5a1978a5aeb0ce83b7655c58ca8efdcb79d21036ea249da95afec2c6" + [[package]] name = "exocrate" version = "0.1.0" @@ -732,48 +626,41 @@ dependencies = [ ] [[package]] -name = "ext-trait" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d772df1c1a777963712fb68e014235e80863d6a91a85c4e06ba2d16243a310e5" -dependencies = [ - "ext-trait-proc_macros", -] - -[[package]] -name = "ext-trait-proc_macros" -version = "1.0.1" +name = "eyre" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ab7934152eaf26aa5aa9f7371408ad5af4c31357073c9e84c3b9d7f11ad639a" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", + "indenter", + "once_cell", ] [[package]] -name = "extension-traits" -version = "1.0.1" +name = "fancy-regex" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a296e5a895621edf9fa8329c83aa1cb69a964643e36cf54d8d7a69b789089537" +checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298" dependencies = [ - "ext-trait", + "bit-set", + "regex-automata", + "regex-syntax", ] [[package]] name = "fastrand" -version = "2.4.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "filetime" -version = "0.2.29" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" +checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" dependencies = [ "cfg-if", "libc", + "libredox", ] [[package]] @@ -782,12 +669,6 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" -[[package]] -name = "fixedbitset" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" - [[package]] name = "flate2" version = "1.1.9" @@ -799,10 +680,13 @@ dependencies = [ ] [[package]] -name = "fnv" -version = "1.0.7" +name = "float-cmp" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" +dependencies = [ + "num-traits", +] [[package]] name = "foldhash" @@ -810,16 +694,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" -[[package]] -name = "fraction" -version = "0.15.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e076045bb43dac435333ed5f04caf35c7463631d0dae2deb2638d94dd0a5b872" -dependencies = [ - "lazy_static", - "num", -] - [[package]] name = "fs2" version = "0.4.3" @@ -830,30 +704,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "futures-core" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" - -[[package]] -name = "futures-task" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" - -[[package]] -name = "futures-util" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" -dependencies = [ - "futures-core", - "futures-task", - "pin-project-lite", - "slab", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -907,13 +757,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" [[package]] -name = "hash32" -version = "0.2.1" +name = "hashbrown" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" -dependencies = [ - "byteorder", -] +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hashbrown" @@ -926,35 +773,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.17.1" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" - -[[package]] -name = "hax-adt-into" -version = "0.3.5" -source = "git+https://github.com/AeneasVerif/charon.git?tag=nightly-2026.06.03#0c91ca1a8e002d6bfa8d8f1f452804fce2f92cf1" -dependencies = [ - "itertools", - "proc-macro2", - "quote", - "syn 1.0.109", - "tracing", -] - -[[package]] -name = "heapless" -version = "0.7.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" -dependencies = [ - "atomic-polyfill", - "hash32", - "rustc_version", - "serde", - "spin", - "stable_deref_trait", -] +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "heck" @@ -964,9 +785,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "http" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ "bytes", "itoa", @@ -985,34 +806,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" [[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "indent_write" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cfe9645a18782869361d9c8732246be7b410ad4e919d3609ebabdac00ba12c3" - -[[package]] -name = "index_vec" -version = "0.1.4" +name = "indenter" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44faf5bb8861a9c72e20d3fb0fdbd59233e43056e2b80475ab0aacdc2e781355" -dependencies = [ - "serde", -] +checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" [[package]] name = "indexmap" -version = "2.14.0" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" dependencies = [ "equivalent", - "hashbrown 0.17.1", + "hashbrown 0.16.1", "serde", "serde_core", ] @@ -1031,15 +837,6 @@ dependencies = [ "web-time", ] -[[package]] -name = "indoc" -version = "2.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" -dependencies = [ - "rustversion", -] - [[package]] name = "is_ci" version = "1.2.0" @@ -1052,15 +849,6 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" -[[package]] -name = "itertools" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.18" @@ -1069,9 +857,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jiff" -version = "0.2.28" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4603d3033e49e2b0e31229fcab20a5d40089c607d975cd9c80551dc69eed9102" +checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" dependencies = [ "jiff-static", "log", @@ -1082,13 +870,13 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.28" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "782d32378dddf207193ac91cefb848ad41abb58195c95168e1291227a0832b47" +checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] @@ -1101,20 +889,12 @@ dependencies = [ "libc", ] -[[package]] -name = "joinery" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72167d68f5fce3b8655487b8038691a3c9984ee769590f93f2a631f4ad64e4f5" - [[package]] name = "js-sys" -version = "0.3.99" +version = "0.3.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" +checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" dependencies = [ - "cfg-if", - "futures-util", "once_cell", "wasm-bindgen", ] @@ -1131,19 +911,40 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" +[[package]] +name = "levenshtein" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" + [[package]] name = "libc" -version = "0.2.186" +version = "0.2.184" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" [[package]] name = "libredox" -version = "0.1.17" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" +checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" dependencies = [ + "bitflags", "libc", + "plain", + "redox_syscall 0.7.3", +] + +[[package]] +name = "libtest-mimic" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14e6ba06f0ade6e504aff834d7c34298e5155c6baca353cc6a4aaff2f9fd7f33" +dependencies = [ + "anstream", + "anstyle", + "clap", + "escape8259", ] [[package]] @@ -1163,34 +964,15 @@ dependencies = [ [[package]] name = "log" -version = "0.4.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "113b30b4cd05f7c06868fdb2854f66a7b9fece9a48425351cd532e810d74024f" - -[[package]] -name = "macros" -version = "0.1.0" -source = "git+https://github.com/AeneasVerif/charon.git?tag=nightly-2026.06.03#0c91ca1a8e002d6bfa8d8f1f452804fce2f92cf1" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "matchers" -version = "0.2.0" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" -dependencies = [ - "regex-automata", -] +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "memchr" -version = "2.8.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "miette" @@ -1219,15 +1001,9 @@ checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] -[[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.8.9" @@ -1239,106 +1015,10 @@ dependencies = [ ] [[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "nom-supreme" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bd3ae6c901f1959588759ff51c95d24b491ecb9ff91aa9c2ef4acc5b1dcab27" -dependencies = [ - "brownstone", - "indent_write", - "joinery", - "memchr", - "nom", -] - -[[package]] -name = "nu-ansi-term" -version = "0.50.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "num" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" -dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", -] - -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-complex" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-conv" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.2" +name = "normalize-line-endings" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" -dependencies = [ - "num-bigint", - "num-integer", - "num-traits", -] +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" [[package]] name = "num-traits" @@ -1349,15 +1029,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "num_threads" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" -dependencies = [ - "libc", -] - [[package]] name = "object" version = "0.37.3" @@ -1391,16 +1062,6 @@ version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - [[package]] name = "parking_lot_core" version = "0.9.12" @@ -1409,17 +1070,11 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.18", "smallvec", "windows-link", ] -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - [[package]] name = "pathdiff" version = "0.2.3" @@ -1432,61 +1087,6 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" -[[package]] -name = "petgraph" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" -dependencies = [ - "fixedbitset", - "hashbrown 0.15.5", - "indexmap", - "serde", -] - -[[package]] -name = "phf" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" -dependencies = [ - "phf_macros", - "phf_shared", - "serde", -] - -[[package]] -name = "phf_generator" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" -dependencies = [ - "fastrand", - "phf_shared", -] - -[[package]] -name = "phf_macros" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" -dependencies = [ - "phf_generator", - "phf_shared", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "phf_shared" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" -dependencies = [ - "siphasher", -] - [[package]] name = "pin-project-lite" version = "0.2.17" @@ -1495,9 +1095,15 @@ checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pkg-config" -version = "0.3.33" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "plain" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] name = "portable-atomic" @@ -1507,32 +1113,13 @@ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" -version = "0.2.7" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +checksum = "091397be61a01d4be58e7841595bd4bfedb15f1cd54977d79b8271e94ed799a3" dependencies = [ "portable-atomic", ] -[[package]] -name = "postcard" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" -dependencies = [ - "cobs", - "embedded-io 0.4.0", - "embedded-io 0.6.1", - "heapless", - "serde", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - [[package]] name = "predicates" version = "3.1.4" @@ -1541,7 +1128,10 @@ checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe" dependencies = [ "anstyle", "difflib", + "float-cmp", + "normalize-line-endings", "predicates-core", + "regex", ] [[package]] @@ -1554,10 +1144,19 @@ checksum = "cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144" name = "predicates-tree" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2" +checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2" +dependencies = [ + "predicates-core", + "termtree", +] + +[[package]] +name = "prettydiff" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac17546d82912e64874e3d5b40681ce32eac4e5834344f51efcf689ff1550a65" dependencies = [ - "predicates-core", - "termtree", + "owo-colors", ] [[package]] @@ -1567,7 +1166,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.117", + "syn", ] [[package]] @@ -1579,16 +1178,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "psm" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645dbe486e346d9b5de3ef16ede18c26e6c70ad97418f4874b8b1889d6e761ea" -dependencies = [ - "ar_archive_writer", - "cc", -] - [[package]] name = "quote" version = "1.0.45" @@ -1612,9 +1201,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rayon" -version = "1.12.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" dependencies = [ "either", "rayon-core", @@ -1639,6 +1228,15 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_syscall" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" +dependencies = [ + "bitflags", +] + [[package]] name = "redox_users" version = "0.5.2" @@ -1647,7 +1245,7 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.17", "libredox", - "thiserror", + "thiserror 2.0.18", ] [[package]] @@ -1699,12 +1297,6 @@ version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" -[[package]] -name = "rustc-hash" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" - [[package]] name = "rustc_version" version = "0.4.1" @@ -1714,6 +1306,18 @@ dependencies = [ "semver", ] +[[package]] +name = "rustfix" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82fa69b198d894d84e23afde8e9ab2af4400b2cba20d6bf2b428a8b01c222c5a" +dependencies = [ + "serde", + "serde_json", + "thiserror 1.0.69", + "tracing", +] + [[package]] name = "rustix" version = "1.1.4" @@ -1729,9 +1333,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.40" +version = "0.23.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ "log", "once_cell", @@ -1744,9 +1348,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ "zeroize", ] @@ -1785,9 +1389,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "semver" -version = "1.0.28" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" dependencies = [ "serde", "serde_core", @@ -1820,15 +1424,16 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn", ] [[package]] name = "serde_json" -version = "1.0.150" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ + "indexmap", "itoa", "memchr", "serde", @@ -1845,45 +1450,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_spanned" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" -dependencies = [ - "serde_core", -] - -[[package]] -name = "serde_stacker" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4936375d50c4be7eff22293a9344f8e46f323ed2b3c243e52f89138d9bb0f4a" -dependencies = [ - "serde", - "serde_core", - "stacker", -] - -[[package]] -name = "serde_state" -version = "1.0.0" -source = "git+https://github.com/Nadrieril/serde_state?branch=main#9bffbc1b28fd93533bbd3ca835385f29cb51888f" -dependencies = [ - "serde", - "serde_state_derive", -] - -[[package]] -name = "serde_state_derive" -version = "1.0.0" -source = "git+https://github.com/Nadrieril/serde_state?branch=main#9bffbc1b28fd93533bbd3ca835385f29cb51888f" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "sha2" version = "0.10.9" @@ -1912,9 +1478,9 @@ dependencies = [ [[package]] name = "shlex" -version = "2.0.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "simd-adler32" @@ -1923,16 +1489,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] -name = "siphasher" -version = "1.0.3" +name = "similar" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" - -[[package]] -name = "slab" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" [[package]] name = "smallvec" @@ -1941,32 +1501,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] -name = "spin" -version = "0.9.8" +name = "spanned" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +checksum = "c92d4b0c055fde758f086eb4a6e73410247df8a3837fd606d2caeeaf72aa566d" dependencies = [ - "lock_api", + "anyhow", + "bstr", + "color-eyre", ] [[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[package]] -name = "stacker" -version = "0.1.24" +name = "streaming-iterator" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "640c8cdd92b6b12f5bcb1803ca3bbf5ab96e5e6b6b96b9ab77dabe9e880b3190" -dependencies = [ - "cc", - "cfg-if", - "libc", - "psm", - "windows-sys 0.61.2", -] +checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520" [[package]] name = "strip-ansi-escapes" @@ -2010,17 +1559,6 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.117" @@ -2032,12 +1570,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "take_mut" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" - [[package]] name = "tar" version = "0.4.46" @@ -2090,64 +1622,51 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.18" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", ] [[package]] -name = "thiserror-impl" +name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", + "thiserror-impl 2.0.18", ] [[package]] -name = "thread_local" -version = "1.1.9" +name = "thiserror-impl" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ - "cfg-if", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "time" -version = "0.3.47" +name = "thiserror-impl" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ - "deranged", - "itoa", - "libc", - "num-conv", - "num_threads", - "powerfmt", - "serde_core", - "time-core", - "time-macros", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "time-core" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" - -[[package]] -name = "time-macros" -version = "0.2.27" +name = "thread_local" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ - "num-conv", - "time-core", + "cfg-if", ] [[package]] @@ -2157,50 +1676,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.11", + "serde_spanned", + "toml_datetime", "toml_edit", ] -[[package]] -name = "toml" -version = "0.9.12+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" -dependencies = [ - "indexmap", - "serde_core", - "serde_spanned 1.1.1", - "toml_datetime 0.7.5+spec-1.1.0", - "toml_parser", - "toml_writer", - "winnow 0.7.15", -] - -[[package]] -name = "toml_const" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389864cf501669b741746e9bc35e66c67f2b80266482ad04c452e2410433f205" -dependencies = [ - "phf", - "toml 0.9.12+spec-1.1.0", - "toml_const_macros", -] - -[[package]] -name = "toml_const_macros" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3e0f202adfb96027640ad9786a0657775414482cfd840c19e9957ada8cfbbfd" -dependencies = [ - "indexmap", - "proc-macro2", - "quote", - "syn 2.0.117", - "toml 0.9.12+spec-1.1.0", -] - [[package]] name = "toml_datetime" version = "0.6.11" @@ -2210,15 +1690,6 @@ dependencies = [ "serde", ] -[[package]] -name = "toml_datetime" -version = "0.7.5+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" -dependencies = [ - "serde_core", -] - [[package]] name = "toml_edit" version = "0.22.27" @@ -2227,19 +1698,10 @@ checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.11", + "serde_spanned", + "toml_datetime", "toml_write", - "winnow 0.7.15", -] - -[[package]] -name = "toml_parser" -version = "1.1.2+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" -dependencies = [ - "winnow 1.0.3", + "winnow", ] [[package]] @@ -2248,12 +1710,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" -[[package]] -name = "toml_writer" -version = "1.1.1+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" - [[package]] name = "tracing" version = "0.1.44" @@ -2261,21 +1717,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", - "tracing-attributes", "tracing-core", ] -[[package]] -name = "tracing-attributes" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "tracing-core" version = "0.1.36" @@ -2287,14 +1731,13 @@ dependencies = [ ] [[package]] -name = "tracing-log" -version = "0.2.0" +name = "tracing-error" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" dependencies = [ - "log", - "once_cell", - "tracing-core", + "tracing", + "tracing-subscriber", ] [[package]] @@ -2303,35 +1746,72 @@ version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex-automata", "sharded-slab", - "smallvec", "thread_local", - "tracing", "tracing-core", - "tracing-log", ] [[package]] -name = "tracing-tree" -version = "0.4.0" -source = "git+https://github.com/Nadrieril/tracing-tree#841286bfffd3c2200810244506cd127013dbeff9" +name = "tree-sitter" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78f873475d258561b06f1c595d93308a7ed124d9977cb26b148c2084a4a3cc87" dependencies = [ - "nu-ansi-term", - "time", - "tracing-core", - "tracing-log", - "tracing-subscriber", + "cc", + "regex", + "regex-syntax", + "serde_json", + "streaming-iterator", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-language" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "009994f150cc0cd50ff54917d5bc8bffe8cad10ca10d81c34da2ec421ae61782" + +[[package]] +name = "tree-sitter-lean4" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d274c5b559d3a46a0d5d200fc0593fa3fda035304fa794d5d4a760fbf69c2a5e" +dependencies = [ + "cc", + "tree-sitter", ] [[package]] name = "typenum" -version = "1.20.1" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "ui_test" +version = "0.30.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ada249620d81f010b9a1472b63a5077ac7c722dd0f4bacf6528b313d0b8c15d8" +dependencies = [ + "annotate-snippets", + "anyhow", + "bstr", + "cargo-platform 0.1.9", + "cargo_metadata 0.18.1", + "color-eyre", + "colored", + "comma", + "crossbeam-channel", + "indicatif", + "levenshtein", + "prettydiff", + "regex", + "rustc_version", + "rustfix", + "serde", + "serde_json", + "spanned", +] [[package]] name = "unicode-ident" @@ -2347,9 +1827,9 @@ checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" [[package]] name = "unicode-segmentation" -version = "1.13.3" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" [[package]] name = "unicode-width" @@ -2410,19 +1890,6 @@ dependencies = [ "log", ] -[[package]] -name = "ustr" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18b19e258aa08450f93369cf56dd78063586adf19e92a75b338a800f799a0208" -dependencies = [ - "ahash", - "byteorder", - "lazy_static", - "parking_lot", - "serde", -] - [[package]] name = "utf8-zero" version = "0.8.1" @@ -2483,11 +1950,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.3+wasi-0.2.9" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ - "wit-bindgen 0.57.1", + "wit-bindgen", ] [[package]] @@ -2496,14 +1963,14 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "wit-bindgen 0.51.0", + "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.122" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" +checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" dependencies = [ "cfg-if", "once_cell", @@ -2514,9 +1981,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.122" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" +checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2524,22 +1991,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.122" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" +checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.117", + "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.122" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" +checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" dependencies = [ "unicode-ident", ] @@ -2590,25 +2057,13 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.7" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" dependencies = [ "rustls-pki-types", ] -[[package]] -name = "which" -version = "7.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d643ce3fd3e5b54854602a080f34fb10ab75e0b813ee32d00ca2b44fa74762" -dependencies = [ - "either", - "env_home", - "rustix", - "winsafe", -] - [[package]] name = "winapi" version = "0.3.9" @@ -2655,15 +2110,6 @@ dependencies = [ "windows-targets", ] -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] - [[package]] name = "windows-sys" version = "0.61.2" @@ -2746,18 +2192,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "winnow" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" - -[[package]] -name = "winsafe" -version = "0.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" - [[package]] name = "wit-bindgen" version = "0.51.0" @@ -2767,12 +2201,6 @@ dependencies = [ "wit-bindgen-rust-macro", ] -[[package]] -name = "wit-bindgen" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" - [[package]] name = "wit-bindgen-core" version = "0.51.0" @@ -2794,7 +2222,7 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn 2.0.117", + "syn", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -2810,7 +2238,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.117", + "syn", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -2862,26 +2290,6 @@ dependencies = [ "rustix", ] -[[package]] -name = "zerocopy" -version = "0.8.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "zeroize" version = "1.8.2" diff --git a/anneal/v1/Cargo.toml b/anneal/v1/Cargo.toml new file mode 100644 index 0000000000..f545b31c8e --- /dev/null +++ b/anneal/v1/Cargo.toml @@ -0,0 +1,112 @@ +[workspace] +members = [".", "tools/doc_gen"] + +[package] +name = "cargo-anneal" +edition = "2024" +version = "0.1.0-alpha.24" +description = "Formally verify that your safety comments are correct." +categories = [ + "development-tools::cargo-plugins", + "development-tools::testing", + "compilers", + "mathematics", + "security", +] +keywords = ["verification", "cargo", "plugin", "unsafe", "lean"] +license = "BSD-2-Clause OR Apache-2.0 OR MIT" +repository = "https://github.com/google/zerocopy/tree/main/anneal/v1" +publish = true + +exclude = [".*", "testdata"] + +# FIXME: Replace these placeholder archive URLs and hashes before publishing +# this crate; `cargo anneal setup` uses this metadata by default when callers +# do not provide a local archive. +[package.metadata.exocrate.linux.x86_64] +sha256 = "d3d7bbcdfd2645f10e3e64a85b3e848197033992337c969a45576a3d0ec517d9" +url = "https://github.com/google/zerocopy/releases/download/anneal-toolchains-v0.1.0-alpha.24-27079750833-09497849a10d/anneal-toolchain-linux-x86_64.tar.zst" + +[package.metadata.exocrate.macos.x86_64] +sha256 = "1252f15262d11f49795c8ed182a8b47f2ae4e627d90babb2f10636349f686438" +url = "https://github.com/google/zerocopy/releases/download/anneal-toolchains-v0.1.0-alpha.24-27079750833-09497849a10d/anneal-toolchain-macos-x86_64.tar.zst" + +[package.metadata.exocrate.linux.aarch64] +sha256 = "a9c496fcf21670b2306d02722b9afcf375149315e929d53b6a7585d4daa19710" +url = "https://github.com/google/zerocopy/releases/download/anneal-toolchains-v0.1.0-alpha.24-27079750833-09497849a10d/anneal-toolchain-linux-aarch64.tar.zst" + +[package.metadata.exocrate.macos.aarch64] +sha256 = "b4e0a5b420eb441e37564c365f06c215f61a3b825fc3df37293da7c3010d2b81" +url = "https://github.com/google/zerocopy/releases/download/anneal-toolchains-v0.1.0-alpha.24-27079750833-09497849a10d/anneal-toolchain-macos-aarch64.tar.zst" + +[dependencies] +anyhow = "1.0.102" +cargo_metadata = "0.23.1" +clap = { version = "4.6.0", features = ["derive"] } +clap-cargo = { version = "0.18.3", features = ["cargo_metadata"] } +dashmap = "6.1.0" +env_logger = "0.11.10" +log = "0.4.29" +miette = { version = "7.6.0", features = ["derive", "fancy"] } +proc-macro2 = { version = "1.0.106", features = ["span-locations"] } +rayon = "1.11.0" +serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.149" +syn = { version = "2.0.117", features = [ + "full", + "visit", + "extra-traits", + "parsing", + "printing", +] } +quote = "1.0" +thiserror = "2.0.18" +walkdir = "2.5.0" +indicatif = { version = "0.18.4", features = ["improved_unicode"] } +console = "0.16.3" +exocrate = { path = "../../exocrate" } +sha2 = "0.10" +fs2 = "0.4" +pathdiff = "0.2.3" +toml = "0.8" +tempfile = "3.27.0" + +[build-dependencies] +sha2 = "0.10" +toml = "0.8" + +[dev-dependencies] +syn = { version = "2.0.117", features = [ + "printing", + "full", + "visit", + "extra-traits", + "parsing", +] } +proc-macro2 = { version = "1.0.106", features = ["span-locations"] } +ui_test = "0.30.4" +assert_cmd = "2.2.0" + +predicates = "3.1.4" + +datatest-stable = "0.3.3" +serde = { version = "1.0", features = ["derive"] } +toml = "0.8" +regex = "1.0" +strip-ansi-escapes = "0.2.1" +similar = "2.7.0" + +[package.metadata.build_rs] + +# The commit hash of the Aeneas repository to use. +# +# FIXME: Add a CI step to verify that this commit exists and matches the +# toolchain version. +aeneas_rev = "42c0e90dacf486f7d3ed5b6cde3a9a81f04915a4" + +# The Lean toolchain version to use. This must match the version of Lean used +# by Aeneas in the `lean-toolchain` file in the commit above. +lean_toolchain = "leanprover/lean4:v4.30.0-rc2" +[[test]] +name = "integration" +harness = false diff --git a/anneal/Dockerfile b/anneal/v1/Dockerfile similarity index 94% rename from anneal/Dockerfile rename to anneal/v1/Dockerfile index 37a9a03d84..6a0c6482c9 100644 --- a/anneal/Dockerfile +++ b/anneal/v1/Dockerfile @@ -1,6 +1,6 @@ FROM python:3.11-slim AS extractor WORKDIR /app -COPY anneal/Cargo.toml ./ +COPY anneal/v1/Cargo.toml ./ # Remove the [workspace] section so it can be built as a standalone package. # Also inject tree-sitter dependencies used by doc_gen to cache them too. RUN sed '1,2d' Cargo.toml > Cargo.toml.no_workspace && \ @@ -51,17 +51,17 @@ ENV ANNEAL_INTEGRATION_TARGET_DIR=/cache/anneal_target # via a volume mount, causing dependencies to be rebuilt once in the volume. # This double-build is intentional to support both environments efficiently. ENV CARGO_TARGET_DIR=/opt/anneal_target -WORKDIR /workspace/anneal +WORKDIR /workspace/anneal/v1 # Copy the workspace configuration (without workspace section) and lockfile. COPY --from=extractor --chown=anneal:anneal /app/Cargo.toml.no_workspace ./Cargo.toml -COPY --chown=anneal:anneal anneal/Cargo.lock ./ +COPY --chown=anneal:anneal anneal/v1/Cargo.lock ./ # Copy source files needed to compile dependency-heavy modules. -COPY --chown=anneal:anneal anneal/src/setup.rs ./src/ +COPY --chown=anneal:anneal anneal/v1/src/setup.rs ./src/ COPY --chown=anneal:anneal exocrate/Cargo.toml /workspace/exocrate/Cargo.toml COPY --chown=anneal:anneal exocrate/src /workspace/exocrate/src -COPY --chown=anneal:anneal anneal/build.rs ./ +COPY --chown=anneal:anneal anneal/v1/build.rs ./ # Create a minimal `main.rs` and a dummy test file. # This allows us to build dependencies in the image without copying the full diff --git a/anneal/v1/LICENSE-APACHE b/anneal/v1/LICENSE-APACHE new file mode 120000 index 0000000000..1cd601d0a3 --- /dev/null +++ b/anneal/v1/LICENSE-APACHE @@ -0,0 +1 @@ +../../LICENSE-APACHE \ No newline at end of file diff --git a/anneal/v1/LICENSE-BSD b/anneal/v1/LICENSE-BSD new file mode 120000 index 0000000000..0af6dcc0d3 --- /dev/null +++ b/anneal/v1/LICENSE-BSD @@ -0,0 +1 @@ +../../LICENSE-BSD \ No newline at end of file diff --git a/anneal/v1/LICENSE-MIT b/anneal/v1/LICENSE-MIT new file mode 120000 index 0000000000..b2cfbdc7b0 --- /dev/null +++ b/anneal/v1/LICENSE-MIT @@ -0,0 +1 @@ +../../LICENSE-MIT \ No newline at end of file diff --git a/anneal/v1/README.md b/anneal/v1/README.md new file mode 100644 index 0000000000..a6e6bd5554 --- /dev/null +++ b/anneal/v1/README.md @@ -0,0 +1,224 @@ +# Anneal + +> **Historical V1 prototype:** This directory preserves Anneal V1 for +> maintenance, reproducibility, and design evidence. The active clean-room V2 +> redesign lives at [`anneal/`](../README.md). V1's syntax, architecture, +> soundness claims, and proof methodology are not commitments for V2. Coding +> agents comparing V1 with the current redesign must begin with the +> [current agent guide](../AGENTS.md). + + + +

logo by tinyneonspark

+ +> **Note:** Anneal is currently in pre-alpha. You're welcome to use it, but many things +> are broken or unsound, and we will change APIs frequently. + +For safe code, Rust promises that "if it compiles, then it is memory-safe." Anneal promises the same for unsafe code. + +Rust promises that all safe Rust code is free of memory safety bugs. It does this by encoding the requirements for memory safety in the type system, ensuring that if a program type checks, then it is free of memory safety bugs. The lifetime system prevents use-after-free bugs, the ownership system prevents double-free bugs, and so on. **There do not exist type-checked safe Rust programs with memory safety bugs.**[^1] The same is not true of unsafe Rust code. **There do exist type-checked unsafe Rust programs with memory safety bugs.** + +[^1]: Barring compiler and platform bugs, which do exist. + +One way to understand this distinction is that a safe Rust function must be free of memory safety bugs *no matter what value is passed in*. Consider [`usize::strict_div`](https://doc.rust-lang.org/std/primitive.usize.html#method.strict_div): + +```rust +impl usize { + /// # Panics + /// + /// This function will panic if `rhs` is zero. + pub fn strict_div(self, rhs: usize) -> usize; +} +``` + +A safe implementation of `strict_div` would be forced to handle *all possible values* of `self` and `rhs`. `strict_div` must handle the case where `rhs == 0` (division by zero). It could choose to return an arbitrary `usize` or, as it does in practice, panic. But it cannot exhibit a memory safety bug – Rust would prevent such a program from compiling. + +By contrast, consider the unsafe [`usize::unchecked_div_exact`](https://doc.rust-lang.org/std/primitive.usize.html#method.unchecked_div_exact): + +```rust +impl usize { + /// # Safety + /// + /// This results in undefined behavior when `rhs == 0` or `self % rhs != 0`. + pub unsafe fn unchecked_div_exact(self, rhs: usize) -> usize; +} +``` + +Rust doesn't have any way to epxress "a pair of `usize`s such `rhs != 0` and `self % rhs == 0`". Thus, the type signature of `unchecked_div_exact` is *too permissive*. While it prevents some illegal values statically (e.g., the type system will not permit you to pass `-1`), some illegal values will compile just fine, and will result in memory safety bugs at runtime: + +```rust +let res: usize = unsafe { 1usize.unchecked_div_exact(0) }; +``` + +In this view, unsafe Rust differs from safe Rust in that **some type-safe values are still illegal**, while in safe Rust, **all type-safe values are legal** (where "illegal" means "will result in a memory safety bug"). + +As we saw before, Rust ensures that **no type-checked safe Rust programs contain memory safety bugs.** Anneal provides the same promise, but for unsafe Rust code. Just as Rust encodes memory safety requirements in its types, Anneal encodes memory safety requirements in its annotations. Unlike Rust types, Anneal annotations are powerful enough to prevent *any* illegal values from compiling, even in unsafe code. In this sense, Anneal annotations are an *extension* to Rust's type system. + +For example, here's how Anneal would encode the safety precondition of `unchecked_div_exact`, preventing buggy programs from compiling: + +```rust +impl usize { + /// # Safety + /// + /// ```anneal + /// requires(nonzero): rhs != 0 + /// requires(divides): self % rhs == 0 + /// ``` + pub unsafe fn unchecked_div_exact(self, rhs: usize) -> usize; +} + +let res: usize = unsafe { 1usize.unchecked_div_exact(0) }; // ERROR: Cannot prove `rhs != 0` +``` + +Anneal removes most `unsafe` code from your [trusted computing base](https://en.wikipedia.org/wiki/Trusted_computing_base) (TCB).* + +| | Rust | Anneal | +|-| ---- | ------ | +| | `cargo check` | `cargo anneal verify` | +| **Types** | Rust types | Rust types + Anneal annotations | +| **Guarantee** | Safe code is free of memory safety bugs | Safe and unsafe code is free of memory safety bugs* | +| **Caveat** | Unsafe code is unchecked | Code outside of the Rust Abstract Machine (AM) is unchecked* | +| **TCB** | Rust toolchain, unsafe code | Rust + Anneal toolchains, code outside of the Rust AM* | + +\* *Anneal cannot verify the soundness of code constructs which exist outside of the Rust Abstract Machine (AM), such as FFI or inline assembly. The programmer must axiomatically assert the behavior of this code, and it remains in the TCB. Note that Rust code which **calls** these constructs (e.g., code which calls a function implemented in C) **can** be verified – Anneal removes this code from the TCB.* + +### Development Philosophy + +Anneal is a [formal verification](https://en.wikipedia.org/wiki/Formal_verification) tool. However, it diverges from many formal verification tools in its philosophy for how it should be used by end users. + +While exceptions exist, the most common form of formal verification has historically been *post-hoc verification*, in which verification is applied to a software artifact which already exists and which was not specifically written with verification in mind. This approach comes with a number of assumptions and limitations which we aim to sidestep: +1. Assumes that verification requires distinct expertise which developers lack +2. Verification is applied to a codebase whose structure may not be amenable to verification +3. Results in long iteration cycles, in which feedback from verification is obtained long after a line of code is written +4. Without extra infrastructure (e.g. in CI), software codebase and verification artifacts (specifications and proofs) can drift, requiring periodic efforts to update the verification to "catch up" with recent code changes + +We believe that all of these can be addressed by thinking of specifications as merely an extension to the type system rather than a parallel artifact. In particular: +1. Developers already have justifications – albeit undocumented – for their belief that their code is correct. Type annotations simply ask them to make these justifications explicit +2. As with any form of static typing, Anneal annotations will shape the codebase and nudge developers towards certain patterns for code organization which are more amenable to verification. This nudging will happen in the developer's inner loop and so will be much more effective than feedback from post-hoc verification +3. We expect `cargo anneal verify` to be just as frequent as `cargo check`, and so iteration cycles are extremely short +4. CI can simply run `cargo anneal verify` to ensure that specifications and proofs never drift or bit rot + +### Human and Agentic Development + +Anneal is designed for use by both human engineers and AI coding agents. By providing machine-checked guarantees for safe and `unsafe` code, Anneal eliminates the cognitive burden of manual review and enables the safe acceleration of systems software development. We have [demonstrated](https://drive.google.com/file/d/1areyf438L0izETTHj7PRMnoSHSX4kM29/view?usp=sharing) that Antigravity can author `unsafe` Rust code and prove its soundness using Anneal. + +Without Anneal: + +```rust +struct PositiveUsize { + // INVARIANT: x > 0 + x: usize, +} + +impl PositiveUsize { + /// Creates a new `PositiveUsize` if `x > 0`. + pub fn new(x: usize) -> Option { + if x > 0 { + // SAFETY: We checked that x > 0. + Some(Self { x }) + } else { + None + } + } +} + +impl std::ops::Div for usize { + type Output = usize; + + fn div(self, rhs: PositiveUsize) -> Self::Output { + // SAFETY: The type invariant of `PositiveUsize` guarantees that + // `rhs.x > 0`. This makes division by zero impossible. + unsafe { std::intrinsics::unchecked_div(self, rhs.x) } + } +} +``` + +With Anneal: + +```rust +/// ```anneal +/// isValid self := self.val.val > 0 +/// ``` +pub struct PositiveUsize { + pub val: usize, +} + +impl PositiveUsize { + /// Creates a new `PositiveUsize` if `x > 0`. + /// + /// ```anneal + /// ensures: + /// match ret with + /// | none => x.val = 0 + /// | some r => r.val.val = x.val + /// ``` + pub fn new(x: usize) -> Option { + if x > 0 { + Some(Self { val: x }) + } else { + None + } + } +} + +/// ```anneal +/// proof (h_progress): +/// unfold div_positive +/// rcases h_req with ⟨h_self_val_is_valid, h_rhs_is_valid⟩ +/// have ho := unchecked_div.spec self_val rhs.val { +/// h_a_is_valid := h_self_val_is_valid +/// h_b_is_valid := by verify_is_valid h_b_is_valid _root_.positive_usize.div_positive +/// h_anon := by simp_all [Anneal.IsValid.isValid] +/// } +/// rcases Aeneas.Std.WP.spec_imp_exists ho with ⟨y, h_eq, _⟩ +/// exact ⟨y, h_eq⟩ +/// ``` +fn div_positive(self_val: usize, rhs: PositiveUsize) -> usize { + unsafe { self_val.unchecked_div(rhs.val) } +} + +impl std::ops::Div for usize { + type Output = usize; + + fn div(self, rhs: PositiveUsize) -> usize { + unsafe { unchecked_div(self_val, rhs.val) } + } +} +``` + +## Installation + +Install Anneal and its required toolchains (Charon and Aeneas): + +```bash +cargo install cargo-anneal@0.1.0-alpha.24 +cargo anneal setup +``` + +## Quick Start + +Write a specification for your function using Anneal annotations in a doc comment: + +```rust +/// ```anneal +/// requires: x.val < Usize.max +/// ensures: ret.val = x.val + 1 +/// proof: +/// scalar_tac +/// ``` +pub fn add_one(x: usize) -> usize { + x + 1 +} +``` + +Verify your crate: + +```bash +cargo anneal verify +``` + +## Usage & Commands + +- **`cargo anneal verify`**: Verifies the target crate. +- **`cargo anneal expand`**: Outputs the generated Lean code without running full verification (useful for debugging). +- **`cargo anneal generate`**: Generates the `.lean` files on disk, allowing you to iterate on proofs using standard Lean tooling before copying them back to Rust source. diff --git a/anneal/build.rs b/anneal/v1/build.rs similarity index 100% rename from anneal/build.rs rename to anneal/v1/build.rs diff --git a/anneal/docker.sh b/anneal/v1/docker.sh similarity index 97% rename from anneal/docker.sh rename to anneal/v1/docker.sh index d0d9ae3085..740f788df6 100755 --- a/anneal/docker.sh +++ b/anneal/v1/docker.sh @@ -36,10 +36,10 @@ fi # Resolve the directory paths required to build the image and mount the # workspace volume into the container. The Docker build context is the repo -# worktree root so the Dockerfile can copy both `anneal/` and the root-level +# worktree root so the Dockerfile can copy both `anneal/v1/` and the root-level # `exocrate/` crate. ANNEAL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" -WORKTREE_DIR="$(cd "$ANNEAL_DIR/.." >/dev/null 2>&1 && pwd)" +WORKTREE_DIR="$(cd "$ANNEAL_DIR/../.." >/dev/null 2>&1 && pwd)" # To avoid pollution between different git worktrees, we generate a unique # worktree ID and store it in a file. This ensures each worktree gets its own # isolated cache volume and Docker image tag. @@ -187,7 +187,7 @@ done # Determine the user's current working directory relative to the repository. # This path is passed to Docker so that the container executes the requested # command in the same relative directory as the caller. -REL_PATH=$(realpath --relative-to="$WORKTREE_DIR" "$(pwd)" 2>/dev/null || echo "anneal") +REL_PATH=$(realpath --relative-to="$WORKTREE_DIR" "$(pwd)" 2>/dev/null || echo "anneal/v1") WORKDIR="/workspace/$REL_PATH" exec "${DOCKER_CMD[@]}" run "${DOCKER_FLAGS[@]}" \ diff --git a/anneal/docs/agent/01_philosophy_and_pipeline.md b/anneal/v1/docs/agent/01_philosophy_and_pipeline.md similarity index 100% rename from anneal/docs/agent/01_philosophy_and_pipeline.md rename to anneal/v1/docs/agent/01_philosophy_and_pipeline.md diff --git a/anneal/docs/agent/02_rust_to_lean_mapping.md b/anneal/v1/docs/agent/02_rust_to_lean_mapping.md similarity index 100% rename from anneal/docs/agent/02_rust_to_lean_mapping.md rename to anneal/v1/docs/agent/02_rust_to_lean_mapping.md diff --git a/anneal/docs/agent/03_memory_model.md b/anneal/v1/docs/agent/03_memory_model.md similarity index 100% rename from anneal/docs/agent/03_memory_model.md rename to anneal/v1/docs/agent/03_memory_model.md diff --git a/anneal/docs/agent/04_specifications_and_syntax.md b/anneal/v1/docs/agent/04_specifications_and_syntax.md similarity index 100% rename from anneal/docs/agent/04_specifications_and_syntax.md rename to anneal/v1/docs/agent/04_specifications_and_syntax.md diff --git a/anneal/docs/agent/05_proof_architecture.md b/anneal/v1/docs/agent/05_proof_architecture.md similarity index 100% rename from anneal/docs/agent/05_proof_architecture.md rename to anneal/v1/docs/agent/05_proof_architecture.md diff --git a/anneal/docs/agent/06_tactics_and_tooling.md b/anneal/v1/docs/agent/06_tactics_and_tooling.md similarity index 100% rename from anneal/docs/agent/06_tactics_and_tooling.md rename to anneal/v1/docs/agent/06_tactics_and_tooling.md diff --git a/anneal/docs/agent/07_workflow.md b/anneal/v1/docs/agent/07_workflow.md similarity index 100% rename from anneal/docs/agent/07_workflow.md rename to anneal/v1/docs/agent/07_workflow.md diff --git a/anneal/docs/design/design.md b/anneal/v1/docs/design/design.md similarity index 100% rename from anneal/docs/design/design.md rename to anneal/v1/docs/design/design.md diff --git a/anneal/docs/design/named_bounds.md b/anneal/v1/docs/design/named_bounds.md similarity index 100% rename from anneal/docs/design/named_bounds.md rename to anneal/v1/docs/design/named_bounds.md diff --git a/anneal/docs/images/logo.svg b/anneal/v1/docs/images/logo.svg similarity index 100% rename from anneal/docs/images/logo.svg rename to anneal/v1/docs/images/logo.svg diff --git a/anneal/examples/abs.rs b/anneal/v1/examples/abs.rs similarity index 100% rename from anneal/examples/abs.rs rename to anneal/v1/examples/abs.rs diff --git a/anneal/examples/anatomy.rs b/anneal/v1/examples/anatomy.rs similarity index 100% rename from anneal/examples/anatomy.rs rename to anneal/v1/examples/anatomy.rs diff --git a/anneal/examples/checked_add.rs b/anneal/v1/examples/checked_add.rs similarity index 100% rename from anneal/examples/checked_add.rs rename to anneal/v1/examples/checked_add.rs diff --git a/anneal/examples/const_generics.rs b/anneal/v1/examples/const_generics.rs similarity index 100% rename from anneal/examples/const_generics.rs rename to anneal/v1/examples/const_generics.rs diff --git a/anneal/examples/design_doc.rs b/anneal/v1/examples/design_doc.rs similarity index 100% rename from anneal/examples/design_doc.rs rename to anneal/v1/examples/design_doc.rs diff --git a/anneal/examples/linked_list.rs b/anneal/v1/examples/linked_list.rs similarity index 100% rename from anneal/examples/linked_list.rs rename to anneal/v1/examples/linked_list.rs diff --git a/anneal/examples/namespaces.rs b/anneal/v1/examples/namespaces.rs similarity index 100% rename from anneal/examples/namespaces.rs rename to anneal/v1/examples/namespaces.rs diff --git a/anneal/examples/never_type.rs b/anneal/v1/examples/never_type.rs similarity index 100% rename from anneal/examples/never_type.rs rename to anneal/v1/examples/never_type.rs diff --git a/anneal/examples/nopanic.rs b/anneal/v1/examples/nopanic.rs similarity index 100% rename from anneal/examples/nopanic.rs rename to anneal/v1/examples/nopanic.rs diff --git a/anneal/examples/positive_usize.rs b/anneal/v1/examples/positive_usize.rs similarity index 100% rename from anneal/examples/positive_usize.rs rename to anneal/v1/examples/positive_usize.rs diff --git a/anneal/examples/ptr_concat.rs b/anneal/v1/examples/ptr_concat.rs similarity index 100% rename from anneal/examples/ptr_concat.rs rename to anneal/v1/examples/ptr_concat.rs diff --git a/anneal/examples/size_of_align_of.rs b/anneal/v1/examples/size_of_align_of.rs similarity index 100% rename from anneal/examples/size_of_align_of.rs rename to anneal/v1/examples/size_of_align_of.rs diff --git a/anneal/examples/swap.rs b/anneal/v1/examples/swap.rs similarity index 100% rename from anneal/examples/swap.rs rename to anneal/v1/examples/swap.rs diff --git a/anneal/examples/unchecked_get.rs b/anneal/v1/examples/unchecked_get.rs similarity index 100% rename from anneal/examples/unchecked_get.rs rename to anneal/v1/examples/unchecked_get.rs diff --git a/anneal/examples/update_max.rs b/anneal/v1/examples/update_max.rs similarity index 100% rename from anneal/examples/update_max.rs rename to anneal/v1/examples/update_max.rs diff --git a/anneal/llms-full.txt b/anneal/v1/llms-full.txt similarity index 100% rename from anneal/llms-full.txt rename to anneal/v1/llms-full.txt diff --git a/anneal/llms.txt b/anneal/v1/llms.txt similarity index 100% rename from anneal/llms.txt rename to anneal/v1/llms.txt diff --git a/anneal/src/Anneal.lean b/anneal/v1/src/Anneal.lean similarity index 100% rename from anneal/src/Anneal.lean rename to anneal/v1/src/Anneal.lean diff --git a/anneal/src/aeneas.rs b/anneal/v1/src/aeneas.rs similarity index 100% rename from anneal/src/aeneas.rs rename to anneal/v1/src/aeneas.rs diff --git a/anneal/src/charon.rs b/anneal/v1/src/charon.rs similarity index 100% rename from anneal/src/charon.rs rename to anneal/v1/src/charon.rs diff --git a/anneal/src/diagnostics.rs b/anneal/v1/src/diagnostics.rs similarity index 100% rename from anneal/src/diagnostics.rs rename to anneal/v1/src/diagnostics.rs diff --git a/anneal/src/errors.rs b/anneal/v1/src/errors.rs similarity index 100% rename from anneal/src/errors.rs rename to anneal/v1/src/errors.rs diff --git a/anneal/src/generate.rs b/anneal/v1/src/generate.rs similarity index 100% rename from anneal/src/generate.rs rename to anneal/v1/src/generate.rs diff --git a/anneal/v1/src/main.rs b/anneal/v1/src/main.rs new file mode 100644 index 0000000000..3d363b977d --- /dev/null +++ b/anneal/v1/src/main.rs @@ -0,0 +1,184 @@ +mod aeneas; +mod charon; +mod diagnostics; +mod errors; +mod generate; +mod parse; +mod resolve; +mod scanner; +mod validate; + +mod setup; +mod ui_test_shim; +mod util; + +use clap::Parser; + +/// Anneal: A Literate Verification Toolchain +#[derive(Parser, Debug)] +#[command(name = "cargo-anneal", version, about, long_about = None)] +struct Cli { + #[command(subcommand)] + command: Commands, +} + +#[derive(clap::Subcommand, Debug)] +enum Commands { + /// Verify a crate + Verify(resolve::Args), + /// Setup Anneal dependencies + Setup(resolve::SetupArgs), + /// Expand a crate's Lean output + Expand(ExpandArgs), + /// Generate Lean workspace and print paths without building + Generate(resolve::Args), + #[command(hide = true)] + ToolchainPath, +} + +#[derive(clap::ValueEnum, Clone, Debug, Default, PartialEq)] +pub enum EmitFormat { + #[default] + All, + Anneal, + Aeneas, +} + +#[derive(Parser, Debug)] +pub struct ExpandArgs { + #[command(flatten)] + pub resolve_args: resolve::Args, + + /// Which tool's generated Lean code to output + #[arg(long, default_value_t, value_enum)] + pub emit: EmitFormat, +} + +fn main() -> anyhow::Result<()> { + // Suppressing timestamps removes a source of nondeterminism that is + // difficult to work around in integration tests. + env_logger::builder().format_timestamp(None).init(); + + if std::env::var("ANNEAL_UI_TEST_MODE").is_ok() { + ui_test_shim::run(); + return Ok(()); + } + + let mut args_iter = std::env::args_os().peekable(); + let bin_name = args_iter.next().unwrap_or_else(|| "cargo-anneal".into()); + // If we're being run as a cargo plugin, the second argument will be "anneal". + if args_iter.peek().is_some_and(|arg| arg == "anneal") { + args_iter.next(); + } + let args = Cli::parse_from(std::iter::once(bin_name).chain(args_iter)); + + match args.command { + Commands::Verify(resolve_args) => { + prepare_and_run(&resolve_args, |locked_roots, packages| { + aeneas::verify_lean_workspace(locked_roots, packages) + })?; + } + Commands::Generate(resolve_args) => { + prepare_and_run(&resolve_args, |locked_roots, packages| { + aeneas::generate_lean_workspace(locked_roots, packages)?; + let lean_root = locked_roots.lean_root(); + let toolchain = setup::Toolchain::resolve()?; + println!("Lean workspace generated at: {}", lean_root.display()); + println!(); + println!("To manually build and experiment:"); + println!(" 1. cd {}", lean_root.display()); + println!( + " 2. LAKE_CACHE_DIR={} {} --keep-toolchain build", + toolchain.cache_dir().display(), + toolchain.lean_bin().join("lake").display() + ); + Ok(()) + })?; + } + Commands::Setup(args) => { + setup::run_setup(setup::SetupArgs { local_archive: args.local_archive })?; + } + Commands::ToolchainPath => { + let toolchain = setup::Toolchain::resolve()?; + println!("{}", toolchain.bin_dir().display()); + } + Commands::Expand(expand_args) => { + prepare_and_run(&expand_args.resolve_args, |locked_roots, packages| { + let lean_generated_root = locked_roots.lean_generated_root(); + + for artifact in packages { + if artifact.start_from.is_empty() { + continue; + } + + let slug = artifact.artifact_slug(); + let output_dir = lean_generated_root.join(&slug); + + let emit_all = expand_args.emit == EmitFormat::All; + let emit_anneal = emit_all || expand_args.emit == EmitFormat::Anneal; + let emit_aeneas = emit_all || expand_args.emit == EmitFormat::Aeneas; + + println!("=== Lean expansion for: {} ===", artifact.name.target_name); + + if emit_aeneas { + println!("--- Aeneas ---"); + // Read Aeneas outputs from disk + let types_path = output_dir.join("Types.lean"); + let types_ext_path = output_dir.join("TypesExternal.lean"); + let funs_path = output_dir.join("Funs.lean"); + let funs_ext_path = output_dir.join("FunsExternal.lean"); + + if types_path.exists() { + println!("{}", std::fs::read_to_string(&types_path)?); + } + if types_ext_path.exists() { + println!("{}", std::fs::read_to_string(&types_ext_path)?); + } + if funs_path.exists() { + println!("{}", std::fs::read_to_string(&funs_path)?); + } + if funs_ext_path.exists() { + println!("{}", std::fs::read_to_string(&funs_ext_path)?); + } + } + + if emit_anneal { + println!("--- Anneal ---"); + let generated = generate::generate_artifact(artifact); + println!("{}", generated.code); + } + } + + Ok(()) + })?; + } + } + + Ok(()) +} + +fn prepare_and_run(resolve_args: &resolve::Args, f: F) -> anyhow::Result> +where + F: FnOnce(&resolve::LockedRoots, &[scanner::AnnealArtifact]) -> anyhow::Result, +{ + let roots = resolve::resolve_roots(resolve_args)?; + + let packages = scanner::scan_workspace(&roots)?; + if packages.is_empty() { + log::warn!( + "No Anneal annotations (/// ```lean ...) found in the selected targets. Nothing to verify." + ); + return Ok(None); + } + + let locked_roots = roots.lock_run_root()?; + validate::validate_artifacts( + &packages, + resolve_args.allow_sorry, + resolve_args.unsound_allow_is_valid, + )?; + charon::run_charon(resolve_args, &locked_roots, &packages)?; + aeneas::run_aeneas(&locked_roots, &packages, resolve_args)?; + + Ok(Some(f(&locked_roots, &packages)?)) +} diff --git a/anneal/src/parse/attr.rs b/anneal/v1/src/parse/attr.rs similarity index 100% rename from anneal/src/parse/attr.rs rename to anneal/v1/src/parse/attr.rs diff --git a/anneal/src/parse/hkd.rs b/anneal/v1/src/parse/hkd.rs similarity index 100% rename from anneal/src/parse/hkd.rs rename to anneal/v1/src/parse/hkd.rs diff --git a/anneal/src/parse/mod.rs b/anneal/v1/src/parse/mod.rs similarity index 100% rename from anneal/src/parse/mod.rs rename to anneal/v1/src/parse/mod.rs diff --git a/anneal/v2/src/resolve.rs b/anneal/v1/src/resolve.rs similarity index 61% rename from anneal/v2/src/resolve.rs rename to anneal/v1/src/resolve.rs index a87fa5cde0..9a44f65296 100644 --- a/anneal/v2/src/resolve.rs +++ b/anneal/v1/src/resolve.rs @@ -1,30 +1,13 @@ -// Copyright 2026 The Fuchsia Authors -// -// Licensed under the 2-Clause BSD License , Apache License, Version 2.0 -// , or the MIT -// license , at your option. -// This file may not be copied, modified, or distributed except according to -// those terms. - -//! Cargo package and target resolution for Anneal. -//! -//! Cargo selectors can identify several compilation artifacts depending on -//! package layout: default selection may include libraries and binaries, -//! `--tests` can build test harnesses plus supporting targets, and a library -//! target can expose several crate types such as `rlib` and `cdylib`. -//! -//! This module resolves that selection into one explicit Anneal artifact per -//! package target and target kind. Downstream stages process those artifacts -//! independently, assigning each its own LLBC path and Charon configuration. -//! This prevents selected rustc units from sharing a `--dest-file`, avoids -//! collisions between workspace packages with the same Rust crate name, and -//! permits per-target Charon options such as `--start-from` and `--opaque`. - -use anyhow::Context as _; -use sha2::Digest as _; - -#[derive(clap::Parser, Debug)] +use std::{env, fs, path::PathBuf}; + +use anyhow::{Context, Result, anyhow}; +use cargo_metadata::{Metadata, MetadataCommand, Package, PackageName, Target, TargetKind}; +use clap::Parser; +use sha2::{Digest, Sha256}; + +use crate::util::DirLock; + +#[derive(Parser, Debug)] pub struct Args { #[command(flatten)] pub manifest: clap_cargo::Manifest, @@ -35,40 +18,60 @@ pub struct Args { #[command(flatten)] pub features: clap_cargo::Features, - /// Verify the library target. + /// Verify the library target #[arg(long)] pub lib: bool, - /// Verify specific binary targets. + /// Verify specific binary targets #[arg(long)] pub bin: Vec, - /// Verify all binary targets. + /// Verify all binary targets #[arg(long)] pub bins: bool, - /// Verify specific example targets. + /// Verify specific example targets #[arg(long)] pub example: Vec, - /// Verify all example targets. + /// Verify all example targets #[arg(long)] pub examples: bool, - /// Verify specific test targets. + /// Verify specific test targets #[arg(long)] pub test: Vec, - /// Verify all test targets. + /// Verify all test targets #[arg(long)] pub tests: bool, - /// Permit Lean proof admissions (`sorry`, `admit`, or `sorryAx`). - /// - /// Without this explicit opt-in, generated Lean treats warnings as errors - /// so every declaration which semantically depends on an admission fails. + /// Allow `sorry` in proofs and inject `sorry` for missing proofs #[arg(long)] pub allow_sorry: bool, + + /// Allow use of `isValid` annotations + /// + /// `isValid` annotations are currently unsound. In particular, Rust does + /// not yet support annotating a field with `unsafe`, denoting that it + /// carries an invariant. Thus, `isValid` annotations are effectively + /// advisory – any code which does not have a Anneal annotation can modify + /// invariant-carrying fields without needing to use an `unsafe` block. + /// Without an `unsafe` block, Anneal has no way of knowing that an + /// operation needs to be analyzed for soundness. + /// + /// Once the `syn` parser supports parsing `unsafe` fields (which are + /// already supported in a nightly Rust feature), Anneal will require that + /// `isValid` is only used on `unsafe` fields. + #[arg(long)] + pub unsound_allow_is_valid: bool, +} + +#[derive(Parser, Debug)] +pub struct SetupArgs { + /// Install dependencies from a locally built exocrate archive. + #[arg(long, value_name = "path-to-local-archive")] + pub local_archive: Option, } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] @@ -94,21 +97,23 @@ pub enum AnnealTargetKind { Test, } +// We map `cargo_metadata::TargetKind` to our own `AnnealTargetKind` to +// strictly validate supported target types and simplify downstream logic. +// While `cargo_metadata` is exhaustive, we only care about a subset of +// targets relevant to verification. + impl AnnealTargetKind { pub fn is_lib(&self) -> bool { use AnnealTargetKind::*; - match self { - Lib | RLib | ProcMacro | CDyLib | DyLib | StaticLib => true, - Bin | Example | Test => false, - } + matches!(self, Lib | RLib | ProcMacro | CDyLib | DyLib | StaticLib) } } -impl std::convert::TryFrom<&cargo_metadata::TargetKind> for AnnealTargetKind { +impl TryFrom<&TargetKind> for AnnealTargetKind { type Error = (); - fn try_from(kind: &cargo_metadata::TargetKind) -> anyhow::Result { - use cargo_metadata::TargetKind::*; + fn try_from(kind: &TargetKind) -> Result { + use TargetKind::*; match kind { Lib => Ok(Self::Lib), RLib => Ok(Self::RLib), @@ -127,15 +132,8 @@ impl std::convert::TryFrom<&cargo_metadata::TargetKind> for AnnealTargetKind { #[derive(Clone, Debug, Hash, PartialEq, Eq)] pub struct AnnealTargetName { - /// The Cargo package that owns this artifact. - pub package_name: cargo_metadata::PackageName, - /// The Cargo target name. + pub package_name: PackageName, pub target_name: String, - /// The target kind compiled for this artifact. - /// - /// This is part of the identity because one Cargo target can expose - /// multiple crate types, and because later Charon and Lean outputs must not - /// rely on target names alone. pub kind: AnnealTargetKind, } @@ -149,32 +147,34 @@ pub struct AnnealTargetName { pub struct AnnealTarget { pub name: AnnealTargetName, pub kind: AnnealTargetKind, - + /// Path to the main source file for this target. + pub src_path: PathBuf, /// Path to the `Cargo.toml` for this target. - pub manifest_path: std::path::PathBuf, + pub manifest_path: PathBuf, } #[derive(Debug)] pub struct Roots { + pub workspace: PathBuf, // E.g., `target/anneal`. - anneal_global_root: std::path::PathBuf, + anneal_global_root: PathBuf, // E.g., `target/anneal/`. - anneal_run_root: std::path::PathBuf, + anneal_run_root: PathBuf, pub roots: Vec, } impl Roots { - pub fn lock_run_root(&self) -> anyhow::Result> { - let lock = crate::util::DirLock::lock_exclusive(self.anneal_run_root.clone())?; - Ok(LockedRoots { roots: self, anneal_run_root: lock, llbc_override: None }) + pub fn lock_run_root(&self) -> Result> { + let lock = DirLock::lock_exclusive(self.anneal_run_root.clone())?; + Ok(LockedRoots { roots: self, anneal_run_root: lock }) } - pub fn cargo_target_dir(&self) -> std::path::PathBuf { + pub fn cargo_target_dir(&self) -> PathBuf { self.anneal_global_root.join("cargo_target") } } -/// A wrapper around [`crate::resolve::Roots`] that proves the build lock is held. +/// A wrapper around `Roots` that proves the build lock is held. /// /// This struct is the *only* way to access paths within the Anneal build /// directory (e.g., LLBC output, Lean generation). This enforces that all @@ -182,37 +182,38 @@ impl Roots { pub struct LockedRoots<'a> { roots: &'a Roots, anneal_run_root: crate::util::DirLock, - pub llbc_override: Option, } impl<'a> LockedRoots<'a> { - pub fn llbc_root(&self) -> std::path::PathBuf { - if let Some(ref over) = self.llbc_override { - over.clone() - } else { - self.anneal_run_root.path.join("llbc") - } + pub fn llbc_root(&self) -> PathBuf { + self.anneal_run_root.path.join("llbc") + } + + pub fn lean_root(&self) -> PathBuf { + self.anneal_run_root.path.join("lean") + } + + pub fn lean_generated_root(&self) -> PathBuf { + self.lean_root().join("generated") } // We expose the Cargo target directory for convenience, as it is used // by downstream tools like Charon to coordinate dependency artifacts. - pub fn cargo_target_dir(&self) -> std::path::PathBuf { + pub fn cargo_target_dir(&self) -> PathBuf { self.roots.cargo_target_dir() } + + pub fn workspace(&self) -> &PathBuf { + &self.roots.workspace + } } /// Resolves all verification roots. /// /// Each entry represents a distinct compilation artifact to be verified. -/// Keeping this artifact list explicit is deliberate: later Charon invocation -/// code should not have to rediscover which files a Cargo flag happened to -/// produce for a particular workspace shape. -pub fn resolve_roots(args: &Args, toolchain: &crate::setup::Toolchain) -> anyhow::Result { +pub fn resolve_roots(args: &Args) -> Result { log::trace!("resolve_roots({:?})", args); - let mut cmd = cargo_metadata::MetadataCommand::new(); - cmd.cargo_path(crate::setup::Tool::Cargo.path(toolchain)) - .env("RUSTC", crate::setup::Tool::Rustc.path(toolchain)) - .env(crate::setup::rust_library_path_env_var(), toolchain.rust_lib()); + let mut cmd = MetadataCommand::new(); if let Some(path) = &args.manifest.manifest_path { cmd.manifest_path(path); @@ -227,19 +228,18 @@ pub fn resolve_roots(args: &Args, toolchain: &crate::setup::Toolchain) -> anyhow args.features.forward_metadata(&mut cmd); let metadata = cmd.exec().context("Failed to run 'cargo metadata'")?; - // We enforce that all local dependencies are contained within the workspace - // root. This is a temporary limitation to simplify the verification model - // and ensure a "hermetic-like" boundary for analysis. It prevents issues - // where experimental or local forks of dependencies might be picked up - // unpredictably, or where Charon might struggle to locate source files - // outside the standard project structure. - check_for_external_deps(&metadata)?; - let selected_packages = resolve_packages(&metadata, &args.workspace, args.manifest.manifest_path.as_deref())?; + check_selected_packages_in_workspace(&metadata, &selected_packages)?; let (anneal_global_root, anneal_run_root) = resolve_run_roots(&metadata); - let mut roots = Roots { anneal_global_root, anneal_run_root, roots: Vec::new() }; + let mut roots = Roots { + workspace: metadata.workspace_root.as_std_path().to_owned(), + // cargo_target_dir: metadata.target_directory.as_std_path().to_owned(), + anneal_global_root, + anneal_run_root, + roots: Vec::new(), + }; for package in selected_packages { log::trace!("Scanning package: {}", package.name); @@ -262,6 +262,7 @@ pub fn resolve_roots(args: &Args, toolchain: &crate::setup::Toolchain) -> anyhow // reference for the rest of the pipeline. This avoids ambiguity // if the CWD changes or if we're working with complex workspace // structures. + src_path: target.src_path.as_std_path().to_owned(), manifest_path: package.manifest_path.as_std_path().to_owned(), })); } @@ -269,22 +270,26 @@ pub fn resolve_roots(args: &Args, toolchain: &crate::setup::Toolchain) -> anyhow Ok(roots) } -fn resolve_run_roots( - metadata: &cargo_metadata::Metadata, -) -> (std::path::PathBuf, std::path::PathBuf) { +fn resolve_run_roots(metadata: &Metadata) -> (PathBuf, PathBuf) { log::trace!("resolve_run_root"); log::debug!("workspace_root: {:?}", metadata.workspace_root.as_std_path()); // NOTE: Automatically handles `CARGO_TARGET_DIR` env var. let target_dir = metadata.target_directory.as_std_path(); let anneal_global = target_dir.join("anneal"); + // Used by integration tests to ensure deterministic shadow dir names. + if let Ok(name) = std::env::var("ANNEAL_TEST_DIR_NAME") { + let run_root = anneal_global.join(name); + return (anneal_global, run_root); + } + // Hash the path to the workspace root to avoid collisions between different // workspaces using the same target directory. We use SHA-256 (truncated to // 64 bits) for stable hashing across Rust versions. This ensures that the // build directory name remains consistent for the same workspace root, // avoiding unnecessary cache invalidation. let workspace_root_hash = { - let mut hasher = sha2::Sha256::new(); + let mut hasher = Sha256::new(); hasher.update(b"anneal_build_salt"); hasher.update(metadata.workspace_root.as_str().as_bytes()); let result = hasher.finalize(); @@ -299,13 +304,13 @@ fn resolve_run_roots( /// Resolves which packages to process based on workspace flags and CWD. fn resolve_packages<'a>( - metadata: &'a cargo_metadata::Metadata, + metadata: &'a Metadata, args: &clap_cargo::Workspace, manifest_path: Option<&std::path::Path>, -) -> anyhow::Result> { +) -> Result> { log::trace!("resolve_packages(workspace: {}, all: {})", args.workspace, args.all); let mut packages = if !args.package.is_empty() { - // Resolve explicitly selected packages (-p / --package). + // Resolve explicitly selected packages (-p / --package) args.package .iter() .map(|name| { @@ -313,9 +318,9 @@ fn resolve_packages<'a>( .packages .iter() .find(|p| p.name == *name) - .ok_or_else(|| anyhow::anyhow!("Package '{}' not found in workspace", name)) + .ok_or_else(|| anyhow!("Package '{}' not found in workspace", name)) }) - .collect::>>()? + .collect::>>()? } else if args.workspace || args.all { // Resolve entire workspace (--workspace / --all). This explicitly // selects all workspace members, ignoring any packages that might be @@ -332,7 +337,7 @@ fn resolve_packages<'a>( let cwd = { let cwd_candidate = manifest_path .map(|p| p.to_path_buf()) - .unwrap_or_else(|| std::env::current_dir().unwrap_or_default()) + .unwrap_or_else(|| env::current_dir().unwrap_or_default()) .canonicalize() .context("Failed to canonicalize CWD")?; @@ -345,7 +350,7 @@ fn resolve_packages<'a>( } }; - // Find the package whose manifest directory is an ancestor of CWD. + // Find the package whose manifest directory is an ancestor of CWD let current_pkg = metadata.packages.iter().find(|p| { let manifest_dir = p.manifest_path.parent().unwrap(); cwd.starts_with(manifest_dir) @@ -363,14 +368,14 @@ fn resolve_packages<'a>( .filter_map(|id| metadata.packages.iter().find(|p| &p.id == id)) .collect() } else { - return Err(anyhow::anyhow!( + return Err(anyhow!( "Could not determine package from current directory. Please use -p or --workspace." )); } } }; - // Filter out excluded packages (--exclude). + // Filter out excluded packages (--exclude) if !args.exclude.is_empty() { packages.retain(|p| !args.exclude.contains(&p.name)); } @@ -378,9 +383,8 @@ fn resolve_packages<'a>( Ok(packages) } -/// Resolves the Cargo targets selected from one package. -/// -/// Returns a list of `(Target, TargetKind)` pairs. +/// Flattening Resolver: +/// Returns a list of (Target, TargetKind) pairs. /// If a target is defined as `crate-type = ["rlib", "cdylib"]`, and both are /// requested, this returns two entries, allowing them to be verified /// independently. @@ -388,12 +392,12 @@ fn resolve_packages<'a>( /// This flattening is critical because different crate types may be compiled /// with different flags or conditional compilation options (although the /// current scanner is CFG-agnostic, future improvements might respect this). -/// Verifying them independently gives later stages a separate artifact identity -/// and output path for every intended compilation mode. +/// Verifying them independently ensures we cover all intended compilation +/// modes. fn resolve_targets<'a>( - package: &'a cargo_metadata::Package, + package: &'a Package, args: &Args, -) -> anyhow::Result> { +) -> Result> { log::trace!("resolve_targets({})", package.name); let default_mode = !args.lib && args.bin.is_empty() @@ -435,36 +439,38 @@ fn resolve_targets<'a>( Ok(selected_artifacts) } -/// Scans the package graph to ensure all local dependencies are contained -/// within the workspace root. Returns an error if an external path dependency -/// is found. -pub fn check_for_external_deps(metadata: &cargo_metadata::Metadata) -> anyhow::Result<()> { - log::trace!("check_for_external_deps"); - // Canonicalize workspace root to handle symlinks correctly. - let workspace_root = std::fs::canonicalize(&metadata.workspace_root) +// TODO: Eventually, we'll want to support selected packages outside the Cargo +// workspace root by analyzing them in-place or teaching downstream stages how +// to map those source files. + +/// Ensures every selected verification root is contained within the workspace root. +fn check_selected_packages_in_workspace(metadata: &Metadata, packages: &[&Package]) -> Result<()> { + log::trace!("check_selected_packages_in_workspace"); + // Canonicalize workspace root to handle symlinks correctly + let workspace_root = fs::canonicalize(&metadata.workspace_root) .context("Failed to canonicalize workspace root")?; - for pkg in &metadata.packages { - // We only care about packages that are "local" (source is None). - // If source is Some(...), it's from crates.io or git, which is fine - // (handled by Cargo). - if pkg.source.is_none() { - let pkg_path = pkg.manifest_path.as_std_path(); - - // Canonicalize the package path for comparison. - let canonical_pkg_path = std::fs::canonicalize(pkg_path) - .with_context(|| format!("Failed to canonicalize path for package {}", pkg.name))?; - - // Check if the package lives outside the workspace tree. - if !canonical_pkg_path.starts_with(&workspace_root) { - anyhow::bail!( - "Unsupported external dependency: '{}' at {:?}.\n\ - Anneal currently only supports verifying workspaces where all local \ - dependencies are contained within the workspace root.", - pkg.name, - pkg_path - ); - } + for pkg in packages { + let pkg_path = pkg.manifest_path.as_std_path(); + + // Canonicalize the package path for comparison + let canonical_pkg_path = fs::canonicalize(pkg_path) + .with_context(|| format!("Failed to canonicalize path for package {}", pkg.name))?; + + // We only constrain the packages Anneal will scan as verification + // roots. Cargo metadata may also include build-only local path + // dependencies from the Anneal implementation itself, such as the + // repository-root `exocrate` helper crate when running + // `cargo run verify`. Rejecting those would make Anneal's own package + // layout constrain user verification. + if !canonical_pkg_path.starts_with(&workspace_root) { + anyhow::bail!( + "Unsupported external package: '{}' at {:?}.\n\ + Anneal currently only supports verifying packages contained \ + within the workspace root.", + pkg.name, + pkg_path + ); } } diff --git a/anneal/v1/src/scanner.rs b/anneal/v1/src/scanner.rs new file mode 100644 index 0000000000..44b66417a8 --- /dev/null +++ b/anneal/v1/src/scanner.rs @@ -0,0 +1,504 @@ +use std::{ + collections::{HashMap, HashSet}, + ffi::OsStr, + path::{Path, PathBuf}, + sync::mpsc::{self, Sender}, +}; + +use anyhow::Result; +use sha2::{Digest as _, Sha256}; + +use crate::{ + parse::{self, ParsedLeanItem}, + resolve::{AnnealTargetKind, AnnealTargetName, LockedRoots, Roots}, +}; + +#[derive(Clone)] +struct ScannerContext { + err_tx: Sender, + // `ParsedLeanItem`s must have their `module_path` field updated to be + // relative to the crate root. + item_tx: Sender<(AnnealTargetName, ParsedLeanItem, String)>, + name: AnnealTargetName, + current_prefix: Vec, +} + +/// A scanned artifact containing all the necessary information to generate +/// a Lean specification. +/// +/// This represents a single Rust target (library, binary, etc.) and includes +/// the list of discovered Anneal items and the calculated entry points for +/// Charon. +pub struct AnnealArtifact { + pub name: AnnealTargetName, + pub target_kind: AnnealTargetKind, + /// The path to the crate's `Cargo.toml`. + pub manifest_path: PathBuf, + pub items: Vec>, + // NOTE: We store `start_from` as a `HashSet` rather than a `Vec` as an + // optimization: when we encounter items which we can't name (which carry + // Anneal annotations), we add their parent module to the list of + // entrypoints. If there are multiple items in the same module, this can + // lead to duplication in the list of entrypoints. Storing them in a + // `HashSet` avoids us having to de-dup later. + pub start_from: HashSet, +} + +impl AnnealArtifact { + /// Returns a unique, Lean-compatible "slug" for this artifact that matches + /// the name that Aeneas will expect for the corresponding Lean module. + /// + /// Guarantees uniqueness based on manifest path even if multiple packages + /// have the same name. The slug is guaranteed to be a valid Lean + /// identifier (no hyphens). + pub fn artifact_slug(&self) -> String { + fn hash(data: &[u8]) -> u64 { + let mut hasher = Sha256::new(); + hasher.update(data); + let result = hasher.finalize(); + let mut bytes = [0u8; 8]; + bytes.copy_from_slice(&result[0..8]); + u64::from_le_bytes(bytes) + } + + // Double-hash to make sure we can distinguish between e.g. + // (manifest_path, target_name) = ("abc", "def") and ("ab", "cdef"), + // which would hash identically if we just hashed their concatenation. + // + // Use SHA-256 not for security but rather stability – Rust's + // `DefaultHasher` doesn't guarantee stability even across runs of the + // same binary. + // + // `ANNEAL_HASH_WITH_REMOVED_PREFIX` allows our integration test + // framework to strip the randomized sandbox prefix from the manifest + // path before hashing, ensuring deterministic hashes even when running + // in a sandboxed environment. + let mut manifest_path_to_hash = self.manifest_path.as_path(); + if let Ok(prefix) = std::env::var("ANNEAL_HASH_WITH_REMOVED_PREFIX") { + if let Ok(stripped) = self.manifest_path.strip_prefix(&prefix) { + manifest_path_to_hash = stripped; + } + } + let h0 = hash(manifest_path_to_hash.as_os_str().as_encoded_bytes()); + let h1 = hash(self.name.target_name.as_bytes()); + let h2 = hash(&[self.target_kind as u8]); + let hashes = [h0, h1, h2]; + let h = hash(&hashes.map(u64::to_ne_bytes).concat()); + + // Converts kebab-case -> PascalCase. + // We convert both package and target names to PascalCase to ensure + // the generated Lean module name is a valid and idiomatic Lean + // identifier, matching Aeneas's output format. + let to_pascal = |s: &str| { + s.split(['-', '_']) + .map(|segment| { + let mut chars = segment.chars(); + match chars.next() { + None => String::new(), + Some(f) => f.to_uppercase().collect::() + chars.as_str(), + } + }) + .collect::() + }; + + let pkg = to_pascal(self.name.package_name.as_str()); + let target = to_pascal(&self.name.target_name); + + // We use the hash to ensure uniqueness. + format!("{}{}{:08x}", pkg, target, h) + } + + /// Returns the name of the `.llbc` file to use for this artifact. + pub fn llbc_file_name(&self) -> String { + format!("{}.llbc", self.artifact_slug()) + } + + /// Returns the name of the `.lean` spec file to use for this artifact. + pub fn lean_spec_file_name(&self) -> String { + format!("{}.lean", self.artifact_slug()) + } + + /// Returns the absolute path to the .llbc file. + /// + /// This method requires `LockedRoots` to ensure that the caller holds the + /// build lock before accessing the build artifact path. + pub fn llbc_path(&self, roots: &LockedRoots) -> PathBuf { + roots.llbc_root().join(self.llbc_file_name()) + } + + /// Returns true if this artifact contains items that should result in a `Funs.lean` file. + pub fn has_functions(&self) -> bool { + self.items.iter().any(|i| { + matches!( + i.item, + crate::parse::ParsedItem::Function(_) | crate::parse::ParsedItem::Impl(_) + ) + }) + } + + /// Returns true if this artifact contains items that should result in a `Types.lean` file. + pub fn has_types(&self) -> bool { + self.items.iter().any(|i| { + matches!(i.item, crate::parse::ParsedItem::Type(_) | crate::parse::ParsedItem::Trait(_)) + }) + } +} + +/// Scans the workspace to identify Anneal entry points (`/// ```lean` blocks) +/// and collects targets for verification. +pub fn scan_workspace(roots: &Roots) -> Result> { + log::trace!("scan_workspace({:?})", roots); + + let (err_tx, err_rx) = mpsc::channel::(); + let (item_tx, item_rx) = + mpsc::channel::<(AnnealTargetName, ParsedLeanItem, String)>(); + + let monitor_handle = std::thread::spawn(move || { + let mut error_count = 0; + for err in err_rx { + if error_count == 0 { + eprintln!("\n=== Anneal Verification Failed ==="); + } + error_count += 1; + // Use eprintln! to print immediately to stderr + // `miette::Report` natively formats with rich diagnostic spans via `{:?}`. + eprintln!("\n[Anneal Error] {:?}", err); + } + error_count + }); + + rayon::scope(|s| { + for target in &roots.roots { + let ctx = ScannerContext { + err_tx: err_tx.clone(), + item_tx: item_tx.clone(), + name: target.name.clone(), + current_prefix: vec!["crate".to_string()], + }; + s.spawn(move |s| { + process_file_recursive( + s, + &target.src_path, + ctx, + false, // Initial call is top-level, so not inside block + Vec::new(), + ) + }); + } + }); + + // Inform the monitor thread that no more errors will be sent, causing it to + // exit. + drop(err_tx); + + let (mut entry_points, mut start_from_map) = { + drop(item_tx); + let mut entry_points = + HashMap::>>::new(); + let mut start_from_map = HashMap::>::new(); + for (target, item, sf_str) in item_rx { + start_from_map.entry(target.clone()).or_default().insert(sf_str); + entry_points.entry(target).or_default().push(item); + } + (entry_points, start_from_map) + }; + + // Wait for the monitor to finish flushing the errors. + let count = monitor_handle.join().expect("Error monitor thread panicked"); + + if count > 0 { + return Err(anyhow::anyhow!("Aborting due to {} previous errors.", count)); + } + + Ok(roots + .roots + .iter() + .filter_map(|target| { + Some(AnnealArtifact { + name: target.name.clone(), + target_kind: target.kind, + manifest_path: target.manifest_path.clone(), + items: entry_points.remove(&target.name)?, + start_from: start_from_map.remove(&target.name).unwrap_or_default(), + }) + }) + .collect()) +} + +// NOTE: It might be tempting to try to deduplicate files to avoid re-processing +// a file that is reachable via multiple paths. However, this is incorrect, as +// this only happens if the file is named in multiple `#[path]` attributes, in +// which case it logically constitutes a distinct module each time it is +// referenced. +fn process_file_recursive<'a>( + scope: &rayon::Scope<'a>, + src_path: &Path, + ctx: ScannerContext, + inside_block: bool, + mut ancestors: Vec, +) { + log::trace!("process_file_recursive(src_path: {:?})", src_path); + + // Canonicalize the path to ensure we don't process the same file multiple + // times (e.g. via symlinks or different relative paths). + let src_path = match std::fs::canonicalize(src_path) { + Ok(p) => p, + Err(e) => { + // It is valid for a module to be declared but not exist (e.g., if + // it is cfg-gated for another platform). In strict Rust, we would + // check the cfg attributes, but since we are just scanning, it is + // safe to warn and return. + // + // Note: The scanner is currently CFG-agnostic. It does not evaluate + // `#[cfg(...)]` attributes, so it may attempt to scan files that + // are disabled for the current target (e.g., Windows-specific code + // on Linux). This is a known limitation that may cause build failures + // in Charon if it tries to verify non-existent items. + log::debug!("Skipping unreachable or missing file {:?}: {}", src_path, e); + return; + } + }; + + // Cycle detection: prevent infinite recursion on symlinks or recursive mod + // declarations, while still allowing identical files to be mounted in + // different branches of the tree. A cycle would represent an invalid crate + // anyway (i.e., an invalid set of `#[path]` attributes). + // + // Note: This check is purely path-based. It does not inspect the content of + // the file. + if ancestors.contains(&src_path) { + return; + } + ancestors.push(src_path.clone()); + + let result = parse::read_file_and_scan_compilation_unit( + &src_path, + inside_block, + |_src, res| match res { + Ok(mut item) => { + item.module_path.splice(0..0, ctx.current_prefix.clone()); + + let unreliable = match &item.item { + crate::parse::ParsedItem::Impl(_) => true, + crate::parse::ParsedItem::Function(f) => { + matches!( + f.item, + crate::parse::FunctionItem::Impl(..) + | crate::parse::FunctionItem::Trait(_) + | crate::parse::FunctionItem::Foreign(_) + ) + } + _ => false, + }; + let module_path = item.module_path.join("::"); + let start_from_str = if unreliable { + // For items where we cannot reliably determine the fully qualified + // name (e.g., items inside `impl` blocks or `trait` definitions), + // we must fall back to using the containing module as the + // entry point. This forces Charon to analyze the entire module, + // which is less efficient but ensures we don't miss the item. + module_path + } else { + format!("{}::{}", module_path, item.item.name().unwrap()) + }; + + use crate::parse::hkd::LiftToSafe; + // Before sending the parsed item across the thread boundary + // (from the `rayon` worker back to the main thread), we must + // "lift" the AST nodes. This internally drops the rich, + // non-`Send` `syn` trees stored in the `Local` mode, + // transforming them into the lightweight, `Send`-safe `Safe` + // equivalents (e.g., `SafeType` and `SafeSignature`). + ctx.item_tx.send((ctx.name.clone(), item.lift(), start_from_str)).unwrap(); + } + Err(e) => { + let _ = ctx.err_tx.send(miette::Report::new(e)); + } + }, + ); + + // After scanning the current file, we look for declared submodules that + // were not loaded inline (i.e., `mod foo;` instead of `mod foo { ... }`). + // `read_file_and_scan_compilation_unit` returns a list of these "unloaded" + // modules so we can resolve their paths and recurse. + let (_, unloaded_modules) = match result { + Ok(res) => res, + Err(e) => { + let _ = ctx.err_tx.send(miette::miette!("{:#}", e)); + return; + } + }; + + // Determine the directory to search for child modules in. + // - For `mod.rs`, `lib.rs`, `main.rs`, children are in the parent directory. + // e.g. `src/lib.rs` -> `mod foo` -> `src/foo.rs` + // - For `my_mod.rs`, children are in a sibling directory of the same name. + // e.g. `src/my_mod.rs` -> `mod sub` -> `src/my_mod/sub.rs` + let file_stem = src_path.file_stem().and_then(OsStr::to_str).unwrap_or(""); + let base_dir = if matches!(file_stem, "mod" | "lib" | "main") { + // If we are in `mod.rs`, `lib.rs`, or `main.rs`, then `mod foo;` + // looks for `foo.rs` or `foo/mod.rs` in the *same* directory. + // + // e.g. `src/lib.rs` -> `mod foo` -> `src/foo.rs` + src_path.parent().unwrap_or(&src_path).to_path_buf() + } else { + // If we are in `src/foo.rs`, then `mod bar;` looks for + // `src/foo/bar.rs` or `src/foo/bar/mod.rs`. + // + // e.g. src/foo.rs -> src/foo/ + src_path.with_extension("") + }; + + // Resolve and queue child modules for processing. + for module in unloaded_modules { + if let Some(mod_path) = + resolve_module_path(&base_dir, &module.name, module.path_attr.as_deref()) + { + // Spawn new tasks for discovered modules. + let mut ctx_clone = ctx.clone(); + ctx_clone.current_prefix.push(module.name); + + // TODO: Can we do something more efficient than cloning here? Maybe + // a linked list? + let ancestors = ancestors.clone(); + scope.spawn(move |s| { + process_file_recursive(s, &mod_path, ctx_clone, module.inside_block, ancestors) + }) + } else { + // This is an expected condition – it shows up when modules are + // conditionally compiled. Instead of implementing conditional + // compilation ourselves, we can just let rustc error later if + // this is actually an error. + // + // Example: `#[cfg(windows)] mod win_only;` on Linux. `win_only.rs` + // might not exist at all, or might verify successfully but be + // ignored by rustc. We just skip it here. + log::debug!("Could not resolve module '{}' in {:?}", module.name, src_path); + } + } +} + +/// Resolves a module name to a file path, checking standard Rust locations. +/// +/// * `base_dir`: The directory containing the parent file. +/// * `mod_name`: The name of the module (e.g., "foo"). +/// * `path_attr`: The optional `#[path = "..."]` attribute string. +fn resolve_module_path( + base_dir: &Path, + mod_name: &str, + path_attr: Option<&str>, +) -> Option { + log::trace!( + "resolve_module_path(base_dir: {:?}, mod_name: {:?}, path_attr: {:?})", + base_dir, + mod_name, + path_attr + ); + + // If `#[path = "..."]` is present, it overrides the standard lookup logic. + // The path is always relative to the current module's directory. + if let Some(custom_path) = path_attr { + let p = base_dir.join(custom_path); + if p.exists() { + return Some(p); + } + return None; + } + + // Standard lookup: `foo.rs` + let inline = base_dir.join(format!("{}.rs", mod_name)); + if inline.exists() { + return Some(inline); + } + + // Standard lookup: `foo/mod.rs` + let nested = base_dir.join(mod_name).join("mod.rs"); + if nested.exists() { + return Some(nested); + } + + None +} + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use cargo_metadata::PackageName; + + use super::*; + use crate::resolve::{AnnealTargetKind, AnnealTargetName}; + + #[test] + fn test_llbc_file_name_collision() { + let name_lib = AnnealTargetName { + package_name: PackageName::new("pkg".to_string()), + target_name: "name".to_string(), + kind: AnnealTargetKind::Lib, + }; + + let artifact_lib = AnnealArtifact { + name: name_lib.clone(), + target_kind: AnnealTargetKind::Lib, + manifest_path: PathBuf::from("Cargo.toml"), + start_from: std::collections::HashSet::new(), + items: vec![], + }; + + let name_bin = AnnealTargetName { + package_name: PackageName::new("pkg".to_string()), + target_name: "name".to_string(), + kind: AnnealTargetKind::Bin, + }; + + let artifact_bin = AnnealArtifact { + name: name_bin, + target_kind: AnnealTargetKind::Bin, + manifest_path: PathBuf::from("Cargo.toml"), + start_from: std::collections::HashSet::new(), + items: vec![], + }; + + let artifact_workspace_collision = AnnealArtifact { + name: name_lib.clone(), + target_kind: AnnealTargetKind::Lib, + // A different manifest but identical package/target semantics + manifest_path: PathBuf::from("crates/other/Cargo.toml"), + start_from: std::collections::HashSet::new(), + items: vec![], + }; + + // The file names must be distinct because of the trailing hash. + assert_ne!(artifact_lib.llbc_file_name(), artifact_bin.llbc_file_name()); + assert!(artifact_lib.llbc_file_name().starts_with("PkgName")); + assert!(artifact_bin.llbc_file_name().starts_with("PkgName")); + + // Distinct `manifest_path`s must prevent collisions even when the + // package name and target semantics are otherwise identical. + assert_ne!(artifact_lib.llbc_file_name(), artifact_workspace_collision.llbc_file_name()); + assert!(artifact_workspace_collision.llbc_file_name().starts_with("PkgName")); + } + + #[test] + fn test_lean_spec_file_name_uses_slug() { + let name = AnnealTargetName { + package_name: PackageName::new("pkg-foo".to_string()), + target_name: "name-bar".to_string(), + kind: AnnealTargetKind::Lib, + }; + + let artifact = AnnealArtifact { + name, + target_kind: AnnealTargetKind::Lib, + manifest_path: PathBuf::from("Cargo.toml"), + start_from: std::collections::HashSet::new(), + items: vec![], + }; + + // Slug should be PascalCase: PkgFoo_NameBar_ + // Spec file should be slug + .lean + let spec_name = artifact.lean_spec_file_name(); + assert!(spec_name.starts_with("PkgFooNameBar")); + assert!(spec_name.ends_with(".lean")); + } +} diff --git a/anneal/v1/src/setup.rs b/anneal/v1/src/setup.rs new file mode 100644 index 0000000000..b6d25ade59 --- /dev/null +++ b/anneal/v1/src/setup.rs @@ -0,0 +1,213 @@ +//! Subcommand for installing Anneal dependencies. + +use std::{path::PathBuf, process::Command}; + +use anyhow::Context as _; + +pub struct SetupArgs { + pub local_archive: Option, +} + +pub const CONFIG: exocrate::Config = exocrate::Config { + rel_dir_path: &["anneal", "toolchain"], + version_slug: env!("ANNEAL_EXOCRATE_VERSION_SLUG"), +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Tool { + Charon, + #[allow(dead_code)] + CharonDriver, + Aeneas, +} + +impl Tool { + pub fn name(&self) -> &'static str { + match self { + Self::Charon => "charon", + Self::CharonDriver => "charon-driver", + Self::Aeneas => "aeneas", + } + } + + pub fn path(&self, toolchain: &Toolchain) -> PathBuf { + match self { + Self::Charon | Self::CharonDriver | Self::Aeneas => { + toolchain.aeneas_bin_dir().join(self.name()) + } + } + } +} + +const AENEAS_DIR: &str = "aeneas"; +const AENEAS_BACKENDS_DIR: &str = "backends"; +const AENEAS_LEAN_DIR: &str = "lean"; +const BIN_DIR: &str = "bin"; +const LIB_DIR: &str = "lib"; +const LEAN_SYSROOT: &str = "lean"; +const RUST_SYSROOT: &str = "rust"; + +pub struct Toolchain { + pub root: PathBuf, +} + +impl Toolchain { + pub fn resolve() -> anyhow::Result { + let root = CONFIG + .resolve_installation_dir(location()) + .context("Toolchain not installed. Please run 'cargo anneal setup' first.")?; + Ok(Self { root }) + } + + pub fn bin_dir(&self) -> PathBuf { + self.aeneas_bin_dir() + } + + pub fn cache_dir(&self) -> PathBuf { + self.root.join("lake-cache") + } + + pub fn aeneas_root(&self) -> PathBuf { + self.root.join(AENEAS_DIR) + } + + pub fn aeneas_bin_dir(&self) -> PathBuf { + self.aeneas_root().join(BIN_DIR) + } + + pub fn aeneas_lean_dir(&self) -> PathBuf { + self.aeneas_root().join(AENEAS_BACKENDS_DIR).join(AENEAS_LEAN_DIR) + } + + pub fn rust_sysroot(&self) -> PathBuf { + self.root.join(RUST_SYSROOT) + } + + pub fn rust_bin(&self) -> PathBuf { + self.rust_sysroot().join(BIN_DIR) + } + + pub fn rust_lib(&self) -> PathBuf { + self.rust_sysroot().join(LIB_DIR) + } + + pub fn lean_sysroot(&self) -> PathBuf { + self.root.join(LEAN_SYSROOT) + } + + pub fn lean_bin(&self) -> PathBuf { + self.lean_sysroot().join(BIN_DIR) + } + + pub fn command(&self, tool: Tool) -> Command { + if std::env::var("ANNEAL_USE_PATH_FOR_TOOLS").is_ok() { + Command::new(tool.name()) + } else { + Command::new(tool.path(self)) + } + } +} + +pub fn run_setup(args: SetupArgs) -> anyhow::Result<()> { + let local_archive = args + .local_archive + .or_else(|| std::env::var_os("ANNEAL_SETUP_LOCAL_ARCHIVE").map(PathBuf::from)); + let source = match local_archive { + Some(local_archive) => exocrate::Source::Local(local_archive), + None => exocrate::Source::Remote(remote_archive()), + }; + + let installation_dir = CONFIG + .resolve_installation_dir_or_install(location(), source) + .context("failed to resolve-or-install dependencies")?; + log::info!("anneal toolchain is installed at {:?}", installation_dir); + Ok(()) +} + +fn location() -> exocrate::Location { + if let Some(dir) = std::env::var_os("ANNEAL_TOOLCHAIN_DIR") { + exocrate::Location::Custom(PathBuf::from(dir)) + } else if std::env::var("__ZEROCOPY_LOCAL_DEV").is_ok() + || std::env::var("__ANNEAL_LOCAL_DEV").is_ok() + { + exocrate::Location::LocalDev + } else { + exocrate::Location::UserGlobal + } +} + +fn remote_archive() -> exocrate::RemoteArchive { + match (std::env::consts::OS, std::env::consts::ARCH) { + ("linux", "x86_64") => remote_archive_for( + env!("ANNEAL_EXOCRATE_LINUX_X86_64_URL"), + env!("ANNEAL_EXOCRATE_LINUX_X86_64_SHA256"), + ), + ("macos", "x86_64") => remote_archive_for( + env!("ANNEAL_EXOCRATE_MACOS_X86_64_URL"), + env!("ANNEAL_EXOCRATE_MACOS_X86_64_SHA256"), + ), + ("linux", "aarch64") => remote_archive_for( + env!("ANNEAL_EXOCRATE_LINUX_AARCH64_URL"), + env!("ANNEAL_EXOCRATE_LINUX_AARCH64_SHA256"), + ), + ("macos", "aarch64") => remote_archive_for( + env!("ANNEAL_EXOCRATE_MACOS_AARCH64_URL"), + env!("ANNEAL_EXOCRATE_MACOS_AARCH64_SHA256"), + ), + (os, arch) => panic!("unsupported platform: {os}-{arch}"), + } +} + +fn remote_archive_for(url: &'static str, sha256: &'static str) -> exocrate::RemoteArchive { + exocrate::RemoteArchive { + url, + sha256: decode_hex(sha256).expect("package.metadata.exocrate sha256 must be valid hex"), + } +} + +fn decode_hex(s: &str) -> Option<[u8; 32]> { + let bytes = s.as_bytes(); + if bytes.len() != 64 { + return None; + } + let mut res = [0u8; 32]; + for i in 0..32 { + let h_nib = decode_nibble(bytes[i * 2])?; + let l_nib = decode_nibble(bytes[i * 2 + 1])?; + res[i] = (h_nib << 4) | l_nib; + } + Some(res) +} + +fn decode_nibble(c: u8) -> Option { + match c { + b'0'..=b'9' => Some(c - b'0'), + b'a'..=b'f' => Some(c - b'a' + 10), + b'A'..=b'F' => Some(c - b'A' + 10), + _ => None, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn tool_paths_use_omnibus_layout() { + let toolchain = Toolchain { root: PathBuf::from("/tmp/toolchain") }; + + assert_eq!(toolchain.bin_dir(), PathBuf::from("/tmp/toolchain/aeneas/bin")); + assert_eq!( + toolchain.aeneas_lean_dir(), + PathBuf::from("/tmp/toolchain/aeneas/backends/lean") + ); + assert_eq!( + Tool::Charon.path(&toolchain), + PathBuf::from("/tmp/toolchain/aeneas/bin/charon") + ); + assert_eq!( + Tool::Aeneas.path(&toolchain), + PathBuf::from("/tmp/toolchain/aeneas/bin/aeneas") + ); + } +} diff --git a/anneal/src/ui_test_shim.rs b/anneal/v1/src/ui_test_shim.rs similarity index 100% rename from anneal/src/ui_test_shim.rs rename to anneal/v1/src/ui_test_shim.rs diff --git a/anneal/v1/src/util.rs b/anneal/v1/src/util.rs new file mode 100644 index 0000000000..fe085b37dc --- /dev/null +++ b/anneal/v1/src/util.rs @@ -0,0 +1,59 @@ +use std::path::PathBuf; + +use anyhow::{Context, Result}; +use fs2::FileExt; + +/// Represents an active, exclusive lock on a directory. +/// +/// This struct guarantees that the process holds an OS-level file lock +/// guarding the specified directory. +pub struct DirLock { + /// The path to the directory being guarded. + pub path: PathBuf, + // Kept alive to hold the flock + _file: std::fs::File, +} + +impl DirLock { + /// Acquires an exclusive lock on the specified directory. + /// + /// This function blocks until the lock can be acquired. We use a + /// separate `.lock` file within the directory rather than locking + /// the directory itself to avoid platform-specific issues with + /// directory locking and to ensure the lock file persists even if + /// the directory is cleaned. + pub fn lock_exclusive(path: PathBuf) -> Result { + let file = Self::open_lock_file(&path)?; + file.lock_exclusive() + .with_context(|| format!("Failed to acquire exclusive lock on {:?}", path))?; + Ok(Self { path, _file: file }) + } + + fn open_lock_file(path: &std::path::Path) -> Result { + let lock_path = path.join(".lock"); + + // Ensure the directory exists + if let Some(parent) = lock_path.parent() { + std::fs::create_dir_all(parent).with_context(|| { + format!("Failed to create directory for lock file: {:?}", parent) + })?; + } + // If the lock file already exists, we open it in read-only mode. + // This prevents failures if the file is read-only (e.g., after + // making the toolchain directory read-only), while still allowing + // us to acquire shared and exclusive locks on the file descriptor. + if lock_path.exists() { + std::fs::OpenOptions::new() + .read(true) + .open(&lock_path) + .with_context(|| format!("Failed to open lock file at {:?}", lock_path)) + } else { + std::fs::OpenOptions::new() + .read(true) + .write(true) + .create(true) + .open(&lock_path) + .with_context(|| format!("Failed to create lock file at {:?}", lock_path)) + } + } +} diff --git a/anneal/src/validate.rs b/anneal/v1/src/validate.rs similarity index 100% rename from anneal/src/validate.rs rename to anneal/v1/src/validate.rs diff --git a/anneal/testdata/setup/aeneas-linux-aarch64.tar.gz b/anneal/v1/testdata/setup/aeneas-linux-aarch64.tar.gz similarity index 100% rename from anneal/testdata/setup/aeneas-linux-aarch64.tar.gz rename to anneal/v1/testdata/setup/aeneas-linux-aarch64.tar.gz diff --git a/anneal/testdata/setup/aeneas-linux-x86_64.tar.gz b/anneal/v1/testdata/setup/aeneas-linux-x86_64.tar.gz similarity index 100% rename from anneal/testdata/setup/aeneas-linux-x86_64.tar.gz rename to anneal/v1/testdata/setup/aeneas-linux-x86_64.tar.gz diff --git a/anneal/testdata/setup/aeneas-macos-aarch64.tar.gz b/anneal/v1/testdata/setup/aeneas-macos-aarch64.tar.gz similarity index 100% rename from anneal/testdata/setup/aeneas-macos-aarch64.tar.gz rename to anneal/v1/testdata/setup/aeneas-macos-aarch64.tar.gz diff --git a/anneal/testdata/setup/aeneas-macos-x86_64.tar.gz b/anneal/v1/testdata/setup/aeneas-macos-x86_64.tar.gz similarity index 100% rename from anneal/testdata/setup/aeneas-macos-x86_64.tar.gz rename to anneal/v1/testdata/setup/aeneas-macos-x86_64.tar.gz diff --git a/anneal/testdata/setup/llvm-tools-nightly-x86_64-unknown-linux-gnu.tar.gz b/anneal/v1/testdata/setup/llvm-tools-nightly-x86_64-unknown-linux-gnu.tar.gz similarity index 100% rename from anneal/testdata/setup/llvm-tools-nightly-x86_64-unknown-linux-gnu.tar.gz rename to anneal/v1/testdata/setup/llvm-tools-nightly-x86_64-unknown-linux-gnu.tar.gz diff --git a/anneal/testdata/setup/llvm-tools-nightly-x86_64-unknown-linux-gnu.tar.xz b/anneal/v1/testdata/setup/llvm-tools-nightly-x86_64-unknown-linux-gnu.tar.xz similarity index 100% rename from anneal/testdata/setup/llvm-tools-nightly-x86_64-unknown-linux-gnu.tar.xz rename to anneal/v1/testdata/setup/llvm-tools-nightly-x86_64-unknown-linux-gnu.tar.xz diff --git a/anneal/testdata/setup/miri-nightly-x86_64-unknown-linux-gnu.tar.gz b/anneal/v1/testdata/setup/miri-nightly-x86_64-unknown-linux-gnu.tar.gz similarity index 100% rename from anneal/testdata/setup/miri-nightly-x86_64-unknown-linux-gnu.tar.gz rename to anneal/v1/testdata/setup/miri-nightly-x86_64-unknown-linux-gnu.tar.gz diff --git a/anneal/testdata/setup/miri-nightly-x86_64-unknown-linux-gnu.tar.xz b/anneal/v1/testdata/setup/miri-nightly-x86_64-unknown-linux-gnu.tar.xz similarity index 100% rename from anneal/testdata/setup/miri-nightly-x86_64-unknown-linux-gnu.tar.xz rename to anneal/v1/testdata/setup/miri-nightly-x86_64-unknown-linux-gnu.tar.xz diff --git a/anneal/testdata/setup/rust-src-nightly.tar.gz b/anneal/v1/testdata/setup/rust-src-nightly.tar.gz similarity index 100% rename from anneal/testdata/setup/rust-src-nightly.tar.gz rename to anneal/v1/testdata/setup/rust-src-nightly.tar.gz diff --git a/anneal/testdata/setup/rust-src-nightly.tar.xz b/anneal/v1/testdata/setup/rust-src-nightly.tar.xz similarity index 100% rename from anneal/testdata/setup/rust-src-nightly.tar.xz rename to anneal/v1/testdata/setup/rust-src-nightly.tar.xz diff --git a/anneal/testdata/setup/rust-std-nightly-x86_64-unknown-linux-gnu.tar.gz b/anneal/v1/testdata/setup/rust-std-nightly-x86_64-unknown-linux-gnu.tar.gz similarity index 100% rename from anneal/testdata/setup/rust-std-nightly-x86_64-unknown-linux-gnu.tar.gz rename to anneal/v1/testdata/setup/rust-std-nightly-x86_64-unknown-linux-gnu.tar.gz diff --git a/anneal/testdata/setup/rust-std-nightly-x86_64-unknown-linux-gnu.tar.xz b/anneal/v1/testdata/setup/rust-std-nightly-x86_64-unknown-linux-gnu.tar.xz similarity index 100% rename from anneal/testdata/setup/rust-std-nightly-x86_64-unknown-linux-gnu.tar.xz rename to anneal/v1/testdata/setup/rust-std-nightly-x86_64-unknown-linux-gnu.tar.xz diff --git a/anneal/testdata/setup/rustc-dev-nightly-x86_64-unknown-linux-gnu.tar.gz b/anneal/v1/testdata/setup/rustc-dev-nightly-x86_64-unknown-linux-gnu.tar.gz similarity index 100% rename from anneal/testdata/setup/rustc-dev-nightly-x86_64-unknown-linux-gnu.tar.gz rename to anneal/v1/testdata/setup/rustc-dev-nightly-x86_64-unknown-linux-gnu.tar.gz diff --git a/anneal/testdata/setup/rustc-dev-nightly-x86_64-unknown-linux-gnu.tar.xz b/anneal/v1/testdata/setup/rustc-dev-nightly-x86_64-unknown-linux-gnu.tar.xz similarity index 100% rename from anneal/testdata/setup/rustc-dev-nightly-x86_64-unknown-linux-gnu.tar.xz rename to anneal/v1/testdata/setup/rustc-dev-nightly-x86_64-unknown-linux-gnu.tar.xz diff --git a/anneal/testdata/setup/rustc-nightly-x86_64-unknown-linux-gnu.tar.gz b/anneal/v1/testdata/setup/rustc-nightly-x86_64-unknown-linux-gnu.tar.gz similarity index 100% rename from anneal/testdata/setup/rustc-nightly-x86_64-unknown-linux-gnu.tar.gz rename to anneal/v1/testdata/setup/rustc-nightly-x86_64-unknown-linux-gnu.tar.gz diff --git a/anneal/testdata/setup/rustc-nightly-x86_64-unknown-linux-gnu.tar.xz b/anneal/v1/testdata/setup/rustc-nightly-x86_64-unknown-linux-gnu.tar.xz similarity index 100% rename from anneal/testdata/setup/rustc-nightly-x86_64-unknown-linux-gnu.tar.xz rename to anneal/v1/testdata/setup/rustc-nightly-x86_64-unknown-linux-gnu.tar.xz diff --git a/anneal/tests/.gitignore b/anneal/v1/tests/.gitignore similarity index 100% rename from anneal/tests/.gitignore rename to anneal/v1/tests/.gitignore diff --git a/anneal/tests/fixtures/allow_sorry_fallbacks/anneal.toml b/anneal/v1/tests/fixtures/allow_sorry_fallbacks/anneal.toml similarity index 100% rename from anneal/tests/fixtures/allow_sorry_fallbacks/anneal.toml rename to anneal/v1/tests/fixtures/allow_sorry_fallbacks/anneal.toml diff --git a/anneal/tests/fixtures/allow_sorry_fallbacks/expected.stderr b/anneal/v1/tests/fixtures/allow_sorry_fallbacks/expected.stderr similarity index 100% rename from anneal/tests/fixtures/allow_sorry_fallbacks/expected.stderr rename to anneal/v1/tests/fixtures/allow_sorry_fallbacks/expected.stderr diff --git a/anneal/tests/fixtures/allow_sorry_fallbacks/source/Cargo.toml b/anneal/v1/tests/fixtures/allow_sorry_fallbacks/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/allow_sorry_fallbacks/source/Cargo.toml rename to anneal/v1/tests/fixtures/allow_sorry_fallbacks/source/Cargo.toml diff --git a/anneal/tests/fixtures/allow_sorry_fallbacks/source/src/lib.rs b/anneal/v1/tests/fixtures/allow_sorry_fallbacks/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/allow_sorry_fallbacks/source/src/lib.rs rename to anneal/v1/tests/fixtures/allow_sorry_fallbacks/source/src/lib.rs diff --git a/anneal/tests/fixtures/anneal_multiple_targets/anneal.toml b/anneal/v1/tests/fixtures/anneal_multiple_targets/anneal.toml similarity index 100% rename from anneal/tests/fixtures/anneal_multiple_targets/anneal.toml rename to anneal/v1/tests/fixtures/anneal_multiple_targets/anneal.toml diff --git a/anneal/tests/fixtures/anneal_multiple_targets/source/Cargo.toml b/anneal/v1/tests/fixtures/anneal_multiple_targets/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/anneal_multiple_targets/source/Cargo.toml rename to anneal/v1/tests/fixtures/anneal_multiple_targets/source/Cargo.toml diff --git a/anneal/tests/fixtures/anneal_multiple_targets/source/src/bin/my_bin.rs b/anneal/v1/tests/fixtures/anneal_multiple_targets/source/src/bin/my_bin.rs similarity index 100% rename from anneal/tests/fixtures/anneal_multiple_targets/source/src/bin/my_bin.rs rename to anneal/v1/tests/fixtures/anneal_multiple_targets/source/src/bin/my_bin.rs diff --git a/anneal/tests/fixtures/anneal_multiple_targets/source/src/lib.rs b/anneal/v1/tests/fixtures/anneal_multiple_targets/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/anneal_multiple_targets/source/src/lib.rs rename to anneal/v1/tests/fixtures/anneal_multiple_targets/source/src/lib.rs diff --git a/anneal/tests/fixtures/anneal_shim_syntax_error/anneal.toml b/anneal/v1/tests/fixtures/anneal_shim_syntax_error/anneal.toml similarity index 100% rename from anneal/tests/fixtures/anneal_shim_syntax_error/anneal.toml rename to anneal/v1/tests/fixtures/anneal_shim_syntax_error/anneal.toml diff --git a/anneal/tests/fixtures/anneal_shim_syntax_error/expected.stderr b/anneal/v1/tests/fixtures/anneal_shim_syntax_error/expected.stderr similarity index 100% rename from anneal/tests/fixtures/anneal_shim_syntax_error/expected.stderr rename to anneal/v1/tests/fixtures/anneal_shim_syntax_error/expected.stderr diff --git a/anneal/tests/fixtures/anneal_shim_syntax_error/mock_charon_output.json b/anneal/v1/tests/fixtures/anneal_shim_syntax_error/mock_charon_output.json similarity index 100% rename from anneal/tests/fixtures/anneal_shim_syntax_error/mock_charon_output.json rename to anneal/v1/tests/fixtures/anneal_shim_syntax_error/mock_charon_output.json diff --git a/anneal/tests/fixtures/anneal_shim_syntax_error/source/Cargo.toml b/anneal/v1/tests/fixtures/anneal_shim_syntax_error/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/anneal_shim_syntax_error/source/Cargo.toml rename to anneal/v1/tests/fixtures/anneal_shim_syntax_error/source/Cargo.toml diff --git a/anneal/tests/fixtures/anneal_shim_syntax_error/source/src/lib.rs b/anneal/v1/tests/fixtures/anneal_shim_syntax_error/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/anneal_shim_syntax_error/source/src/lib.rs rename to anneal/v1/tests/fixtures/anneal_shim_syntax_error/source/src/lib.rs diff --git a/anneal/tests/fixtures/archive_lake_cache_reuse/anneal.toml b/anneal/v1/tests/fixtures/archive_lake_cache_reuse/anneal.toml similarity index 100% rename from anneal/tests/fixtures/archive_lake_cache_reuse/anneal.toml rename to anneal/v1/tests/fixtures/archive_lake_cache_reuse/anneal.toml diff --git a/anneal/tests/fixtures/associated_types/anneal.toml b/anneal/v1/tests/fixtures/associated_types/anneal.toml similarity index 100% rename from anneal/tests/fixtures/associated_types/anneal.toml rename to anneal/v1/tests/fixtures/associated_types/anneal.toml diff --git a/anneal/tests/fixtures/associated_types/expected.stderr b/anneal/v1/tests/fixtures/associated_types/expected.stderr similarity index 100% rename from anneal/tests/fixtures/associated_types/expected.stderr rename to anneal/v1/tests/fixtures/associated_types/expected.stderr diff --git a/anneal/tests/fixtures/associated_types/source/Cargo.toml b/anneal/v1/tests/fixtures/associated_types/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/associated_types/source/Cargo.toml rename to anneal/v1/tests/fixtures/associated_types/source/Cargo.toml diff --git a/anneal/tests/fixtures/associated_types/source/src/lib.rs b/anneal/v1/tests/fixtures/associated_types/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/associated_types/source/src/lib.rs rename to anneal/v1/tests/fixtures/associated_types/source/src/lib.rs diff --git a/anneal/tests/fixtures/broken_doc_block/anneal.toml b/anneal/v1/tests/fixtures/broken_doc_block/anneal.toml similarity index 100% rename from anneal/tests/fixtures/broken_doc_block/anneal.toml rename to anneal/v1/tests/fixtures/broken_doc_block/anneal.toml diff --git a/anneal/tests/fixtures/broken_doc_block/expected.stderr b/anneal/v1/tests/fixtures/broken_doc_block/expected.stderr similarity index 100% rename from anneal/tests/fixtures/broken_doc_block/expected.stderr rename to anneal/v1/tests/fixtures/broken_doc_block/expected.stderr diff --git a/anneal/tests/fixtures/broken_doc_block/source/Cargo.toml b/anneal/v1/tests/fixtures/broken_doc_block/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/broken_doc_block/source/Cargo.toml rename to anneal/v1/tests/fixtures/broken_doc_block/source/Cargo.toml diff --git a/anneal/tests/fixtures/broken_doc_block/source/src/lib.rs b/anneal/v1/tests/fixtures/broken_doc_block/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/broken_doc_block/source/src/lib.rs rename to anneal/v1/tests/fixtures/broken_doc_block/source/src/lib.rs diff --git a/anneal/tests/fixtures/canonicalized_workspace/anneal.toml b/anneal/v1/tests/fixtures/canonicalized_workspace/anneal.toml similarity index 100% rename from anneal/tests/fixtures/canonicalized_workspace/anneal.toml rename to anneal/v1/tests/fixtures/canonicalized_workspace/anneal.toml diff --git a/anneal/tests/fixtures/canonicalized_workspace/source/Cargo.toml b/anneal/v1/tests/fixtures/canonicalized_workspace/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/canonicalized_workspace/source/Cargo.toml rename to anneal/v1/tests/fixtures/canonicalized_workspace/source/Cargo.toml diff --git a/anneal/tests/fixtures/canonicalized_workspace/source/crates/app/Cargo.toml b/anneal/v1/tests/fixtures/canonicalized_workspace/source/crates/app/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/canonicalized_workspace/source/crates/app/Cargo.toml rename to anneal/v1/tests/fixtures/canonicalized_workspace/source/crates/app/Cargo.toml diff --git a/anneal/tests/fixtures/canonicalized_workspace/source/crates/app/src/lib.rs b/anneal/v1/tests/fixtures/canonicalized_workspace/source/crates/app/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/canonicalized_workspace/source/crates/app/src/lib.rs rename to anneal/v1/tests/fixtures/canonicalized_workspace/source/crates/app/src/lib.rs diff --git a/anneal/tests/fixtures/canonicalized_workspace/source/crates/lib/Cargo.toml b/anneal/v1/tests/fixtures/canonicalized_workspace/source/crates/lib/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/canonicalized_workspace/source/crates/lib/Cargo.toml rename to anneal/v1/tests/fixtures/canonicalized_workspace/source/crates/lib/Cargo.toml diff --git a/anneal/tests/fixtures/canonicalized_workspace/source/crates/lib/src/lib.rs b/anneal/v1/tests/fixtures/canonicalized_workspace/source/crates/lib/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/canonicalized_workspace/source/crates/lib/src/lib.rs rename to anneal/v1/tests/fixtures/canonicalized_workspace/source/crates/lib/src/lib.rs diff --git a/anneal/tests/fixtures/cfg_blind_spot/anneal.toml b/anneal/v1/tests/fixtures/cfg_blind_spot/anneal.toml similarity index 100% rename from anneal/tests/fixtures/cfg_blind_spot/anneal.toml rename to anneal/v1/tests/fixtures/cfg_blind_spot/anneal.toml diff --git a/anneal/tests/fixtures/cfg_blind_spot/expected.stderr b/anneal/v1/tests/fixtures/cfg_blind_spot/expected.stderr similarity index 100% rename from anneal/tests/fixtures/cfg_blind_spot/expected.stderr rename to anneal/v1/tests/fixtures/cfg_blind_spot/expected.stderr diff --git a/anneal/tests/fixtures/cfg_blind_spot/source/Cargo.toml b/anneal/v1/tests/fixtures/cfg_blind_spot/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/cfg_blind_spot/source/Cargo.toml rename to anneal/v1/tests/fixtures/cfg_blind_spot/source/Cargo.toml diff --git a/anneal/tests/fixtures/cfg_blind_spot/source/src/lib.rs b/anneal/v1/tests/fixtures/cfg_blind_spot/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/cfg_blind_spot/source/src/lib.rs rename to anneal/v1/tests/fixtures/cfg_blind_spot/source/src/lib.rs diff --git a/anneal/tests/fixtures/charon_version_match/anneal.toml b/anneal/v1/tests/fixtures/charon_version_match/anneal.toml similarity index 100% rename from anneal/tests/fixtures/charon_version_match/anneal.toml rename to anneal/v1/tests/fixtures/charon_version_match/anneal.toml diff --git a/anneal/tests/fixtures/charon_version_match/expected.stderr b/anneal/v1/tests/fixtures/charon_version_match/expected.stderr similarity index 100% rename from anneal/tests/fixtures/charon_version_match/expected.stderr rename to anneal/v1/tests/fixtures/charon_version_match/expected.stderr diff --git a/anneal/tests/fixtures/charon_version_match/mock_charon.sh b/anneal/v1/tests/fixtures/charon_version_match/mock_charon.sh similarity index 100% rename from anneal/tests/fixtures/charon_version_match/mock_charon.sh rename to anneal/v1/tests/fixtures/charon_version_match/mock_charon.sh diff --git a/anneal/tests/fixtures/charon_version_match/source/Cargo.toml b/anneal/v1/tests/fixtures/charon_version_match/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/charon_version_match/source/Cargo.toml rename to anneal/v1/tests/fixtures/charon_version_match/source/Cargo.toml diff --git a/anneal/tests/fixtures/charon_version_match/source/src/lib.rs b/anneal/v1/tests/fixtures/charon_version_match/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/charon_version_match/source/src/lib.rs rename to anneal/v1/tests/fixtures/charon_version_match/source/src/lib.rs diff --git a/anneal/tests/fixtures/collision/anneal.toml b/anneal/v1/tests/fixtures/collision/anneal.toml similarity index 100% rename from anneal/tests/fixtures/collision/anneal.toml rename to anneal/v1/tests/fixtures/collision/anneal.toml diff --git a/anneal/tests/fixtures/collision/source/Cargo.toml b/anneal/v1/tests/fixtures/collision/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/collision/source/Cargo.toml rename to anneal/v1/tests/fixtures/collision/source/Cargo.toml diff --git a/anneal/tests/fixtures/collision/source/crates/a/Cargo.toml b/anneal/v1/tests/fixtures/collision/source/crates/a/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/collision/source/crates/a/Cargo.toml rename to anneal/v1/tests/fixtures/collision/source/crates/a/Cargo.toml diff --git a/anneal/tests/fixtures/collision/source/crates/a/src/lib.rs b/anneal/v1/tests/fixtures/collision/source/crates/a/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/collision/source/crates/a/src/lib.rs rename to anneal/v1/tests/fixtures/collision/source/crates/a/src/lib.rs diff --git a/anneal/tests/fixtures/collision/source/crates/b/Cargo.toml b/anneal/v1/tests/fixtures/collision/source/crates/b/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/collision/source/crates/b/Cargo.toml rename to anneal/v1/tests/fixtures/collision/source/crates/b/Cargo.toml diff --git a/anneal/tests/fixtures/collision/source/crates/b/src/lib.rs b/anneal/v1/tests/fixtures/collision/source/crates/b/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/collision/source/crates/b/src/lib.rs rename to anneal/v1/tests/fixtures/collision/source/crates/b/src/lib.rs diff --git a/anneal/tests/fixtures/const_block_blind_spot/anneal.toml b/anneal/v1/tests/fixtures/const_block_blind_spot/anneal.toml similarity index 100% rename from anneal/tests/fixtures/const_block_blind_spot/anneal.toml rename to anneal/v1/tests/fixtures/const_block_blind_spot/anneal.toml diff --git a/anneal/tests/fixtures/const_block_blind_spot/expected.stderr b/anneal/v1/tests/fixtures/const_block_blind_spot/expected.stderr similarity index 100% rename from anneal/tests/fixtures/const_block_blind_spot/expected.stderr rename to anneal/v1/tests/fixtures/const_block_blind_spot/expected.stderr diff --git a/anneal/tests/fixtures/const_block_blind_spot/source/Cargo.toml b/anneal/v1/tests/fixtures/const_block_blind_spot/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/const_block_blind_spot/source/Cargo.toml rename to anneal/v1/tests/fixtures/const_block_blind_spot/source/Cargo.toml diff --git a/anneal/tests/fixtures/const_block_blind_spot/source/src/lib.rs b/anneal/v1/tests/fixtures/const_block_blind_spot/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/const_block_blind_spot/source/src/lib.rs rename to anneal/v1/tests/fixtures/const_block_blind_spot/source/src/lib.rs diff --git a/anneal/tests/fixtures/const_generics/anneal.toml b/anneal/v1/tests/fixtures/const_generics/anneal.toml similarity index 100% rename from anneal/tests/fixtures/const_generics/anneal.toml rename to anneal/v1/tests/fixtures/const_generics/anneal.toml diff --git a/anneal/tests/fixtures/const_generics/expected.stderr b/anneal/v1/tests/fixtures/const_generics/expected.stderr similarity index 100% rename from anneal/tests/fixtures/const_generics/expected.stderr rename to anneal/v1/tests/fixtures/const_generics/expected.stderr diff --git a/anneal/tests/fixtures/const_generics/source/Cargo.toml b/anneal/v1/tests/fixtures/const_generics/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/const_generics/source/Cargo.toml rename to anneal/v1/tests/fixtures/const_generics/source/Cargo.toml diff --git a/anneal/tests/fixtures/const_generics/source/src/lib.rs b/anneal/v1/tests/fixtures/const_generics/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/const_generics/source/src/lib.rs rename to anneal/v1/tests/fixtures/const_generics/source/src/lib.rs diff --git a/anneal/tests/fixtures/cyclic_paths/anneal.toml b/anneal/v1/tests/fixtures/cyclic_paths/anneal.toml similarity index 100% rename from anneal/tests/fixtures/cyclic_paths/anneal.toml rename to anneal/v1/tests/fixtures/cyclic_paths/anneal.toml diff --git a/anneal/tests/fixtures/cyclic_paths/expected.stderr b/anneal/v1/tests/fixtures/cyclic_paths/expected.stderr similarity index 100% rename from anneal/tests/fixtures/cyclic_paths/expected.stderr rename to anneal/v1/tests/fixtures/cyclic_paths/expected.stderr diff --git a/anneal/tests/fixtures/cyclic_paths/source/Cargo.toml b/anneal/v1/tests/fixtures/cyclic_paths/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/cyclic_paths/source/Cargo.toml rename to anneal/v1/tests/fixtures/cyclic_paths/source/Cargo.toml diff --git a/anneal/tests/fixtures/cyclic_paths/source/src/lib.rs b/anneal/v1/tests/fixtures/cyclic_paths/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/cyclic_paths/source/src/lib.rs rename to anneal/v1/tests/fixtures/cyclic_paths/source/src/lib.rs diff --git a/anneal/tests/fixtures/diagnostic_mapping/anneal.toml b/anneal/v1/tests/fixtures/diagnostic_mapping/anneal.toml similarity index 100% rename from anneal/tests/fixtures/diagnostic_mapping/anneal.toml rename to anneal/v1/tests/fixtures/diagnostic_mapping/anneal.toml diff --git a/anneal/tests/fixtures/diagnostic_mapping/expected.stderr b/anneal/v1/tests/fixtures/diagnostic_mapping/expected.stderr similarity index 100% rename from anneal/tests/fixtures/diagnostic_mapping/expected.stderr rename to anneal/v1/tests/fixtures/diagnostic_mapping/expected.stderr diff --git a/anneal/tests/fixtures/diagnostic_mapping/source/Cargo.toml b/anneal/v1/tests/fixtures/diagnostic_mapping/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/diagnostic_mapping/source/Cargo.toml rename to anneal/v1/tests/fixtures/diagnostic_mapping/source/Cargo.toml diff --git a/anneal/tests/fixtures/diagnostic_mapping/source/src/lib.rs b/anneal/v1/tests/fixtures/diagnostic_mapping/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/diagnostic_mapping/source/src/lib.rs rename to anneal/v1/tests/fixtures/diagnostic_mapping/source/src/lib.rs diff --git a/anneal/tests/fixtures/dirty_sandbox/anneal.toml b/anneal/v1/tests/fixtures/dirty_sandbox/anneal.toml similarity index 100% rename from anneal/tests/fixtures/dirty_sandbox/anneal.toml rename to anneal/v1/tests/fixtures/dirty_sandbox/anneal.toml diff --git a/anneal/tests/fixtures/dirty_sandbox/source/main.rs b/anneal/v1/tests/fixtures/dirty_sandbox/source/main.rs similarity index 100% rename from anneal/tests/fixtures/dirty_sandbox/source/main.rs rename to anneal/v1/tests/fixtures/dirty_sandbox/source/main.rs diff --git a/anneal/tests/fixtures/dirty_sandbox/source/target/file b/anneal/v1/tests/fixtures/dirty_sandbox/source/target/file similarity index 100% rename from anneal/tests/fixtures/dirty_sandbox/source/target/file rename to anneal/v1/tests/fixtures/dirty_sandbox/source/target/file diff --git a/anneal/tests/fixtures/dst_layout/anneal.toml b/anneal/v1/tests/fixtures/dst_layout/anneal.toml similarity index 100% rename from anneal/tests/fixtures/dst_layout/anneal.toml rename to anneal/v1/tests/fixtures/dst_layout/anneal.toml diff --git a/anneal/tests/fixtures/dst_layout/expected.stderr b/anneal/v1/tests/fixtures/dst_layout/expected.stderr similarity index 100% rename from anneal/tests/fixtures/dst_layout/expected.stderr rename to anneal/v1/tests/fixtures/dst_layout/expected.stderr diff --git a/anneal/tests/fixtures/dst_layout/source/Cargo.toml b/anneal/v1/tests/fixtures/dst_layout/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/dst_layout/source/Cargo.toml rename to anneal/v1/tests/fixtures/dst_layout/source/Cargo.toml diff --git a/anneal/tests/fixtures/dst_layout/source/src/FunsExternal.lean b/anneal/v1/tests/fixtures/dst_layout/source/src/FunsExternal.lean similarity index 100% rename from anneal/tests/fixtures/dst_layout/source/src/FunsExternal.lean rename to anneal/v1/tests/fixtures/dst_layout/source/src/FunsExternal.lean diff --git a/anneal/tests/fixtures/dst_layout/source/src/lib.rs b/anneal/v1/tests/fixtures/dst_layout/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/dst_layout/source/src/lib.rs rename to anneal/v1/tests/fixtures/dst_layout/source/src/lib.rs diff --git a/anneal/tests/fixtures/duplicate_blocks/anneal.toml b/anneal/v1/tests/fixtures/duplicate_blocks/anneal.toml similarity index 100% rename from anneal/tests/fixtures/duplicate_blocks/anneal.toml rename to anneal/v1/tests/fixtures/duplicate_blocks/anneal.toml diff --git a/anneal/tests/fixtures/duplicate_blocks/expected.stderr b/anneal/v1/tests/fixtures/duplicate_blocks/expected.stderr similarity index 100% rename from anneal/tests/fixtures/duplicate_blocks/expected.stderr rename to anneal/v1/tests/fixtures/duplicate_blocks/expected.stderr diff --git a/anneal/tests/fixtures/duplicate_blocks/source/Cargo.toml b/anneal/v1/tests/fixtures/duplicate_blocks/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/duplicate_blocks/source/Cargo.toml rename to anneal/v1/tests/fixtures/duplicate_blocks/source/Cargo.toml diff --git a/anneal/tests/fixtures/duplicate_blocks/source/src/lib.rs b/anneal/v1/tests/fixtures/duplicate_blocks/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/duplicate_blocks/source/src/lib.rs rename to anneal/v1/tests/fixtures/duplicate_blocks/source/src/lib.rs diff --git a/anneal/tests/fixtures/edge_cases_cfg/args.txt b/anneal/v1/tests/fixtures/edge_cases_cfg/args.txt similarity index 100% rename from anneal/tests/fixtures/edge_cases_cfg/args.txt rename to anneal/v1/tests/fixtures/edge_cases_cfg/args.txt diff --git a/anneal/tests/fixtures/edge_cases_cfg/test_7_1_phantom_fn/anneal.toml b/anneal/v1/tests/fixtures/edge_cases_cfg/test_7_1_phantom_fn/anneal.toml similarity index 100% rename from anneal/tests/fixtures/edge_cases_cfg/test_7_1_phantom_fn/anneal.toml rename to anneal/v1/tests/fixtures/edge_cases_cfg/test_7_1_phantom_fn/anneal.toml diff --git a/anneal/tests/fixtures/edge_cases_cfg/test_7_1_phantom_fn/expected.stderr b/anneal/v1/tests/fixtures/edge_cases_cfg/test_7_1_phantom_fn/expected.stderr similarity index 100% rename from anneal/tests/fixtures/edge_cases_cfg/test_7_1_phantom_fn/expected.stderr rename to anneal/v1/tests/fixtures/edge_cases_cfg/test_7_1_phantom_fn/expected.stderr diff --git a/anneal/tests/fixtures/edge_cases_cfg/test_7_1_phantom_fn/source/Cargo.toml b/anneal/v1/tests/fixtures/edge_cases_cfg/test_7_1_phantom_fn/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/edge_cases_cfg/test_7_1_phantom_fn/source/Cargo.toml rename to anneal/v1/tests/fixtures/edge_cases_cfg/test_7_1_phantom_fn/source/Cargo.toml diff --git a/anneal/tests/fixtures/edge_cases_cfg/test_7_1_phantom_fn/source/src/lib.rs b/anneal/v1/tests/fixtures/edge_cases_cfg/test_7_1_phantom_fn/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/edge_cases_cfg/test_7_1_phantom_fn/source/src/lib.rs rename to anneal/v1/tests/fixtures/edge_cases_cfg/test_7_1_phantom_fn/source/src/lib.rs diff --git a/anneal/tests/fixtures/edge_cases_cfg/test_7_3_ghost_spec/anneal.toml b/anneal/v1/tests/fixtures/edge_cases_cfg/test_7_3_ghost_spec/anneal.toml similarity index 100% rename from anneal/tests/fixtures/edge_cases_cfg/test_7_3_ghost_spec/anneal.toml rename to anneal/v1/tests/fixtures/edge_cases_cfg/test_7_3_ghost_spec/anneal.toml diff --git a/anneal/tests/fixtures/edge_cases_cfg/test_7_3_ghost_spec/expected.stderr b/anneal/v1/tests/fixtures/edge_cases_cfg/test_7_3_ghost_spec/expected.stderr similarity index 100% rename from anneal/tests/fixtures/edge_cases_cfg/test_7_3_ghost_spec/expected.stderr rename to anneal/v1/tests/fixtures/edge_cases_cfg/test_7_3_ghost_spec/expected.stderr diff --git a/anneal/tests/fixtures/edge_cases_cfg/test_7_3_ghost_spec/source/Cargo.toml b/anneal/v1/tests/fixtures/edge_cases_cfg/test_7_3_ghost_spec/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/edge_cases_cfg/test_7_3_ghost_spec/source/Cargo.toml rename to anneal/v1/tests/fixtures/edge_cases_cfg/test_7_3_ghost_spec/source/Cargo.toml diff --git a/anneal/tests/fixtures/edge_cases_cfg/test_7_3_ghost_spec/source/src/lib.rs b/anneal/v1/tests/fixtures/edge_cases_cfg/test_7_3_ghost_spec/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/edge_cases_cfg/test_7_3_ghost_spec/source/src/lib.rs rename to anneal/v1/tests/fixtures/edge_cases_cfg/test_7_3_ghost_spec/source/src/lib.rs diff --git a/anneal/tests/fixtures/edge_cases_charon/args.txt b/anneal/v1/tests/fixtures/edge_cases_charon/args.txt similarity index 100% rename from anneal/tests/fixtures/edge_cases_charon/args.txt rename to anneal/v1/tests/fixtures/edge_cases_charon/args.txt diff --git a/anneal/tests/fixtures/edge_cases_charon/test_8_2_unions/anneal.toml b/anneal/v1/tests/fixtures/edge_cases_charon/test_8_2_unions/anneal.toml similarity index 100% rename from anneal/tests/fixtures/edge_cases_charon/test_8_2_unions/anneal.toml rename to anneal/v1/tests/fixtures/edge_cases_charon/test_8_2_unions/anneal.toml diff --git a/anneal/tests/fixtures/edge_cases_charon/test_8_2_unions/expected.stderr b/anneal/v1/tests/fixtures/edge_cases_charon/test_8_2_unions/expected.stderr similarity index 100% rename from anneal/tests/fixtures/edge_cases_charon/test_8_2_unions/expected.stderr rename to anneal/v1/tests/fixtures/edge_cases_charon/test_8_2_unions/expected.stderr diff --git a/anneal/tests/fixtures/edge_cases_charon/test_8_2_unions/source/Cargo.toml b/anneal/v1/tests/fixtures/edge_cases_charon/test_8_2_unions/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/edge_cases_charon/test_8_2_unions/source/Cargo.toml rename to anneal/v1/tests/fixtures/edge_cases_charon/test_8_2_unions/source/Cargo.toml diff --git a/anneal/tests/fixtures/edge_cases_charon/test_8_2_unions/source/src/lib.rs b/anneal/v1/tests/fixtures/edge_cases_charon/test_8_2_unions/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/edge_cases_charon/test_8_2_unions/source/src/lib.rs rename to anneal/v1/tests/fixtures/edge_cases_charon/test_8_2_unions/source/src/lib.rs diff --git a/anneal/tests/fixtures/edge_cases_control/args.txt b/anneal/v1/tests/fixtures/edge_cases_control/args.txt similarity index 100% rename from anneal/tests/fixtures/edge_cases_control/args.txt rename to anneal/v1/tests/fixtures/edge_cases_control/args.txt diff --git a/anneal/tests/fixtures/edge_cases_invariants/args.txt b/anneal/v1/tests/fixtures/edge_cases_invariants/args.txt similarity index 100% rename from anneal/tests/fixtures/edge_cases_invariants/args.txt rename to anneal/v1/tests/fixtures/edge_cases_invariants/args.txt diff --git a/anneal/tests/fixtures/edge_cases_invariants/test_4_6_assoc_types/anneal.toml b/anneal/v1/tests/fixtures/edge_cases_invariants/test_4_6_assoc_types/anneal.toml similarity index 100% rename from anneal/tests/fixtures/edge_cases_invariants/test_4_6_assoc_types/anneal.toml rename to anneal/v1/tests/fixtures/edge_cases_invariants/test_4_6_assoc_types/anneal.toml diff --git a/anneal/tests/fixtures/edge_cases_invariants/test_4_6_assoc_types/expected.stderr b/anneal/v1/tests/fixtures/edge_cases_invariants/test_4_6_assoc_types/expected.stderr similarity index 100% rename from anneal/tests/fixtures/edge_cases_invariants/test_4_6_assoc_types/expected.stderr rename to anneal/v1/tests/fixtures/edge_cases_invariants/test_4_6_assoc_types/expected.stderr diff --git a/anneal/tests/fixtures/edge_cases_invariants/test_4_6_assoc_types/source/Cargo.toml b/anneal/v1/tests/fixtures/edge_cases_invariants/test_4_6_assoc_types/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/edge_cases_invariants/test_4_6_assoc_types/source/Cargo.toml rename to anneal/v1/tests/fixtures/edge_cases_invariants/test_4_6_assoc_types/source/Cargo.toml diff --git a/anneal/tests/fixtures/edge_cases_invariants/test_4_6_assoc_types/source/src/lib.rs b/anneal/v1/tests/fixtures/edge_cases_invariants/test_4_6_assoc_types/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/edge_cases_invariants/test_4_6_assoc_types/source/src/lib.rs rename to anneal/v1/tests/fixtures/edge_cases_invariants/test_4_6_assoc_types/source/src/lib.rs diff --git a/anneal/tests/fixtures/edge_cases_logic/args.txt b/anneal/v1/tests/fixtures/edge_cases_logic/args.txt similarity index 100% rename from anneal/tests/fixtures/edge_cases_logic/args.txt rename to anneal/v1/tests/fixtures/edge_cases_logic/args.txt diff --git a/anneal/tests/fixtures/edge_cases_logic/test_5_4_std_types/anneal.toml b/anneal/v1/tests/fixtures/edge_cases_logic/test_5_4_std_types/anneal.toml similarity index 100% rename from anneal/tests/fixtures/edge_cases_logic/test_5_4_std_types/anneal.toml rename to anneal/v1/tests/fixtures/edge_cases_logic/test_5_4_std_types/anneal.toml diff --git a/anneal/tests/fixtures/edge_cases_logic/test_5_4_std_types/expected.stderr b/anneal/v1/tests/fixtures/edge_cases_logic/test_5_4_std_types/expected.stderr similarity index 100% rename from anneal/tests/fixtures/edge_cases_logic/test_5_4_std_types/expected.stderr rename to anneal/v1/tests/fixtures/edge_cases_logic/test_5_4_std_types/expected.stderr diff --git a/anneal/tests/fixtures/edge_cases_logic/test_5_4_std_types/source/Cargo.toml b/anneal/v1/tests/fixtures/edge_cases_logic/test_5_4_std_types/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/edge_cases_logic/test_5_4_std_types/source/Cargo.toml rename to anneal/v1/tests/fixtures/edge_cases_logic/test_5_4_std_types/source/Cargo.toml diff --git a/anneal/tests/fixtures/edge_cases_logic/test_5_4_std_types/source/src/lib.rs b/anneal/v1/tests/fixtures/edge_cases_logic/test_5_4_std_types/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/edge_cases_logic/test_5_4_std_types/source/src/lib.rs rename to anneal/v1/tests/fixtures/edge_cases_logic/test_5_4_std_types/source/src/lib.rs diff --git a/anneal/tests/fixtures/edge_cases_macros/args.txt b/anneal/v1/tests/fixtures/edge_cases_macros/args.txt similarity index 100% rename from anneal/tests/fixtures/edge_cases_macros/args.txt rename to anneal/v1/tests/fixtures/edge_cases_macros/args.txt diff --git a/anneal/tests/fixtures/edge_cases_modules/args.txt b/anneal/v1/tests/fixtures/edge_cases_modules/args.txt similarity index 100% rename from anneal/tests/fixtures/edge_cases_modules/args.txt rename to anneal/v1/tests/fixtures/edge_cases_modules/args.txt diff --git a/anneal/tests/fixtures/edge_cases_modules/test_6_1_external_crate/anneal.toml b/anneal/v1/tests/fixtures/edge_cases_modules/test_6_1_external_crate/anneal.toml similarity index 100% rename from anneal/tests/fixtures/edge_cases_modules/test_6_1_external_crate/anneal.toml rename to anneal/v1/tests/fixtures/edge_cases_modules/test_6_1_external_crate/anneal.toml diff --git a/anneal/tests/fixtures/edge_cases_modules/test_6_1_external_crate/expected.stderr b/anneal/v1/tests/fixtures/edge_cases_modules/test_6_1_external_crate/expected.stderr similarity index 100% rename from anneal/tests/fixtures/edge_cases_modules/test_6_1_external_crate/expected.stderr rename to anneal/v1/tests/fixtures/edge_cases_modules/test_6_1_external_crate/expected.stderr diff --git a/anneal/tests/fixtures/edge_cases_modules/test_6_1_external_crate/source/Cargo.toml b/anneal/v1/tests/fixtures/edge_cases_modules/test_6_1_external_crate/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/edge_cases_modules/test_6_1_external_crate/source/Cargo.toml rename to anneal/v1/tests/fixtures/edge_cases_modules/test_6_1_external_crate/source/Cargo.toml diff --git a/anneal/tests/fixtures/edge_cases_modules/test_6_1_external_crate/source/src/lib.rs b/anneal/v1/tests/fixtures/edge_cases_modules/test_6_1_external_crate/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/edge_cases_modules/test_6_1_external_crate/source/src/lib.rs rename to anneal/v1/tests/fixtures/edge_cases_modules/test_6_1_external_crate/source/src/lib.rs diff --git a/anneal/tests/fixtures/edge_cases_modules/test_6_3_renaming/anneal.toml b/anneal/v1/tests/fixtures/edge_cases_modules/test_6_3_renaming/anneal.toml similarity index 100% rename from anneal/tests/fixtures/edge_cases_modules/test_6_3_renaming/anneal.toml rename to anneal/v1/tests/fixtures/edge_cases_modules/test_6_3_renaming/anneal.toml diff --git a/anneal/tests/fixtures/edge_cases_modules/test_6_3_renaming/expected.stderr b/anneal/v1/tests/fixtures/edge_cases_modules/test_6_3_renaming/expected.stderr similarity index 100% rename from anneal/tests/fixtures/edge_cases_modules/test_6_3_renaming/expected.stderr rename to anneal/v1/tests/fixtures/edge_cases_modules/test_6_3_renaming/expected.stderr diff --git a/anneal/tests/fixtures/edge_cases_modules/test_6_3_renaming/source/Cargo.toml b/anneal/v1/tests/fixtures/edge_cases_modules/test_6_3_renaming/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/edge_cases_modules/test_6_3_renaming/source/Cargo.toml rename to anneal/v1/tests/fixtures/edge_cases_modules/test_6_3_renaming/source/Cargo.toml diff --git a/anneal/tests/fixtures/edge_cases_modules/test_6_3_renaming/source/src/lib.rs b/anneal/v1/tests/fixtures/edge_cases_modules/test_6_3_renaming/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/edge_cases_modules/test_6_3_renaming/source/src/lib.rs rename to anneal/v1/tests/fixtures/edge_cases_modules/test_6_3_renaming/source/src/lib.rs diff --git a/anneal/tests/fixtures/edge_cases_mut_refs/args.txt b/anneal/v1/tests/fixtures/edge_cases_mut_refs/args.txt similarity index 100% rename from anneal/tests/fixtures/edge_cases_mut_refs/args.txt rename to anneal/v1/tests/fixtures/edge_cases_mut_refs/args.txt diff --git a/anneal/tests/fixtures/edge_cases_mut_refs/test_3_3_ret_mut/anneal.toml b/anneal/v1/tests/fixtures/edge_cases_mut_refs/test_3_3_ret_mut/anneal.toml similarity index 100% rename from anneal/tests/fixtures/edge_cases_mut_refs/test_3_3_ret_mut/anneal.toml rename to anneal/v1/tests/fixtures/edge_cases_mut_refs/test_3_3_ret_mut/anneal.toml diff --git a/anneal/tests/fixtures/edge_cases_mut_refs/test_3_3_ret_mut/expected.stderr b/anneal/v1/tests/fixtures/edge_cases_mut_refs/test_3_3_ret_mut/expected.stderr similarity index 100% rename from anneal/tests/fixtures/edge_cases_mut_refs/test_3_3_ret_mut/expected.stderr rename to anneal/v1/tests/fixtures/edge_cases_mut_refs/test_3_3_ret_mut/expected.stderr diff --git a/anneal/tests/fixtures/edge_cases_mut_refs/test_3_3_ret_mut/source/Cargo.toml b/anneal/v1/tests/fixtures/edge_cases_mut_refs/test_3_3_ret_mut/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/edge_cases_mut_refs/test_3_3_ret_mut/source/Cargo.toml rename to anneal/v1/tests/fixtures/edge_cases_mut_refs/test_3_3_ret_mut/source/Cargo.toml diff --git a/anneal/tests/fixtures/edge_cases_mut_refs/test_3_3_ret_mut/source/src/lib.rs b/anneal/v1/tests/fixtures/edge_cases_mut_refs/test_3_3_ret_mut/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/edge_cases_mut_refs/test_3_3_ret_mut/source/src/lib.rs rename to anneal/v1/tests/fixtures/edge_cases_mut_refs/test_3_3_ret_mut/source/src/lib.rs diff --git a/anneal/tests/fixtures/edge_cases_naming/args.txt b/anneal/v1/tests/fixtures/edge_cases_naming/args.txt similarity index 100% rename from anneal/tests/fixtures/edge_cases_naming/args.txt rename to anneal/v1/tests/fixtures/edge_cases_naming/args.txt diff --git a/anneal/tests/fixtures/edge_cases_naming/test_1_1_lean_keywords_fns/anneal.toml b/anneal/v1/tests/fixtures/edge_cases_naming/test_1_1_lean_keywords_fns/anneal.toml similarity index 100% rename from anneal/tests/fixtures/edge_cases_naming/test_1_1_lean_keywords_fns/anneal.toml rename to anneal/v1/tests/fixtures/edge_cases_naming/test_1_1_lean_keywords_fns/anneal.toml diff --git a/anneal/tests/fixtures/edge_cases_naming/test_1_1_lean_keywords_fns/expected.stderr b/anneal/v1/tests/fixtures/edge_cases_naming/test_1_1_lean_keywords_fns/expected.stderr similarity index 100% rename from anneal/tests/fixtures/edge_cases_naming/test_1_1_lean_keywords_fns/expected.stderr rename to anneal/v1/tests/fixtures/edge_cases_naming/test_1_1_lean_keywords_fns/expected.stderr diff --git a/anneal/tests/fixtures/edge_cases_naming/test_1_1_lean_keywords_fns/source/Cargo.toml b/anneal/v1/tests/fixtures/edge_cases_naming/test_1_1_lean_keywords_fns/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/edge_cases_naming/test_1_1_lean_keywords_fns/source/Cargo.toml rename to anneal/v1/tests/fixtures/edge_cases_naming/test_1_1_lean_keywords_fns/source/Cargo.toml diff --git a/anneal/tests/fixtures/edge_cases_naming/test_1_1_lean_keywords_fns/source/src/lib.rs b/anneal/v1/tests/fixtures/edge_cases_naming/test_1_1_lean_keywords_fns/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/edge_cases_naming/test_1_1_lean_keywords_fns/source/src/lib.rs rename to anneal/v1/tests/fixtures/edge_cases_naming/test_1_1_lean_keywords_fns/source/src/lib.rs diff --git a/anneal/tests/fixtures/edge_cases_naming/test_1_2_lean_keywords_args/anneal.toml b/anneal/v1/tests/fixtures/edge_cases_naming/test_1_2_lean_keywords_args/anneal.toml similarity index 100% rename from anneal/tests/fixtures/edge_cases_naming/test_1_2_lean_keywords_args/anneal.toml rename to anneal/v1/tests/fixtures/edge_cases_naming/test_1_2_lean_keywords_args/anneal.toml diff --git a/anneal/tests/fixtures/edge_cases_naming/test_1_2_lean_keywords_args/expected.stderr b/anneal/v1/tests/fixtures/edge_cases_naming/test_1_2_lean_keywords_args/expected.stderr similarity index 100% rename from anneal/tests/fixtures/edge_cases_naming/test_1_2_lean_keywords_args/expected.stderr rename to anneal/v1/tests/fixtures/edge_cases_naming/test_1_2_lean_keywords_args/expected.stderr diff --git a/anneal/tests/fixtures/edge_cases_naming/test_1_2_lean_keywords_args/source/Cargo.toml b/anneal/v1/tests/fixtures/edge_cases_naming/test_1_2_lean_keywords_args/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/edge_cases_naming/test_1_2_lean_keywords_args/source/Cargo.toml rename to anneal/v1/tests/fixtures/edge_cases_naming/test_1_2_lean_keywords_args/source/Cargo.toml diff --git a/anneal/tests/fixtures/edge_cases_naming/test_1_2_lean_keywords_args/source/src/lib.rs b/anneal/v1/tests/fixtures/edge_cases_naming/test_1_2_lean_keywords_args/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/edge_cases_naming/test_1_2_lean_keywords_args/source/src/lib.rs rename to anneal/v1/tests/fixtures/edge_cases_naming/test_1_2_lean_keywords_args/source/src/lib.rs diff --git a/anneal/tests/fixtures/edge_cases_naming/test_1_3_rust_keywords_idents/anneal.toml b/anneal/v1/tests/fixtures/edge_cases_naming/test_1_3_rust_keywords_idents/anneal.toml similarity index 100% rename from anneal/tests/fixtures/edge_cases_naming/test_1_3_rust_keywords_idents/anneal.toml rename to anneal/v1/tests/fixtures/edge_cases_naming/test_1_3_rust_keywords_idents/anneal.toml diff --git a/anneal/tests/fixtures/edge_cases_naming/test_1_3_rust_keywords_idents/expected.stderr b/anneal/v1/tests/fixtures/edge_cases_naming/test_1_3_rust_keywords_idents/expected.stderr similarity index 100% rename from anneal/tests/fixtures/edge_cases_naming/test_1_3_rust_keywords_idents/expected.stderr rename to anneal/v1/tests/fixtures/edge_cases_naming/test_1_3_rust_keywords_idents/expected.stderr diff --git a/anneal/tests/fixtures/edge_cases_naming/test_1_3_rust_keywords_idents/source/Cargo.toml b/anneal/v1/tests/fixtures/edge_cases_naming/test_1_3_rust_keywords_idents/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/edge_cases_naming/test_1_3_rust_keywords_idents/source/Cargo.toml rename to anneal/v1/tests/fixtures/edge_cases_naming/test_1_3_rust_keywords_idents/source/Cargo.toml diff --git a/anneal/tests/fixtures/edge_cases_naming/test_1_3_rust_keywords_idents/source/src/lib.rs b/anneal/v1/tests/fixtures/edge_cases_naming/test_1_3_rust_keywords_idents/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/edge_cases_naming/test_1_3_rust_keywords_idents/source/src/lib.rs rename to anneal/v1/tests/fixtures/edge_cases_naming/test_1_3_rust_keywords_idents/source/src/lib.rs diff --git a/anneal/tests/fixtures/edge_cases_stress/args.txt b/anneal/v1/tests/fixtures/edge_cases_stress/args.txt similarity index 100% rename from anneal/tests/fixtures/edge_cases_stress/args.txt rename to anneal/v1/tests/fixtures/edge_cases_stress/args.txt diff --git a/anneal/tests/fixtures/edge_cases_stress/test_11_2_monolith/anneal.toml b/anneal/v1/tests/fixtures/edge_cases_stress/test_11_2_monolith/anneal.toml similarity index 100% rename from anneal/tests/fixtures/edge_cases_stress/test_11_2_monolith/anneal.toml rename to anneal/v1/tests/fixtures/edge_cases_stress/test_11_2_monolith/anneal.toml diff --git a/anneal/tests/fixtures/edge_cases_stress/test_11_2_monolith/expected.stderr b/anneal/v1/tests/fixtures/edge_cases_stress/test_11_2_monolith/expected.stderr similarity index 100% rename from anneal/tests/fixtures/edge_cases_stress/test_11_2_monolith/expected.stderr rename to anneal/v1/tests/fixtures/edge_cases_stress/test_11_2_monolith/expected.stderr diff --git a/anneal/tests/fixtures/edge_cases_stress/test_11_2_monolith/source/Cargo.toml b/anneal/v1/tests/fixtures/edge_cases_stress/test_11_2_monolith/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/edge_cases_stress/test_11_2_monolith/source/Cargo.toml rename to anneal/v1/tests/fixtures/edge_cases_stress/test_11_2_monolith/source/Cargo.toml diff --git a/anneal/tests/fixtures/edge_cases_stress/test_11_2_monolith/source/src/lib.rs b/anneal/v1/tests/fixtures/edge_cases_stress/test_11_2_monolith/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/edge_cases_stress/test_11_2_monolith/source/src/lib.rs rename to anneal/v1/tests/fixtures/edge_cases_stress/test_11_2_monolith/source/src/lib.rs diff --git a/anneal/tests/fixtures/edge_cases_types/args.txt b/anneal/v1/tests/fixtures/edge_cases_types/args.txt similarity index 100% rename from anneal/tests/fixtures/edge_cases_types/args.txt rename to anneal/v1/tests/fixtures/edge_cases_types/args.txt diff --git a/anneal/tests/fixtures/edge_cases_types/test_2_3_zst/anneal.toml b/anneal/v1/tests/fixtures/edge_cases_types/test_2_3_zst/anneal.toml similarity index 100% rename from anneal/tests/fixtures/edge_cases_types/test_2_3_zst/anneal.toml rename to anneal/v1/tests/fixtures/edge_cases_types/test_2_3_zst/anneal.toml diff --git a/anneal/tests/fixtures/edge_cases_types/test_2_3_zst/expected.stderr b/anneal/v1/tests/fixtures/edge_cases_types/test_2_3_zst/expected.stderr similarity index 100% rename from anneal/tests/fixtures/edge_cases_types/test_2_3_zst/expected.stderr rename to anneal/v1/tests/fixtures/edge_cases_types/test_2_3_zst/expected.stderr diff --git a/anneal/tests/fixtures/edge_cases_types/test_2_3_zst/source/Cargo.toml b/anneal/v1/tests/fixtures/edge_cases_types/test_2_3_zst/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/edge_cases_types/test_2_3_zst/source/Cargo.toml rename to anneal/v1/tests/fixtures/edge_cases_types/test_2_3_zst/source/Cargo.toml diff --git a/anneal/tests/fixtures/edge_cases_types/test_2_3_zst/source/src/lib.rs b/anneal/v1/tests/fixtures/edge_cases_types/test_2_3_zst/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/edge_cases_types/test_2_3_zst/source/src/lib.rs rename to anneal/v1/tests/fixtures/edge_cases_types/test_2_3_zst/source/src/lib.rs diff --git a/anneal/tests/fixtures/edge_cases_types/test_2_6_nested_refs/anneal.toml b/anneal/v1/tests/fixtures/edge_cases_types/test_2_6_nested_refs/anneal.toml similarity index 100% rename from anneal/tests/fixtures/edge_cases_types/test_2_6_nested_refs/anneal.toml rename to anneal/v1/tests/fixtures/edge_cases_types/test_2_6_nested_refs/anneal.toml diff --git a/anneal/tests/fixtures/edge_cases_types/test_2_6_nested_refs/expected.stderr b/anneal/v1/tests/fixtures/edge_cases_types/test_2_6_nested_refs/expected.stderr similarity index 100% rename from anneal/tests/fixtures/edge_cases_types/test_2_6_nested_refs/expected.stderr rename to anneal/v1/tests/fixtures/edge_cases_types/test_2_6_nested_refs/expected.stderr diff --git a/anneal/tests/fixtures/edge_cases_types/test_2_6_nested_refs/source/Cargo.toml b/anneal/v1/tests/fixtures/edge_cases_types/test_2_6_nested_refs/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/edge_cases_types/test_2_6_nested_refs/source/Cargo.toml rename to anneal/v1/tests/fixtures/edge_cases_types/test_2_6_nested_refs/source/Cargo.toml diff --git a/anneal/tests/fixtures/edge_cases_types/test_2_6_nested_refs/source/src/lib.rs b/anneal/v1/tests/fixtures/edge_cases_types/test_2_6_nested_refs/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/edge_cases_types/test_2_6_nested_refs/source/src/lib.rs rename to anneal/v1/tests/fixtures/edge_cases_types/test_2_6_nested_refs/source/src/lib.rs diff --git a/anneal/tests/fixtures/edge_cases_types/test_2_7_arrays/anneal.toml b/anneal/v1/tests/fixtures/edge_cases_types/test_2_7_arrays/anneal.toml similarity index 100% rename from anneal/tests/fixtures/edge_cases_types/test_2_7_arrays/anneal.toml rename to anneal/v1/tests/fixtures/edge_cases_types/test_2_7_arrays/anneal.toml diff --git a/anneal/tests/fixtures/edge_cases_types/test_2_7_arrays/source/Cargo.toml b/anneal/v1/tests/fixtures/edge_cases_types/test_2_7_arrays/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/edge_cases_types/test_2_7_arrays/source/Cargo.toml rename to anneal/v1/tests/fixtures/edge_cases_types/test_2_7_arrays/source/Cargo.toml diff --git a/anneal/tests/fixtures/edge_cases_types/test_2_7_arrays/source/src/lib.rs b/anneal/v1/tests/fixtures/edge_cases_types/test_2_7_arrays/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/edge_cases_types/test_2_7_arrays/source/src/lib.rs rename to anneal/v1/tests/fixtures/edge_cases_types/test_2_7_arrays/source/src/lib.rs diff --git a/anneal/tests/fixtures/enums_pattern_matching/anneal.toml b/anneal/v1/tests/fixtures/enums_pattern_matching/anneal.toml similarity index 100% rename from anneal/tests/fixtures/enums_pattern_matching/anneal.toml rename to anneal/v1/tests/fixtures/enums_pattern_matching/anneal.toml diff --git a/anneal/tests/fixtures/enums_pattern_matching/expected.stderr b/anneal/v1/tests/fixtures/enums_pattern_matching/expected.stderr similarity index 100% rename from anneal/tests/fixtures/enums_pattern_matching/expected.stderr rename to anneal/v1/tests/fixtures/enums_pattern_matching/expected.stderr diff --git a/anneal/tests/fixtures/enums_pattern_matching/source/Cargo.toml b/anneal/v1/tests/fixtures/enums_pattern_matching/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/enums_pattern_matching/source/Cargo.toml rename to anneal/v1/tests/fixtures/enums_pattern_matching/source/Cargo.toml diff --git a/anneal/tests/fixtures/enums_pattern_matching/source/src/lib.rs b/anneal/v1/tests/fixtures/enums_pattern_matching/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/enums_pattern_matching/source/src/lib.rs rename to anneal/v1/tests/fixtures/enums_pattern_matching/source/src/lib.rs diff --git a/anneal/tests/fixtures/env_interleaved_stdout/anneal.toml b/anneal/v1/tests/fixtures/env_interleaved_stdout/anneal.toml similarity index 100% rename from anneal/tests/fixtures/env_interleaved_stdout/anneal.toml rename to anneal/v1/tests/fixtures/env_interleaved_stdout/anneal.toml diff --git a/anneal/tests/fixtures/env_interleaved_stdout/expected.stderr b/anneal/v1/tests/fixtures/env_interleaved_stdout/expected.stderr similarity index 100% rename from anneal/tests/fixtures/env_interleaved_stdout/expected.stderr rename to anneal/v1/tests/fixtures/env_interleaved_stdout/expected.stderr diff --git a/anneal/tests/fixtures/env_interleaved_stdout/mock_charon.sh b/anneal/v1/tests/fixtures/env_interleaved_stdout/mock_charon.sh similarity index 100% rename from anneal/tests/fixtures/env_interleaved_stdout/mock_charon.sh rename to anneal/v1/tests/fixtures/env_interleaved_stdout/mock_charon.sh diff --git a/anneal/tests/fixtures/env_interleaved_stdout/mock_charon_output.json b/anneal/v1/tests/fixtures/env_interleaved_stdout/mock_charon_output.json similarity index 100% rename from anneal/tests/fixtures/env_interleaved_stdout/mock_charon_output.json rename to anneal/v1/tests/fixtures/env_interleaved_stdout/mock_charon_output.json diff --git a/anneal/tests/fixtures/env_interleaved_stdout/source/Cargo.toml b/anneal/v1/tests/fixtures/env_interleaved_stdout/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/env_interleaved_stdout/source/Cargo.toml rename to anneal/v1/tests/fixtures/env_interleaved_stdout/source/Cargo.toml diff --git a/anneal/tests/fixtures/env_interleaved_stdout/source/src/lib.rs b/anneal/v1/tests/fixtures/env_interleaved_stdout/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/env_interleaved_stdout/source/src/lib.rs rename to anneal/v1/tests/fixtures/env_interleaved_stdout/source/src/lib.rs diff --git a/anneal/tests/fixtures/expand_output/anneal.toml b/anneal/v1/tests/fixtures/expand_output/anneal.toml similarity index 100% rename from anneal/tests/fixtures/expand_output/anneal.toml rename to anneal/v1/tests/fixtures/expand_output/anneal.toml diff --git a/anneal/tests/fixtures/expand_output/expected-aeneas.stdout b/anneal/v1/tests/fixtures/expand_output/expected-aeneas.stdout similarity index 100% rename from anneal/tests/fixtures/expand_output/expected-aeneas.stdout rename to anneal/v1/tests/fixtures/expand_output/expected-aeneas.stdout diff --git a/anneal/tests/fixtures/expand_output/expected-all.stdout b/anneal/v1/tests/fixtures/expand_output/expected-all.stdout similarity index 100% rename from anneal/tests/fixtures/expand_output/expected-all.stdout rename to anneal/v1/tests/fixtures/expand_output/expected-all.stdout diff --git a/anneal/tests/fixtures/expand_output/expected-anneal.stdout b/anneal/v1/tests/fixtures/expand_output/expected-anneal.stdout similarity index 100% rename from anneal/tests/fixtures/expand_output/expected-anneal.stdout rename to anneal/v1/tests/fixtures/expand_output/expected-anneal.stdout diff --git a/anneal/tests/fixtures/expand_output/source/Cargo.toml b/anneal/v1/tests/fixtures/expand_output/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/expand_output/source/Cargo.toml rename to anneal/v1/tests/fixtures/expand_output/source/Cargo.toml diff --git a/anneal/tests/fixtures/expand_output/source/src/lib.rs b/anneal/v1/tests/fixtures/expand_output/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/expand_output/source/src/lib.rs rename to anneal/v1/tests/fixtures/expand_output/source/src/lib.rs diff --git a/anneal/tests/fixtures/extern_never_verified/anneal.toml b/anneal/v1/tests/fixtures/extern_never_verified/anneal.toml similarity index 100% rename from anneal/tests/fixtures/extern_never_verified/anneal.toml rename to anneal/v1/tests/fixtures/extern_never_verified/anneal.toml diff --git a/anneal/tests/fixtures/extern_never_verified/out.txt b/anneal/v1/tests/fixtures/extern_never_verified/out.txt similarity index 100% rename from anneal/tests/fixtures/extern_never_verified/out.txt rename to anneal/v1/tests/fixtures/extern_never_verified/out.txt diff --git a/anneal/tests/fixtures/extern_never_verified/source/Cargo.toml b/anneal/v1/tests/fixtures/extern_never_verified/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/extern_never_verified/source/Cargo.toml rename to anneal/v1/tests/fixtures/extern_never_verified/source/Cargo.toml diff --git a/anneal/tests/fixtures/extern_never_verified/source/src/lib.rs b/anneal/v1/tests/fixtures/extern_never_verified/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/extern_never_verified/source/src/lib.rs rename to anneal/v1/tests/fixtures/extern_never_verified/source/src/lib.rs diff --git a/anneal/tests/fixtures/external_dep_failure/anneal.toml b/anneal/v1/tests/fixtures/external_dep_failure/anneal.toml similarity index 100% rename from anneal/tests/fixtures/external_dep_failure/anneal.toml rename to anneal/v1/tests/fixtures/external_dep_failure/anneal.toml diff --git a/anneal/tests/fixtures/external_dep_failure/source/Cargo.toml b/anneal/v1/tests/fixtures/external_dep_failure/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/external_dep_failure/source/Cargo.toml rename to anneal/v1/tests/fixtures/external_dep_failure/source/Cargo.toml diff --git a/anneal/tests/fixtures/external_dep_failure/source/app/Cargo.toml b/anneal/v1/tests/fixtures/external_dep_failure/source/app/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/external_dep_failure/source/app/Cargo.toml rename to anneal/v1/tests/fixtures/external_dep_failure/source/app/Cargo.toml diff --git a/anneal/tests/fixtures/external_dep_failure/source/app/src/lib.rs b/anneal/v1/tests/fixtures/external_dep_failure/source/app/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/external_dep_failure/source/app/src/lib.rs rename to anneal/v1/tests/fixtures/external_dep_failure/source/app/src/lib.rs diff --git a/anneal/tests/fixtures/external_dep_failure/source/utils/Cargo.toml b/anneal/v1/tests/fixtures/external_dep_failure/source/utils/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/external_dep_failure/source/utils/Cargo.toml rename to anneal/v1/tests/fixtures/external_dep_failure/source/utils/Cargo.toml diff --git a/anneal/tests/fixtures/external_dep_failure/source/utils/src/lib.rs b/anneal/v1/tests/fixtures/external_dep_failure/source/utils/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/external_dep_failure/source/utils/src/lib.rs rename to anneal/v1/tests/fixtures/external_dep_failure/source/utils/src/lib.rs diff --git a/anneal/tests/fixtures/external_path_dependency/anneal.toml b/anneal/v1/tests/fixtures/external_path_dependency/anneal.toml similarity index 100% rename from anneal/tests/fixtures/external_path_dependency/anneal.toml rename to anneal/v1/tests/fixtures/external_path_dependency/anneal.toml diff --git a/anneal/tests/fixtures/external_path_dependency/source/external_dep/Cargo.toml b/anneal/v1/tests/fixtures/external_path_dependency/source/external_dep/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/external_path_dependency/source/external_dep/Cargo.toml rename to anneal/v1/tests/fixtures/external_path_dependency/source/external_dep/Cargo.toml diff --git a/anneal/tests/fixtures/external_path_dependency/source/external_dep/src/lib.rs b/anneal/v1/tests/fixtures/external_path_dependency/source/external_dep/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/external_path_dependency/source/external_dep/src/lib.rs rename to anneal/v1/tests/fixtures/external_path_dependency/source/external_dep/src/lib.rs diff --git a/anneal/tests/fixtures/external_path_dependency/source/workspace/Cargo.toml b/anneal/v1/tests/fixtures/external_path_dependency/source/workspace/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/external_path_dependency/source/workspace/Cargo.toml rename to anneal/v1/tests/fixtures/external_path_dependency/source/workspace/Cargo.toml diff --git a/anneal/tests/fixtures/external_path_dependency/source/workspace/app/Cargo.toml b/anneal/v1/tests/fixtures/external_path_dependency/source/workspace/app/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/external_path_dependency/source/workspace/app/Cargo.toml rename to anneal/v1/tests/fixtures/external_path_dependency/source/workspace/app/Cargo.toml diff --git a/anneal/tests/fixtures/external_path_dependency/source/workspace/app/src/lib.rs b/anneal/v1/tests/fixtures/external_path_dependency/source/workspace/app/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/external_path_dependency/source/workspace/app/src/lib.rs rename to anneal/v1/tests/fixtures/external_path_dependency/source/workspace/app/src/lib.rs diff --git a/anneal/tests/fixtures/fs_missing_source/anneal.toml b/anneal/v1/tests/fixtures/fs_missing_source/anneal.toml similarity index 100% rename from anneal/tests/fixtures/fs_missing_source/anneal.toml rename to anneal/v1/tests/fixtures/fs_missing_source/anneal.toml diff --git a/anneal/tests/fixtures/fs_missing_source/expected.stderr b/anneal/v1/tests/fixtures/fs_missing_source/expected.stderr similarity index 100% rename from anneal/tests/fixtures/fs_missing_source/expected.stderr rename to anneal/v1/tests/fixtures/fs_missing_source/expected.stderr diff --git a/anneal/tests/fixtures/fs_missing_source/mock_charon_output.json b/anneal/v1/tests/fixtures/fs_missing_source/mock_charon_output.json similarity index 100% rename from anneal/tests/fixtures/fs_missing_source/mock_charon_output.json rename to anneal/v1/tests/fixtures/fs_missing_source/mock_charon_output.json diff --git a/anneal/tests/fixtures/fs_missing_source/source/Cargo.toml b/anneal/v1/tests/fixtures/fs_missing_source/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/fs_missing_source/source/Cargo.toml rename to anneal/v1/tests/fixtures/fs_missing_source/source/Cargo.toml diff --git a/anneal/tests/fixtures/fs_missing_source/source/src/lib.rs b/anneal/v1/tests/fixtures/fs_missing_source/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/fs_missing_source/source/src/lib.rs rename to anneal/v1/tests/fixtures/fs_missing_source/source/src/lib.rs diff --git a/anneal/tests/fixtures/generate_output/anneal.toml b/anneal/v1/tests/fixtures/generate_output/anneal.toml similarity index 100% rename from anneal/tests/fixtures/generate_output/anneal.toml rename to anneal/v1/tests/fixtures/generate_output/anneal.toml diff --git a/anneal/tests/fixtures/generate_output/expected-generate.stdout b/anneal/v1/tests/fixtures/generate_output/expected-generate.stdout similarity index 100% rename from anneal/tests/fixtures/generate_output/expected-generate.stdout rename to anneal/v1/tests/fixtures/generate_output/expected-generate.stdout diff --git a/anneal/tests/fixtures/generate_output/source/Cargo.toml b/anneal/v1/tests/fixtures/generate_output/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/generate_output/source/Cargo.toml rename to anneal/v1/tests/fixtures/generate_output/source/Cargo.toml diff --git a/anneal/tests/fixtures/generate_output/source/src/lib.rs b/anneal/v1/tests/fixtures/generate_output/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/generate_output/source/src/lib.rs rename to anneal/v1/tests/fixtures/generate_output/source/src/lib.rs diff --git a/anneal/tests/fixtures/hidden_syntax_error/anneal.toml b/anneal/v1/tests/fixtures/hidden_syntax_error/anneal.toml similarity index 100% rename from anneal/tests/fixtures/hidden_syntax_error/anneal.toml rename to anneal/v1/tests/fixtures/hidden_syntax_error/anneal.toml diff --git a/anneal/tests/fixtures/hidden_syntax_error/expected.stderr b/anneal/v1/tests/fixtures/hidden_syntax_error/expected.stderr similarity index 100% rename from anneal/tests/fixtures/hidden_syntax_error/expected.stderr rename to anneal/v1/tests/fixtures/hidden_syntax_error/expected.stderr diff --git a/anneal/tests/fixtures/hidden_syntax_error/source/Cargo.toml b/anneal/v1/tests/fixtures/hidden_syntax_error/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/hidden_syntax_error/source/Cargo.toml rename to anneal/v1/tests/fixtures/hidden_syntax_error/source/Cargo.toml diff --git a/anneal/tests/fixtures/hidden_syntax_error/source/src/lib.rs b/anneal/v1/tests/fixtures/hidden_syntax_error/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/hidden_syntax_error/source/src/lib.rs rename to anneal/v1/tests/fixtures/hidden_syntax_error/source/src/lib.rs diff --git a/anneal/tests/fixtures/idempotency/anneal.toml b/anneal/v1/tests/fixtures/idempotency/anneal.toml similarity index 100% rename from anneal/tests/fixtures/idempotency/anneal.toml rename to anneal/v1/tests/fixtures/idempotency/anneal.toml diff --git a/anneal/tests/fixtures/idempotency/source/Cargo.toml b/anneal/v1/tests/fixtures/idempotency/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/idempotency/source/Cargo.toml rename to anneal/v1/tests/fixtures/idempotency/source/Cargo.toml diff --git a/anneal/tests/fixtures/idempotency/source/src/lib.rs b/anneal/v1/tests/fixtures/idempotency/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/idempotency/source/src/lib.rs rename to anneal/v1/tests/fixtures/idempotency/source/src/lib.rs diff --git a/anneal/tests/fixtures/is_valid_verification/anneal.toml b/anneal/v1/tests/fixtures/is_valid_verification/anneal.toml similarity index 100% rename from anneal/tests/fixtures/is_valid_verification/anneal.toml rename to anneal/v1/tests/fixtures/is_valid_verification/anneal.toml diff --git a/anneal/tests/fixtures/is_valid_verification/expected.stderr b/anneal/v1/tests/fixtures/is_valid_verification/expected.stderr similarity index 100% rename from anneal/tests/fixtures/is_valid_verification/expected.stderr rename to anneal/v1/tests/fixtures/is_valid_verification/expected.stderr diff --git a/anneal/tests/fixtures/is_valid_verification/source/Cargo.toml b/anneal/v1/tests/fixtures/is_valid_verification/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/is_valid_verification/source/Cargo.toml rename to anneal/v1/tests/fixtures/is_valid_verification/source/Cargo.toml diff --git a/anneal/tests/fixtures/is_valid_verification/source/src/lib.rs b/anneal/v1/tests/fixtures/is_valid_verification/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/is_valid_verification/source/src/lib.rs rename to anneal/v1/tests/fixtures/is_valid_verification/source/src/lib.rs diff --git a/anneal/tests/fixtures/json_code_suggestions/anneal.toml b/anneal/v1/tests/fixtures/json_code_suggestions/anneal.toml similarity index 100% rename from anneal/tests/fixtures/json_code_suggestions/anneal.toml rename to anneal/v1/tests/fixtures/json_code_suggestions/anneal.toml diff --git a/anneal/tests/fixtures/json_code_suggestions/expected.stderr b/anneal/v1/tests/fixtures/json_code_suggestions/expected.stderr similarity index 100% rename from anneal/tests/fixtures/json_code_suggestions/expected.stderr rename to anneal/v1/tests/fixtures/json_code_suggestions/expected.stderr diff --git a/anneal/tests/fixtures/json_code_suggestions/mock_charon_output.json b/anneal/v1/tests/fixtures/json_code_suggestions/mock_charon_output.json similarity index 100% rename from anneal/tests/fixtures/json_code_suggestions/mock_charon_output.json rename to anneal/v1/tests/fixtures/json_code_suggestions/mock_charon_output.json diff --git a/anneal/tests/fixtures/json_code_suggestions/source/Cargo.toml b/anneal/v1/tests/fixtures/json_code_suggestions/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/json_code_suggestions/source/Cargo.toml rename to anneal/v1/tests/fixtures/json_code_suggestions/source/Cargo.toml diff --git a/anneal/tests/fixtures/json_code_suggestions/source/src/lib.rs b/anneal/v1/tests/fixtures/json_code_suggestions/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/json_code_suggestions/source/src/lib.rs rename to anneal/v1/tests/fixtures/json_code_suggestions/source/src/lib.rs diff --git a/anneal/tests/fixtures/json_linker_error/anneal.toml b/anneal/v1/tests/fixtures/json_linker_error/anneal.toml similarity index 100% rename from anneal/tests/fixtures/json_linker_error/anneal.toml rename to anneal/v1/tests/fixtures/json_linker_error/anneal.toml diff --git a/anneal/tests/fixtures/json_linker_error/expected.stderr b/anneal/v1/tests/fixtures/json_linker_error/expected.stderr similarity index 100% rename from anneal/tests/fixtures/json_linker_error/expected.stderr rename to anneal/v1/tests/fixtures/json_linker_error/expected.stderr diff --git a/anneal/tests/fixtures/json_linker_error/mock_charon_output.json b/anneal/v1/tests/fixtures/json_linker_error/mock_charon_output.json similarity index 100% rename from anneal/tests/fixtures/json_linker_error/mock_charon_output.json rename to anneal/v1/tests/fixtures/json_linker_error/mock_charon_output.json diff --git a/anneal/tests/fixtures/json_linker_error/source/Cargo.toml b/anneal/v1/tests/fixtures/json_linker_error/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/json_linker_error/source/Cargo.toml rename to anneal/v1/tests/fixtures/json_linker_error/source/Cargo.toml diff --git a/anneal/tests/fixtures/json_linker_error/source/src/lib.rs b/anneal/v1/tests/fixtures/json_linker_error/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/json_linker_error/source/src/lib.rs rename to anneal/v1/tests/fixtures/json_linker_error/source/src/lib.rs diff --git a/anneal/tests/fixtures/json_nested_children/anneal.toml b/anneal/v1/tests/fixtures/json_nested_children/anneal.toml similarity index 100% rename from anneal/tests/fixtures/json_nested_children/anneal.toml rename to anneal/v1/tests/fixtures/json_nested_children/anneal.toml diff --git a/anneal/tests/fixtures/json_nested_children/expected.stderr b/anneal/v1/tests/fixtures/json_nested_children/expected.stderr similarity index 100% rename from anneal/tests/fixtures/json_nested_children/expected.stderr rename to anneal/v1/tests/fixtures/json_nested_children/expected.stderr diff --git a/anneal/tests/fixtures/json_nested_children/mock_charon_output.json b/anneal/v1/tests/fixtures/json_nested_children/mock_charon_output.json similarity index 100% rename from anneal/tests/fixtures/json_nested_children/mock_charon_output.json rename to anneal/v1/tests/fixtures/json_nested_children/mock_charon_output.json diff --git a/anneal/tests/fixtures/json_nested_children/source/Cargo.toml b/anneal/v1/tests/fixtures/json_nested_children/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/json_nested_children/source/Cargo.toml rename to anneal/v1/tests/fixtures/json_nested_children/source/Cargo.toml diff --git a/anneal/tests/fixtures/json_nested_children/source/src/lib.rs b/anneal/v1/tests/fixtures/json_nested_children/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/json_nested_children/source/src/lib.rs rename to anneal/v1/tests/fixtures/json_nested_children/source/src/lib.rs diff --git a/anneal/tests/fixtures/lean_edge_cases/anneal.toml b/anneal/v1/tests/fixtures/lean_edge_cases/anneal.toml similarity index 100% rename from anneal/tests/fixtures/lean_edge_cases/anneal.toml rename to anneal/v1/tests/fixtures/lean_edge_cases/anneal.toml diff --git a/anneal/tests/fixtures/lean_edge_cases/source/Cargo.toml b/anneal/v1/tests/fixtures/lean_edge_cases/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/lean_edge_cases/source/Cargo.toml rename to anneal/v1/tests/fixtures/lean_edge_cases/source/Cargo.toml diff --git a/anneal/tests/fixtures/lean_edge_cases/source/src/lib.rs b/anneal/v1/tests/fixtures/lean_edge_cases/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/lean_edge_cases/source/src/lib.rs rename to anneal/v1/tests/fixtures/lean_edge_cases/source/src/lib.rs diff --git a/anneal/tests/fixtures/lean_generation/anneal.toml b/anneal/v1/tests/fixtures/lean_generation/anneal.toml similarity index 100% rename from anneal/tests/fixtures/lean_generation/anneal.toml rename to anneal/v1/tests/fixtures/lean_generation/anneal.toml diff --git a/anneal/tests/fixtures/lean_generation/expected.stderr b/anneal/v1/tests/fixtures/lean_generation/expected.stderr similarity index 100% rename from anneal/tests/fixtures/lean_generation/expected.stderr rename to anneal/v1/tests/fixtures/lean_generation/expected.stderr diff --git a/anneal/tests/fixtures/lean_generation/source/Cargo.lock b/anneal/v1/tests/fixtures/lean_generation/source/Cargo.lock similarity index 100% rename from anneal/tests/fixtures/lean_generation/source/Cargo.lock rename to anneal/v1/tests/fixtures/lean_generation/source/Cargo.lock diff --git a/anneal/tests/fixtures/lean_generation/source/Cargo.toml b/anneal/v1/tests/fixtures/lean_generation/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/lean_generation/source/Cargo.toml rename to anneal/v1/tests/fixtures/lean_generation/source/Cargo.toml diff --git a/anneal/tests/fixtures/lean_generation/source/src/lib.rs b/anneal/v1/tests/fixtures/lean_generation/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/lean_generation/source/src/lib.rs rename to anneal/v1/tests/fixtures/lean_generation/source/src/lib.rs diff --git a/anneal/tests/fixtures/libtest_ptr.rlib b/anneal/v1/tests/fixtures/libtest_ptr.rlib similarity index 100% rename from anneal/tests/fixtures/libtest_ptr.rlib rename to anneal/v1/tests/fixtures/libtest_ptr.rlib diff --git a/anneal/tests/fixtures/map_canonical_mismatch/anneal.toml b/anneal/v1/tests/fixtures/map_canonical_mismatch/anneal.toml similarity index 100% rename from anneal/tests/fixtures/map_canonical_mismatch/anneal.toml rename to anneal/v1/tests/fixtures/map_canonical_mismatch/anneal.toml diff --git a/anneal/tests/fixtures/map_canonical_mismatch/expected.stderr b/anneal/v1/tests/fixtures/map_canonical_mismatch/expected.stderr similarity index 100% rename from anneal/tests/fixtures/map_canonical_mismatch/expected.stderr rename to anneal/v1/tests/fixtures/map_canonical_mismatch/expected.stderr diff --git a/anneal/tests/fixtures/map_canonical_mismatch/mock_charon_output.json b/anneal/v1/tests/fixtures/map_canonical_mismatch/mock_charon_output.json similarity index 100% rename from anneal/tests/fixtures/map_canonical_mismatch/mock_charon_output.json rename to anneal/v1/tests/fixtures/map_canonical_mismatch/mock_charon_output.json diff --git a/anneal/tests/fixtures/map_canonical_mismatch/source/Cargo.toml b/anneal/v1/tests/fixtures/map_canonical_mismatch/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/map_canonical_mismatch/source/Cargo.toml rename to anneal/v1/tests/fixtures/map_canonical_mismatch/source/Cargo.toml diff --git a/anneal/tests/fixtures/map_canonical_mismatch/source/crates/utils/src/lib.rs b/anneal/v1/tests/fixtures/map_canonical_mismatch/source/crates/utils/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/map_canonical_mismatch/source/crates/utils/src/lib.rs rename to anneal/v1/tests/fixtures/map_canonical_mismatch/source/crates/utils/src/lib.rs diff --git a/anneal/tests/fixtures/map_canonical_mismatch/source/src/lib.rs b/anneal/v1/tests/fixtures/map_canonical_mismatch/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/map_canonical_mismatch/source/src/lib.rs rename to anneal/v1/tests/fixtures/map_canonical_mismatch/source/src/lib.rs diff --git a/anneal/tests/fixtures/map_canonical_mismatch/source/src/macros.rs b/anneal/v1/tests/fixtures/map_canonical_mismatch/source/src/macros.rs similarity index 100% rename from anneal/tests/fixtures/map_canonical_mismatch/source/src/macros.rs rename to anneal/v1/tests/fixtures/map_canonical_mismatch/source/src/macros.rs diff --git a/anneal/tests/fixtures/map_canonical_mismatch/source/src/rewritten.rs b/anneal/v1/tests/fixtures/map_canonical_mismatch/source/src/rewritten.rs similarity index 100% rename from anneal/tests/fixtures/map_canonical_mismatch/source/src/rewritten.rs rename to anneal/v1/tests/fixtures/map_canonical_mismatch/source/src/rewritten.rs diff --git a/anneal/tests/fixtures/map_canonical_mismatch/source/src/symlinked.rs b/anneal/v1/tests/fixtures/map_canonical_mismatch/source/src/symlinked.rs similarity index 100% rename from anneal/tests/fixtures/map_canonical_mismatch/source/src/symlinked.rs rename to anneal/v1/tests/fixtures/map_canonical_mismatch/source/src/symlinked.rs diff --git a/anneal/tests/fixtures/map_external_dep/anneal.toml b/anneal/v1/tests/fixtures/map_external_dep/anneal.toml similarity index 100% rename from anneal/tests/fixtures/map_external_dep/anneal.toml rename to anneal/v1/tests/fixtures/map_external_dep/anneal.toml diff --git a/anneal/tests/fixtures/map_external_dep/expected.stderr b/anneal/v1/tests/fixtures/map_external_dep/expected.stderr similarity index 100% rename from anneal/tests/fixtures/map_external_dep/expected.stderr rename to anneal/v1/tests/fixtures/map_external_dep/expected.stderr diff --git a/anneal/tests/fixtures/map_external_dep/mock_charon_output.json b/anneal/v1/tests/fixtures/map_external_dep/mock_charon_output.json similarity index 100% rename from anneal/tests/fixtures/map_external_dep/mock_charon_output.json rename to anneal/v1/tests/fixtures/map_external_dep/mock_charon_output.json diff --git a/anneal/tests/fixtures/map_external_dep/source/Cargo.toml b/anneal/v1/tests/fixtures/map_external_dep/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/map_external_dep/source/Cargo.toml rename to anneal/v1/tests/fixtures/map_external_dep/source/Cargo.toml diff --git a/anneal/tests/fixtures/map_external_dep/source/src/lib.rs b/anneal/v1/tests/fixtures/map_external_dep/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/map_external_dep/source/src/lib.rs rename to anneal/v1/tests/fixtures/map_external_dep/source/src/lib.rs diff --git a/anneal/tests/fixtures/map_macro_expansion/anneal.toml b/anneal/v1/tests/fixtures/map_macro_expansion/anneal.toml similarity index 100% rename from anneal/tests/fixtures/map_macro_expansion/anneal.toml rename to anneal/v1/tests/fixtures/map_macro_expansion/anneal.toml diff --git a/anneal/tests/fixtures/map_macro_expansion/expected.stderr b/anneal/v1/tests/fixtures/map_macro_expansion/expected.stderr similarity index 100% rename from anneal/tests/fixtures/map_macro_expansion/expected.stderr rename to anneal/v1/tests/fixtures/map_macro_expansion/expected.stderr diff --git a/anneal/tests/fixtures/map_macro_expansion/mock_charon_output.json b/anneal/v1/tests/fixtures/map_macro_expansion/mock_charon_output.json similarity index 100% rename from anneal/tests/fixtures/map_macro_expansion/mock_charon_output.json rename to anneal/v1/tests/fixtures/map_macro_expansion/mock_charon_output.json diff --git a/anneal/tests/fixtures/map_macro_expansion/source/Cargo.toml b/anneal/v1/tests/fixtures/map_macro_expansion/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/map_macro_expansion/source/Cargo.toml rename to anneal/v1/tests/fixtures/map_macro_expansion/source/Cargo.toml diff --git a/anneal/tests/fixtures/map_macro_expansion/source/crates/utils/src/lib.rs b/anneal/v1/tests/fixtures/map_macro_expansion/source/crates/utils/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/map_macro_expansion/source/crates/utils/src/lib.rs rename to anneal/v1/tests/fixtures/map_macro_expansion/source/crates/utils/src/lib.rs diff --git a/anneal/tests/fixtures/map_macro_expansion/source/src/lib.rs b/anneal/v1/tests/fixtures/map_macro_expansion/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/map_macro_expansion/source/src/lib.rs rename to anneal/v1/tests/fixtures/map_macro_expansion/source/src/lib.rs diff --git a/anneal/tests/fixtures/map_macro_expansion/source/src/macros.rs b/anneal/v1/tests/fixtures/map_macro_expansion/source/src/macros.rs similarity index 100% rename from anneal/tests/fixtures/map_macro_expansion/source/src/macros.rs rename to anneal/v1/tests/fixtures/map_macro_expansion/source/src/macros.rs diff --git a/anneal/tests/fixtures/map_macro_expansion/source/src/rewritten.rs b/anneal/v1/tests/fixtures/map_macro_expansion/source/src/rewritten.rs similarity index 100% rename from anneal/tests/fixtures/map_macro_expansion/source/src/rewritten.rs rename to anneal/v1/tests/fixtures/map_macro_expansion/source/src/rewritten.rs diff --git a/anneal/tests/fixtures/map_macro_expansion/source/src/symlinked.rs b/anneal/v1/tests/fixtures/map_macro_expansion/source/src/symlinked.rs similarity index 100% rename from anneal/tests/fixtures/map_macro_expansion/source/src/symlinked.rs rename to anneal/v1/tests/fixtures/map_macro_expansion/source/src/symlinked.rs diff --git a/anneal/tests/fixtures/map_mixed_diagnostics/anneal.toml b/anneal/v1/tests/fixtures/map_mixed_diagnostics/anneal.toml similarity index 100% rename from anneal/tests/fixtures/map_mixed_diagnostics/anneal.toml rename to anneal/v1/tests/fixtures/map_mixed_diagnostics/anneal.toml diff --git a/anneal/tests/fixtures/map_mixed_diagnostics/expected.stderr b/anneal/v1/tests/fixtures/map_mixed_diagnostics/expected.stderr similarity index 100% rename from anneal/tests/fixtures/map_mixed_diagnostics/expected.stderr rename to anneal/v1/tests/fixtures/map_mixed_diagnostics/expected.stderr diff --git a/anneal/tests/fixtures/map_mixed_diagnostics/mock_charon_output.json b/anneal/v1/tests/fixtures/map_mixed_diagnostics/mock_charon_output.json similarity index 100% rename from anneal/tests/fixtures/map_mixed_diagnostics/mock_charon_output.json rename to anneal/v1/tests/fixtures/map_mixed_diagnostics/mock_charon_output.json diff --git a/anneal/tests/fixtures/map_mixed_diagnostics/source/Cargo.toml b/anneal/v1/tests/fixtures/map_mixed_diagnostics/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/map_mixed_diagnostics/source/Cargo.toml rename to anneal/v1/tests/fixtures/map_mixed_diagnostics/source/Cargo.toml diff --git a/anneal/tests/fixtures/map_mixed_diagnostics/source/crates/utils/src/lib.rs b/anneal/v1/tests/fixtures/map_mixed_diagnostics/source/crates/utils/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/map_mixed_diagnostics/source/crates/utils/src/lib.rs rename to anneal/v1/tests/fixtures/map_mixed_diagnostics/source/crates/utils/src/lib.rs diff --git a/anneal/tests/fixtures/map_mixed_diagnostics/source/src/lib.rs b/anneal/v1/tests/fixtures/map_mixed_diagnostics/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/map_mixed_diagnostics/source/src/lib.rs rename to anneal/v1/tests/fixtures/map_mixed_diagnostics/source/src/lib.rs diff --git a/anneal/tests/fixtures/map_mixed_diagnostics/source/src/macros.rs b/anneal/v1/tests/fixtures/map_mixed_diagnostics/source/src/macros.rs similarity index 100% rename from anneal/tests/fixtures/map_mixed_diagnostics/source/src/macros.rs rename to anneal/v1/tests/fixtures/map_mixed_diagnostics/source/src/macros.rs diff --git a/anneal/tests/fixtures/map_mixed_diagnostics/source/src/rewritten.rs b/anneal/v1/tests/fixtures/map_mixed_diagnostics/source/src/rewritten.rs similarity index 100% rename from anneal/tests/fixtures/map_mixed_diagnostics/source/src/rewritten.rs rename to anneal/v1/tests/fixtures/map_mixed_diagnostics/source/src/rewritten.rs diff --git a/anneal/tests/fixtures/map_mixed_diagnostics/source/src/symlinked.rs b/anneal/v1/tests/fixtures/map_mixed_diagnostics/source/src/symlinked.rs similarity index 100% rename from anneal/tests/fixtures/map_mixed_diagnostics/source/src/symlinked.rs rename to anneal/v1/tests/fixtures/map_mixed_diagnostics/source/src/symlinked.rs diff --git a/anneal/tests/fixtures/map_path_traversal/anneal.toml b/anneal/v1/tests/fixtures/map_path_traversal/anneal.toml similarity index 100% rename from anneal/tests/fixtures/map_path_traversal/anneal.toml rename to anneal/v1/tests/fixtures/map_path_traversal/anneal.toml diff --git a/anneal/tests/fixtures/map_path_traversal/expected.stderr b/anneal/v1/tests/fixtures/map_path_traversal/expected.stderr similarity index 100% rename from anneal/tests/fixtures/map_path_traversal/expected.stderr rename to anneal/v1/tests/fixtures/map_path_traversal/expected.stderr diff --git a/anneal/tests/fixtures/map_path_traversal/mock_charon_output.json b/anneal/v1/tests/fixtures/map_path_traversal/mock_charon_output.json similarity index 100% rename from anneal/tests/fixtures/map_path_traversal/mock_charon_output.json rename to anneal/v1/tests/fixtures/map_path_traversal/mock_charon_output.json diff --git a/anneal/tests/fixtures/map_path_traversal/source/Cargo.toml b/anneal/v1/tests/fixtures/map_path_traversal/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/map_path_traversal/source/Cargo.toml rename to anneal/v1/tests/fixtures/map_path_traversal/source/Cargo.toml diff --git a/anneal/tests/fixtures/map_path_traversal/source/src/lib.rs b/anneal/v1/tests/fixtures/map_path_traversal/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/map_path_traversal/source/src/lib.rs rename to anneal/v1/tests/fixtures/map_path_traversal/source/src/lib.rs diff --git a/anneal/tests/fixtures/map_relative_path/anneal.toml b/anneal/v1/tests/fixtures/map_relative_path/anneal.toml similarity index 100% rename from anneal/tests/fixtures/map_relative_path/anneal.toml rename to anneal/v1/tests/fixtures/map_relative_path/anneal.toml diff --git a/anneal/tests/fixtures/map_relative_path/expected.stderr b/anneal/v1/tests/fixtures/map_relative_path/expected.stderr similarity index 100% rename from anneal/tests/fixtures/map_relative_path/expected.stderr rename to anneal/v1/tests/fixtures/map_relative_path/expected.stderr diff --git a/anneal/tests/fixtures/map_relative_path/mock_charon_output.json b/anneal/v1/tests/fixtures/map_relative_path/mock_charon_output.json similarity index 100% rename from anneal/tests/fixtures/map_relative_path/mock_charon_output.json rename to anneal/v1/tests/fixtures/map_relative_path/mock_charon_output.json diff --git a/anneal/tests/fixtures/map_relative_path/source/Cargo.toml b/anneal/v1/tests/fixtures/map_relative_path/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/map_relative_path/source/Cargo.toml rename to anneal/v1/tests/fixtures/map_relative_path/source/Cargo.toml diff --git a/anneal/tests/fixtures/map_relative_path/source/crates/utils/src/lib.rs b/anneal/v1/tests/fixtures/map_relative_path/source/crates/utils/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/map_relative_path/source/crates/utils/src/lib.rs rename to anneal/v1/tests/fixtures/map_relative_path/source/crates/utils/src/lib.rs diff --git a/anneal/tests/fixtures/map_relative_path/source/src/lib.rs b/anneal/v1/tests/fixtures/map_relative_path/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/map_relative_path/source/src/lib.rs rename to anneal/v1/tests/fixtures/map_relative_path/source/src/lib.rs diff --git a/anneal/tests/fixtures/map_relative_path/source/src/macros.rs b/anneal/v1/tests/fixtures/map_relative_path/source/src/macros.rs similarity index 100% rename from anneal/tests/fixtures/map_relative_path/source/src/macros.rs rename to anneal/v1/tests/fixtures/map_relative_path/source/src/macros.rs diff --git a/anneal/tests/fixtures/map_relative_path/source/src/rewritten.rs b/anneal/v1/tests/fixtures/map_relative_path/source/src/rewritten.rs similarity index 100% rename from anneal/tests/fixtures/map_relative_path/source/src/rewritten.rs rename to anneal/v1/tests/fixtures/map_relative_path/source/src/rewritten.rs diff --git a/anneal/tests/fixtures/map_relative_path/source/src/symlinked.rs b/anneal/v1/tests/fixtures/map_relative_path/source/src/symlinked.rs similarity index 100% rename from anneal/tests/fixtures/map_relative_path/source/src/symlinked.rs rename to anneal/v1/tests/fixtures/map_relative_path/source/src/symlinked.rs diff --git a/anneal/tests/fixtures/map_std_lib/anneal.toml b/anneal/v1/tests/fixtures/map_std_lib/anneal.toml similarity index 100% rename from anneal/tests/fixtures/map_std_lib/anneal.toml rename to anneal/v1/tests/fixtures/map_std_lib/anneal.toml diff --git a/anneal/tests/fixtures/map_std_lib/expected.stderr b/anneal/v1/tests/fixtures/map_std_lib/expected.stderr similarity index 100% rename from anneal/tests/fixtures/map_std_lib/expected.stderr rename to anneal/v1/tests/fixtures/map_std_lib/expected.stderr diff --git a/anneal/tests/fixtures/map_std_lib/mock_charon_output.json b/anneal/v1/tests/fixtures/map_std_lib/mock_charon_output.json similarity index 100% rename from anneal/tests/fixtures/map_std_lib/mock_charon_output.json rename to anneal/v1/tests/fixtures/map_std_lib/mock_charon_output.json diff --git a/anneal/tests/fixtures/map_std_lib/source/Cargo.toml b/anneal/v1/tests/fixtures/map_std_lib/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/map_std_lib/source/Cargo.toml rename to anneal/v1/tests/fixtures/map_std_lib/source/Cargo.toml diff --git a/anneal/tests/fixtures/map_std_lib/source/crates/utils/src/lib.rs b/anneal/v1/tests/fixtures/map_std_lib/source/crates/utils/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/map_std_lib/source/crates/utils/src/lib.rs rename to anneal/v1/tests/fixtures/map_std_lib/source/crates/utils/src/lib.rs diff --git a/anneal/tests/fixtures/map_std_lib/source/src/lib.rs b/anneal/v1/tests/fixtures/map_std_lib/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/map_std_lib/source/src/lib.rs rename to anneal/v1/tests/fixtures/map_std_lib/source/src/lib.rs diff --git a/anneal/tests/fixtures/map_std_lib/source/src/macros.rs b/anneal/v1/tests/fixtures/map_std_lib/source/src/macros.rs similarity index 100% rename from anneal/tests/fixtures/map_std_lib/source/src/macros.rs rename to anneal/v1/tests/fixtures/map_std_lib/source/src/macros.rs diff --git a/anneal/tests/fixtures/map_std_lib/source/src/rewritten.rs b/anneal/v1/tests/fixtures/map_std_lib/source/src/rewritten.rs similarity index 100% rename from anneal/tests/fixtures/map_std_lib/source/src/rewritten.rs rename to anneal/v1/tests/fixtures/map_std_lib/source/src/rewritten.rs diff --git a/anneal/tests/fixtures/map_std_lib/source/src/symlinked.rs b/anneal/v1/tests/fixtures/map_std_lib/source/src/symlinked.rs similarity index 100% rename from anneal/tests/fixtures/map_std_lib/source/src/symlinked.rs rename to anneal/v1/tests/fixtures/map_std_lib/source/src/symlinked.rs diff --git a/anneal/tests/fixtures/map_std_library/anneal.toml b/anneal/v1/tests/fixtures/map_std_library/anneal.toml similarity index 100% rename from anneal/tests/fixtures/map_std_library/anneal.toml rename to anneal/v1/tests/fixtures/map_std_library/anneal.toml diff --git a/anneal/tests/fixtures/map_std_library/expected.stderr b/anneal/v1/tests/fixtures/map_std_library/expected.stderr similarity index 100% rename from anneal/tests/fixtures/map_std_library/expected.stderr rename to anneal/v1/tests/fixtures/map_std_library/expected.stderr diff --git a/anneal/tests/fixtures/map_std_library/mock_charon_output.json b/anneal/v1/tests/fixtures/map_std_library/mock_charon_output.json similarity index 100% rename from anneal/tests/fixtures/map_std_library/mock_charon_output.json rename to anneal/v1/tests/fixtures/map_std_library/mock_charon_output.json diff --git a/anneal/tests/fixtures/map_std_library/source/Cargo.toml b/anneal/v1/tests/fixtures/map_std_library/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/map_std_library/source/Cargo.toml rename to anneal/v1/tests/fixtures/map_std_library/source/Cargo.toml diff --git a/anneal/tests/fixtures/map_std_library/source/src/lib.rs b/anneal/v1/tests/fixtures/map_std_library/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/map_std_library/source/src/lib.rs rename to anneal/v1/tests/fixtures/map_std_library/source/src/lib.rs diff --git a/anneal/tests/fixtures/map_symlinked_file/anneal.toml b/anneal/v1/tests/fixtures/map_symlinked_file/anneal.toml similarity index 100% rename from anneal/tests/fixtures/map_symlinked_file/anneal.toml rename to anneal/v1/tests/fixtures/map_symlinked_file/anneal.toml diff --git a/anneal/tests/fixtures/map_symlinked_file/expected.stderr b/anneal/v1/tests/fixtures/map_symlinked_file/expected.stderr similarity index 100% rename from anneal/tests/fixtures/map_symlinked_file/expected.stderr rename to anneal/v1/tests/fixtures/map_symlinked_file/expected.stderr diff --git a/anneal/tests/fixtures/map_symlinked_file/mock_charon_output.json b/anneal/v1/tests/fixtures/map_symlinked_file/mock_charon_output.json similarity index 100% rename from anneal/tests/fixtures/map_symlinked_file/mock_charon_output.json rename to anneal/v1/tests/fixtures/map_symlinked_file/mock_charon_output.json diff --git a/anneal/tests/fixtures/map_symlinked_file/source/Cargo.toml b/anneal/v1/tests/fixtures/map_symlinked_file/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/map_symlinked_file/source/Cargo.toml rename to anneal/v1/tests/fixtures/map_symlinked_file/source/Cargo.toml diff --git a/anneal/tests/fixtures/map_symlinked_file/source/src/lib.rs b/anneal/v1/tests/fixtures/map_symlinked_file/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/map_symlinked_file/source/src/lib.rs rename to anneal/v1/tests/fixtures/map_symlinked_file/source/src/lib.rs diff --git a/anneal/tests/fixtures/map_symlinked_file/source/src/untransformed.rs b/anneal/v1/tests/fixtures/map_symlinked_file/source/src/untransformed.rs similarity index 100% rename from anneal/tests/fixtures/map_symlinked_file/source/src/untransformed.rs rename to anneal/v1/tests/fixtures/map_symlinked_file/source/src/untransformed.rs diff --git a/anneal/tests/fixtures/map_workspace_path_dep/anneal.toml b/anneal/v1/tests/fixtures/map_workspace_path_dep/anneal.toml similarity index 100% rename from anneal/tests/fixtures/map_workspace_path_dep/anneal.toml rename to anneal/v1/tests/fixtures/map_workspace_path_dep/anneal.toml diff --git a/anneal/tests/fixtures/map_workspace_path_dep/expected.stderr b/anneal/v1/tests/fixtures/map_workspace_path_dep/expected.stderr similarity index 100% rename from anneal/tests/fixtures/map_workspace_path_dep/expected.stderr rename to anneal/v1/tests/fixtures/map_workspace_path_dep/expected.stderr diff --git a/anneal/tests/fixtures/map_workspace_path_dep/mock_charon_output.json b/anneal/v1/tests/fixtures/map_workspace_path_dep/mock_charon_output.json similarity index 100% rename from anneal/tests/fixtures/map_workspace_path_dep/mock_charon_output.json rename to anneal/v1/tests/fixtures/map_workspace_path_dep/mock_charon_output.json diff --git a/anneal/tests/fixtures/map_workspace_path_dep/source/Cargo.toml b/anneal/v1/tests/fixtures/map_workspace_path_dep/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/map_workspace_path_dep/source/Cargo.toml rename to anneal/v1/tests/fixtures/map_workspace_path_dep/source/Cargo.toml diff --git a/anneal/tests/fixtures/map_workspace_path_dep/source/crates/utils/src/lib.rs b/anneal/v1/tests/fixtures/map_workspace_path_dep/source/crates/utils/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/map_workspace_path_dep/source/crates/utils/src/lib.rs rename to anneal/v1/tests/fixtures/map_workspace_path_dep/source/crates/utils/src/lib.rs diff --git a/anneal/tests/fixtures/map_workspace_path_dep/source/src/lib.rs b/anneal/v1/tests/fixtures/map_workspace_path_dep/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/map_workspace_path_dep/source/src/lib.rs rename to anneal/v1/tests/fixtures/map_workspace_path_dep/source/src/lib.rs diff --git a/anneal/tests/fixtures/map_workspace_path_dep/source/src/macros.rs b/anneal/v1/tests/fixtures/map_workspace_path_dep/source/src/macros.rs similarity index 100% rename from anneal/tests/fixtures/map_workspace_path_dep/source/src/macros.rs rename to anneal/v1/tests/fixtures/map_workspace_path_dep/source/src/macros.rs diff --git a/anneal/tests/fixtures/map_workspace_path_dep/source/src/rewritten.rs b/anneal/v1/tests/fixtures/map_workspace_path_dep/source/src/rewritten.rs similarity index 100% rename from anneal/tests/fixtures/map_workspace_path_dep/source/src/rewritten.rs rename to anneal/v1/tests/fixtures/map_workspace_path_dep/source/src/rewritten.rs diff --git a/anneal/tests/fixtures/map_workspace_path_dep/source/src/symlinked.rs b/anneal/v1/tests/fixtures/map_workspace_path_dep/source/src/symlinked.rs similarity index 100% rename from anneal/tests/fixtures/map_workspace_path_dep/source/src/symlinked.rs rename to anneal/v1/tests/fixtures/map_workspace_path_dep/source/src/symlinked.rs diff --git a/anneal/tests/fixtures/missing_output/anneal.toml b/anneal/v1/tests/fixtures/missing_output/anneal.toml similarity index 100% rename from anneal/tests/fixtures/missing_output/anneal.toml rename to anneal/v1/tests/fixtures/missing_output/anneal.toml diff --git a/anneal/tests/fixtures/missing_output/expected.stderr b/anneal/v1/tests/fixtures/missing_output/expected.stderr similarity index 100% rename from anneal/tests/fixtures/missing_output/expected.stderr rename to anneal/v1/tests/fixtures/missing_output/expected.stderr diff --git a/anneal/tests/fixtures/missing_output/mock_aeneas.sh b/anneal/v1/tests/fixtures/missing_output/mock_aeneas.sh similarity index 100% rename from anneal/tests/fixtures/missing_output/mock_aeneas.sh rename to anneal/v1/tests/fixtures/missing_output/mock_aeneas.sh diff --git a/anneal/tests/fixtures/missing_output/source/Cargo.toml b/anneal/v1/tests/fixtures/missing_output/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/missing_output/source/Cargo.toml rename to anneal/v1/tests/fixtures/missing_output/source/Cargo.toml diff --git a/anneal/tests/fixtures/missing_output/source/src/lib.rs b/anneal/v1/tests/fixtures/missing_output/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/missing_output/source/src/lib.rs rename to anneal/v1/tests/fixtures/missing_output/source/src/lib.rs diff --git a/anneal/tests/fixtures/mixed_workspace/anneal.toml b/anneal/v1/tests/fixtures/mixed_workspace/anneal.toml similarity index 100% rename from anneal/tests/fixtures/mixed_workspace/anneal.toml rename to anneal/v1/tests/fixtures/mixed_workspace/anneal.toml diff --git a/anneal/tests/fixtures/mixed_workspace/source/Cargo.toml b/anneal/v1/tests/fixtures/mixed_workspace/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/mixed_workspace/source/Cargo.toml rename to anneal/v1/tests/fixtures/mixed_workspace/source/Cargo.toml diff --git a/anneal/tests/fixtures/mixed_workspace/source/crates/app/Cargo.toml b/anneal/v1/tests/fixtures/mixed_workspace/source/crates/app/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/mixed_workspace/source/crates/app/Cargo.toml rename to anneal/v1/tests/fixtures/mixed_workspace/source/crates/app/Cargo.toml diff --git a/anneal/tests/fixtures/mixed_workspace/source/crates/app/src/lib.rs b/anneal/v1/tests/fixtures/mixed_workspace/source/crates/app/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/mixed_workspace/source/crates/app/src/lib.rs rename to anneal/v1/tests/fixtures/mixed_workspace/source/crates/app/src/lib.rs diff --git a/anneal/tests/fixtures/mixed_workspace/source/crates/dep/Cargo.toml b/anneal/v1/tests/fixtures/mixed_workspace/source/crates/dep/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/mixed_workspace/source/crates/dep/Cargo.toml rename to anneal/v1/tests/fixtures/mixed_workspace/source/crates/dep/Cargo.toml diff --git a/anneal/tests/fixtures/mixed_workspace/source/crates/dep/src/lib.rs b/anneal/v1/tests/fixtures/mixed_workspace/source/crates/dep/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/mixed_workspace/source/crates/dep/src/lib.rs rename to anneal/v1/tests/fixtures/mixed_workspace/source/crates/dep/src/lib.rs diff --git a/anneal/tests/fixtures/module_resolution/anneal.toml b/anneal/v1/tests/fixtures/module_resolution/anneal.toml similarity index 100% rename from anneal/tests/fixtures/module_resolution/anneal.toml rename to anneal/v1/tests/fixtures/module_resolution/anneal.toml diff --git a/anneal/tests/fixtures/module_resolution/extra.rs b/anneal/v1/tests/fixtures/module_resolution/extra.rs similarity index 100% rename from anneal/tests/fixtures/module_resolution/extra.rs rename to anneal/v1/tests/fixtures/module_resolution/extra.rs diff --git a/anneal/tests/fixtures/module_resolution/source/Cargo.toml b/anneal/v1/tests/fixtures/module_resolution/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/module_resolution/source/Cargo.toml rename to anneal/v1/tests/fixtures/module_resolution/source/Cargo.toml diff --git a/anneal/tests/fixtures/module_resolution/source/src/a.rs b/anneal/v1/tests/fixtures/module_resolution/source/src/a.rs similarity index 100% rename from anneal/tests/fixtures/module_resolution/source/src/a.rs rename to anneal/v1/tests/fixtures/module_resolution/source/src/a.rs diff --git a/anneal/tests/fixtures/module_resolution/source/src/a/b.rs b/anneal/v1/tests/fixtures/module_resolution/source/src/a/b.rs similarity index 100% rename from anneal/tests/fixtures/module_resolution/source/src/a/b.rs rename to anneal/v1/tests/fixtures/module_resolution/source/src/a/b.rs diff --git a/anneal/tests/fixtures/module_resolution/source/src/bar/mod.rs b/anneal/v1/tests/fixtures/module_resolution/source/src/bar/mod.rs similarity index 100% rename from anneal/tests/fixtures/module_resolution/source/src/bar/mod.rs rename to anneal/v1/tests/fixtures/module_resolution/source/src/bar/mod.rs diff --git a/anneal/tests/fixtures/module_resolution/source/src/c.rs b/anneal/v1/tests/fixtures/module_resolution/source/src/c.rs similarity index 100% rename from anneal/tests/fixtures/module_resolution/source/src/c.rs rename to anneal/v1/tests/fixtures/module_resolution/source/src/c.rs diff --git a/anneal/tests/fixtures/module_resolution/source/src/foo.rs b/anneal/v1/tests/fixtures/module_resolution/source/src/foo.rs similarity index 100% rename from anneal/tests/fixtures/module_resolution/source/src/foo.rs rename to anneal/v1/tests/fixtures/module_resolution/source/src/foo.rs diff --git a/anneal/tests/fixtures/module_resolution/source/src/lib.rs b/anneal/v1/tests/fixtures/module_resolution/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/module_resolution/source/src/lib.rs rename to anneal/v1/tests/fixtures/module_resolution/source/src/lib.rs diff --git a/anneal/tests/fixtures/module_resolution/source/src/sys/unix.rs b/anneal/v1/tests/fixtures/module_resolution/source/src/sys/unix.rs similarity index 100% rename from anneal/tests/fixtures/module_resolution/source/src/sys/unix.rs rename to anneal/v1/tests/fixtures/module_resolution/source/src/sys/unix.rs diff --git a/anneal/tests/fixtures/multi_artifact/anneal.toml b/anneal/v1/tests/fixtures/multi_artifact/anneal.toml similarity index 100% rename from anneal/tests/fixtures/multi_artifact/anneal.toml rename to anneal/v1/tests/fixtures/multi_artifact/anneal.toml diff --git a/anneal/tests/fixtures/multi_artifact/expected b/anneal/v1/tests/fixtures/multi_artifact/expected similarity index 100% rename from anneal/tests/fixtures/multi_artifact/expected rename to anneal/v1/tests/fixtures/multi_artifact/expected diff --git a/anneal/tests/fixtures/multi_artifact/expected.stderr b/anneal/v1/tests/fixtures/multi_artifact/expected.stderr similarity index 100% rename from anneal/tests/fixtures/multi_artifact/expected.stderr rename to anneal/v1/tests/fixtures/multi_artifact/expected.stderr diff --git a/anneal/tests/fixtures/multi_artifact/source/Cargo.toml b/anneal/v1/tests/fixtures/multi_artifact/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/multi_artifact/source/Cargo.toml rename to anneal/v1/tests/fixtures/multi_artifact/source/Cargo.toml diff --git a/anneal/tests/fixtures/multi_artifact/source/src/lib.rs b/anneal/v1/tests/fixtures/multi_artifact/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/multi_artifact/source/src/lib.rs rename to anneal/v1/tests/fixtures/multi_artifact/source/src/lib.rs diff --git a/anneal/tests/fixtures/multiple_errors/anneal.toml b/anneal/v1/tests/fixtures/multiple_errors/anneal.toml similarity index 100% rename from anneal/tests/fixtures/multiple_errors/anneal.toml rename to anneal/v1/tests/fixtures/multiple_errors/anneal.toml diff --git a/anneal/tests/fixtures/multiple_errors/expected.stderr b/anneal/v1/tests/fixtures/multiple_errors/expected.stderr similarity index 100% rename from anneal/tests/fixtures/multiple_errors/expected.stderr rename to anneal/v1/tests/fixtures/multiple_errors/expected.stderr diff --git a/anneal/tests/fixtures/multiple_errors/source/Cargo.toml b/anneal/v1/tests/fixtures/multiple_errors/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/multiple_errors/source/Cargo.toml rename to anneal/v1/tests/fixtures/multiple_errors/source/Cargo.toml diff --git a/anneal/tests/fixtures/multiple_errors/source/src/a.rs b/anneal/v1/tests/fixtures/multiple_errors/source/src/a.rs similarity index 100% rename from anneal/tests/fixtures/multiple_errors/source/src/a.rs rename to anneal/v1/tests/fixtures/multiple_errors/source/src/a.rs diff --git a/anneal/tests/fixtures/multiple_errors/source/src/b.rs b/anneal/v1/tests/fixtures/multiple_errors/source/src/b.rs similarity index 100% rename from anneal/tests/fixtures/multiple_errors/source/src/b.rs rename to anneal/v1/tests/fixtures/multiple_errors/source/src/b.rs diff --git a/anneal/tests/fixtures/multiple_errors/source/src/lib.rs b/anneal/v1/tests/fixtures/multiple_errors/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/multiple_errors/source/src/lib.rs rename to anneal/v1/tests/fixtures/multiple_errors/source/src/lib.rs diff --git a/anneal/tests/fixtures/named_bounds_failures/anneal.toml b/anneal/v1/tests/fixtures/named_bounds_failures/anneal.toml similarity index 100% rename from anneal/tests/fixtures/named_bounds_failures/anneal.toml rename to anneal/v1/tests/fixtures/named_bounds_failures/anneal.toml diff --git a/anneal/tests/fixtures/named_bounds_failures/expected.stderr b/anneal/v1/tests/fixtures/named_bounds_failures/expected.stderr similarity index 100% rename from anneal/tests/fixtures/named_bounds_failures/expected.stderr rename to anneal/v1/tests/fixtures/named_bounds_failures/expected.stderr diff --git a/anneal/tests/fixtures/named_bounds_failures/source/Cargo.toml b/anneal/v1/tests/fixtures/named_bounds_failures/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/named_bounds_failures/source/Cargo.toml rename to anneal/v1/tests/fixtures/named_bounds_failures/source/Cargo.toml diff --git a/anneal/tests/fixtures/named_bounds_failures/source/src/lib.rs b/anneal/v1/tests/fixtures/named_bounds_failures/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/named_bounds_failures/source/src/lib.rs rename to anneal/v1/tests/fixtures/named_bounds_failures/source/src/lib.rs diff --git a/anneal/tests/fixtures/named_bounds_lean_failures/anneal.toml b/anneal/v1/tests/fixtures/named_bounds_lean_failures/anneal.toml similarity index 100% rename from anneal/tests/fixtures/named_bounds_lean_failures/anneal.toml rename to anneal/v1/tests/fixtures/named_bounds_lean_failures/anneal.toml diff --git a/anneal/tests/fixtures/named_bounds_lean_failures/expected.stderr b/anneal/v1/tests/fixtures/named_bounds_lean_failures/expected.stderr similarity index 100% rename from anneal/tests/fixtures/named_bounds_lean_failures/expected.stderr rename to anneal/v1/tests/fixtures/named_bounds_lean_failures/expected.stderr diff --git a/anneal/tests/fixtures/named_bounds_lean_failures/source/Cargo.toml b/anneal/v1/tests/fixtures/named_bounds_lean_failures/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/named_bounds_lean_failures/source/Cargo.toml rename to anneal/v1/tests/fixtures/named_bounds_lean_failures/source/Cargo.toml diff --git a/anneal/tests/fixtures/named_bounds_lean_failures/source/src/lib.rs b/anneal/v1/tests/fixtures/named_bounds_lean_failures/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/named_bounds_lean_failures/source/src/lib.rs rename to anneal/v1/tests/fixtures/named_bounds_lean_failures/source/src/lib.rs diff --git a/anneal/tests/fixtures/named_bounds_validation_failures/anneal.toml b/anneal/v1/tests/fixtures/named_bounds_validation_failures/anneal.toml similarity index 100% rename from anneal/tests/fixtures/named_bounds_validation_failures/anneal.toml rename to anneal/v1/tests/fixtures/named_bounds_validation_failures/anneal.toml diff --git a/anneal/tests/fixtures/named_bounds_validation_failures/expected.stderr b/anneal/v1/tests/fixtures/named_bounds_validation_failures/expected.stderr similarity index 100% rename from anneal/tests/fixtures/named_bounds_validation_failures/expected.stderr rename to anneal/v1/tests/fixtures/named_bounds_validation_failures/expected.stderr diff --git a/anneal/tests/fixtures/named_bounds_validation_failures/source/Cargo.toml b/anneal/v1/tests/fixtures/named_bounds_validation_failures/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/named_bounds_validation_failures/source/Cargo.toml rename to anneal/v1/tests/fixtures/named_bounds_validation_failures/source/Cargo.toml diff --git a/anneal/tests/fixtures/named_bounds_validation_failures/source/src/lib.rs b/anneal/v1/tests/fixtures/named_bounds_validation_failures/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/named_bounds_validation_failures/source/src/lib.rs rename to anneal/v1/tests/fixtures/named_bounds_validation_failures/source/src/lib.rs diff --git a/anneal/tests/fixtures/nested_item_error/anneal.toml b/anneal/v1/tests/fixtures/nested_item_error/anneal.toml similarity index 100% rename from anneal/tests/fixtures/nested_item_error/anneal.toml rename to anneal/v1/tests/fixtures/nested_item_error/anneal.toml diff --git a/anneal/tests/fixtures/nested_item_error/expected.stderr b/anneal/v1/tests/fixtures/nested_item_error/expected.stderr similarity index 100% rename from anneal/tests/fixtures/nested_item_error/expected.stderr rename to anneal/v1/tests/fixtures/nested_item_error/expected.stderr diff --git a/anneal/tests/fixtures/nested_item_error/source/Cargo.toml b/anneal/v1/tests/fixtures/nested_item_error/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/nested_item_error/source/Cargo.toml rename to anneal/v1/tests/fixtures/nested_item_error/source/Cargo.toml diff --git a/anneal/tests/fixtures/nested_item_error/source/src/lib.rs b/anneal/v1/tests/fixtures/nested_item_error/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/nested_item_error/source/src/lib.rs rename to anneal/v1/tests/fixtures/nested_item_error/source/src/lib.rs diff --git a/anneal/tests/fixtures/nested_out_of_line_mod/anneal.toml b/anneal/v1/tests/fixtures/nested_out_of_line_mod/anneal.toml similarity index 100% rename from anneal/tests/fixtures/nested_out_of_line_mod/anneal.toml rename to anneal/v1/tests/fixtures/nested_out_of_line_mod/anneal.toml diff --git a/anneal/tests/fixtures/nested_out_of_line_mod/expected.stderr b/anneal/v1/tests/fixtures/nested_out_of_line_mod/expected.stderr similarity index 100% rename from anneal/tests/fixtures/nested_out_of_line_mod/expected.stderr rename to anneal/v1/tests/fixtures/nested_out_of_line_mod/expected.stderr diff --git a/anneal/tests/fixtures/nested_out_of_line_mod/source/Cargo.toml b/anneal/v1/tests/fixtures/nested_out_of_line_mod/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/nested_out_of_line_mod/source/Cargo.toml rename to anneal/v1/tests/fixtures/nested_out_of_line_mod/source/Cargo.toml diff --git a/anneal/tests/fixtures/nested_out_of_line_mod/source/src/hidden.rs b/anneal/v1/tests/fixtures/nested_out_of_line_mod/source/src/hidden.rs similarity index 100% rename from anneal/tests/fixtures/nested_out_of_line_mod/source/src/hidden.rs rename to anneal/v1/tests/fixtures/nested_out_of_line_mod/source/src/hidden.rs diff --git a/anneal/tests/fixtures/nested_out_of_line_mod/source/src/lib.rs b/anneal/v1/tests/fixtures/nested_out_of_line_mod/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/nested_out_of_line_mod/source/src/lib.rs rename to anneal/v1/tests/fixtures/nested_out_of_line_mod/source/src/lib.rs diff --git a/anneal/tests/fixtures/os_arg_limit_exceeded/anneal.toml b/anneal/v1/tests/fixtures/os_arg_limit_exceeded/anneal.toml similarity index 100% rename from anneal/tests/fixtures/os_arg_limit_exceeded/anneal.toml rename to anneal/v1/tests/fixtures/os_arg_limit_exceeded/anneal.toml diff --git a/anneal/tests/fixtures/os_arg_limit_exceeded/expected.stderr b/anneal/v1/tests/fixtures/os_arg_limit_exceeded/expected.stderr similarity index 100% rename from anneal/tests/fixtures/os_arg_limit_exceeded/expected.stderr rename to anneal/v1/tests/fixtures/os_arg_limit_exceeded/expected.stderr diff --git a/anneal/tests/fixtures/os_arg_limit_exceeded/source/Cargo.toml b/anneal/v1/tests/fixtures/os_arg_limit_exceeded/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/os_arg_limit_exceeded/source/Cargo.toml rename to anneal/v1/tests/fixtures/os_arg_limit_exceeded/source/Cargo.toml diff --git a/anneal/tests/fixtures/os_arg_limit_exceeded/source/src/lib.rs b/anneal/v1/tests/fixtures/os_arg_limit_exceeded/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/os_arg_limit_exceeded/source/src/lib.rs rename to anneal/v1/tests/fixtures/os_arg_limit_exceeded/source/src/lib.rs diff --git a/anneal/tests/fixtures/raw_ptr_dst_layout/anneal.toml b/anneal/v1/tests/fixtures/raw_ptr_dst_layout/anneal.toml similarity index 100% rename from anneal/tests/fixtures/raw_ptr_dst_layout/anneal.toml rename to anneal/v1/tests/fixtures/raw_ptr_dst_layout/anneal.toml diff --git a/anneal/tests/fixtures/raw_ptr_dst_layout/expected.stderr b/anneal/v1/tests/fixtures/raw_ptr_dst_layout/expected.stderr similarity index 100% rename from anneal/tests/fixtures/raw_ptr_dst_layout/expected.stderr rename to anneal/v1/tests/fixtures/raw_ptr_dst_layout/expected.stderr diff --git a/anneal/tests/fixtures/raw_ptr_dst_layout/source/Cargo.toml b/anneal/v1/tests/fixtures/raw_ptr_dst_layout/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/raw_ptr_dst_layout/source/Cargo.toml rename to anneal/v1/tests/fixtures/raw_ptr_dst_layout/source/Cargo.toml diff --git a/anneal/tests/fixtures/raw_ptr_dst_layout/source/out.log b/anneal/v1/tests/fixtures/raw_ptr_dst_layout/source/out.log similarity index 100% rename from anneal/tests/fixtures/raw_ptr_dst_layout/source/out.log rename to anneal/v1/tests/fixtures/raw_ptr_dst_layout/source/out.log diff --git a/anneal/tests/fixtures/raw_ptr_dst_layout/source/src/lib.rs b/anneal/v1/tests/fixtures/raw_ptr_dst_layout/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/raw_ptr_dst_layout/source/src/lib.rs rename to anneal/v1/tests/fixtures/raw_ptr_dst_layout/source/src/lib.rs diff --git a/anneal/tests/fixtures/reject_safe_is_safe/anneal.toml b/anneal/v1/tests/fixtures/reject_safe_is_safe/anneal.toml similarity index 100% rename from anneal/tests/fixtures/reject_safe_is_safe/anneal.toml rename to anneal/v1/tests/fixtures/reject_safe_is_safe/anneal.toml diff --git a/anneal/tests/fixtures/reject_safe_is_safe/expected.stderr b/anneal/v1/tests/fixtures/reject_safe_is_safe/expected.stderr similarity index 100% rename from anneal/tests/fixtures/reject_safe_is_safe/expected.stderr rename to anneal/v1/tests/fixtures/reject_safe_is_safe/expected.stderr diff --git a/anneal/tests/fixtures/reject_safe_is_safe/source/Cargo.toml b/anneal/v1/tests/fixtures/reject_safe_is_safe/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/reject_safe_is_safe/source/Cargo.toml rename to anneal/v1/tests/fixtures/reject_safe_is_safe/source/Cargo.toml diff --git a/anneal/tests/fixtures/reject_safe_is_safe/source/src/lib.rs b/anneal/v1/tests/fixtures/reject_safe_is_safe/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/reject_safe_is_safe/source/src/lib.rs rename to anneal/v1/tests/fixtures/reject_safe_is_safe/source/src/lib.rs diff --git a/anneal/tests/fixtures/reject_safe_requires/anneal.toml b/anneal/v1/tests/fixtures/reject_safe_requires/anneal.toml similarity index 100% rename from anneal/tests/fixtures/reject_safe_requires/anneal.toml rename to anneal/v1/tests/fixtures/reject_safe_requires/anneal.toml diff --git a/anneal/tests/fixtures/reject_safe_requires/expected.stderr b/anneal/v1/tests/fixtures/reject_safe_requires/expected.stderr similarity index 100% rename from anneal/tests/fixtures/reject_safe_requires/expected.stderr rename to anneal/v1/tests/fixtures/reject_safe_requires/expected.stderr diff --git a/anneal/tests/fixtures/reject_safe_requires/source/Cargo.toml b/anneal/v1/tests/fixtures/reject_safe_requires/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/reject_safe_requires/source/Cargo.toml rename to anneal/v1/tests/fixtures/reject_safe_requires/source/Cargo.toml diff --git a/anneal/tests/fixtures/reject_safe_requires/source/src/lib.rs b/anneal/v1/tests/fixtures/reject_safe_requires/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/reject_safe_requires/source/src/lib.rs rename to anneal/v1/tests/fixtures/reject_safe_requires/source/src/lib.rs diff --git a/anneal/tests/fixtures/select_bin/anneal.toml b/anneal/v1/tests/fixtures/select_bin/anneal.toml similarity index 100% rename from anneal/tests/fixtures/select_bin/anneal.toml rename to anneal/v1/tests/fixtures/select_bin/anneal.toml diff --git a/anneal/tests/fixtures/select_bin/source/Cargo.toml b/anneal/v1/tests/fixtures/select_bin/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/select_bin/source/Cargo.toml rename to anneal/v1/tests/fixtures/select_bin/source/Cargo.toml diff --git a/anneal/tests/fixtures/select_bin/source/src/lib.rs b/anneal/v1/tests/fixtures/select_bin/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/select_bin/source/src/lib.rs rename to anneal/v1/tests/fixtures/select_bin/source/src/lib.rs diff --git a/anneal/tests/fixtures/select_bin/source/src/main.rs b/anneal/v1/tests/fixtures/select_bin/source/src/main.rs similarity index 100% rename from anneal/tests/fixtures/select_bin/source/src/main.rs rename to anneal/v1/tests/fixtures/select_bin/source/src/main.rs diff --git a/anneal/tests/fixtures/select_lib/anneal.toml b/anneal/v1/tests/fixtures/select_lib/anneal.toml similarity index 100% rename from anneal/tests/fixtures/select_lib/anneal.toml rename to anneal/v1/tests/fixtures/select_lib/anneal.toml diff --git a/anneal/tests/fixtures/select_lib/source/Cargo.toml b/anneal/v1/tests/fixtures/select_lib/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/select_lib/source/Cargo.toml rename to anneal/v1/tests/fixtures/select_lib/source/Cargo.toml diff --git a/anneal/tests/fixtures/select_lib/source/src/lib.rs b/anneal/v1/tests/fixtures/select_lib/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/select_lib/source/src/lib.rs rename to anneal/v1/tests/fixtures/select_lib/source/src/lib.rs diff --git a/anneal/tests/fixtures/select_lib/source/src/main.rs b/anneal/v1/tests/fixtures/select_lib/source/src/main.rs similarity index 100% rename from anneal/tests/fixtures/select_lib/source/src/main.rs rename to anneal/v1/tests/fixtures/select_lib/source/src/main.rs diff --git a/anneal/tests/fixtures/size_of_align_of/anneal.toml b/anneal/v1/tests/fixtures/size_of_align_of/anneal.toml similarity index 100% rename from anneal/tests/fixtures/size_of_align_of/anneal.toml rename to anneal/v1/tests/fixtures/size_of_align_of/anneal.toml diff --git a/anneal/tests/fixtures/size_of_align_of/source/Cargo.toml b/anneal/v1/tests/fixtures/size_of_align_of/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/size_of_align_of/source/Cargo.toml rename to anneal/v1/tests/fixtures/size_of_align_of/source/Cargo.toml diff --git a/anneal/tests/fixtures/size_of_align_of/source/src/lib.rs b/anneal/v1/tests/fixtures/size_of_align_of/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/size_of_align_of/source/src/lib.rs rename to anneal/v1/tests/fixtures/size_of_align_of/source/src/lib.rs diff --git a/anneal/tests/fixtures/sizedness/anneal.toml b/anneal/v1/tests/fixtures/sizedness/anneal.toml similarity index 100% rename from anneal/tests/fixtures/sizedness/anneal.toml rename to anneal/v1/tests/fixtures/sizedness/anneal.toml diff --git a/anneal/tests/fixtures/sizedness/expected.stderr b/anneal/v1/tests/fixtures/sizedness/expected.stderr similarity index 100% rename from anneal/tests/fixtures/sizedness/expected.stderr rename to anneal/v1/tests/fixtures/sizedness/expected.stderr diff --git a/anneal/tests/fixtures/sizedness/source/Cargo.toml b/anneal/v1/tests/fixtures/sizedness/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/sizedness/source/Cargo.toml rename to anneal/v1/tests/fixtures/sizedness/source/Cargo.toml diff --git a/anneal/tests/fixtures/sizedness/source/out.txt b/anneal/v1/tests/fixtures/sizedness/source/out.txt similarity index 100% rename from anneal/tests/fixtures/sizedness/source/out.txt rename to anneal/v1/tests/fixtures/sizedness/source/out.txt diff --git a/anneal/tests/fixtures/sizedness/source/pass/Cargo.toml b/anneal/v1/tests/fixtures/sizedness/source/pass/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/sizedness/source/pass/Cargo.toml rename to anneal/v1/tests/fixtures/sizedness/source/pass/Cargo.toml diff --git a/anneal/tests/fixtures/sizedness/source/pass/src/lib.rs b/anneal/v1/tests/fixtures/sizedness/source/pass/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/sizedness/source/pass/src/lib.rs rename to anneal/v1/tests/fixtures/sizedness/source/pass/src/lib.rs diff --git a/anneal/tests/fixtures/sizedness/source/reject/Cargo.toml b/anneal/v1/tests/fixtures/sizedness/source/reject/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/sizedness/source/reject/Cargo.toml rename to anneal/v1/tests/fixtures/sizedness/source/reject/Cargo.toml diff --git a/anneal/tests/fixtures/sizedness/source/reject/src/lib.rs b/anneal/v1/tests/fixtures/sizedness/source/reject/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/sizedness/source/reject/src/lib.rs rename to anneal/v1/tests/fixtures/sizedness/source/reject/src/lib.rs diff --git a/anneal/tests/fixtures/span_empty_unsafe_block/anneal.toml b/anneal/v1/tests/fixtures/span_empty_unsafe_block/anneal.toml similarity index 100% rename from anneal/tests/fixtures/span_empty_unsafe_block/anneal.toml rename to anneal/v1/tests/fixtures/span_empty_unsafe_block/anneal.toml diff --git a/anneal/tests/fixtures/span_empty_unsafe_block/expected.stderr b/anneal/v1/tests/fixtures/span_empty_unsafe_block/expected.stderr similarity index 100% rename from anneal/tests/fixtures/span_empty_unsafe_block/expected.stderr rename to anneal/v1/tests/fixtures/span_empty_unsafe_block/expected.stderr diff --git a/anneal/tests/fixtures/span_empty_unsafe_block/mock_charon_output.json b/anneal/v1/tests/fixtures/span_empty_unsafe_block/mock_charon_output.json similarity index 100% rename from anneal/tests/fixtures/span_empty_unsafe_block/mock_charon_output.json rename to anneal/v1/tests/fixtures/span_empty_unsafe_block/mock_charon_output.json diff --git a/anneal/tests/fixtures/span_empty_unsafe_block/source/Cargo.toml b/anneal/v1/tests/fixtures/span_empty_unsafe_block/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/span_empty_unsafe_block/source/Cargo.toml rename to anneal/v1/tests/fixtures/span_empty_unsafe_block/source/Cargo.toml diff --git a/anneal/tests/fixtures/span_empty_unsafe_block/source/src/lib.rs b/anneal/v1/tests/fixtures/span_empty_unsafe_block/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/span_empty_unsafe_block/source/src/lib.rs rename to anneal/v1/tests/fixtures/span_empty_unsafe_block/source/src/lib.rs diff --git a/anneal/tests/fixtures/span_empty_unsafe_block/source/src/untransformed.rs b/anneal/v1/tests/fixtures/span_empty_unsafe_block/source/src/untransformed.rs similarity index 100% rename from anneal/tests/fixtures/span_empty_unsafe_block/source/src/untransformed.rs rename to anneal/v1/tests/fixtures/span_empty_unsafe_block/source/src/untransformed.rs diff --git a/anneal/tests/fixtures/span_macro_expansion/anneal.toml b/anneal/v1/tests/fixtures/span_macro_expansion/anneal.toml similarity index 100% rename from anneal/tests/fixtures/span_macro_expansion/anneal.toml rename to anneal/v1/tests/fixtures/span_macro_expansion/anneal.toml diff --git a/anneal/tests/fixtures/span_macro_expansion/expected.stderr b/anneal/v1/tests/fixtures/span_macro_expansion/expected.stderr similarity index 100% rename from anneal/tests/fixtures/span_macro_expansion/expected.stderr rename to anneal/v1/tests/fixtures/span_macro_expansion/expected.stderr diff --git a/anneal/tests/fixtures/span_macro_expansion/mock_charon_output.json b/anneal/v1/tests/fixtures/span_macro_expansion/mock_charon_output.json similarity index 100% rename from anneal/tests/fixtures/span_macro_expansion/mock_charon_output.json rename to anneal/v1/tests/fixtures/span_macro_expansion/mock_charon_output.json diff --git a/anneal/tests/fixtures/span_macro_expansion/source/Cargo.toml b/anneal/v1/tests/fixtures/span_macro_expansion/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/span_macro_expansion/source/Cargo.toml rename to anneal/v1/tests/fixtures/span_macro_expansion/source/Cargo.toml diff --git a/anneal/tests/fixtures/span_macro_expansion/source/src/lib.rs b/anneal/v1/tests/fixtures/span_macro_expansion/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/span_macro_expansion/source/src/lib.rs rename to anneal/v1/tests/fixtures/span_macro_expansion/source/src/lib.rs diff --git a/anneal/tests/fixtures/span_macro_expansion/source/src/untransformed.rs b/anneal/v1/tests/fixtures/span_macro_expansion/source/src/untransformed.rs similarity index 100% rename from anneal/tests/fixtures/span_macro_expansion/source/src/untransformed.rs rename to anneal/v1/tests/fixtures/span_macro_expansion/source/src/untransformed.rs diff --git a/anneal/tests/fixtures/span_mapping_basic/anneal.toml b/anneal/v1/tests/fixtures/span_mapping_basic/anneal.toml similarity index 100% rename from anneal/tests/fixtures/span_mapping_basic/anneal.toml rename to anneal/v1/tests/fixtures/span_mapping_basic/anneal.toml diff --git a/anneal/tests/fixtures/span_mapping_basic/expected.stderr b/anneal/v1/tests/fixtures/span_mapping_basic/expected.stderr similarity index 100% rename from anneal/tests/fixtures/span_mapping_basic/expected.stderr rename to anneal/v1/tests/fixtures/span_mapping_basic/expected.stderr diff --git a/anneal/tests/fixtures/span_mapping_basic/mock_charon_output.json b/anneal/v1/tests/fixtures/span_mapping_basic/mock_charon_output.json similarity index 100% rename from anneal/tests/fixtures/span_mapping_basic/mock_charon_output.json rename to anneal/v1/tests/fixtures/span_mapping_basic/mock_charon_output.json diff --git a/anneal/tests/fixtures/span_mapping_basic/source/Cargo.toml b/anneal/v1/tests/fixtures/span_mapping_basic/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/span_mapping_basic/source/Cargo.toml rename to anneal/v1/tests/fixtures/span_mapping_basic/source/Cargo.toml diff --git a/anneal/tests/fixtures/span_mapping_basic/source/src/lib.rs b/anneal/v1/tests/fixtures/span_mapping_basic/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/span_mapping_basic/source/src/lib.rs rename to anneal/v1/tests/fixtures/span_mapping_basic/source/src/lib.rs diff --git a/anneal/tests/fixtures/span_multibyte_offsets/anneal.toml b/anneal/v1/tests/fixtures/span_multibyte_offsets/anneal.toml similarity index 100% rename from anneal/tests/fixtures/span_multibyte_offsets/anneal.toml rename to anneal/v1/tests/fixtures/span_multibyte_offsets/anneal.toml diff --git a/anneal/tests/fixtures/span_multibyte_offsets/expected.stderr b/anneal/v1/tests/fixtures/span_multibyte_offsets/expected.stderr similarity index 100% rename from anneal/tests/fixtures/span_multibyte_offsets/expected.stderr rename to anneal/v1/tests/fixtures/span_multibyte_offsets/expected.stderr diff --git a/anneal/tests/fixtures/span_multibyte_offsets/mock_charon_output.json b/anneal/v1/tests/fixtures/span_multibyte_offsets/mock_charon_output.json similarity index 100% rename from anneal/tests/fixtures/span_multibyte_offsets/mock_charon_output.json rename to anneal/v1/tests/fixtures/span_multibyte_offsets/mock_charon_output.json diff --git a/anneal/tests/fixtures/span_multibyte_offsets/source/Cargo.toml b/anneal/v1/tests/fixtures/span_multibyte_offsets/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/span_multibyte_offsets/source/Cargo.toml rename to anneal/v1/tests/fixtures/span_multibyte_offsets/source/Cargo.toml diff --git a/anneal/tests/fixtures/span_multibyte_offsets/source/src/lib.rs b/anneal/v1/tests/fixtures/span_multibyte_offsets/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/span_multibyte_offsets/source/src/lib.rs rename to anneal/v1/tests/fixtures/span_multibyte_offsets/source/src/lib.rs diff --git a/anneal/tests/fixtures/span_multibyte_offsets/source/src/untransformed.rs b/anneal/v1/tests/fixtures/span_multibyte_offsets/source/src/untransformed.rs similarity index 100% rename from anneal/tests/fixtures/span_multibyte_offsets/source/src/untransformed.rs rename to anneal/v1/tests/fixtures/span_multibyte_offsets/source/src/untransformed.rs diff --git a/anneal/tests/fixtures/split_artifact/anneal.toml b/anneal/v1/tests/fixtures/split_artifact/anneal.toml similarity index 100% rename from anneal/tests/fixtures/split_artifact/anneal.toml rename to anneal/v1/tests/fixtures/split_artifact/anneal.toml diff --git a/anneal/tests/fixtures/split_artifact/expected.stderr b/anneal/v1/tests/fixtures/split_artifact/expected.stderr similarity index 100% rename from anneal/tests/fixtures/split_artifact/expected.stderr rename to anneal/v1/tests/fixtures/split_artifact/expected.stderr diff --git a/anneal/tests/fixtures/split_artifact/source/Cargo.toml b/anneal/v1/tests/fixtures/split_artifact/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/split_artifact/source/Cargo.toml rename to anneal/v1/tests/fixtures/split_artifact/source/Cargo.toml diff --git a/anneal/tests/fixtures/split_artifact/source/src/lib.rs b/anneal/v1/tests/fixtures/split_artifact/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/split_artifact/source/src/lib.rs rename to anneal/v1/tests/fixtures/split_artifact/source/src/lib.rs diff --git a/anneal/tests/fixtures/split_artifact/source/src/main.rs b/anneal/v1/tests/fixtures/split_artifact/source/src/main.rs similarity index 100% rename from anneal/tests/fixtures/split_artifact/source/src/main.rs rename to anneal/v1/tests/fixtures/split_artifact/source/src/main.rs diff --git a/anneal/tests/fixtures/stale_output/anneal.toml b/anneal/v1/tests/fixtures/stale_output/anneal.toml similarity index 100% rename from anneal/tests/fixtures/stale_output/anneal.toml rename to anneal/v1/tests/fixtures/stale_output/anneal.toml diff --git a/anneal/tests/fixtures/stale_output/source/Cargo.toml b/anneal/v1/tests/fixtures/stale_output/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/stale_output/source/Cargo.toml rename to anneal/v1/tests/fixtures/stale_output/source/Cargo.toml diff --git a/anneal/tests/fixtures/stale_output/source/src/lib.rs b/anneal/v1/tests/fixtures/stale_output/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/stale_output/source/src/lib.rs rename to anneal/v1/tests/fixtures/stale_output/source/src/lib.rs diff --git a/anneal/tests/fixtures/stuck_wps/anneal.toml b/anneal/v1/tests/fixtures/stuck_wps/anneal.toml similarity index 100% rename from anneal/tests/fixtures/stuck_wps/anneal.toml rename to anneal/v1/tests/fixtures/stuck_wps/anneal.toml diff --git a/anneal/tests/fixtures/stuck_wps/expected.stderr b/anneal/v1/tests/fixtures/stuck_wps/expected.stderr similarity index 100% rename from anneal/tests/fixtures/stuck_wps/expected.stderr rename to anneal/v1/tests/fixtures/stuck_wps/expected.stderr diff --git a/anneal/tests/fixtures/stuck_wps/source/Cargo.toml b/anneal/v1/tests/fixtures/stuck_wps/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/stuck_wps/source/Cargo.toml rename to anneal/v1/tests/fixtures/stuck_wps/source/Cargo.toml diff --git a/anneal/tests/fixtures/stuck_wps/source/crates/app/Cargo.toml b/anneal/v1/tests/fixtures/stuck_wps/source/crates/app/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/stuck_wps/source/crates/app/Cargo.toml rename to anneal/v1/tests/fixtures/stuck_wps/source/crates/app/Cargo.toml diff --git a/anneal/tests/fixtures/stuck_wps/source/crates/app/src/lib.rs b/anneal/v1/tests/fixtures/stuck_wps/source/crates/app/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/stuck_wps/source/crates/app/src/lib.rs rename to anneal/v1/tests/fixtures/stuck_wps/source/crates/app/src/lib.rs diff --git a/anneal/tests/fixtures/stuck_wps/source/crates/dep/Cargo.toml b/anneal/v1/tests/fixtures/stuck_wps/source/crates/dep/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/stuck_wps/source/crates/dep/Cargo.toml rename to anneal/v1/tests/fixtures/stuck_wps/source/crates/dep/Cargo.toml diff --git a/anneal/tests/fixtures/stuck_wps/source/crates/dep/src/lib.rs b/anneal/v1/tests/fixtures/stuck_wps/source/crates/dep/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/stuck_wps/source/crates/dep/src/lib.rs rename to anneal/v1/tests/fixtures/stuck_wps/source/crates/dep/src/lib.rs diff --git a/anneal/tests/fixtures/success_allow_sorry/anneal.toml b/anneal/v1/tests/fixtures/success_allow_sorry/anneal.toml similarity index 100% rename from anneal/tests/fixtures/success_allow_sorry/anneal.toml rename to anneal/v1/tests/fixtures/success_allow_sorry/anneal.toml diff --git a/anneal/tests/fixtures/success_allow_sorry/source/Cargo.toml b/anneal/v1/tests/fixtures/success_allow_sorry/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/success_allow_sorry/source/Cargo.toml rename to anneal/v1/tests/fixtures/success_allow_sorry/source/Cargo.toml diff --git a/anneal/tests/fixtures/success_allow_sorry/source/src/cfg_and_macros.rs b/anneal/v1/tests/fixtures/success_allow_sorry/source/src/cfg_and_macros.rs similarity index 100% rename from anneal/tests/fixtures/success_allow_sorry/source/src/cfg_and_macros.rs rename to anneal/v1/tests/fixtures/success_allow_sorry/source/src/cfg_and_macros.rs diff --git a/anneal/tests/fixtures/success_allow_sorry/source/src/cfg_and_macros/macro_blind_spot/hidden.rs b/anneal/v1/tests/fixtures/success_allow_sorry/source/src/cfg_and_macros/macro_blind_spot/hidden.rs similarity index 100% rename from anneal/tests/fixtures/success_allow_sorry/source/src/cfg_and_macros/macro_blind_spot/hidden.rs rename to anneal/v1/tests/fixtures/success_allow_sorry/source/src/cfg_and_macros/macro_blind_spot/hidden.rs diff --git a/anneal/tests/fixtures/success_allow_sorry/source/src/cfg_and_macros/warn_cfg_attr_path/sys_unix.rs b/anneal/v1/tests/fixtures/success_allow_sorry/source/src/cfg_and_macros/warn_cfg_attr_path/sys_unix.rs similarity index 100% rename from anneal/tests/fixtures/success_allow_sorry/source/src/cfg_and_macros/warn_cfg_attr_path/sys_unix.rs rename to anneal/v1/tests/fixtures/success_allow_sorry/source/src/cfg_and_macros/warn_cfg_attr_path/sys_unix.rs diff --git a/anneal/tests/fixtures/success_allow_sorry/source/src/hierarchy_and_stress.rs b/anneal/v1/tests/fixtures/success_allow_sorry/source/src/hierarchy_and_stress.rs similarity index 100% rename from anneal/tests/fixtures/success_allow_sorry/source/src/hierarchy_and_stress.rs rename to anneal/v1/tests/fixtures/success_allow_sorry/source/src/hierarchy_and_stress.rs diff --git a/anneal/tests/fixtures/success_allow_sorry/source/src/hierarchy_and_stress/deep_invocation/nested.rs b/anneal/v1/tests/fixtures/success_allow_sorry/source/src/hierarchy_and_stress/deep_invocation/nested.rs similarity index 100% rename from anneal/tests/fixtures/success_allow_sorry/source/src/hierarchy_and_stress/deep_invocation/nested.rs rename to anneal/v1/tests/fixtures/success_allow_sorry/source/src/hierarchy_and_stress/deep_invocation/nested.rs diff --git a/anneal/tests/fixtures/success_allow_sorry/source/src/lib.rs b/anneal/v1/tests/fixtures/success_allow_sorry/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/success_allow_sorry/source/src/lib.rs rename to anneal/v1/tests/fixtures/success_allow_sorry/source/src/lib.rs diff --git a/anneal/tests/fixtures/success_allow_sorry/source/src/logic_and_patterns.rs b/anneal/v1/tests/fixtures/success_allow_sorry/source/src/logic_and_patterns.rs similarity index 100% rename from anneal/tests/fixtures/success_allow_sorry/source/src/logic_and_patterns.rs rename to anneal/v1/tests/fixtures/success_allow_sorry/source/src/logic_and_patterns.rs diff --git a/anneal/tests/fixtures/success_allow_sorry/source/src/naming_and_imports.rs b/anneal/v1/tests/fixtures/success_allow_sorry/source/src/naming_and_imports.rs similarity index 100% rename from anneal/tests/fixtures/success_allow_sorry/source/src/naming_and_imports.rs rename to anneal/v1/tests/fixtures/success_allow_sorry/source/src/naming_and_imports.rs diff --git a/anneal/tests/fixtures/success_allow_sorry/source/src/primitives.rs b/anneal/v1/tests/fixtures/success_allow_sorry/source/src/primitives.rs similarity index 100% rename from anneal/tests/fixtures/success_allow_sorry/source/src/primitives.rs rename to anneal/v1/tests/fixtures/success_allow_sorry/source/src/primitives.rs diff --git a/anneal/tests/fixtures/success_allow_sorry/source/src/types.rs b/anneal/v1/tests/fixtures/success_allow_sorry/source/src/types.rs similarity index 100% rename from anneal/tests/fixtures/success_allow_sorry/source/src/types.rs rename to anneal/v1/tests/fixtures/success_allow_sorry/source/src/types.rs diff --git a/anneal/tests/fixtures/success_allow_sorry_is_valid/anneal.toml b/anneal/v1/tests/fixtures/success_allow_sorry_is_valid/anneal.toml similarity index 100% rename from anneal/tests/fixtures/success_allow_sorry_is_valid/anneal.toml rename to anneal/v1/tests/fixtures/success_allow_sorry_is_valid/anneal.toml diff --git a/anneal/tests/fixtures/success_allow_sorry_is_valid/source/Cargo.toml b/anneal/v1/tests/fixtures/success_allow_sorry_is_valid/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/success_allow_sorry_is_valid/source/Cargo.toml rename to anneal/v1/tests/fixtures/success_allow_sorry_is_valid/source/Cargo.toml diff --git a/anneal/tests/fixtures/success_allow_sorry_is_valid/source/src/framework.rs b/anneal/v1/tests/fixtures/success_allow_sorry_is_valid/source/src/framework.rs similarity index 100% rename from anneal/tests/fixtures/success_allow_sorry_is_valid/source/src/framework.rs rename to anneal/v1/tests/fixtures/success_allow_sorry_is_valid/source/src/framework.rs diff --git a/anneal/tests/fixtures/success_allow_sorry_is_valid/source/src/lib.rs b/anneal/v1/tests/fixtures/success_allow_sorry_is_valid/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/success_allow_sorry_is_valid/source/src/lib.rs rename to anneal/v1/tests/fixtures/success_allow_sorry_is_valid/source/src/lib.rs diff --git a/anneal/tests/fixtures/success_allow_sorry_is_valid/source/src/logic_and_control.rs b/anneal/v1/tests/fixtures/success_allow_sorry_is_valid/source/src/logic_and_control.rs similarity index 100% rename from anneal/tests/fixtures/success_allow_sorry_is_valid/source/src/logic_and_control.rs rename to anneal/v1/tests/fixtures/success_allow_sorry_is_valid/source/src/logic_and_control.rs diff --git a/anneal/tests/fixtures/success_allow_sorry_is_valid/source/src/macro_checks.rs b/anneal/v1/tests/fixtures/success_allow_sorry_is_valid/source/src/macro_checks.rs similarity index 100% rename from anneal/tests/fixtures/success_allow_sorry_is_valid/source/src/macro_checks.rs rename to anneal/v1/tests/fixtures/success_allow_sorry_is_valid/source/src/macro_checks.rs diff --git a/anneal/tests/fixtures/success_allow_sorry_is_valid/source/src/memory_and_borrows.rs b/anneal/v1/tests/fixtures/success_allow_sorry_is_valid/source/src/memory_and_borrows.rs similarity index 100% rename from anneal/tests/fixtures/success_allow_sorry_is_valid/source/src/memory_and_borrows.rs rename to anneal/v1/tests/fixtures/success_allow_sorry_is_valid/source/src/memory_and_borrows.rs diff --git a/anneal/tests/fixtures/success_allow_sorry_is_valid/source/src/spec_syntax.rs b/anneal/v1/tests/fixtures/success_allow_sorry_is_valid/source/src/spec_syntax.rs similarity index 100% rename from anneal/tests/fixtures/success_allow_sorry_is_valid/source/src/spec_syntax.rs rename to anneal/v1/tests/fixtures/success_allow_sorry_is_valid/source/src/spec_syntax.rs diff --git a/anneal/tests/fixtures/success_allow_sorry_is_valid/source/src/traits_and_impls.rs b/anneal/v1/tests/fixtures/success_allow_sorry_is_valid/source/src/traits_and_impls.rs similarity index 100% rename from anneal/tests/fixtures/success_allow_sorry_is_valid/source/src/traits_and_impls.rs rename to anneal/v1/tests/fixtures/success_allow_sorry_is_valid/source/src/traits_and_impls.rs diff --git a/anneal/tests/fixtures/success_allow_sorry_is_valid/source/src/types_and_data.rs b/anneal/v1/tests/fixtures/success_allow_sorry_is_valid/source/src/types_and_data.rs similarity index 100% rename from anneal/tests/fixtures/success_allow_sorry_is_valid/source/src/types_and_data.rs rename to anneal/v1/tests/fixtures/success_allow_sorry_is_valid/source/src/types_and_data.rs diff --git a/anneal/tests/fixtures/syntax_error/anneal.toml b/anneal/v1/tests/fixtures/syntax_error/anneal.toml similarity index 100% rename from anneal/tests/fixtures/syntax_error/anneal.toml rename to anneal/v1/tests/fixtures/syntax_error/anneal.toml diff --git a/anneal/tests/fixtures/syntax_error/expected.stderr b/anneal/v1/tests/fixtures/syntax_error/expected.stderr similarity index 100% rename from anneal/tests/fixtures/syntax_error/expected.stderr rename to anneal/v1/tests/fixtures/syntax_error/expected.stderr diff --git a/anneal/tests/fixtures/syntax_error/source/Cargo.toml b/anneal/v1/tests/fixtures/syntax_error/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/syntax_error/source/Cargo.toml rename to anneal/v1/tests/fixtures/syntax_error/source/Cargo.toml diff --git a/anneal/tests/fixtures/syntax_error/source/src/lib.rs b/anneal/v1/tests/fixtures/syntax_error/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/syntax_error/source/src/lib.rs rename to anneal/v1/tests/fixtures/syntax_error/source/src/lib.rs diff --git a/anneal/tests/fixtures/target_selection/anneal.toml b/anneal/v1/tests/fixtures/target_selection/anneal.toml similarity index 100% rename from anneal/tests/fixtures/target_selection/anneal.toml rename to anneal/v1/tests/fixtures/target_selection/anneal.toml diff --git a/anneal/tests/fixtures/target_selection/expected.stderr b/anneal/v1/tests/fixtures/target_selection/expected.stderr similarity index 100% rename from anneal/tests/fixtures/target_selection/expected.stderr rename to anneal/v1/tests/fixtures/target_selection/expected.stderr diff --git a/anneal/tests/fixtures/target_selection/source/Cargo.toml b/anneal/v1/tests/fixtures/target_selection/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/target_selection/source/Cargo.toml rename to anneal/v1/tests/fixtures/target_selection/source/Cargo.toml diff --git a/anneal/tests/fixtures/target_selection/source/src/lib.rs b/anneal/v1/tests/fixtures/target_selection/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/target_selection/source/src/lib.rs rename to anneal/v1/tests/fixtures/target_selection/source/src/lib.rs diff --git a/anneal/tests/fixtures/target_selection/source/src/main.rs b/anneal/v1/tests/fixtures/target_selection/source/src/main.rs similarity index 100% rename from anneal/tests/fixtures/target_selection/source/src/main.rs rename to anneal/v1/tests/fixtures/target_selection/source/src/main.rs diff --git a/anneal/tests/fixtures/test_ptr_crate/Cargo.toml b/anneal/v1/tests/fixtures/test_ptr_crate/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/test_ptr_crate/Cargo.toml rename to anneal/v1/tests/fixtures/test_ptr_crate/Cargo.toml diff --git a/anneal/tests/fixtures/test_ptr_crate/src/lib.rs b/anneal/v1/tests/fixtures/test_ptr_crate/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/test_ptr_crate/src/lib.rs rename to anneal/v1/tests/fixtures/test_ptr_crate/src/lib.rs diff --git a/anneal/tests/fixtures/type_features/anneal.toml b/anneal/v1/tests/fixtures/type_features/anneal.toml similarity index 100% rename from anneal/tests/fixtures/type_features/anneal.toml rename to anneal/v1/tests/fixtures/type_features/anneal.toml diff --git a/anneal/tests/fixtures/type_features/expected.stderr b/anneal/v1/tests/fixtures/type_features/expected.stderr similarity index 100% rename from anneal/tests/fixtures/type_features/expected.stderr rename to anneal/v1/tests/fixtures/type_features/expected.stderr diff --git a/anneal/tests/fixtures/type_features/source/Cargo.toml b/anneal/v1/tests/fixtures/type_features/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/type_features/source/Cargo.toml rename to anneal/v1/tests/fixtures/type_features/source/Cargo.toml diff --git a/anneal/tests/fixtures/type_features/source/src/lib.rs b/anneal/v1/tests/fixtures/type_features/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/type_features/source/src/lib.rs rename to anneal/v1/tests/fixtures/type_features/source/src/lib.rs diff --git a/anneal/tests/fixtures/ui_build_script_noise/anneal.toml b/anneal/v1/tests/fixtures/ui_build_script_noise/anneal.toml similarity index 100% rename from anneal/tests/fixtures/ui_build_script_noise/anneal.toml rename to anneal/v1/tests/fixtures/ui_build_script_noise/anneal.toml diff --git a/anneal/tests/fixtures/ui_build_script_noise/expected.stderr b/anneal/v1/tests/fixtures/ui_build_script_noise/expected.stderr similarity index 100% rename from anneal/tests/fixtures/ui_build_script_noise/expected.stderr rename to anneal/v1/tests/fixtures/ui_build_script_noise/expected.stderr diff --git a/anneal/tests/fixtures/ui_build_script_noise/mock_charon_output.json b/anneal/v1/tests/fixtures/ui_build_script_noise/mock_charon_output.json similarity index 100% rename from anneal/tests/fixtures/ui_build_script_noise/mock_charon_output.json rename to anneal/v1/tests/fixtures/ui_build_script_noise/mock_charon_output.json diff --git a/anneal/tests/fixtures/ui_build_script_noise/source/Cargo.toml b/anneal/v1/tests/fixtures/ui_build_script_noise/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/ui_build_script_noise/source/Cargo.toml rename to anneal/v1/tests/fixtures/ui_build_script_noise/source/Cargo.toml diff --git a/anneal/tests/fixtures/ui_build_script_noise/source/src/lib.rs b/anneal/v1/tests/fixtures/ui_build_script_noise/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/ui_build_script_noise/source/src/lib.rs rename to anneal/v1/tests/fixtures/ui_build_script_noise/source/src/lib.rs diff --git a/anneal/tests/fixtures/ui_hanging_build/anneal.toml b/anneal/v1/tests/fixtures/ui_hanging_build/anneal.toml similarity index 100% rename from anneal/tests/fixtures/ui_hanging_build/anneal.toml rename to anneal/v1/tests/fixtures/ui_hanging_build/anneal.toml diff --git a/anneal/tests/fixtures/ui_hanging_build/expected.stderr b/anneal/v1/tests/fixtures/ui_hanging_build/expected.stderr similarity index 100% rename from anneal/tests/fixtures/ui_hanging_build/expected.stderr rename to anneal/v1/tests/fixtures/ui_hanging_build/expected.stderr diff --git a/anneal/tests/fixtures/ui_hanging_build/source/Cargo.toml b/anneal/v1/tests/fixtures/ui_hanging_build/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/ui_hanging_build/source/Cargo.toml rename to anneal/v1/tests/fixtures/ui_hanging_build/source/Cargo.toml diff --git a/anneal/tests/fixtures/ui_hanging_build/source/build.rs b/anneal/v1/tests/fixtures/ui_hanging_build/source/build.rs similarity index 100% rename from anneal/tests/fixtures/ui_hanging_build/source/build.rs rename to anneal/v1/tests/fixtures/ui_hanging_build/source/build.rs diff --git a/anneal/tests/fixtures/ui_hanging_build/source/src/lib.rs b/anneal/v1/tests/fixtures/ui_hanging_build/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/ui_hanging_build/source/src/lib.rs rename to anneal/v1/tests/fixtures/ui_hanging_build/source/src/lib.rs diff --git a/anneal/tests/fixtures/ui_silent_panic/anneal.toml b/anneal/v1/tests/fixtures/ui_silent_panic/anneal.toml similarity index 100% rename from anneal/tests/fixtures/ui_silent_panic/anneal.toml rename to anneal/v1/tests/fixtures/ui_silent_panic/anneal.toml diff --git a/anneal/tests/fixtures/ui_silent_panic/expected.stderr b/anneal/v1/tests/fixtures/ui_silent_panic/expected.stderr similarity index 100% rename from anneal/tests/fixtures/ui_silent_panic/expected.stderr rename to anneal/v1/tests/fixtures/ui_silent_panic/expected.stderr diff --git a/anneal/tests/fixtures/ui_silent_panic/source/Cargo.toml b/anneal/v1/tests/fixtures/ui_silent_panic/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/ui_silent_panic/source/Cargo.toml rename to anneal/v1/tests/fixtures/ui_silent_panic/source/Cargo.toml diff --git a/anneal/tests/fixtures/ui_silent_panic/source/build.rs b/anneal/v1/tests/fixtures/ui_silent_panic/source/build.rs similarity index 100% rename from anneal/tests/fixtures/ui_silent_panic/source/build.rs rename to anneal/v1/tests/fixtures/ui_silent_panic/source/build.rs diff --git a/anneal/tests/fixtures/ui_silent_panic/source/src/lib.rs b/anneal/v1/tests/fixtures/ui_silent_panic/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/ui_silent_panic/source/src/lib.rs rename to anneal/v1/tests/fixtures/ui_silent_panic/source/src/lib.rs diff --git a/anneal/tests/fixtures/ui_the_flood/anneal.toml b/anneal/v1/tests/fixtures/ui_the_flood/anneal.toml similarity index 100% rename from anneal/tests/fixtures/ui_the_flood/anneal.toml rename to anneal/v1/tests/fixtures/ui_the_flood/anneal.toml diff --git a/anneal/tests/fixtures/ui_the_flood/expected.stderr b/anneal/v1/tests/fixtures/ui_the_flood/expected.stderr similarity index 100% rename from anneal/tests/fixtures/ui_the_flood/expected.stderr rename to anneal/v1/tests/fixtures/ui_the_flood/expected.stderr diff --git a/anneal/tests/fixtures/ui_the_flood/mock_charon_output.json b/anneal/v1/tests/fixtures/ui_the_flood/mock_charon_output.json similarity index 100% rename from anneal/tests/fixtures/ui_the_flood/mock_charon_output.json rename to anneal/v1/tests/fixtures/ui_the_flood/mock_charon_output.json diff --git a/anneal/tests/fixtures/ui_the_flood/source/Cargo.toml b/anneal/v1/tests/fixtures/ui_the_flood/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/ui_the_flood/source/Cargo.toml rename to anneal/v1/tests/fixtures/ui_the_flood/source/Cargo.toml diff --git a/anneal/tests/fixtures/ui_the_flood/source/src/lib.rs b/anneal/v1/tests/fixtures/ui_the_flood/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/ui_the_flood/source/src/lib.rs rename to anneal/v1/tests/fixtures/ui_the_flood/source/src/lib.rs diff --git a/anneal/tests/fixtures/ui_visual_ghosting/anneal.toml b/anneal/v1/tests/fixtures/ui_visual_ghosting/anneal.toml similarity index 100% rename from anneal/tests/fixtures/ui_visual_ghosting/anneal.toml rename to anneal/v1/tests/fixtures/ui_visual_ghosting/anneal.toml diff --git a/anneal/tests/fixtures/ui_visual_ghosting/expected.stderr b/anneal/v1/tests/fixtures/ui_visual_ghosting/expected.stderr similarity index 100% rename from anneal/tests/fixtures/ui_visual_ghosting/expected.stderr rename to anneal/v1/tests/fixtures/ui_visual_ghosting/expected.stderr diff --git a/anneal/tests/fixtures/ui_visual_ghosting/mock_charon_output.json b/anneal/v1/tests/fixtures/ui_visual_ghosting/mock_charon_output.json similarity index 100% rename from anneal/tests/fixtures/ui_visual_ghosting/mock_charon_output.json rename to anneal/v1/tests/fixtures/ui_visual_ghosting/mock_charon_output.json diff --git a/anneal/tests/fixtures/ui_visual_ghosting/source/Cargo.toml b/anneal/v1/tests/fixtures/ui_visual_ghosting/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/ui_visual_ghosting/source/Cargo.toml rename to anneal/v1/tests/fixtures/ui_visual_ghosting/source/Cargo.toml diff --git a/anneal/tests/fixtures/ui_visual_ghosting/source/src/lib.rs b/anneal/v1/tests/fixtures/ui_visual_ghosting/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/ui_visual_ghosting/source/src/lib.rs rename to anneal/v1/tests/fixtures/ui_visual_ghosting/source/src/lib.rs diff --git a/anneal/tests/fixtures/unions/anneal.toml b/anneal/v1/tests/fixtures/unions/anneal.toml similarity index 100% rename from anneal/tests/fixtures/unions/anneal.toml rename to anneal/v1/tests/fixtures/unions/anneal.toml diff --git a/anneal/tests/fixtures/unions/expected.stderr b/anneal/v1/tests/fixtures/unions/expected.stderr similarity index 100% rename from anneal/tests/fixtures/unions/expected.stderr rename to anneal/v1/tests/fixtures/unions/expected.stderr diff --git a/anneal/tests/fixtures/unions/source/Cargo.toml b/anneal/v1/tests/fixtures/unions/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/unions/source/Cargo.toml rename to anneal/v1/tests/fixtures/unions/source/Cargo.toml diff --git a/anneal/tests/fixtures/unions/source/src/lib.rs b/anneal/v1/tests/fixtures/unions/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/unions/source/src/lib.rs rename to anneal/v1/tests/fixtures/unions/source/src/lib.rs diff --git a/anneal/tests/fixtures/unsound_is_valid_error/anneal.toml b/anneal/v1/tests/fixtures/unsound_is_valid_error/anneal.toml similarity index 100% rename from anneal/tests/fixtures/unsound_is_valid_error/anneal.toml rename to anneal/v1/tests/fixtures/unsound_is_valid_error/anneal.toml diff --git a/anneal/tests/fixtures/unsound_is_valid_error/expected.stderr b/anneal/v1/tests/fixtures/unsound_is_valid_error/expected.stderr similarity index 100% rename from anneal/tests/fixtures/unsound_is_valid_error/expected.stderr rename to anneal/v1/tests/fixtures/unsound_is_valid_error/expected.stderr diff --git a/anneal/tests/fixtures/unsound_is_valid_error/source/Cargo.toml b/anneal/v1/tests/fixtures/unsound_is_valid_error/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/unsound_is_valid_error/source/Cargo.toml rename to anneal/v1/tests/fixtures/unsound_is_valid_error/source/Cargo.toml diff --git a/anneal/tests/fixtures/unsound_is_valid_error/source/src/lib.rs b/anneal/v1/tests/fixtures/unsound_is_valid_error/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/unsound_is_valid_error/source/src/lib.rs rename to anneal/v1/tests/fixtures/unsound_is_valid_error/source/src/lib.rs diff --git a/anneal/tests/fixtures/verify_is_valid_macro/anneal.toml b/anneal/v1/tests/fixtures/verify_is_valid_macro/anneal.toml similarity index 100% rename from anneal/tests/fixtures/verify_is_valid_macro/anneal.toml rename to anneal/v1/tests/fixtures/verify_is_valid_macro/anneal.toml diff --git a/anneal/tests/fixtures/verify_is_valid_macro/source/Cargo.toml b/anneal/v1/tests/fixtures/verify_is_valid_macro/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/verify_is_valid_macro/source/Cargo.toml rename to anneal/v1/tests/fixtures/verify_is_valid_macro/source/Cargo.toml diff --git a/anneal/tests/fixtures/verify_is_valid_macro/source/src/lib.rs b/anneal/v1/tests/fixtures/verify_is_valid_macro/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/verify_is_valid_macro/source/src/lib.rs rename to anneal/v1/tests/fixtures/verify_is_valid_macro/source/src/lib.rs diff --git a/anneal/tests/fixtures/warnings_vs_errors/anneal.toml b/anneal/v1/tests/fixtures/warnings_vs_errors/anneal.toml similarity index 100% rename from anneal/tests/fixtures/warnings_vs_errors/anneal.toml rename to anneal/v1/tests/fixtures/warnings_vs_errors/anneal.toml diff --git a/anneal/tests/fixtures/warnings_vs_errors/expected.stderr b/anneal/v1/tests/fixtures/warnings_vs_errors/expected.stderr similarity index 100% rename from anneal/tests/fixtures/warnings_vs_errors/expected.stderr rename to anneal/v1/tests/fixtures/warnings_vs_errors/expected.stderr diff --git a/anneal/tests/fixtures/warnings_vs_errors/mock_charon_output.json b/anneal/v1/tests/fixtures/warnings_vs_errors/mock_charon_output.json similarity index 100% rename from anneal/tests/fixtures/warnings_vs_errors/mock_charon_output.json rename to anneal/v1/tests/fixtures/warnings_vs_errors/mock_charon_output.json diff --git a/anneal/tests/fixtures/warnings_vs_errors/source/Cargo.toml b/anneal/v1/tests/fixtures/warnings_vs_errors/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/warnings_vs_errors/source/Cargo.toml rename to anneal/v1/tests/fixtures/warnings_vs_errors/source/Cargo.toml diff --git a/anneal/tests/fixtures/warnings_vs_errors/source/crates/utils/src/lib.rs b/anneal/v1/tests/fixtures/warnings_vs_errors/source/crates/utils/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/warnings_vs_errors/source/crates/utils/src/lib.rs rename to anneal/v1/tests/fixtures/warnings_vs_errors/source/crates/utils/src/lib.rs diff --git a/anneal/tests/fixtures/warnings_vs_errors/source/src/lib.rs b/anneal/v1/tests/fixtures/warnings_vs_errors/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/warnings_vs_errors/source/src/lib.rs rename to anneal/v1/tests/fixtures/warnings_vs_errors/source/src/lib.rs diff --git a/anneal/tests/fixtures/warnings_vs_errors/source/src/macros.rs b/anneal/v1/tests/fixtures/warnings_vs_errors/source/src/macros.rs similarity index 100% rename from anneal/tests/fixtures/warnings_vs_errors/source/src/macros.rs rename to anneal/v1/tests/fixtures/warnings_vs_errors/source/src/macros.rs diff --git a/anneal/tests/fixtures/warnings_vs_errors/source/src/rewritten.rs b/anneal/v1/tests/fixtures/warnings_vs_errors/source/src/rewritten.rs similarity index 100% rename from anneal/tests/fixtures/warnings_vs_errors/source/src/rewritten.rs rename to anneal/v1/tests/fixtures/warnings_vs_errors/source/src/rewritten.rs diff --git a/anneal/tests/fixtures/warnings_vs_errors/source/src/symlinked.rs b/anneal/v1/tests/fixtures/warnings_vs_errors/source/src/symlinked.rs similarity index 100% rename from anneal/tests/fixtures/warnings_vs_errors/source/src/symlinked.rs rename to anneal/v1/tests/fixtures/warnings_vs_errors/source/src/symlinked.rs diff --git a/anneal/tests/fixtures/weird_functions/anneal.toml b/anneal/v1/tests/fixtures/weird_functions/anneal.toml similarity index 100% rename from anneal/tests/fixtures/weird_functions/anneal.toml rename to anneal/v1/tests/fixtures/weird_functions/anneal.toml diff --git a/anneal/tests/fixtures/weird_functions/expected.stderr b/anneal/v1/tests/fixtures/weird_functions/expected.stderr similarity index 100% rename from anneal/tests/fixtures/weird_functions/expected.stderr rename to anneal/v1/tests/fixtures/weird_functions/expected.stderr diff --git a/anneal/tests/fixtures/weird_functions/source/Cargo.toml b/anneal/v1/tests/fixtures/weird_functions/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/weird_functions/source/Cargo.toml rename to anneal/v1/tests/fixtures/weird_functions/source/Cargo.toml diff --git a/anneal/tests/fixtures/weird_functions/source/src/lib.rs b/anneal/v1/tests/fixtures/weird_functions/source/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/weird_functions/source/src/lib.rs rename to anneal/v1/tests/fixtures/weird_functions/source/src/lib.rs diff --git a/anneal/tests/fixtures/workspace_path_dep/args.txt b/anneal/v1/tests/fixtures/workspace_path_dep/args.txt similarity index 100% rename from anneal/tests/fixtures/workspace_path_dep/args.txt rename to anneal/v1/tests/fixtures/workspace_path_dep/args.txt diff --git a/anneal/tests/fixtures/workspace_path_dep/source/Cargo.toml b/anneal/v1/tests/fixtures/workspace_path_dep/source/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/workspace_path_dep/source/Cargo.toml rename to anneal/v1/tests/fixtures/workspace_path_dep/source/Cargo.toml diff --git a/anneal/tests/fixtures/workspace_path_dep/source/crates/app/Cargo.toml b/anneal/v1/tests/fixtures/workspace_path_dep/source/crates/app/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/workspace_path_dep/source/crates/app/Cargo.toml rename to anneal/v1/tests/fixtures/workspace_path_dep/source/crates/app/Cargo.toml diff --git a/anneal/tests/fixtures/workspace_path_dep/source/crates/app/src/lib.rs b/anneal/v1/tests/fixtures/workspace_path_dep/source/crates/app/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/workspace_path_dep/source/crates/app/src/lib.rs rename to anneal/v1/tests/fixtures/workspace_path_dep/source/crates/app/src/lib.rs diff --git a/anneal/tests/fixtures/workspace_path_dep/source/crates/utils/Cargo.toml b/anneal/v1/tests/fixtures/workspace_path_dep/source/crates/utils/Cargo.toml similarity index 100% rename from anneal/tests/fixtures/workspace_path_dep/source/crates/utils/Cargo.toml rename to anneal/v1/tests/fixtures/workspace_path_dep/source/crates/utils/Cargo.toml diff --git a/anneal/tests/fixtures/workspace_path_dep/source/crates/utils/src/lib.rs b/anneal/v1/tests/fixtures/workspace_path_dep/source/crates/utils/src/lib.rs similarity index 100% rename from anneal/tests/fixtures/workspace_path_dep/source/crates/utils/src/lib.rs rename to anneal/v1/tests/fixtures/workspace_path_dep/source/crates/utils/src/lib.rs diff --git a/anneal/tests/integration.rs b/anneal/v1/tests/integration.rs similarity index 100% rename from anneal/tests/integration.rs rename to anneal/v1/tests/integration.rs diff --git a/anneal/tests/ui.rs b/anneal/v1/tests/ui.rs similarity index 100% rename from anneal/tests/ui.rs rename to anneal/v1/tests/ui.rs diff --git a/anneal/tests/ui/fail_precondition.rs b/anneal/v1/tests/ui/fail_precondition.rs similarity index 100% rename from anneal/tests/ui/fail_precondition.rs rename to anneal/v1/tests/ui/fail_precondition.rs diff --git a/anneal/tests/ui/fail_precondition.stderr b/anneal/v1/tests/ui/fail_precondition.stderr similarity index 100% rename from anneal/tests/ui/fail_precondition.stderr rename to anneal/v1/tests/ui/fail_precondition.stderr diff --git a/anneal/tests/ui/fail_syntax.rs b/anneal/v1/tests/ui/fail_syntax.rs similarity index 100% rename from anneal/tests/ui/fail_syntax.rs rename to anneal/v1/tests/ui/fail_syntax.rs diff --git a/anneal/tests/ui/fail_syntax.stderr b/anneal/v1/tests/ui/fail_syntax.stderr similarity index 100% rename from anneal/tests/ui/fail_syntax.stderr rename to anneal/v1/tests/ui/fail_syntax.stderr diff --git a/anneal/tests/ui/pass_simple.rs b/anneal/v1/tests/ui/pass_simple.rs similarity index 100% rename from anneal/tests/ui/pass_simple.rs rename to anneal/v1/tests/ui/pass_simple.rs diff --git a/anneal/tools/check-release-flow-dry-run.sh b/anneal/v1/tools/check-release-flow-dry-run.sh similarity index 83% rename from anneal/tools/check-release-flow-dry-run.sh rename to anneal/v1/tools/check-release-flow-dry-run.sh index bab19fee67..5a26e47b10 100644 --- a/anneal/tools/check-release-flow-dry-run.sh +++ b/anneal/v1/tools/check-release-flow-dry-run.sh @@ -28,13 +28,13 @@ cd "$WORKTREE" ./ci/release_anneal_version.sh "$VERSION" -python3 anneal/tools/check-release-pr-files.py \ +python3 anneal/v1/tools/check-release-pr-files.py \ --context "Release dry-run version bump" \ --include-untracked \ - --allowed anneal/Cargo.lock \ - --allowed anneal/Cargo.toml \ - --allowed anneal/README.md \ - --required anneal/Cargo.toml + --allowed anneal/v1/Cargo.lock \ + --allowed anneal/v1/Cargo.toml \ + --allowed anneal/v1/README.md \ + --required anneal/v1/Cargo.toml git diff --binary > "$PATCH" if [ ! -s "$PATCH" ]; then @@ -47,15 +47,15 @@ git clean -fdx >/dev/null git apply --check "$PATCH" git apply "$PATCH" -python3 anneal/tools/check-release-pr-files.py \ +python3 anneal/v1/tools/check-release-pr-files.py \ --context "Release dry-run applied source patch" \ --include-untracked \ - --allowed anneal/Cargo.lock \ - --allowed anneal/Cargo.toml \ - --allowed anneal/README.md \ - --required anneal/Cargo.toml + --allowed anneal/v1/Cargo.lock \ + --allowed anneal/v1/Cargo.toml \ + --allowed anneal/v1/README.md \ + --required anneal/v1/Cargo.toml -mkdir -p anneal/release-metadata +mkdir -p anneal/v1/release-metadata for target in linux-x86_64 linux-aarch64 macos-x86_64 macos-aarch64; do case "$target" in linux-x86_64) @@ -82,7 +82,7 @@ for target in linux-x86_64 linux-aarch64 macos-x86_64 macos-aarch64; do sha256="$(python3 -c 'import hashlib, sys; print(hashlib.sha256(sys.argv[1].encode()).hexdigest())' "$target")" url="https://github.com/google/zerocopy/releases/download/${TAG_NAME}/anneal-toolchain-${target}.tar.zst" - cat > "anneal/release-metadata/${target}.json" < "anneal/v1/release-metadata/${target}.json" < None: - status = " M anneal/Cargo.toml\n?? anneal/release-metadata/linux.json\nR old/path -> anneal/README.md\n" + status = " M anneal/v1/Cargo.toml\n?? anneal/v1/release-metadata/linux.json\nR old/path -> anneal/v1/README.md\n" self.assertEqual( check_release_pr_files.parse_porcelain_status_paths(status), - ["anneal/Cargo.toml", "anneal/release-metadata/linux.json", "old/path", "anneal/README.md"], + ["anneal/v1/Cargo.toml", "anneal/v1/release-metadata/linux.json", "old/path", "anneal/v1/README.md"], ) def test_validation_catches_unexpected_and_missing_files(self) -> None: errors = check_release_pr_files.validation_errors( - ["anneal/Cargo.toml", "anneal/release-metadata/linux.json"], - ["anneal/Cargo.toml", "anneal/Cargo.lock", "anneal/README.md"], - ["anneal/Cargo.toml", "anneal/README.md"], + ["anneal/v1/Cargo.toml", "anneal/v1/release-metadata/linux.json"], + ["anneal/v1/Cargo.toml", "anneal/v1/Cargo.lock", "anneal/v1/README.md"], + ["anneal/v1/Cargo.toml", "anneal/v1/README.md"], ) self.assertEqual(len(errors), 2) - self.assertIn("anneal/release-metadata/linux.json", errors[0]) - self.assertIn("anneal/README.md", errors[1]) + self.assertIn("anneal/v1/release-metadata/linux.json", errors[0]) + self.assertIn("anneal/v1/README.md", errors[1]) if __name__ == "__main__": diff --git a/anneal/tools/update-exocrate-metadata.py b/anneal/v1/tools/update-exocrate-metadata.py similarity index 97% rename from anneal/tools/update-exocrate-metadata.py rename to anneal/v1/tools/update-exocrate-metadata.py index f8e2fc4516..96c4cdde80 100755 --- a/anneal/tools/update-exocrate-metadata.py +++ b/anneal/v1/tools/update-exocrate-metadata.py @@ -8,7 +8,7 @@ # This file may not be copied, modified, or distributed except according to # those terms. -"""Update anneal/Cargo.toml's exocrate archive URLs and hashes.""" +"""Update anneal/v1/Cargo.toml's exocrate archive URLs and hashes.""" import argparse import json @@ -124,7 +124,7 @@ def update_manifest(cargo_toml: Path, metadata: dict[tuple[str, str], dict[str, def main() -> None: parser = argparse.ArgumentParser() - parser.add_argument("--cargo-toml", default="anneal/Cargo.toml", type=Path) + parser.add_argument("--cargo-toml", default="anneal/v1/Cargo.toml", type=Path) parser.add_argument("--metadata-dir", type=Path) parser.add_argument("--metadata", action="append", default=[], type=Path) parser.add_argument("--expected-release-tag") diff --git a/anneal/v2/Cargo.toml b/anneal/v2/Cargo.toml deleted file mode 100644 index e91b731c5c..0000000000 --- a/anneal/v2/Cargo.toml +++ /dev/null @@ -1,69 +0,0 @@ -[workspace] -members = ["."] - -[features] -# Enables tests that assume a prebuilt exocrate archive. -exocrate_tests = [] - -[package] -name = "cargo-anneal" -edition = "2024" -version = "0.1.0-alpha.22" -description = "Formally verify that your safety comments are correct." -categories = [ - "development-tools::cargo-plugins", - "development-tools::testing", - "compilers", - "mathematics", - "security", -] -keywords = ["verification", "cargo", "plugin", "unsafe", "lean"] -license = "BSD-2-Clause OR Apache-2.0 OR MIT" -repository = "https://github.com/google/zerocopy/tree/main/anneal" -publish = true - -exclude = [".*", "testdata"] - -# FIXME: Replace these placeholder archive URLs and hashes before publishing -# this crate; `cargo anneal setup` uses this metadata by default when callers -# do not provide a local archive. -[package.metadata.exocrate.linux.x86_64] -sha256 = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" -url = "https://example.com/linux-x86_64.tar.zst" - -[package.metadata.exocrate.macos.x86_64] -sha256 = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" -url = "https://example.com/macos-x86_64.tar.zst" - -[package.metadata.exocrate.linux.aarch64] -sha256 = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" -url = "https://example.com/linux-aarch64.tar.zst" - -[package.metadata.exocrate.macos.aarch64] -sha256 = "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" -url = "https://example.com/macos-aarch64.tar.zst" - -[dependencies] -exocrate = { path = "../../exocrate" } -toml_const = "1.3.0" -clap = { version = "4.5", features = ["derive"] } -clap-cargo = { version = "0.18.3", features = ["cargo_metadata"] } -env_logger = "0.11" -log = "0.4" -anyhow = "1.0.102" -cargo_metadata = "0.23.1" -miette = { version = "7.6.0", features = ["derive", "fancy"] } -thiserror = "2.0.18" -serde_json = "1.0.149" -serde = { version = "1.0.228", features = ["derive"] } -indicatif = { version = "0.18.4", features = ["improved_unicode"] } -fs2 = "0.4" -walkdir = "2.5.0" -sha2 = "0.10" -tempfile = "3.27.0" -rayon = "1.11.0" -# `tag` is `main` branch HEAD on 2026-06-03. -charon_lib = { package = "charon", git = "https://github.com/AeneasVerif/charon.git", tag = "nightly-2026.06.03", default-features = false } - -[dev-dependencies] -pathdiff = "0.2" diff --git a/anneal/v2/src/main.rs b/anneal/v2/src/main.rs deleted file mode 100644 index b947700606..0000000000 --- a/anneal/v2/src/main.rs +++ /dev/null @@ -1,342 +0,0 @@ -// Copyright 2026 The Fuchsia Authors -// -// Licensed under the 2-Clause BSD License , Apache License, Version 2.0 -// , or the MIT -// license , at your option. -// This file may not be copied, modified, or distributed except according to -// those terms. - -use clap::Parser as _; - -#[allow(dead_code)] -mod resolve; -#[allow(dead_code)] -mod scanner; -#[allow(dead_code)] -mod setup; -#[allow(dead_code)] -mod util; - -/// Anneal -#[derive(clap::Parser, Debug)] -#[command(name = "cargo-anneal", version, about, long_about = None)] -struct Cli { - #[command(subcommand)] - command: Commands, -} - -#[derive(clap::Subcommand, Debug)] -enum Commands { - /// Setup Anneal dependencies - Setup(SetupArgs), -} - -#[derive(clap::Parser, Debug)] -pub struct SetupArgs { - /// Path to a local dependency archive to use instead of downloading. - #[arg(long, value_name = "path-to-local-archive")] - pub local_archive: Option, -} - -exocrate::config! { - const CONFIG: Config = Config { - rel_dir_path: [".anneal", "toolchain"], - versioned_files: &["../Cargo.toml", "../Cargo.lock"], - }; -} - -exocrate::parse_remote_archive! { - const REMOTE: RemoteArchive = "Cargo.toml" [ - (linux, x86_64), - (macos, x86_64), - (linux, aarch64), - (macos, aarch64), - ]; -} - -fn setup_installation_dir(args: SetupArgs) -> std::path::PathBuf { - let location = if std::env::var("__ANNEAL_LOCAL_DEV").is_ok() { - exocrate::Location::LocalDev - } else { - exocrate::Location::UserGlobal - }; - let source = match args.local_archive { - Some(local_archive) => exocrate::Source::Local(local_archive), - None => exocrate::Source::Remote(REMOTE), - }; - - let (installation_dir, _) = CONFIG - .resolve_installation_dir_or_install(location, source) - // FIXME: Implement unified error reporting (e.g., via `anyhow`). - .expect("failed to resolve-or-install dependencies"); - installation_dir -} - -fn setup(args: SetupArgs) { - let installation_dir = setup_installation_dir(args); - log::info!("anneal toolchain is installed at {:?}", installation_dir); -} - -fn main() { - // Suppressing timestamps removes a source of nondeterminism that is - // difficult to work around in integration tests. - env_logger::builder().format_timestamp(None).init(); - - let mut args_iter = std::env::args_os().peekable(); - let bin_name = args_iter.next().unwrap_or_else(|| "cargo-anneal".into()); - // If we're being run as a cargo plugin, the second argument will be "anneal". - if args_iter.peek().is_some_and(|arg| arg == "anneal") { - args_iter.next(); - } - let args = Cli::parse_from(std::iter::once(bin_name).chain(args_iter)); - - match args.command { - Commands::Setup(args) => setup(args), - } -} - -#[cfg(test)] -mod tests { - #[cfg(feature = "exocrate_tests")] - mod exocrate_tests { - use std::{ - fs, io, - path::{Path, PathBuf}, - process::Command, - sync::OnceLock, - }; - - use serde_json::{Value, json}; - - const LOCAL_ARCHIVE: &str = "target/anneal-exocrate.tar.zst"; - static INSTALLATION_DIR: OnceLock = OnceLock::new(); - - #[test] - fn test_setup() { - install_local_archive(); - } - - #[test] - fn test_archive_lake_cache_reuse() { - let installation_dir = install_local_archive(); - let temp = tempfile::Builder::new() - .prefix("anneal-v2-archive-cache-reuse-") - .tempdir() - .expect("failed to create archive cache reuse tempdir"); - assert_archive_lake_cache_reuse(&installation_dir, temp.path()) - .expect("archive Lake cache reuse test failed"); - } - - fn install_local_archive() -> PathBuf { - // ASSUMPTION: The CI dependency builder downloads the Nix-built - // archive artifact to this path before running v2 tests. - INSTALLATION_DIR - .get_or_init(|| { - super::super::setup_installation_dir(super::super::SetupArgs { - local_archive: Some(LOCAL_ARCHIVE.into()), - }) - }) - .clone() - } - - fn assert_archive_lake_cache_reuse( - toolchain_root: &Path, - temp_root: &Path, - ) -> Result<(), Box> { - let aeneas_root = toolchain_root.join("aeneas"); - let aeneas_lean = aeneas_root.join("backends/lean"); - let lean_root = toolchain_root.join("lean"); - let workspace = temp_root.join("generated-workspace"); - - assert_no_write_bits(&aeneas_root)?; - - fs::create_dir_all(workspace.join("generated"))?; - fs::copy(aeneas_lean.join("lean-toolchain"), workspace.join("lean-toolchain"))?; - fs::write(workspace.join("generated/Generated.lean"), "import Aeneas\n")?; - fs::write( - workspace.join("lakefile.lean"), - format!( - r#"import Lake -open Lake DSL - -require aeneas from "{}" - -package anneal_verification - -@[default_target] -lean_lib Generated where - srcDir := "generated" - roots := #[`Generated] -"#, - lake_string(&aeneas_lean) - ), - )?; - write_relative_archive_manifest(&workspace, &aeneas_lean)?; - - // The Nix archive must support fresh generated workspaces without - // reconfiguring packages or rebuilding read-only Lake artifacts. - run_lake_archive_command( - &workspace, - &lean_root, - &["--keep-toolchain", "--old", "build", "Generated"], - )?; - run_lake_archive_command( - &workspace, - &lean_root, - &["--keep-toolchain", "env", "lean", "--json", "generated/Generated.lean"], - )?; - - Ok(()) - } - - fn assert_no_write_bits(root: &Path) -> Result<(), Box> { - let metadata = fs::symlink_metadata(root)?; - if metadata.file_type().is_symlink() { - return Ok(()); - } - if has_write_bits(&metadata.permissions()) { - panic!("archive path should be read-only: {}", root.display()); - } - if metadata.is_dir() { - for entry in fs::read_dir(root)? { - assert_no_write_bits(&entry?.path())?; - } - } - Ok(()) - } - - #[cfg(unix)] - fn has_write_bits(permissions: &fs::Permissions) -> bool { - use std::os::unix::fs::PermissionsExt as _; - permissions.mode() & 0o222 != 0 - } - - #[cfg(not(unix))] - fn has_write_bits(permissions: &fs::Permissions) -> bool { - !permissions.readonly() - } - - fn write_relative_archive_manifest( - workspace: &Path, - aeneas_lean: &Path, - ) -> Result<(), Box> { - let aeneas_lean = fs::canonicalize(aeneas_lean)?; - let workspace = fs::canonicalize(workspace)?; - let manifest_path = aeneas_lean.join("lake-manifest.json"); - let manifest: Value = serde_json::from_reader(fs::File::open(&manifest_path)?)?; - let aeneas_packages = - manifest.get("packages").and_then(Value::as_array).ok_or_else(|| { - invalid_data(format!( - "Aeneas Lake manifest {} is missing packages", - manifest_path.display() - )) - })?; - - let aeneas_dir = relative_manifest_string(&aeneas_lean, &workspace)?; - let mut packages = vec![json!({ - "type": "path", - "name": "aeneas", - "dir": aeneas_dir, - "inherited": false, - })]; - - for entry in aeneas_packages { - let mut entry = entry.as_object().cloned().ok_or_else(|| { - invalid_data("Aeneas Lake manifest package entry is not an object") - })?; - let package_type = entry.get("type").and_then(Value::as_str).ok_or_else(|| { - invalid_data("Aeneas Lake manifest package entry is missing type") - })?; - if package_type != "path" { - return Err(invalid_data(format!( - "Aeneas Lake manifest package entry is {package_type:?}, not a path dependency" - )) - .into()); - } - let package_dir = entry.get("dir").and_then(Value::as_str).ok_or_else(|| { - invalid_data("Aeneas Lake manifest package entry is missing dir") - })?; - let package_dir = Path::new(package_dir); - let package_dir = if package_dir.is_absolute() { - package_dir.to_path_buf() - } else { - aeneas_lean.join(package_dir) - }; - let package_dir = fs::canonicalize(package_dir)?; - entry.insert( - "dir".to_string(), - json!(relative_manifest_string(&package_dir, &workspace)?), - ); - entry.insert("inherited".to_string(), json!(true)); - packages.push(Value::Object(entry)); - } - - let manifest = json!({ - "version": "1.2.0", - "packagesDir": ".lake/packages", - "packages": packages, - "name": "anneal_verification", - "lakeDir": ".lake", - "fixedToolchain": false, - }); - fs::write( - workspace.join("lake-manifest.json"), - format!("{}\n", serde_json::to_string_pretty(&manifest)?), - )?; - Ok(()) - } - - fn relative_manifest_string( - path: &Path, - base: &Path, - ) -> Result> { - let path = pathdiff::diff_paths(path, base).ok_or_else(|| { - invalid_data(format!( - "failed to compute relative path from {} to {}", - base.display(), - path.display() - )) - })?; - Ok(path.to_string_lossy().into_owned()) - } - - fn lake_string(path: &Path) -> String { - path.to_string_lossy().replace('\\', "\\\\").replace('"', "\\\"") - } - - fn run_lake_archive_command( - workspace: &Path, - lean_root: &Path, - args: &[&str], - ) -> Result<(), Box> { - let lean_bin = lean_root.join("bin"); - let mut cmd = Command::new(lean_bin.join("lake")); - cmd.args(args).current_dir(workspace).env_clear(); - - let lib_var = - if cfg!(target_os = "macos") { "DYLD_LIBRARY_PATH" } else { "LD_LIBRARY_PATH" }; - cmd.env( - lib_var, - std::env::join_paths([lean_root.join("lib"), lean_root.join("lib/lean")])?, - ); - - let output = cmd.output()?; - if !output.status.success() { - return Err(io::Error::other(format!( - "lake {:?} failed with status {}\nstdout:\n{}\nstderr:\n{}", - args, - output.status, - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - )) - .into()); - } - Ok(()) - } - - fn invalid_data(message: impl Into) -> io::Error { - io::Error::new(io::ErrorKind::InvalidData, message.into()) - } - } -} diff --git a/anneal/v2/src/scanner.rs b/anneal/v2/src/scanner.rs deleted file mode 100644 index 7b4884c9ac..0000000000 --- a/anneal/v2/src/scanner.rs +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright 2026 The Fuchsia Authors -// -// Licensed under the 2-Clause BSD License , Apache License, Version 2.0 -// , or the MIT -// license , at your option. -// This file may not be copied, modified, or distributed except according to -// those terms. - -use sha2::Digest as _; - -/// Represents a compilation target (artifact) that needs to be processed. -/// -/// Charon compiles the entire target to generate LLBC files. The generated -/// LLBC is the source of truth for processing Anneal annotations that affect -/// Aeneas code generation. -#[derive(Clone, Debug)] -pub struct AnnealArtifact { - pub name: crate::resolve::AnnealTargetName, - pub target_kind: crate::resolve::AnnealTargetKind, - /// The path to the crate's `Cargo.toml`. - pub manifest_path: std::path::PathBuf, -} - -impl From<&crate::resolve::AnnealTarget> for AnnealArtifact { - fn from(target: &crate::resolve::AnnealTarget) -> Self { - Self { - name: target.name.clone(), - target_kind: target.kind, - manifest_path: target.manifest_path.clone(), - } - } -} - -impl AnnealArtifact { - /// Returns a unique, Lean-compatible artifact slug. - /// - /// Charon uses the slug as the file stem for the target's emitted LLBC - /// file. Later Aeneas/Lean stages can reuse the same Lean-compatible stem - /// when associating generated Lean code with this artifact. The slug is - /// guaranteed to be a valid Lean identifier (no hyphens), and is unique - /// based on the manifest path, target name, and target kind. - pub fn artifact_slug(&self) -> String { - fn hash(data: &[u8]) -> u64 { - // Use SHA-256 not for security but rather stability; Rust's - // `DefaultHasher` doesn't guarantee stability even across runs of - // the same binary. - let mut hasher = sha2::Sha256::new(); - hasher.update(data); - let result = hasher.finalize(); - let mut bytes = [0u8; 8]; - bytes.copy_from_slice(&result[0..8]); - u64::from_le_bytes(bytes) - } - - // Compute `hash([hash(manifest_path), hash(target_name), hash(target_kind)])` to - // distinguish between e.g. (manifest_path, target_name) = ("abc", "def") and - // ("ab", "cdef"), which would hash identically if we just hashed their - // concatenation. - let h0 = hash(self.manifest_path.as_os_str().as_encoded_bytes()); - let h1 = hash(self.name.target_name.as_bytes()); - let h2 = hash(&[self.target_kind as u8]); - let hashes = [h0, h1, h2]; - let h = hash(&hashes.map(u64::to_ne_bytes).concat()); - - // Converts kebab-case -> PascalCase. - // We convert both package and target names to PascalCase to ensure - // the generated Lean module name is a valid and idiomatic Lean - // identifier, matching Aeneas's output format. - let to_pascal = |s: &str| { - s.split(['-', '_']) - .map(|segment| { - let mut chars = segment.chars(); - match chars.next() { - None => String::new(), - Some(f) => f.to_uppercase().collect::() + chars.as_str(), - } - }) - .collect::() - }; - - let pkg = to_pascal(self.name.package_name.as_str()); - let target = to_pascal(&self.name.target_name); - - // We use the hash to ensure uniqueness even if our prior `to_pascal` - // mapping caused a collision. - format!("{}{}{:08x}", pkg, target, h) - } - - /// Returns the name of the `.llbc` file to use for this artifact. - pub fn llbc_file_name(&self) -> String { - format!("{}.llbc", self.artifact_slug()) - } - - /// Returns the absolute path to the .llbc file. - /// - /// This method requires [`crate::resolve::LockedRoots`] to ensure that the caller holds the - /// build lock before accessing the build artifact path. - pub fn llbc_path(&self, roots: &crate::resolve::LockedRoots) -> std::path::PathBuf { - roots.llbc_root().join(self.llbc_file_name()) - } -} diff --git a/anneal/v2/src/setup.rs b/anneal/v2/src/setup.rs deleted file mode 100644 index 9e17911f4d..0000000000 --- a/anneal/v2/src/setup.rs +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright 2026 The Fuchsia Authors -// -// Licensed under the 2-Clause BSD License , Apache License, Version 2.0 -// , or the MIT -// license , at your option. -// This file may not be copied, modified, or distributed except according to -// those terms. - -use anyhow::Context as _; - -pub struct SetupArgs { - pub local_archive: Option, -} - -exocrate::config! { - pub const CONFIG: Config = Config { - rel_dir_path: [".anneal", "toolchain"], - versioned_files: &["../Cargo.toml", "../Cargo.lock"], - }; -} - -exocrate::parse_remote_archive! { - pub const REMOTE: RemoteArchive = "Cargo.toml" [ - (linux, x86_64), - (macos, x86_64), - (linux, aarch64), - (macos, aarch64), - ]; -} - -pub enum Tool { - Cargo, - Charon, - Rustc, -} - -impl Tool { - pub fn name(&self) -> &'static str { - match self { - Self::Cargo => "cargo", - Self::Charon => "charon", - Self::Rustc => "rustc", - } - } - - pub fn path(&self, toolchain: &Toolchain) -> std::path::PathBuf { - match self { - Self::Cargo | Self::Rustc => toolchain.rust_bin().join(self.name()), - Self::Charon => toolchain.aeneas_bin_dir().join(self.name()), - } - } -} - -const AENEAS_DIR: &str = "aeneas"; -const RUST_SYSROOT: &str = "rust"; -const AENEAS_BIN_DIR: &str = "bin"; -const RUST_BIN_DIR: &str = "bin"; -const RUST_LIB_DIR: &str = "lib"; - -pub struct Toolchain { - root: std::path::PathBuf, -} - -impl Toolchain { - pub fn resolve() -> anyhow::Result { - let location = resolve_location(); - let root = CONFIG - .resolve_installation_dir(location) - .context("Toolchain not installed. Please run 'cargo anneal setup' first.")?; - Ok(Self { root }) - } - - #[cfg(all(test, feature = "exocrate_tests"))] - pub fn root(&self) -> &std::path::Path { - &self.root - } - - pub fn aeneas_bin_dir(&self) -> std::path::PathBuf { - self.root.join(AENEAS_DIR).join(AENEAS_BIN_DIR) - } - - pub fn rust_sysroot(&self) -> std::path::PathBuf { - self.root.join(RUST_SYSROOT) - } - - pub fn rust_bin(&self) -> std::path::PathBuf { - self.rust_sysroot().join(RUST_BIN_DIR) - } - - pub fn rust_lib(&self) -> std::path::PathBuf { - self.rust_sysroot().join(RUST_LIB_DIR) - } - - pub fn command(&self, tool: Tool) -> anyhow::Result { - let mut cmd = std::process::Command::new(tool.path(self)); - cmd.env_clear(); - match tool { - Tool::Cargo | Tool::Rustc => {} - Tool::Charon => { - // The archive supplies Rust tools, but not host build tools - // such as the linker. Keep the caller's `PATH` after our Rust - // bin directory so Cargo builds use the managed Rust toolchain - // while still finding those host tools. - cmd.env("CHARON_TOOLCHAIN_IS_IN_PATH", "1") - .env("PATH", prepend_current_path(self.rust_bin())?) - .env(rust_library_path_env_var(), self.rust_lib()); - } - } - Ok(cmd) - } -} - -fn prepend_current_path(path: std::path::PathBuf) -> anyhow::Result { - let mut paths = vec![path]; - if let Some(current_path) = std::env::var_os("PATH") { - paths.extend(std::env::split_paths(¤t_path)); - } - std::env::join_paths(paths).context("failed to construct PATH for tool command") -} - -/// Returns the platform library search path variable used by Rust tools. -pub(crate) fn rust_library_path_env_var() -> &'static str { - if cfg!(target_os = "macos") { "DYLD_LIBRARY_PATH" } else { "LD_LIBRARY_PATH" } -} - -pub fn run_setup(args: SetupArgs) -> anyhow::Result<()> { - let location = resolve_location(); - let source = match args.local_archive { - Some(local_archive) => exocrate::Source::Local(local_archive), - None => exocrate::Source::Remote(REMOTE), - }; - - let (installation_dir, status) = CONFIG - .resolve_installation_dir_or_install(location, source) - .context("failed to resolve-or-install dependencies")?; - match status { - exocrate::ResolvedOrInstalled::ResolvedExisting => { - log::warn!("anneal toolchain was already installed at {:?}", installation_dir); - } - exocrate::ResolvedOrInstalled::NewlyInstalled => { - log::info!("anneal toolchain freshly installed at {:?}", installation_dir); - } - } - Ok(()) -} - -fn resolve_location() -> exocrate::Location { - if std::env::var("__ANNEAL_LOCAL_DEV").is_ok() { - exocrate::Location::LocalDev - } else { - exocrate::Location::UserGlobal - } -} diff --git a/anneal/v2/src/util.rs b/anneal/v2/src/util.rs deleted file mode 100644 index 602e1585d1..0000000000 --- a/anneal/v2/src/util.rs +++ /dev/null @@ -1,283 +0,0 @@ -// Copyright 2026 The Fuchsia Authors -// -// Licensed under the 2-Clause BSD License , Apache License, Version 2.0 -// , or the MIT -// license , at your option. -// This file may not be copied, modified, or distributed except according to -// those terms. - -use std::io::BufRead as _; - -use anyhow::Context as _; -use fs2::FileExt as _; - -/// Represents an active, exclusive lock on a directory. -/// -/// This struct guarantees that the process holds an OS-level file lock -/// guarding the specified directory. -pub(crate) struct DirLock { - /// The path to the directory being guarded. - pub(crate) path: std::path::PathBuf, - // Kept alive to hold the flock. - _file: std::fs::File, -} - -impl DirLock { - /// Acquires an exclusive lock on the specified directory. - /// - /// This function blocks until the lock can be acquired. We use a - /// separate `.lock` file within the directory rather than locking - /// the directory itself to avoid platform-specific issues with - /// directory locking and to ensure the lock file persists even if - /// the directory is cleaned. - pub(crate) fn lock_exclusive(path: std::path::PathBuf) -> anyhow::Result { - let file = Self::open_lock_file(&path)?; - file.lock_exclusive() - .with_context(|| format!("Failed to acquire exclusive lock on {:?}", path))?; - Ok(Self { path, _file: file }) - } - - /// Acquires a shared lock on the specified directory. - /// - /// Multiple processes can hold shared locks simultaneously, but an - /// exclusive lock will block until all shared locks are released. - #[cfg(any(test, feature = "exocrate_tests"))] - pub(crate) fn lock_shared(path: std::path::PathBuf) -> anyhow::Result { - let file = Self::open_lock_file(&path)?; - file.lock_shared() - .with_context(|| format!("Failed to acquire shared lock on {:?}", path))?; - Ok(Self { path, _file: file }) - } - - fn open_lock_file(path: &std::path::Path) -> anyhow::Result { - let lock_path = path.join(".lock"); - - // Ensure the directory exists. - if let Some(parent) = lock_path.parent() { - std::fs::create_dir_all(parent).with_context(|| { - format!("Failed to create directory for lock file: {:?}", parent) - })?; - } - // If the lock file already exists, we open it in read-only mode. - // This prevents failures if the file is read-only (e.g., after - // making the toolchain directory read-only), while still allowing - // us to acquire shared and exclusive locks on the file descriptor. - if lock_path.exists() { - std::fs::OpenOptions::new() - .read(true) - .open(&lock_path) - .with_context(|| format!("Failed to open lock file at {:?}", lock_path)) - } else { - std::fs::OpenOptions::new() - .read(true) - .write(true) - .create(true) - .open(&lock_path) - .with_context(|| format!("Failed to create lock file at {:?}", lock_path)) - } - } -} - -pub(crate) struct ProcessOutput { - pub status: std::process::ExitStatus, - pub stderr_lines: Vec, -} - -/// Spawns a child process, drains its stderr in a background thread, and processes -/// its stdout line-by-line in the main thread while showing a progress spinner. -pub(crate) fn run_command_with_progress( - mut cmd: std::process::Command, - pb: Option, - mut process_stdout_line: F, -) -> anyhow::Result -where - F: FnMut(&str, Option<&indicatif::ProgressBar>) -> anyhow::Result<()>, -{ - cmd.stdout(std::process::Stdio::piped()); - cmd.stderr(std::process::Stdio::piped()); - - let mut child = cmd.spawn().context("Failed to spawn child process")?; - - let stderr_buffer = std::sync::Arc::new(std::sync::Mutex::new(Vec::new())); - let stderr_buffer_clone = std::sync::Arc::clone(&stderr_buffer); - - let mut stderr_thread = None; - if let Some(stderr) = child.stderr.take() { - stderr_thread = Some(std::thread::spawn(move || { - let reader = std::io::BufReader::new(stderr); - for line in reader.lines().map_while(Result::ok) { - stderr_buffer_clone.lock().unwrap().push(line); - } - })); - } - - if let Some(ref p) = pb { - p.enable_steady_tick(std::time::Duration::from_millis(100)); - } - - if let Some(stdout) = child.stdout.take() { - let reader = std::io::BufReader::new(stdout); - for line in reader.lines().map_while(Result::ok) { - process_stdout_line(&line, pb.as_ref())?; - if let Some(ref p) = pb { - p.tick(); - } - } - } - - if let Some(ref p) = pb { - p.finish_and_clear(); - } - - let status = child.wait().context("Failed to wait for child process")?; - - if let Some(thread) = stderr_thread { - let _ = thread.join(); - } - - let stderr_lines = std::sync::Arc::try_unwrap(stderr_buffer).unwrap().into_inner().unwrap(); - - Ok(ProcessOutput { status, stderr_lines }) -} - -/// Performs a lock test action according to the `role` of the current actor. Actors may: -/// -/// - Obtain an exclusive or shared lock for `lock_dir`, -/// - Log actions in `log_file`, -/// - Wait for a signal from `sig_file`. -/// -/// Individual tests compose multiple role-based actions and verify the resulting action log. -#[cfg(feature = "exocrate_tests")] -pub(crate) fn run_test_lock_helper( - role: &str, - lock_dir: &std::path::Path, - log_file: &std::path::Path, - sig_file: &std::path::Path, -) -> anyhow::Result<()> { - use std::io::Write as _; - - let append_log = |msg: &str| -> anyhow::Result<()> { - let mut file = std::fs::OpenOptions::new().create(true).append(true).open(log_file)?; - writeln!(file, "{}", msg)?; - Ok(()) - }; - - let wait_for_sig = || -> anyhow::Result<()> { - let start = std::time::Instant::now(); - while !sig_file.exists() { - if start.elapsed() > std::time::Duration::from_secs(3) { - anyhow::bail!("Timeout waiting for signal file {:?}", sig_file); - } - std::thread::sleep(std::time::Duration::from_millis(50)); - } - Ok(()) - }; - - match role { - "reader-a" => { - let _lock = DirLock::lock_shared(lock_dir.to_path_buf())?; - append_log("SHARED_START_A")?; - wait_for_sig()?; - append_log("SHARED_END_A")?; - } - "reader-b" => { - let _lock = DirLock::lock_shared(lock_dir.to_path_buf())?; - append_log("SHARED_START_B")?; - std::fs::write(sig_file, "")?; - append_log("SHARED_END_B")?; - } - "writer-a" => { - let _lock = DirLock::lock_exclusive(lock_dir.to_path_buf())?; - append_log("EXCLUSIVE_START_A")?; - wait_for_sig()?; - append_log("EXCLUSIVE_END_A")?; - } - "reader-exclusion" => { - std::fs::write(sig_file, "")?; - let _lock = DirLock::lock_shared(lock_dir.to_path_buf())?; - append_log("SHARED_START_B")?; - append_log("SHARED_END_B")?; - } - _ => anyhow::bail!("Unknown test-lock-helper role: {}", role), - } - - Ok(()) -} - -#[cfg(test)] -#[macro_export] -macro_rules! workspace_fixture { - ($dir:expr, { $($path:expr => $content:expr),* $(,)? }) => {{ - let root = $dir.path(); - $( - let file_path = root.join($path); - if let Some(parent) = file_path.parent() { - std::fs::create_dir_all(parent).unwrap(); - } - std::fs::write(&file_path, $content).unwrap(); - )* - }}; -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_dir_lock_exclusive_mutual_exclusion() { - let temp_dir = tempfile::tempdir().unwrap(); - let lock_path = temp_dir.path().to_path_buf(); - - let barrier = std::sync::Arc::new(std::sync::Barrier::new(2)); - let barrier_clone = std::sync::Arc::clone(&barrier); - let lock_path_clone = lock_path.clone(); - - let lock_released = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)); - let lock_released_clone = std::sync::Arc::clone(&lock_released); - - // Thread A acquires the lock. - let thread_a = std::thread::spawn(move || { - let _lock = DirLock::lock_exclusive(lock_path_clone).expect("Failed to lock exclusive"); - barrier_clone.wait(); // Signal Thread B that A holds the lock. - - // Simulate brief work holding the lock. - std::thread::sleep(std::time::Duration::from_millis(100)); - lock_released_clone.store(true, std::sync::atomic::Ordering::Relaxed); - // _lock drops here, releasing the lock. - }); - - // Thread B waits for Thread A to acquire the lock, then tries to acquire it itself. - let thread_b = std::thread::spawn(move || { - barrier.wait(); // Wait for Thread A to acquire lock. - - // Attempt to acquire lock. This should block until Thread A releases it. - let _lock = DirLock::lock_exclusive(lock_path).expect("Failed to lock exclusive in B"); - - // Assert that B only successfully locked the directory AFTER A released it. - assert!( - lock_released.load(std::sync::atomic::Ordering::Relaxed), - "Thread B acquired lock before Thread A released it!" - ); - }); - - thread_a.join().unwrap(); - thread_b.join().unwrap(); - } - - #[test] - fn test_dir_lock_shared_coexistence() { - let temp_dir = tempfile::tempdir().unwrap(); - let lock_path = temp_dir.path().to_path_buf(); - - // Thread A acquires shared lock. - let lock_a = DirLock::lock_shared(lock_path.clone()).expect("Failed to lock shared"); - - // Thread B should be able to acquire shared lock immediately without blocking. - let lock_b = DirLock::lock_shared(lock_path).expect("Failed to lock shared concurrently"); - - // Both locks are held. - drop(lock_a); - drop(lock_b); - } -} diff --git a/ci/check_fmt.sh b/ci/check_fmt.sh index 1e17b4e468..c9ea6b63d2 100755 --- a/ci/check_fmt.sh +++ b/ci/check_fmt.sh @@ -23,5 +23,5 @@ zerocopy/ci/check_fmt.sh "$@" cargo +"$NIGHTLY" fmt --manifest-path tools/Cargo.toml --all "${FMT_FLAGS[@]}" >&2 cargo +"$NIGHTLY" fmt --manifest-path anneal/Cargo.toml --all "${FMT_FLAGS[@]}" >&2 -cargo +"$NIGHTLY" fmt --manifest-path anneal/v2/Cargo.toml --all "${FMT_FLAGS[@]}" >&2 +cargo +"$NIGHTLY" fmt --manifest-path anneal/v1/Cargo.toml --all "${FMT_FLAGS[@]}" >&2 cargo +"$NIGHTLY" fmt --manifest-path exocrate/Cargo.toml "${FMT_FLAGS[@]}" >&2 diff --git a/ci/release_anneal_version.sh b/ci/release_anneal_version.sh index f28d0ef285..d66cce51fd 100755 --- a/ci/release_anneal_version.sh +++ b/ci/release_anneal_version.sh @@ -20,16 +20,16 @@ VERSION="$1" # Update the package version in the Anneal crate's manifest. This is the # authoritative version for the crate. -sed -i -e "s/^version = \"[0-9a-zA-Z\.-]*\"/version = \"$VERSION\"/" anneal/Cargo.toml +sed -i -e "s/^version = \"[0-9a-zA-Z\.-]*\"/version = \"$VERSION\"/" anneal/v1/Cargo.toml # Update the installation instructions in the README to reflect the new version. # This ensures that users copying instructions get the latest version. -sed -i -e "s/cargo install cargo-anneal@[0-9a-zA-Z\.-]*/cargo install cargo-anneal@$VERSION/" anneal/README.md +sed -i -e "s/cargo install cargo-anneal@[0-9a-zA-Z\.-]*/cargo install cargo-anneal@$VERSION/" anneal/v1/README.md # Update Cargo.lock to reflect the version change in Cargo.toml. We must run -# this in the anneal subdirectory because it is a separate workspace with its +# this in the anneal/v1 subdirectory because it is a separate workspace with its # own lockfile. Use `cargo update` on the local package itself instead of # regenerating the entire lockfile; the release version bump should not also # roll dependency versions. -cd anneal +cd anneal/v1 cargo update -p cargo-anneal --precise "$VERSION" diff --git a/hermes b/hermes index 103b3362a1..76e9261845 120000 --- a/hermes +++ b/hermes @@ -1 +1 @@ -anneal/ \ No newline at end of file +anneal/v1/ \ No newline at end of file diff --git a/tools/hermes b/tools/hermes index e567b44430..9833c976ef 120000 --- a/tools/hermes +++ b/tools/hermes @@ -1 +1 @@ -../anneal/ \ No newline at end of file +../anneal/v1/ \ No newline at end of file