Skip to content

fix(pacquet/store-dir): close writer×verifier race in verify_file#11825

Merged
zkochan merged 1 commit into
mainfrom
pacquet-cas-verify-lock
May 21, 2026
Merged

fix(pacquet/store-dir): close writer×verifier race in verify_file#11825
zkochan merged 1 commit into
mainfrom
pacquet-cas-verify-lock

Conversation

@zkochan

@zkochan zkochan commented May 21, 2026

Copy link
Copy Markdown
Member

Summary

Closes the writer×verifier race that surfaces in CI as failed to import "<store>/v11/files/…": No such file or directory (os error 2). While ensure_file was holding the per-path cas_write_lock and partway through write_all, a sibling snapshot's check_pkg_files_integrity would stat the same CAS path (no lock), see a partial size, and remove_stale_cafs_entry it. The writer's write_all then carried on against an orphan inode; once the fd closed, the inode was freed and the install's cas_paths was pointing at a now-missing dirent.

The fix

Acquire cas_write_lock(path) in verify_file before any branch that could call remove_stale_cafs_entry. The lock is the same per-path mutex ensure_file already holds, so a concurrent writer's write_all always lands before the verifier evaluates.

Implementation:

  • verify_file's fast path stays lock-free. When the file's mtime is within the 100 ms checked_at slack (the common case — file untouched since a prior install), no lock is acquired and no fs operations beyond the original stat.
  • The slow path (where the verifier could end up calling remove_stale_cafs_entry) acquires the lock, re-stats under the lock, and only then decides. A file that vanished between the fast-path stat and lock acquisition surfaces as a graceful "cache miss / re-fetch" instead of a panic.
  • pacquet_fs::cas_write_lock was promoted from fn to pub fn so the store-dir crate can reach it.

Performance

  • Fast path: zero overhead.
  • Slow path: one uncontended Mutex acquire per file, sub-microsecond.
  • Contention: only fires when a writer + verifier hit the same blob simultaneously. Wait is bounded by one write_all (milliseconds), trading a millisecond-scale wait for avoiding the network re-fetch the install would otherwise have to do.

Reproducer test

pacquet/crates/store-dir/tests/verify_writer_race.rs — deterministic, fails pre-fix, passes post-fix.

The test holds cas_write_lock from the test thread (standing in for an in-flight writer) and pre-seeds a partial CAS file at the matching path. A background thread runs check_pkg_files_integrity for a row that references the same path. The assertion checks whether the file still exists while the "writer" holds the lock:

  • Pre-Option-C: verifier doesn't acquire the lock, sees partial file, unlinks. Assertion fails.
  • Post-Option-C: verifier acquires the lock and blocks. Assertion holds.

I verified the test flips green only because of the fix:

$ git stash    # un-apply the fix
$ cargo nextest run -p pacquet-store-dir --test verify_writer_race
…FAIL [   0.327s] (2/2) pacquet-store-dir::verify_writer_race verify_does_not_unlink_file_while_writer_holds_cas_lock

$ git stash pop   # re-apply
$ cargo nextest run -p pacquet-store-dir --test verify_writer_race
…PASS [   0.478s] (2/2) pacquet-store-dir::verify_writer_race verify_does_not_unlink_file_while_writer_holds_cas_lock

Test plan

  • cargo nextest run -p pacquet-store-dir — 105 tests pass (incl. 2 new)
  • cargo nextest run -p pacquet-fs — 28 tests pass
  • cargo fmt --all -- --check, just lint, just dylint — clean

Written by an agent (Claude Code, claude-opus-4-7).

Summary by CodeRabbit

  • Bug Fixes
    • Improved package file integrity verification with enhanced concurrency handling. File verification now properly synchronizes with concurrent write operations to prevent race conditions where verification processes could interfere with active writers or cause files to be prematurely deleted. This ensures more reliable integrity validation during simultaneous package operations.

Review Change Stack

…write_lock

The verifier was racing in-flight writers: while `ensure_file` held
`cas_write_lock(path)` and was partway through `write_all`, a
sibling snapshot's `check_pkg_files_integrity` would stat the same
CAS path (no lock), see a partial size, and call
`remove_stale_cafs_entry(path)`. The writer's `write_all` then
continued against an orphan inode, the writer's `cas_paths` was
populated with the now-deleted path, and `link_file` later hit
ENOENT — the CI failure shape on #11816 / 0.2.2-7.

Fix (Option C): keep `verify_file`'s lock-free fast path (the
common case: file unchanged since prior install, `is_modified`
false), but acquire `cas_write_lock(path)` before any branch that
could call `remove_stale_cafs_entry`. Re-`check_file` under the
lock so a writer's full `write_all` lands before we evaluate.

Performance: the fast path adds zero overhead. The slow path —
files whose mtime is > 100 ms past the recorded `checked_at` —
takes one uncontended Mutex acquire per file, sub-microsecond
on uncontended locks. Contention only fires when a writer + a
verifier hit the same blob simultaneously; the wait is bounded
by one `write_all` and trades a millisecond-scale wait for
avoiding a network re-fetch.

The new integration test in `pacquet-store-dir/tests/` is a
deterministic reproducer: it acquires `cas_write_lock` from the
test thread (standing in for an in-flight writer), pre-seeds a
partial CAS file at the matching path, and runs the verifier in
the background. Pre-fix, the verifier unlinks the file while the
"writer" is still simulated as in-progress; post-fix, the
verifier blocks on the lock until released.

To make `cas_write_lock` reachable from the store-dir crate the
function was promoted from `fn` to `pub fn` in pacquet-fs.
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented May 21, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR fixes a writer-verifier race condition in CAS file verification by exporting a per-path write lock and updating the verification logic to acquire it before re-statting and potentially deleting modified files. Adds comprehensive concurrency tests validating the locking behavior.

Changes

CAS Write Lock and Verification Concurrency

Layer / File(s) Summary
Public write lock export
pacquet/crates/fs/src/ensure_file.rs
cas_write_lock is now publicly accessible with expanded documentation describing its role in coordinating writers and verifiers to prevent deletion races.
Verification locking discipline
pacquet/crates/store-dir/src/check_pkg_files_integrity.rs
verify_file implements a two-path strategy: lock-free fast-path for unmodified files, and slow-path lock acquisition for modified files to prevent verifiers from deleting paths while writers hold the lock.
Test infrastructure and race reproducer
pacquet/crates/store-dir/Cargo.toml, pacquet/crates/store-dir/tests/verify_writer_race.rs
Adds sha2 to dev-dependencies and introduces concurrency tests: a deterministic reproducer validating file survival under write lock, and a sanity test confirming per-path lock sharing across threads.

Sequence Diagram

sequenceDiagram
  participant Verifier
  participant MetadataCheck
  participant WriteLock
  participant FileSystem
  Verifier->>MetadataCheck: is_modified check (fast path)
  alt File not modified
    MetadataCheck-->>Verifier: return early
  else File appears modified
    Verifier->>WriteLock: acquire cas_write_lock
    WriteLock->>MetadataCheck: re-check under lock
    MetadataCheck->>FileSystem: stat file
    alt File still modified
      MetadataCheck-->>Verifier: file changed, proceed with validation
    else File no longer modified or missing
      FileSystem-->>Verifier: file gone or unchanged
      Verifier-->>Verifier: trigger re-fetch
    end
    Verifier->>WriteLock: release lock
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • pnpm/pacquet#260: This PR directly implements the pnpm-style checked_at fast-path and per-path write-lock coordination for CAS file verification, resolving the writer-verifier race condition described in the issue.

Possibly related PRs

  • pnpm/pnpm#11758: Both PRs implement per-path CAS write-verifier concurrency coordination: the related PR adds per-path mutex serialization in ensure_file, while this PR exports that lock and updates verify_file to acquire it before re-statting and potentially modifying CAS paths.

Poem

🐰 A lock for each file path we find,
Fast checks for the unmodified kind,
When changes appear, we pause and wait,
Writers and verifiers coordinate fate—
No racing ahead, both safe and sublime!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main fix: preventing a race condition between CAS writers and verifiers in the verify_file function by implementing locking discipline.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pacquet-cas-verify-lock

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

Copy link
Copy Markdown
Contributor

Micro-Benchmark Results

Linux

group                          main                                   pr
-----                          ----                                   --
tarball/download_dependency    1.00      7.7±0.27ms   565.4 KB/sec    1.04      8.0±0.32ms   543.0 KB/sec

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
pacquet/crates/store-dir/tests/verify_writer_race.rs (1)

134-147: ⚡ Quick win

Assert the verifier result, not just file survival.

Line 147 throws away result.passed, so this stays green even if a future regression waits on the lock but still returns passed = false. For this race fix, the stronger contract is that verification succeeds after the writer commits the final blob.

Proposed tightening
-    let result_slot: Arc<Mutex<Option<bool>>> = Arc::new(Mutex::new(None));
-
     let verify_store = StoreDir::new(tmp.path().to_path_buf());
     let verify_content = expected_content.clone();
-    let result_slot_writer = Arc::clone(&result_slot);
     let target_for_verifier = target.clone();
     let verifier = thread::spawn(move || {
         verifier_started_tx.send(()).expect("send start");
         let pkg_index = make_index("LICENSE", &verify_content);
         let cache = VerifiedFilesCache::new();
         let result = check_pkg_files_integrity(&verify_store, pkg_index, &cache);
-        // Record whether the file survived the verifier's run.
-        *result_slot_writer.lock().expect("result mutex") =
-            Some((target_for_verifier.exists(), result.passed).0);
+        (target_for_verifier.exists(), result.passed)
     });
@@
-    verifier.join().expect("verifier thread should not panic");
+    let (_file_survived_verifier, verification_passed) =
+        verifier.join().expect("verifier thread should not panic");
     let file_exists_after_verify = target.exists();
@@
     assert!(
         file_exists_after_verify,
         "After the simulated writer released its lock, the verifier should observe \
          the final (correct) content and not delete it",
     );
+    assert!(
+        verification_passed,
+        "After the writer commits the full blob and releases the lock, verification should pass",
+    );

Also applies to: 173-199

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pacquet/crates/store-dir/tests/verify_writer_race.rs` around lines 134 - 147,
The verifier thread currently stores only file existence into result_slot (via
target_for_verifier.exists()) and drops result.passed; change the assignment in
the verifier closure (where result = check_pkg_files_integrity(...) and you set
*result_slot_writer.lock() = Some(...)) to record the verification outcome
instead of (or in addition to) existence — e.g., store result.passed (or store a
conjunction of exists() && result.passed) so the test asserts the verifier
returned passed; apply the same change in the other verifier block mentioned
(lines 173–199) to record check_pkg_files_integrity(...)'s result.passed.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@pacquet/crates/store-dir/tests/verify_writer_race.rs`:
- Around line 134-147: The verifier thread currently stores only file existence
into result_slot (via target_for_verifier.exists()) and drops result.passed;
change the assignment in the verifier closure (where result =
check_pkg_files_integrity(...) and you set *result_slot_writer.lock() =
Some(...)) to record the verification outcome instead of (or in addition to)
existence — e.g., store result.passed (or store a conjunction of exists() &&
result.passed) so the test asserts the verifier returned passed; apply the same
change in the other verifier block mentioned (lines 173–199) to record
check_pkg_files_integrity(...)'s result.passed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a5d93efa-93f6-412b-9e63-a34fe8d8e744

📥 Commits

Reviewing files that changed from the base of the PR and between f9a0abe and db94c30.

📒 Files selected for processing (4)
  • pacquet/crates/fs/src/ensure_file.rs
  • pacquet/crates/store-dir/Cargo.toml
  • pacquet/crates/store-dir/src/check_pkg_files_integrity.rs
  • pacquet/crates/store-dir/tests/verify_writer_race.rs
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Lint and Test (macos-latest)
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Code Coverage
  • GitHub Check: Lint and Test (windows-latest)
  • GitHub Check: Lint and Test (ubuntu-latest)
  • GitHub Check: Compile & Lint
🧰 Additional context used
📓 Path-based instructions (2)
pacquet/**/*.rs

📄 CodeRabbit inference engine (pacquet/AGENTS.md)

pacquet/**/*.rs: When porting a function that fires pnpm:<channel> events through globalLogger, logger.debug(), or streamParser.write(), mirror the call site, payload, and ordering so the reporter parses pacquet's NDJSON the same way it parses pnpm's.
Declare a newtype wrapper for branded string types. Do not collapse the brand into a plain String or &str.
If upstream always validates before construction, validate in pacquet's wrapper too. The wrapper must construct only via TryFrom<String> and/or FromStr. Do not provide an infallible public constructor.
If upstream never validates, just brand for type-safety. Expose an infallible From<String> (and From<&str> when convenient).
If upstream occasionally constructs without validation, expose from_str_unchecked as an escape hatch alongside the validating constructor.
Match upstream serde behavior for branded types that cross JSON, YAML, or INI boundaries. Use #[serde(try_from = "String")] for deserialization and #[serde(into = "String")] for serialization.
Use #[derive(derive_more::From)] and #[derive(derive_more::Into)] for mechanical conversion impls. Fall back to manual impl only when conversion needs custom logic.
String-literal unions should become enums, not newtype wrappers. Model closed sets of valid string values as enums.
Template literal types should be treated as branded strings with validation discipline from rules 2-5.
Choose owned vs. borrowed parameters to minimize copies. Widen to the most encompassing type (&Path over &PathBuf, &str over &String) when it doesn't force extra copies.
Prefer Arc::clone(&x) / Rc::clone(&x) over x.clone() for reference-counted types, so the cost is visible at the call site.
Follow Rust API Guidelines for naming conventions.
Do not use star imports inside module bodies. Write use super::{Foo, bar} instead of use super::*;. Two forms stay allowed: external-crate preludes like use rayon::prelude::*; and root-of-module re-...

Files:

  • pacquet/crates/fs/src/ensure_file.rs
  • pacquet/crates/store-dir/src/check_pkg_files_integrity.rs
  • pacquet/crates/store-dir/tests/verify_writer_race.rs
pacquet/**/{tests,test}/**/*.rs

📄 CodeRabbit inference engine (pacquet/AGENTS.md)

pacquet/**/{tests,test}/**/*.rs: Prefer real fixtures; reach for the dependency-injection seam only when they can't cover the branch. Most happy paths and error paths should be tested with tempfile::TempDir, the mocked registry, or an integration test that spawns the actual binary.
Use the DI seam — a capability trait on the Host provider, threaded as Sys: <Bounds> — only for branches a real fixture can't reach portably: filesystem error kinds, deterministic time, shared process-global state, or external-service happy paths.
Log before non-assert_eq! assertions, dbg! complex structures, skip logging for simple scalar assert_eq!. Follow the test-logging guidance in CODE_STYLE_GUIDE.md.
Tests must not be tolerant of missing build/runtime environment by silently returning early when a tool isn't found. If the test needs a tool, let it panic when the tool is absent.
Prefer #[cfg_attr(target_os = "windows", ignore = "...")] (or #[cfg(unix)]) over a runtime probe-and-skip helper for platform-locked tools.
Port relevant tests from pnpm when porting behavior. Matching test coverage is the easiest way to prove behavioral parity.
When porting behavior from pnpm, consult plans/TEST_PORTING.md before adding ported tests. Use allow_known_failure! at the not-yet-implemented boundary and update checkboxes as items land.

Files:

  • pacquet/crates/store-dir/tests/verify_writer_race.rs
🧠 Learnings (2)
📚 Learning: 2026-05-20T19:40:55.051Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11774
File: pacquet/crates/resolving-deps-resolver/src/resolve_peers.rs:0-0
Timestamp: 2026-05-20T19:40:55.051Z
Learning: In the pacquet Rust code, ensure the semver implementation uses the `node-semver` crate (not `nodejs-semver`). `node-semver`’s public API does not include a `satisfies_with_prerelease`-style method; prerelease-tolerant matching should be implemented inline by first calling `Range::satisfies`, and when it rejects a prerelease version, retry matching against a stripped `MAJOR.MINOR.PATCH` base of the prerelease version.

Applied to files:

  • pacquet/crates/fs/src/ensure_file.rs
  • pacquet/crates/store-dir/src/check_pkg_files_integrity.rs
  • pacquet/crates/store-dir/tests/verify_writer_race.rs
📚 Learning: 2026-05-20T23:07:58.444Z
Learnt from: zkochan
Repo: pnpm/pnpm PR: 11784
File: pacquet/crates/resolving-deps-resolver/src/hoist_peers.rs:120-133
Timestamp: 2026-05-20T23:07:58.444Z
Learning: When reviewing code in this pacquet Rust port, follow the upstream pnpm compatibility rule: only match pnpm’s behavior exactly. Do not propose review changes that intentionally deviate from pnpm’s documented/observed behavior, even if pnpm appears buggy. If you identify a real bug in pnpm behavior, the review should prioritize fixing it upstream in pnpm first, and avoid implementing a pnpm-behavior workaround here unless the same fix has already landed upstream.

Applied to files:

  • pacquet/crates/fs/src/ensure_file.rs
  • pacquet/crates/store-dir/src/check_pkg_files_integrity.rs
  • pacquet/crates/store-dir/tests/verify_writer_race.rs

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 70.00000% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.56%. Comparing base (f9a0abe) to head (db94c30).

Files with missing lines Patch % Lines
.../crates/store-dir/src/check_pkg_files_integrity.rs 66.66% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #11825      +/-   ##
==========================================
- Coverage   87.58%   87.56%   -0.02%     
==========================================
  Files         204      204              
  Lines       24126    24134       +8     
==========================================
+ Hits        21130    21133       +3     
- Misses       2996     3001       +5     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

Copy link
Copy Markdown
Contributor

Integrated-Benchmark Report (Linux)

Scenario: Frozen Lockfile

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 2.511 ± 0.073 2.420 2.680 1.00 ± 0.04
pacquet@main 2.502 ± 0.073 2.415 2.655 1.00
pnpm 4.906 ± 0.083 4.807 5.051 1.96 ± 0.07
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 2.5108529235399994,
      "stddev": 0.07278386610851326,
      "median": 2.50602192784,
      "user": 2.8045107199999997,
      "system": 3.8364530399999994,
      "min": 2.41995201084,
      "max": 2.68037487984,
      "times": [
        2.52225799784,
        2.68037487984,
        2.46667402484,
        2.55806566384,
        2.43895093484,
        2.48748832384,
        2.41995201084,
        2.52272154384,
        2.52122908984,
        2.4908147658399997
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 2.50246213284,
      "stddev": 0.07316369430843171,
      "median": 2.47922121734,
      "user": 2.8134892199999992,
      "system": 3.7427576399999998,
      "min": 2.4146320438399997,
      "max": 2.65537001484,
      "times": [
        2.47959889484,
        2.65537001484,
        2.45072237784,
        2.4788435398399997,
        2.4146320438399997,
        2.5824194448399997,
        2.52982296884,
        2.52945403984,
        2.46484369084,
        2.43891431284
      ]
    },
    {
      "command": "pnpm",
      "mean": 4.90615295124,
      "stddev": 0.08332186036095118,
      "median": 4.899686620340001,
      "user": 8.207016020000001,
      "system": 4.29235154,
      "min": 4.80724024984,
      "max": 5.051169515840001,
      "times": [
        4.96068857784,
        5.051169515840001,
        4.8224825108400005,
        5.01598645384,
        4.91961155484,
        4.9217723728400005,
        4.849334504840001,
        4.80724024984,
        4.87976168584,
        4.83348208584
      ]
    }
  ]
}

Scenario: Frozen Lockfile (Hot Cache)

Command Mean [ms] Min [ms] Max [ms] Relative
pacquet@HEAD 707.1 ± 25.5 683.8 775.6 1.00
pacquet@main 753.4 ± 75.1 683.5 900.3 1.07 ± 0.11
pnpm 2640.8 ± 133.5 2543.0 2996.0 3.73 ± 0.23
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 0.7070786985200002,
      "stddev": 0.02547465509121021,
      "median": 0.70198123722,
      "user": 0.41339625999999996,
      "system": 1.5909168999999999,
      "min": 0.6837631562200001,
      "max": 0.7755508942200001,
      "times": [
        0.7755508942200001,
        0.7089839372200001,
        0.70286868822,
        0.70945463622,
        0.6933932552200001,
        0.70565551122,
        0.7007211802200001,
        0.6837631562200001,
        0.7010937862200001,
        0.6893019402200001
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 0.7534291311200002,
      "stddev": 0.07507416882872918,
      "median": 0.7373454877200001,
      "user": 0.39031276,
      "system": 1.6015404,
      "min": 0.6835312532200001,
      "max": 0.9002825352200001,
      "times": [
        0.75647686422,
        0.7440627362200001,
        0.6906408472200001,
        0.6835312532200001,
        0.6989095072200001,
        0.9002825352200001,
        0.7652079112200001,
        0.6958497422200001,
        0.7306282392200001,
        0.8687016752200001
      ]
    },
    {
      "command": "pnpm",
      "mean": 2.6407794508199998,
      "stddev": 0.1334952063207141,
      "median": 2.59132448622,
      "user": 3.30970296,
      "system": 2.291588,
      "min": 2.54296654422,
      "max": 2.99601454722,
      "times": [
        2.69412061722,
        2.58087054922,
        2.99601454722,
        2.54296654422,
        2.58362910222,
        2.55575121122,
        2.59880970722,
        2.59343879622,
        2.58921017622,
        2.67298325722
      ]
    }
  ]
}

@zkochan zkochan merged commit a5a2c24 into main May 21, 2026
28 checks passed
@zkochan zkochan deleted the pacquet-cas-verify-lock branch May 21, 2026 16:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants