From 2f2a00fbe572d3c2388651dffd371c2933fb622c Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Thu, 24 Sep 2026 02:10:19 +0300 Subject: [PATCH 1/6] feat!: adopt the CoordiNode v0.6.0 protocol Server v0.6.0 replaced the one-dimensional write concern level with two independent axes, so the client could no longer build a WriteConcern at all against the new proto. - write_concern takes a WriteConcern(w, journal, timeout_ms), or the shorthands "majority" and a member count. Combinations the server refuses (a volatile journal with majority or more than one ack, counts or timeouts outside uint32) are rejected before the call. - NodeResult.version exposes the version a node is at. - commit(expect={node_id: version | None}) commits only while the nodes are still at those versions (None: must not exist); a mismatch aborts with REVISION_MISMATCH. Transaction.commit_ts carries the commit timestamp, the exact at_timestamp anchor for the write. - A rejected expect argument on the sync commit() no longer marks the transaction indeterminate; it stays open. - proto 41273fe, coordinode-rs v0.6.0, and the integration, compose and demo images pinned to the v0.6.0 digest. The embedded lockfile moves to coordinode-lsm-tree 5.11.2, which v0.6.0 needs to build. - The demo Jupyter image pins grpcio, grpcio-tools and protobuf to the uv.lock versions: it generates stubs into the mounted checkout, and a newer grpcio-tools there broke the host's own test runs on that tree. - Inbound traversal works on v0.6.0, so its xfail marker is removed. BREAKING CHANGE: write_concern no longer accepts "w0", "w1", "memory" or "cache"; use 0, 1, or WriteConcern(w=1, journal="memory" | "cache"). Closes #101 --- .github/workflows/ci.yml | 6 +- README.md | 2 +- coordinode-embedded/Cargo.lock | 3343 +++++++++++++++++++++- coordinode-rs | 2 +- coordinode/README.md | 17 +- coordinode/coordinode/__init__.py | 3 + coordinode/coordinode/client.py | 160 +- demo/Dockerfile.jupyter | 12 +- demo/README.md | 8 +- demo/docker-compose.yml | 4 +- demo/notebooks/03_langgraph_agent.ipynb | 2 +- demo/notebooks/04_whats_new_in_0_5.ipynb | 74 +- docker-compose.yml | 4 +- proto | 2 +- tests/integration/test_sdk.py | 138 +- tests/unit/test_consistency_helpers.py | 98 +- tests/unit/test_transactions.py | 86 + uv.lock | 2 +- 18 files changed, 3650 insertions(+), 313 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 42c1e05..60181fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -140,10 +140,10 @@ jobs: services: coordinode: # Pinned by digest, not by tag: the proto submodule pins a server - # version, and a tag can be re-pushed, so `:0.5.7` alone does not name - # one fixed server. The digest below is 0.5.7; bump both together with + # version, and a tag can be re-pushed, so `:0.6.0` alone does not name + # one fixed server. The digest below is 0.6.0; bump both together with # the submodule. - image: ghcr.io/structured-world/coordinode@sha256:75a6242beb4cea8ab6726c842898fafc03ca9f59c6f7e36e462934a4ca64874c + image: ghcr.io/structured-world/coordinode@sha256:f7e0ee8515868bc068f1e2f66d499a4acf5e9b7bce76bd5913bf805c7f9fd6da ports: - 7080:7080 - 7084:7084 diff --git a/README.md b/README.md index 2fb6408..1068916 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ with CoordinodeClient("localhost:7080") as db: raise ``` -Requires a CoordiNode server of **v0.5.7 or newer** — the release this client +Requires a CoordiNode server of **v0.6.0 or newer** — the release this client is integration-tested against. `health()` exercises a different service, so a server without the transaction RPCs passes the health check and then refuses `transaction()`. diff --git a/coordinode-embedded/Cargo.lock b/coordinode-embedded/Cargo.lock index bea3925..d4bfc39 100644 --- a/coordinode-embedded/Cargo.lock +++ b/coordinode-embedded/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "aead" version = "0.5.2" @@ -12,6 +18,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "aead" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1973cfbc1a2daf9cf550e74e1f088c28e7f7d8c1e1418fb6c9dc5184b7e84c99" +dependencies = [ + "crypto-common 0.2.2", + "inout 0.2.2", +] + [[package]] name = "aes" version = "0.8.4" @@ -19,34 +35,47 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", - "cipher", + "cipher 0.4.4", "cpufeatures 0.2.17", ] +[[package]] +name = "aes" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35f0f96ce78e38c3dc6d8948aa8163d06385be74000f3c7a95bf1eef35d3ea32" +dependencies = [ + "cipher 0.5.2", + "cpubits", + "cpufeatures 0.3.0", +] + [[package]] name = "aes-gcm" version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" dependencies = [ - "aead", - "aes", - "cipher", - "ctr", - "ghash", + "aead 0.5.2", + "aes 0.8.4", + "cipher 0.4.4", + "ctr 0.9.2", + "ghash 0.5.1", "subtle", ] [[package]] -name = "ahash" -version = "0.8.12" +name = "aes-gcm" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +checksum = "7f2b8006a0c83f52b62ba44a97b58bf76fe2f70a329e588f67f89691d93d498f" dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", + "aead 0.6.1", + "aes 0.9.3", + "cipher 0.5.2", + "ctr 0.10.1", + "ctutils", + "ghash 0.6.0", ] [[package]] @@ -58,6 +87,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloca" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4" +dependencies = [ + "cc", +] + [[package]] name = "allocator-api2" version = "0.2.21" @@ -73,6 +111,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + [[package]] name = "anstream" version = "1.0.0" @@ -138,6 +182,15 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "ar_archive_writer" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73cd58deff2140a0a8eae87e417bd01db68a33e148aa93d1e8cd837e55e312b6" +dependencies = [ + "object", +] + [[package]] name = "arc-swap" version = "1.9.1" @@ -147,6 +200,57 @@ dependencies = [ "rustversion", ] +[[package]] +name = "array-init" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc" + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + +[[package]] +name = "asn1-rs" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f43a50ac4fdca5df8e885c21b835997f0a1cdee65494a6847694a98652d9d8" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure 0.13.2", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "async-trait" version = "0.1.89" @@ -170,6 +274,30 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-lc-rs" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b281d307588d634de920874890732659e2e7672f72b5e10e81badc1a8a83621e" +dependencies = [ + "aws-lc-sys", + "untrusted 0.7.1", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bff6c3b54fad79a2e60b8102caf565819711497c1f5f092f49508e2f5c31b27" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", + "pkg-config", +] + [[package]] name = "axum" version = "0.8.8" @@ -177,11 +305,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" dependencies = [ "axum-core", + "axum-macros", "bytes", + "form_urlencoded", "futures-util", "http", "http-body", "http-body-util", + "hyper", + "hyper-util", "itoa", "matchit", "memchr", @@ -189,10 +321,15 @@ dependencies = [ "percent-encoding", "pin-project-lite", "serde_core", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", "sync_wrapper", + "tokio", "tower", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -211,6 +348,18 @@ dependencies = [ "sync_wrapper", "tower-layer", "tower-service", + "tracing", +] + +[[package]] +name = "axum-macros" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aa268c23bfbbd2c4363b9cd302a4f504fb2a9dfe7e3451d66f35dd392e20aca" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] @@ -237,12 +386,28 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + [[package]] name = "base64ct" version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" +[[package]] +name = "bcder" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b593e5aeaf7992d388c08a9831c921cd703718064b3e50ba8e6d666d6cf86ca7" +dependencies = [ + "bytes", + "smallvec", +] + [[package]] name = "bigdecimal" version = "0.4.10" @@ -257,6 +422,30 @@ dependencies = [ "serde", ] +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec 0.8.0", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bit-vec" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" +dependencies = [ + "serde", +] + [[package]] name = "bitflags" version = "2.11.0" @@ -306,15 +495,49 @@ version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "519bd3116aeeb42d5372c29d982d16d0170d3d4a5ed85fc7dd91642ffff3c67c" dependencies = [ - "darling", + "darling 0.23.0", "ident_case", - "prettyplease", + "prettyplease 0.2.37", "proc-macro2", "quote", "rustversion", "syn 2.0.117", ] +[[package]] +name = "borsh" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "553c5d846a6ba5150c65e3b1b8ec073bcf1abc20f9b7220de384a4443ea4e20a" +dependencies = [ + "borsh-derive", + "bytes", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12cdfe656708a01f89b451a7d36466e6fe6c414de0aa18fc54f864f6f9ca9f56" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "bstr" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f" +dependencies = [ + "memchr", + "serde_core", +] + [[package]] name = "bumpalo" version = "3.20.2" @@ -345,6 +568,22 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c53ba0f290bfc610084c05582d9c5d421662128fc69f4bf236707af6fd321b9" +[[package]] +name = "bzip2-rs" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beeb59e7e4c811ab37cc73680c798c7a5da77fc9989c62b09138e31ee740f735" +dependencies = [ + "crc32fast", + "tinyvec", +] + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "cc" version = "1.2.60" @@ -352,7 +591,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" dependencies = [ "find-msvc-tools", - "shlex", + "jobserver", + "libc", + "shlex 1.3.0", ] [[package]] @@ -367,6 +608,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + [[package]] name = "chacha20" version = "0.9.1" @@ -374,7 +621,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" dependencies = [ "cfg-if", - "cipher", + "cipher 0.4.4", "cpufeatures 0.2.17", ] @@ -395,9 +642,9 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ - "aead", + "aead 0.5.2", "chacha20 0.9.1", - "cipher", + "cipher 0.4.4", "poly1305", "zeroize", ] @@ -424,7 +671,7 @@ checksum = "d59ae0466b83e838b81a54256c39d5d7c20b9d7daa10510a242d9b75abd5936e" dependencies = [ "chrono", "chrono-tz-build", - "phf", + "phf 0.11.3", "serde", ] @@ -435,10 +682,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "433e39f13c9a060046954e0592a8d0a4bcb1040125cbf91cb8ee58964cfb350f" dependencies = [ "parse-zoneinfo", - "phf", + "phf 0.11.3", "phf_codegen", ] +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "cipher" version = "0.4.4" @@ -446,10 +720,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common 0.1.7", - "inout", + "inout 0.1.4", "zeroize", ] +[[package]] +name = "cipher" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" +dependencies = [ + "block-buffer 0.12.0", + "crypto-common 0.2.2", + "inout 0.2.2", +] + [[package]] name = "clap" version = "4.6.0" @@ -490,6 +775,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + [[package]] name = "cmov" version = "0.5.4" @@ -502,6 +796,16 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "combine" +version = "4.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -525,20 +829,21 @@ dependencies = [ [[package]] name = "coordinode-cluster" -version = "0.5.7" +version = "0.6.0" dependencies = [ "coordinode-storage", + "coordinode-workspace-hack", "parking_lot", "serde", "smallvec", - "thiserror", + "thiserror 2.0.18", "tokio", "tracing", ] [[package]] name = "coordinode-core" -version = "0.5.7" +version = "0.6.0" dependencies = [ "bytes", "rmp", @@ -546,15 +851,16 @@ dependencies = [ "rmpv", "serde", "sha2 0.11.0", + "smallvec", "streamvbyte64", - "thiserror", + "thiserror 2.0.18", "tokio", "tracing", ] [[package]] name = "coordinode-embed" -version = "0.5.7" +version = "0.6.0" dependencies = [ "coordinode-core", "coordinode-lsm-tree", @@ -564,18 +870,19 @@ dependencies = [ "coordinode-search", "coordinode-storage", "coordinode-vector", + "coordinode-workspace-hack", "parking_lot", "rmp-serde", "rmpv", "serde", "serde_json", - "thiserror", + "thiserror 2.0.18", "tracing", ] [[package]] name = "coordinode-embedded" -version = "2.0.0" +version = "2.1.1" dependencies = [ "coordinode-core", "coordinode-embed", @@ -588,9 +895,9 @@ dependencies = [ [[package]] name = "coordinode-lsm-tree" -version = "5.8.3" +version = "5.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad26b0c73ee5b2070f376602394efa2a54797d8f29a0ef2caa75a0a0d6e04e6d" +checksum = "cce5b3c4f35bf94861150fc9002e163bd59970a0975f42a46a2a0463b96bfa0d" dependencies = [ "arc-swap", "cpufeatures 0.3.0", @@ -606,6 +913,7 @@ dependencies = [ "rustc-hash", "self_cell", "spin 0.12.3", + "structured-zstd", "tempfile", "varint-rs", "xxhash-rust", @@ -613,30 +921,33 @@ dependencies = [ [[package]] name = "coordinode-modality" -version = "0.5.7" +version = "0.6.0" dependencies = [ "coordinode-core", "coordinode-lsm-tree", "coordinode-storage", "coordinode-vector", + "coordinode-workspace-hack", "rmp-serde", "rmpv", "s2", "serde", - "thiserror", + "thiserror 2.0.18", "tracing", ] [[package]] name = "coordinode-query" -version = "0.5.7" +version = "0.6.0" dependencies = [ + "chrono", "coordinode-cluster", "coordinode-core", "coordinode-modality", "coordinode-search", "coordinode-storage", "coordinode-vector", + "coordinode-workspace-hack", "pest", "pest_derive", "rand 0.10.1", @@ -648,19 +959,20 @@ dependencies = [ "serde", "sha2 0.11.0", "sqlparser", - "thiserror", + "thiserror 2.0.18", "tracing", "unicode-normalization", ] [[package]] name = "coordinode-raft" -version = "0.5.7" +version = "0.6.0" dependencies = [ "bytes", "coordinode-core", "coordinode-storage", "coordinode-wire", + "coordinode-workspace-hack", "futures-util", "metrics", "openraft", @@ -668,7 +980,7 @@ dependencies = [ "rmp-serde", "serde", "tempfile", - "thiserror", + "thiserror 2.0.18", "tokio", "tonic", "tonic-prost", @@ -678,30 +990,32 @@ dependencies = [ [[package]] name = "coordinode-search" -version = "0.5.7" +version = "0.6.0" dependencies = [ - "aes-gcm", + "aes-gcm 0.11.1", "coordinode-storage", + "coordinode-workspace-hack", "hmac 0.13.0", "rand 0.10.1", "rust-stemmers 1.2.0 (git+https://github.com/structured-world/rust-stemmers.git?branch=main)", "serde", "sha2 0.11.0", "tantivy", - "thiserror", + "thiserror 2.0.18", "tracing", "whatlang", ] [[package]] name = "coordinode-storage" -version = "0.5.7" +version = "0.6.0" dependencies = [ "byteorder-lite", "bytes", "byteview", "coordinode-core", "coordinode-lsm-tree", + "coordinode-workspace-hack", "crc32c", "crc32fast", "dashmap", @@ -709,81 +1023,646 @@ dependencies = [ "log", "lz4_flex 0.14.0", "metrics", + "parking_lot", "rayon", "rmp-serde", "rmpv", "serde", "serde_bytes", "tempfile", - "thiserror", + "thiserror 2.0.18", "tracing", "xxhash-rust", ] [[package]] name = "coordinode-vector" -version = "0.5.7" +version = "0.6.0" dependencies = [ "coordinode-core", "coordinode-storage", + "coordinode-workspace-hack", "crossbeam-epoch", "loom", "rayon", "serde", "smallvec", - "thiserror", + "thiserror 2.0.18", "tracing", ] [[package]] name = "coordinode-wire" -version = "0.5.7" +version = "0.6.0" dependencies = [ "bytes", + "coordinode-workspace-hack", "prost", "rustls", "rustls-pki-types", "rustls-rustcrypto", "structured-zstd", - "thiserror", + "thiserror 2.0.18", "tonic", ] [[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "cpufeatures" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32c" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a47af21622d091a8f0fb295b88bc886ac74efcc613efc19f5d0b21de5c89e47" +name = "coordinode-workspace-hack" +version = "0.1.0" dependencies = [ - "rustc_version", -] - -[[package]] -name = "crc32fast" + "adler2", + "aead 0.5.2", + "aead 0.6.1", + "aes 0.8.4", + "aes 0.9.3", + "aes-gcm 0.10.3", + "aes-gcm 0.11.1", + "aho-corasick", + "alloca", + "allocator-api2", + "anes", + "anstream", + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "anyerror", + "anyhow", + "ar_archive_writer", + "arc-swap", + "array-init", + "arrayvec", + "async-trait", + "atomic-waker", + "autocfg", + "aws-lc-rs", + "aws-lc-sys", + "axum", + "axum-core", + "axum-macros", + "backoff-series", + "base16ct", + "base2histogram", + "base64 0.22.1", + "base64 0.23.1", + "base64ct", + "bcder", + "bigdecimal", + "bit-set", + "bit-vec 0.8.0", + "bitflags", + "bitpacking", + "block-buffer 0.10.4", + "block-buffer 0.12.0", + "bon", + "bon-macros", + "bstr", + "byteorder", + "byteorder-lite", + "bytes", + "byteview", + "bzip2-rs", + "cast", + "cc", + "census", + "cfg-if", + "chacha20 0.10.0", + "chacha20 0.9.1", + "chacha20poly1305", + "chrono", + "chrono-tz", + "chrono-tz-build", + "ciborium", + "ciborium-io", + "ciborium-ll", + "cipher 0.4.4", + "cipher 0.5.2", + "clap", + "clap_builder", + "clap_derive", + "clap_lex", + "cmake", + "cmov", + "colorchoice", + "const-oid 0.10.2", + "const-oid 0.9.6", + "convert_case", + "core-foundation", + "core-foundation-sys", + "cpubits", + "cpufeatures 0.2.17", + "cpufeatures 0.3.0", + "crc32c", + "crc32fast", + "criterion", + "criterion-plot", + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-utils", + "crunchy", + "crypto-bigint", + "crypto-common 0.1.7", + "crypto-common 0.2.2", + "ctr 0.10.1", + "ctr 0.9.2", + "ctutils", + "curve25519-dalek", + "curve25519-dalek-derive", + "darling 0.24.1", + "darling_core 0.24.1", + "darling_macro 0.24.1", + "dashmap", + "datasketches", + "der", + "deranged", + "derive-new", + "derive_more", + "derive_more-impl", + "digest 0.10.7", + "digest 0.11.2", + "display-more", + "displaydoc", + "downcast-rs", + "dunce", + "ecdsa", + "ed25519", + "ed25519-dalek", + "either", + "elliptic-curve", + "endian-type", + "env_filter", + "env_logger", + "envoy-types", + "equivalent", + "erased-serde", + "errno", + "evmap", + "fallible-iterator", + "fastdivide", + "fastrand", + "ff", + "find-msvc-tools", + "fixedbitset", + "flate2", + "flume", + "fnv", + "foldhash 0.1.5", + "foldhash 0.2.0", + "form_urlencoded", + "fs4", + "fs_extra", + "futures", + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "futures-util", + "generic-array", + "getrandom 0.2.17", + "getrandom 0.3.4", + "getrandom 0.4.2", + "ghash 0.5.1", + "ghash 0.6.0", + "globset", + "group", + "h2", + "half", + "hashbag", + "hashbrown 0.14.5", + "hashbrown 0.15.5", + "hashbrown 0.16.1", + "hashbrown 0.17.0", + "heck", + "hex", + "hkdf", + "hmac 0.12.1", + "hmac 0.13.0", + "htmlescape", + "http", + "http-body", + "http-body-util", + "httparse", + "httpdate", + "hybrid-array", + "hyper", + "hyper-rustls", + "hyper-timeout", + "hyper-util", + "iana-time-zone", + "icu_collections", + "icu_locale_core", + "icu_normalizer", + "icu_normalizer_data", + "icu_properties", + "icu_properties_data", + "icu_provider", + "ident_case", + "idna", + "idna_adapter", + "indexmap", + "inout 0.1.4", + "inout 0.2.2", + "inventory", + "ipnet", + "is_terminal_polyfill", + "itertools 0.13.0", + "itertools 0.14.0", + "itertools 0.15.0", + "itoa", + "jobserver", + "jsonwebtoken", + "lazy-regex", + "lazy-regex-proc_macros", + "lazy_static", + "left-right", + "levenshtein_automata", + "libc", + "libm", + "linux-raw-sys", + "litemap", + "lock_api", + "log", + "lru", + "lz4_flex 0.13.0", + "lz4_flex 0.14.0", + "maplit", + "matchers", + "matchit", + "md-5", + "md5", + "measure_time", + "memchr", + "memmap2", + "metrics", + "metrics-exporter-prometheus", + "metrics-util", + "mime", + "minimal-lexical", + "miniz_oxide", + "mio", + "multimap", + "murmurhash32", + "nanoid", + "nibble_vec", + "nom", + "ntapi", + "nu-ansi-term", + "num-bigint", + "num-bigint-dig", + "num-conv", + "num-integer", + "num-iter", + "num-traits", + "objc2", + "objc2-core-foundation", + "objc2-encode", + "objc2-foundation", + "objc2-io-kit", + "objc2-open-directory", + "objc2-system-configuration", + "object", + "once_cell", + "once_cell_polyfill", + "oneshot", + "oorandom", + "opaque-debug", + "openraft", + "openraft-macros", + "openraft-rt", + "openraft-rt-tokio", + "openssl-probe", + "ordered-float 2.10.1", + "ordered-float 5.3.0", + "ownedbytes", + "p256", + "p384", + "page_size", + "parking_lot", + "parking_lot_core", + "parse-size", + "parse-zoneinfo", + "paste", + "peel-off", + "pem 3.0.6", + "pem 4.0.0", + "pem-rfc7468", + "percent-encoding", + "pest", + "pest_derive", + "pest_generator", + "pest_meta", + "petgraph", + "pg_interval", + "pgwire", + "phf 0.11.3", + "phf 0.13.1", + "phf_codegen", + "phf_generator", + "phf_shared 0.11.3", + "phf_shared 0.13.1", + "pin-project", + "pin-project-internal", + "pin-project-lite", + "pkcs1", + "pkcs5", + "pkcs8", + "pkg-config", + "plotters", + "plotters-backend", + "plotters-svg", + "poly1305", + "polyval 0.6.2", + "polyval 0.7.3", + "postgres-protocol", + "postgres-types", + "potential_utf", + "powerfmt", + "ppv-lite86", + "prettyplease 0.2.37", + "prettyplease 0.3.0", + "primeorder", + "proc-macro2", + "prometheus", + "proptest", + "prost", + "prost-build", + "prost-derive", + "prost-reflect", + "prost-types", + "protobuf", + "protobuf-support", + "psm", + "pulldown-cmark", + "pulldown-cmark-to-cmark", + "quanta", + "quick-error", + "quote", + "radix_trie", + "rand 0.10.1", + "rand 0.8.8", + "rand 0.9.5", + "rand_chacha 0.3.1", + "rand_chacha 0.9.0", + "rand_core 0.10.0", + "rand_core 0.6.4", + "rand_core 0.9.5", + "rand_xorshift", + "rand_xoshiro", + "rapidhash", + "raw-cpuid", + "rayon", + "rayon-core", + "rcgen", + "recursive", + "recursive-proc-macro-impl", + "regex", + "regex-automata", + "regex-lite", + "regex-syntax", + "reqwest", + "rfc6979", + "ring", + "rlimit", + "rmp", + "rmp-serde", + "rmpv", + "rsa", + "rust-stemmers 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rust-stemmers 1.2.0 (git+https://github.com/structured-world/rust-stemmers.git?branch=main)", + "rust_decimal", + "rustc-hash", + "rustc_version", + "rustix", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "rustls-platform-verifier", + "rustls-rustcrypto", + "rustls-webpki 0.102.8", + "rustls-webpki 0.103.15", + "rustversion", + "rusty-fork", + "ryu", + "s2", + "same-file", + "scopeguard", + "sec1", + "security-framework", + "security-framework-sys", + "semver", + "serde", + "serde-value", + "serde_bytes", + "serde_core", + "serde_derive", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "serde_yaml", + "serde_yaml_ng", + "sha2 0.10.9", + "sha2 0.11.0", + "sharded-slab", + "shlex 2.0.1", + "signal-hook-registry", + "signature", + "simd-adler32", + "simple_asn1", + "siphasher", + "sketches-ddsketch 0.3.1", + "sketches-ddsketch 0.4.0", + "slab", + "smallvec", + "smol_str", + "socket2", + "spin 0.9.8", + "spki", + "sqlparser", + "stable_deref_trait", + "stacker", + "streamvbyte64", + "stringprep", + "strsim", + "structured-proxy", + "structured-zstd", + "subtle", + "syn 2.0.117", + "syn 3.0.4", + "sync_wrapper", + "synstructure 0.14.0", + "sysinfo", + "tantivy", + "tantivy-bitpacker", + "tantivy-columnar", + "tantivy-common", + "tantivy-fst", + "tantivy-query-grammar", + "tantivy-sstable", + "tantivy-stacker", + "tantivy-tokenizer-api", + "tempfile", + "test-log", + "test-log-core", + "test-log-macros", + "thiserror 1.0.69", + "thiserror 2.0.18", + "thiserror-impl 1.0.69", + "thiserror-impl 2.0.18", + "thread_local", + "time", + "time-core", + "time-macros", + "tinystr", + "tinytemplate", + "tinyvec", + "tokio", + "tokio-macros", + "tokio-postgres", + "tokio-rustls", + "tokio-stream", + "tokio-test", + "tokio-util", + "tonic", + "tonic-build", + "tonic-health", + "tonic-prost", + "tonic-prost-build", + "tonic-types", + "tower", + "tower-http 0.6.11", + "tower-http 0.7.1", + "tower-layer", + "tower-service", + "tracing", + "tracing-attributes", + "tracing-core", + "tracing-log", + "tracing-serde", + "tracing-subscriber", + "try-lock", + "twox-hash", + "typeid", + "typenum", + "typetag", + "typetag-impl", + "ucd-trie", + "unarray", + "unicase", + "unicode-bidi", + "unicode-ident", + "unicode-normalization", + "unicode-properties", + "unicode-segmentation", + "unicode-xid", + "universal-hash 0.5.1", + "universal-hash 0.6.1", + "unsafe-libyaml", + "untrusted 0.7.1", + "untrusted 0.9.0", + "url", + "utf8-ranges", + "utf8_iter", + "utf8parse", + "uuid", + "validit", + "version_check", + "wait-timeout", + "walkdir", + "want", + "webpki-roots", + "whatlang", + "whoami", + "winapi", + "winapi-util", + "windows", + "windows-collections", + "windows-core", + "windows-future", + "windows-implement", + "windows-interface", + "windows-link", + "windows-numerics", + "windows-result", + "windows-strings", + "windows-sys 0.59.0", + "windows-sys 0.61.2", + "windows-targets", + "windows-threading", + "windows_x86_64_msvc", + "writeable", + "x25519-dalek", + "x509-certificate", + "xxhash-rust", + "yasna", + "yoke", + "yoke-derive", + "zerocopy", + "zerocopy-derive", + "zerofrom", + "zerofrom-derive", + "zeroize", + "zeroize_derive", + "zerotrie", + "zerovec", + "zerovec-derive", + "zmij", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpubits" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32c" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a47af21622d091a8f0fb295b88bc886ac74efcc613efc19f5d0b21de5c89e47" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "crc32fast" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" @@ -791,6 +1670,41 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "criterion" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "950046b2aa2492f9a536f5f4f9a3de7b9e2476e575e05bd6c333371add4d98f3" +dependencies = [ + "alloca", + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "itertools 0.13.0", + "num-traits", + "oorandom", + "page_size", + "plotters", + "rayon", + "regex", + "serde", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8d80a2f4f5b554395e47b5d8305bc3d27813bacb73493eb1001e8f76dae29ea" +dependencies = [ + "cast", + "itertools 0.13.0", +] + [[package]] name = "crossbeam-channel" version = "0.5.15" @@ -850,17 +1764,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", - "rand_core 0.6.4", "typenum", ] [[package]] name = "crypto-common" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ + "getrandom 0.4.2", "hybrid-array", + "rand_core 0.10.0", ] [[package]] @@ -869,7 +1784,16 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" dependencies = [ - "cipher", + "cipher 0.4.4", +] + +[[package]] +name = "ctr" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baaca1c4b237092596f64d571e9db6ce4109c4ef9742e27590f1709594461f21" +dependencies = [ + "cipher 0.5.2", ] [[package]] @@ -914,8 +1838,18 @@ version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.23.0", + "darling_macro 0.23.0", +] + +[[package]] +name = "darling" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed17f5901b6630b993ca003def43f2f8ef4014fc13b047b57aad617ff32bc2ec" +dependencies = [ + "darling_core 0.24.1", + "darling_macro 0.24.1", ] [[package]] @@ -931,17 +1865,41 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "darling_core" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6837e2cf7485aaae18f86181d2f0e9a7ed297a025e220aeabf63fdebd3a2ddff" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 3.0.4", +] + [[package]] name = "darling_macro" version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ - "darling_core", + "darling_core 0.23.0", "quote", "syn 2.0.117", ] +[[package]] +name = "darling_macro" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785" +dependencies = [ + "darling_core 0.24.1", + "quote", + "syn 3.0.4", +] + [[package]] name = "dashmap" version = "6.1.0" @@ -956,6 +1914,12 @@ dependencies = [ "parking_lot_core", ] +[[package]] +name = "data-encoding" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" + [[package]] name = "datasketches" version = "0.2.0" @@ -973,6 +1937,20 @@ dependencies = [ "zeroize", ] +[[package]] +name = "der-parser" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + [[package]] name = "deranged" version = "0.5.8" @@ -983,6 +1961,17 @@ dependencies = [ "serde_core", ] +[[package]] +name = "derive-new" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cdc8d50f426189eef89dac62fabfa0abb27d5cc008f25bf4156a0203325becc" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "derive_more" version = "2.1.1" @@ -1026,10 +2015,20 @@ checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c" dependencies = [ "block-buffer 0.12.0", "const-oid 0.10.2", - "crypto-common 0.2.1", + "crypto-common 0.2.2", "ctutils", ] +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags", + "objc2", +] + [[package]] name = "display-more" version = "0.2.6" @@ -1040,12 +2039,29 @@ dependencies = [ "chrono-tz", ] +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + [[package]] name = "downcast-rs" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117240f60069e65410b3ae1bb213295bd828f707b5bec6596a1afc8793ce0cbc" +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "ecdsa" version = "0.16.9" @@ -1111,6 +2127,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + [[package]] name = "enum_dispatch" version = "0.3.13" @@ -1123,6 +2145,40 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "env_filter" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "log", +] + +[[package]] +name = "envoy-types" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1fdd3fa7e8347872dd5d6e8626441589f0fd87d7fcf3003a4cca2103ecc39c0" +dependencies = [ + "futures-core", + "prost", + "tonic", + "tonic-prost", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -1150,6 +2206,23 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "evmap" +version = "11.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8874945f036109c72242964c1174cf99434e30cfa45bf45fedc983f50046f8" +dependencies = [ + "hashbag", + "left-right", + "smallvec", +] + +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + [[package]] name = "fastdivide" version = "0.4.2" @@ -1190,6 +2263,17 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" +[[package]] +name = "flate2" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" +dependencies = [ + "crc32fast", + "miniz_oxide", + "zlib-rs", +] + [[package]] name = "flume" version = "0.12.0" @@ -1217,6 +2301,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + [[package]] name = "fs4" version = "0.13.1" @@ -1228,19 +2321,58 @@ dependencies = [ ] [[package]] -name = "futures-channel" +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", + "futures-task", + "futures-util", ] [[package]] -name = "futures-core" -version = "0.3.32" +name = "futures-io" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" [[package]] name = "futures-macro" @@ -1271,9 +2403,13 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ + "futures-channel", "futures-core", + "futures-io", "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "slab", ] @@ -1312,7 +2448,19 @@ checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", ] [[package]] @@ -1323,7 +2471,7 @@ checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 6.0.0", "rand_core 0.10.0", "wasip2", "wasip3", @@ -1336,7 +2484,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" dependencies = [ "opaque-debug", - "polyval", + "polyval 0.6.2", +] + +[[package]] +name = "ghash" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eecf2d5dc9b66b732b97707a0210906b1d30523eb773193ab777c0c84b3e8d5" +dependencies = [ + "polyval 0.7.3", +] + +[[package]] +name = "globset" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e47d37d2ae4464254884b60ab7071be2b876a9c35b696bd018ddcc76847309cd" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", ] [[package]] @@ -1369,6 +2539,23 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + +[[package]] +name = "hashbag" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7040a10f52cba493ddb09926e15d10a9d8a28043708a405931fe4c6f19fac064" + [[package]] name = "hashbrown" version = "0.14.5" @@ -1412,6 +2599,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hkdf" version = "0.12.4" @@ -1521,6 +2714,21 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.27.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa8e654703247911e29c23fbeaa261834bd9bb74efba2f9acddc37bfb127f53" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", +] + [[package]] name = "hyper-timeout" version = "0.5.2" @@ -1540,13 +2748,16 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ + "base64 0.22.1", "bytes", "futures-channel", "futures-util", "http", "http-body", "hyper", + "ipnet", "libc", + "percent-encoding", "pin-project-lite", "socket2", "tokio", @@ -1578,6 +2789,89 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" + +[[package]] +name = "icu_properties" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" + +[[package]] +name = "icu_provider" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + [[package]] name = "id-arena" version = "2.3.0" @@ -1590,6 +2884,27 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + [[package]] name = "indexmap" version = "2.14.0" @@ -1620,6 +2935,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "inout" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" +dependencies = [ + "hybrid-array", +] + [[package]] name = "inventory" version = "0.3.24" @@ -1629,12 +2953,27 @@ dependencies = [ "rustversion", ] +[[package]] +name = "ipnet" +version = "2.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" + [[package]] name = "is_terminal_polyfill" 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 = "itertools" version = "0.14.0" @@ -1659,16 +2998,123 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror 2.0.18", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.117", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.2", + "libc", +] + [[package]] name = "js-sys" -version = "0.3.95" +version = "0.3.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" +checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e" dependencies = [ - "once_cell", + "cfg-if", + "futures-util", "wasm-bindgen", ] +[[package]] +name = "jsonwebtoken" +version = "11.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e75fe14a82d81e5f5af639997db37d8b96045938a7ac6ab18cdbe1c7467e05e1" +dependencies = [ + "base64 0.22.1", + "ed25519-dalek", + "getrandom 0.2.17", + "hmac 0.12.1", + "js-sys", + "p256", + "p384", + "pem 3.0.6", + "rand 0.8.8", + "rsa", + "serde", + "serde_json", + "sha2 0.10.9", + "signature", + "simple_asn1", + "zeroize", +] + +[[package]] +name = "lazy-regex" +version = "3.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bae91019476d3ec7147de9aa291cadb6d870abf2f3015d2da73a90325ac1496" +dependencies = [ + "lazy-regex-proc_macros", + "once_cell", + "regex-lite", +] + +[[package]] +name = "lazy-regex-proc_macros" +version = "3.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4de9c1e1439d8b7b3061b2d209809f447ca33241733d9a3c01eabf2dc8d94358" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn 2.0.117", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -1684,6 +3130,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" +[[package]] +name = "left-right" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bc015ded5d9b3054dbbdb63332cdd6ee42352ccef19e911e25117490e2f48ee" +dependencies = [ + "crossbeam-utils", + "loom", + "slab", +] + [[package]] name = "levenshtein_automata" version = "0.2.1" @@ -1692,9 +3149,9 @@ checksum = "0c2cdeb66e45e9f36bfad5bbdb4d2384e70936afbee843c6f6543f0c551ebb25" [[package]] name = "libc" -version = "0.2.185" +version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "libm" @@ -1702,12 +3159,27 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" +[[package]] +name = "libredox" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61ff90caf6077a803a240f62fdbe88645a890bbca49ef8174c3cb0404362171d" +dependencies = [ + "libc", +] + [[package]] name = "linux-raw-sys" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" +[[package]] +name = "litemap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" + [[package]] name = "lock_api" version = "0.4.14" @@ -1791,6 +3263,22 @@ dependencies = [ "rawpointer", ] +[[package]] +name = "md-5" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98" +dependencies = [ + "cfg-if", + "digest 0.11.2", +] + +[[package]] +name = "md5" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebb8d8732c6a6df3d8f032a82911cfc747e00efb95cc46e8d0acd5b5b88570c" + [[package]] name = "measure_time" version = "0.9.0" @@ -1826,12 +3314,48 @@ dependencies = [ [[package]] name = "metrics" -version = "0.24.3" +version = "0.24.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d5312e9ba3771cfa961b585728215e3d972c950a3eed9252aa093d6301277e8" +checksum = "89550ee9f79e88fef3119de263694973a8adb26c21d75322164fb8c493039fe2" dependencies = [ - "ahash", "portable-atomic", + "rapidhash", +] + +[[package]] +name = "metrics-exporter-prometheus" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1db0d8f1fc9e62caebd0319e11eaec5822b0186c171568f0480b46a0137f9108" +dependencies = [ + "base64 0.22.1", + "evmap", + "indexmap", + "metrics", + "metrics-util", + "quanta", + "thiserror 2.0.18", +] + +[[package]] +name = "metrics-util" +version = "0.20.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96f8722f8562635f92f8ed992f26df0532266eb03d5202607c20c0d7e9745e13" +dependencies = [ + "aho-corasick", + "crossbeam-epoch", + "crossbeam-utils", + "hashbrown 0.16.1", + "indexmap", + "metrics", + "ordered-float 5.3.0", + "quanta", + "radix_trie", + "rand 0.9.5", + "rand_xoshiro", + "rapidhash", + "sketches-ddsketch 0.3.1", ] [[package]] @@ -1846,6 +3370,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "miniz_oxide" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "mio" version = "1.2.0" @@ -1853,7 +3387,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", "windows-sys 0.61.2", ] @@ -1869,6 +3403,15 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2195bf6aa996a481483b29d62a7663eed3fe39600c460e323f8ff41e90bdd89b" +[[package]] +name = "nanoid" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8628de41fe064cc3f0cf07f3d299ee3e73521adaff72278731d5c8cae3797873" +dependencies = [ + "rand 0.9.5", +] + [[package]] name = "ndarray" version = "0.16.1" @@ -1884,6 +3427,15 @@ dependencies = [ "rawpointer", ] +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + [[package]] name = "nom" version = "7.1.3" @@ -1894,6 +3446,15 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "ntapi" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" +dependencies = [ + "winapi", +] + [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -1989,6 +3550,90 @@ dependencies = [ "rustc-hash", ] +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags", + "dispatch2", + "objc2", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags", + "objc2", +] + +[[package]] +name = "objc2-io-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" +dependencies = [ + "libc", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-open-directory" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb82bed227edf5201dfedf072bba4015a33d3d4a98519837295a90f0a23f676d" +dependencies = [ + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-system-configuration" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7216bd11cbda54ccabcab84d523dc93b858ec75ecfb3a7d89513fa22464da396" +dependencies = [ + "objc2-core-foundation", +] + +[[package]] +name = "object" +version = "0.39.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e5a6c098c7a3b6547378093f5cc30bc54fd361ce711e05293a5cc589562739b" +dependencies = [ + "memchr", +] + +[[package]] +name = "oid-registry" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" +dependencies = [ + "asn1-rs", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -2007,6 +3652,12 @@ version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "269bca4c2591a28585d6bf10d9ed0332b7d76900a1b02bec41bdc3a2cdcda107" +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + [[package]] name = "opaque-debug" version = "0.3.1" @@ -2037,7 +3688,7 @@ dependencies = [ "rand 0.10.1", "serde", "smallvec", - "thiserror", + "thiserror 2.0.18", "tracing", "validit", ] @@ -2080,6 +3731,21 @@ dependencies = [ "tokio", ] +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + [[package]] name = "ordered-float" version = "5.3.0" @@ -2122,6 +3788,16 @@ dependencies = [ "sha2 0.10.9", ] +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "parking_lot" version = "0.12.5" @@ -2172,6 +3848,26 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3420ea4424090cbd75a688996f696a807c68d6744b4863591b86435dc3078e9" +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64 0.22.1", + "serde_core", +] + +[[package]] +name = "pem" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d354a98a3d1251555de99e8fdd8afda05573c31b82f59063a7b0a29b5527f120" +dependencies = [ + "base64 0.23.1", + "serde_core", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -2241,13 +3937,67 @@ dependencies = [ "indexmap", ] +[[package]] +name = "pg_interval" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8408fb1e08ebf2e7676d001caa6482d1b33e7e1f1dad9d3a758a05640eea2026" +dependencies = [ + "bytes", + "chrono", + "postgres-types", +] + +[[package]] +name = "pgwire" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c667f8f4046889d52ed11e17aae954b1816d3df48dd23bdebcf199cef332df93" +dependencies = [ + "async-trait", + "aws-lc-rs", + "base64 0.23.1", + "bytes", + "chrono", + "derive-new", + "futures", + "hex", + "lazy-regex", + "md5", + "pg_interval", + "postgres-types", + "rand 0.10.1", + "rust_decimal", + "rustls-pki-types", + "ryu", + "serde", + "serde_json", + "smol_str", + "stringprep", + "thiserror 2.0.18", + "tokio", + "tokio-rustls", + "tokio-util", + "x509-certificate", +] + [[package]] name = "phf" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ - "phf_shared", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_shared 0.13.1", + "serde", ] [[package]] @@ -2257,7 +4007,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" dependencies = [ "phf_generator", - "phf_shared", + "phf_shared 0.11.3", ] [[package]] @@ -2266,7 +4016,7 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ - "phf_shared", + "phf_shared 0.11.3", "rand 0.8.8", ] @@ -2279,6 +4029,15 @@ dependencies = [ "siphasher", ] +[[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" version = "1.1.11" @@ -2337,6 +4096,40 @@ dependencies = [ "spki", ] +[[package]] +name = "pkg-config" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" + +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + [[package]] name = "poly1305" version = "0.8.0" @@ -2345,7 +4138,7 @@ checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" dependencies = [ "cpufeatures 0.2.17", "opaque-debug", - "universal-hash", + "universal-hash 0.5.1", ] [[package]] @@ -2357,7 +4150,18 @@ dependencies = [ "cfg-if", "cpufeatures 0.2.17", "opaque-debug", - "universal-hash", + "universal-hash 0.5.1", +] + +[[package]] +name = "polyval" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0fa31d631f2b2cb2a544d0aa321ce847a94764d701ca2becc411138b93d49cd" +dependencies = [ + "cpubits", + "cpufeatures 0.3.0", + "universal-hash 0.6.1", ] [[package]] @@ -2375,6 +4179,48 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "postgres-protocol" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08808e3c483c46e999108051c78334f473d5adb59d78bb80a1268c7e6aa6c514" +dependencies = [ + "base64 0.22.1", + "byteorder", + "bytes", + "fallible-iterator", + "hmac 0.13.0", + "md-5", + "memchr", + "rand 0.10.1", + "sha2 0.11.0", + "stringprep", +] + +[[package]] +name = "postgres-types" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "851ca9db4932932d69f3ea811b1abe63087a0f740a47692619dd40d4899b68be" +dependencies = [ + "array-init", + "bytes", + "chrono", + "fallible-iterator", + "postgres-protocol", + "serde_core", + "serde_json", +] + +[[package]] +name = "potential_utf" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +dependencies = [ + "zerovec", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -2400,6 +4246,16 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "prettyplease" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bfe0f4c752e450fc2faf62654f1c134747922825d5b04ca717b8874f41a40c0" +dependencies = [ + "proc-macro2", + "syn 3.0.4", +] + [[package]] name = "primeorder" version = "0.13.6" @@ -2409,6 +4265,15 @@ dependencies = [ "elliptic-curve", ] +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -2418,6 +4283,40 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prometheus" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ca5326d8d0b950a9acd87e6a3f94745394f62e4dae1b1ee22b2bc0c394af43a" +dependencies = [ + "cfg-if", + "fnv", + "lazy_static", + "memchr", + "parking_lot", + "protobuf", + "thiserror 2.0.18", +] + +[[package]] +name = "proptest" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" +dependencies = [ + "bit-set", + "bit-vec 0.8.0", + "bitflags", + "num-traits", + "rand 0.9.5", + "rand_chacha 0.9.0", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + [[package]] name = "prost" version = "0.14.3" @@ -2439,7 +4338,7 @@ dependencies = [ "log", "multimap", "petgraph", - "prettyplease", + "prettyplease 0.2.37", "prost", "prost-types", "pulldown-cmark", @@ -2462,6 +4361,19 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "prost-reflect" +version = "0.16.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01b80ea363c31af2de2b92e3c07ed1156628f7838c4afb4df75ee78a37fedbd1" +dependencies = [ + "base64 0.22.1", + "prost", + "prost-types", + "serde", + "serde-value", +] + [[package]] name = "prost-types" version = "0.14.3" @@ -2471,6 +4383,36 @@ dependencies = [ "prost", ] +[[package]] +name = "protobuf" +version = "3.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d65a1d4ddae7d8b5de68153b48f6aa3bba8cb002b243dbdbc55a5afbc98f99f4" +dependencies = [ + "once_cell", + "protobuf-support", + "thiserror 1.0.69", +] + +[[package]] +name = "protobuf-support" +version = "3.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e36c2f31e0a47f9280fb347ef5e461ffcd2c52dd520d8e216b52f93b0b0d7d6" +dependencies = [ + "thiserror 1.0.69", +] + +[[package]] +name = "psm" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd034599e63b970727f70d79e02d62390a4a84f7c6b827c27c46d5ac3fa622" +dependencies = [ + "ar_archive_writer", + "cc", +] + [[package]] name = "pulldown-cmark" version = "0.13.3" @@ -2554,6 +4496,27 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "quanta" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7" +dependencies = [ + "crossbeam-utils", + "libc", + "once_cell", + "raw-cpuid", + "wasi 0.11.1+wasi-snapshot-preview1", + "web-sys", + "winapi", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.45" @@ -2563,22 +4526,49 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "r-efi" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + [[package]] name = "rand" version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" dependencies = [ - "rand_chacha", + "libc", + "rand_chacha 0.3.1", "rand_core 0.6.4", ] +[[package]] +name = "rand" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + [[package]] name = "rand" version = "0.10.1" @@ -2600,6 +4590,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + [[package]] name = "rand_core" version = "0.6.4" @@ -2609,12 +4609,57 @@ dependencies = [ "getrandom 0.2.17", ] +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + [[package]] name = "rand_core" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core 0.9.5", +] + +[[package]] +name = "rand_xoshiro" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f703f4665700daf5512dcca5f43afa6af89f09db47fb56be587f80636bda2d41" +dependencies = [ + "rand_core 0.9.5", +] + +[[package]] +name = "rapidhash" +version = "4.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5da7e78a036ce858e8d55b7e7dc8ba3a88b78350fd2155d3591bbd966b58589e" +dependencies = [ + "rustversion", +] + +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags", +] + [[package]] name = "rawpointer" version = "0.2.1" @@ -2641,6 +4686,40 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "rcgen" +version = "0.14.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8774e05a7d0de114588e6a28fe7e71694b82614ed569d86d8b389dfbc98b8ad8" +dependencies = [ + "pem 4.0.0", + "ring", + "rustls-pki-types", + "time", + "x509-parser", + "yasna", +] + +[[package]] +name = "recursive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0786a43debb760f491b1bc0269fe5e84155353c67482b9e60d0cfb596054b43e" +dependencies = [ + "recursive-proc-macro-impl", + "stacker", +] + +[[package]] +name = "recursive-proc-macro-impl" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76009fbe0614077fc1a2ce255e3a1881a2e3a3527097d5dc6d8212c585e7e38b" +dependencies = [ + "quote", + "syn 2.0.117", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -2673,12 +4752,54 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-lite" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" + [[package]] name = "regex-syntax" version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +[[package]] +name = "reqwest" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16a1cfa75cc186dd73d5818e510e042e40927bccc9c236b061cea97e1eb08029" +dependencies = [ + "base64 0.23.1", + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "serde", + "serde_json", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http 0.6.11", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "rfc6979" version = "0.4.0" @@ -2699,10 +4820,19 @@ dependencies = [ "cfg-if", "getrandom 0.2.17", "libc", - "untrusted", + "untrusted 0.9.0", "windows-sys 0.52.0", ] +[[package]] +name = "rlimit" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f35ee2729c56bb610f6dba436bf78135f728b7373bdffae2ec815b2d3eb98cc3" +dependencies = [ + "libc", +] + [[package]] name = "rmp" version = "0.8.15" @@ -2773,6 +4903,24 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "rust_decimal" +version = "1.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7653272e75dcac41dc199fbea6f5797633994fafd339943c06c9af16bf29cd3a" +dependencies = [ + "arrayvec", + "borsh", + "bytes", + "num-traits", + "postgres-types", + "rand 0.8.8", + "rand 0.9.5", + "serde", + "serde_json", + "wasm-bindgen", +] + [[package]] name = "rustc-hash" version = "2.1.2" @@ -2788,6 +4936,15 @@ dependencies = [ "semver", ] +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + [[package]] name = "rustix" version = "1.1.4" @@ -2807,14 +4964,28 @@ version = "0.23.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" dependencies = [ + "aws-lc-rs", "log", "once_cell", + "ring", "rustls-pki-types", "rustls-webpki 0.103.15", "subtle", "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pki-types" version = "1.15.1" @@ -2824,14 +4995,41 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki 0.103.15", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + [[package]] name = "rustls-rustcrypto" version = "0.0.2-alpha" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f12052947763ab8515f753315357599e9b0b4dab3b8ba15f30f725fe6d025557" dependencies = [ - "aead", - "aes-gcm", + "aead 0.5.2", + "aes-gcm 0.10.3", "chacha20poly1305", "crypto-common 0.1.7", "der", @@ -2862,7 +5060,7 @@ checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ "ring", "rustls-pki-types", - "untrusted", + "untrusted 0.9.0", ] [[package]] @@ -2871,9 +5069,10 @@ version = "0.103.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" dependencies = [ + "aws-lc-rs", "ring", "rustls-pki-types", - "untrusted", + "untrusted 0.9.0", ] [[package]] @@ -2882,6 +5081,24 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "rusty-fork" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + [[package]] name = "s2" version = "0.2.0" @@ -2893,6 +5110,24 @@ dependencies = [ "serde", ] +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -2919,6 +5154,29 @@ dependencies = [ "zeroize", ] +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "self_cell" version = "1.2.2" @@ -2941,6 +5199,16 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float 2.10.1", + "serde", +] + [[package]] name = "serde_bytes" version = "0.11.19" @@ -2984,6 +5252,55 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "serde_yaml_ng" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4db627b98b36d4203a7b458cf3573730f2bb591b28871d916dfa9efabfd41f" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "sha2" version = "0.10.9" @@ -3017,9 +5334,15 @@ dependencies = [ [[package]] name = "shlex" -version = "1.3.0" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "shlex" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "signal-hook-registry" @@ -3041,12 +5364,52 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "simple_asn1" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror 2.0.18", + "time", +] + [[package]] name = "siphasher" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" +[[package]] +name = "sketches-ddsketch" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6f73aeb92d671e0cc4dca167e59b2deb6387c375391bc99ee743f326994a2b" + [[package]] name = "sketches-ddsketch" version = "0.4.0" @@ -3071,6 +5434,16 @@ dependencies = [ "serde", ] +[[package]] +name = "smol_str" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aaa7368fcf4852a4c2dd92df0cace6a71f2091ca0a23391ce7f3a31833f1523" +dependencies = [ + "borsh", + "serde_core", +] + [[package]] name = "socket2" version = "0.6.3" @@ -3108,11 +5481,12 @@ dependencies = [ [[package]] name = "sqlparser" -version = "0.52.0" +version = "0.63.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a875d8cd437cc8a97e9aeaeea352ec9a19aea99c23e9effb17757291de80b08" +checksum = "3679862809bd1f92e563cf6fd820e28f39135303668001c10331d5027183e8b4" dependencies = [ "log", + "recursive", ] [[package]] @@ -3121,6 +5495,19 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "stacker" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707f49d46706bacf8a2b00d51dace3f9de527c13eec3778f570c411f89e69967" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "windows-sys 0.61.2", +] + [[package]] name = "streamvbyte64" version = "0.2.0" @@ -3131,18 +5518,72 @@ dependencies = [ "num-traits", ] +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "structured-proxy" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d68fd53b65a2af84a2a89596d0aaa6247e03d6a6e99f958296c40342104a177b" +dependencies = [ + "anyhow", + "async-trait", + "axum", + "base64 0.23.1", + "bytes", + "clap", + "dashmap", + "envoy-types", + "futures", + "getrandom 0.4.2", + "globset", + "http", + "ipnet", + "jsonwebtoken", + "prometheus", + "prost", + "prost-reflect", + "reqwest", + "rustls", + "serde", + "serde_json", + "serde_urlencoded", + "serde_yaml", + "sha2 0.11.0", + "thiserror 2.0.18", + "tokio", + "tonic", + "tonic-health", + "tower", + "tower-http 0.7.1", + "tracing", + "tracing-subscriber", + "url", + "webpki-roots", +] + [[package]] name = "structured-zstd" -version = "0.0.49" +version = "0.0.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "392eea135985654a313cdfee001e93031bacd96a3693a5dc4b0d13793e40b27a" +checksum = "9c56d0b6324414776e43d891a0f5a6ecf0d2f5bef008e80f627cc0f164f0a9d5" dependencies = [ + "fastrand", "twox-hash", ] @@ -3179,6 +5620,46 @@ name = "sync_wrapper" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "synstructure" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "sysinfo" +version = "0.39.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2071df9448915b71c4fe6d25deaf1c22f12bd234f01540b77312bb8e41361e6" +dependencies = [ + "libc", + "memchr", + "ntapi", + "objc2-core-foundation", + "objc2-io-kit", + "objc2-open-directory", + "windows", +] [[package]] name = "tantivy" @@ -3188,7 +5669,7 @@ checksum = "778da245841522199d512d19511b041425d8cff3a8f262b4e1516fceb050289a" dependencies = [ "aho-corasick", "arc-swap", - "base64", + "base64 0.22.1", "bitpacking", "bon", "byteorder", @@ -3216,7 +5697,7 @@ dependencies = [ "rustc-hash", "serde", "serde_json", - "sketches-ddsketch", + "sketches-ddsketch 0.4.0", "smallvec", "tantivy-bitpacker", "tantivy-columnar", @@ -3226,7 +5707,7 @@ dependencies = [ "tantivy-stacker", "tantivy-tokenizer-api", "tempfile", - "thiserror", + "thiserror 2.0.18", "time", "typetag", "uuid", @@ -3290,7 +5771,7 @@ checksum = "dfadb8526b6da90704feb293b0701a6aae62ea14983143344be2dc5ce30f1d82" dependencies = [ "fnv", "nom", - "ordered-float", + "ordered-float 5.3.0", "serde", "serde_json", ] @@ -3340,19 +5821,71 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.2", + "getrandom 0.3.4", "once_cell", "rustix", "windows-sys 0.52.0", ] +[[package]] +name = "test-log" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b9c218384242b5c89b68303ab6f6fc53a312d923f0c14dc6bb860c6aeee40f1" +dependencies = [ + "env_logger", + "test-log-macros", + "tracing-subscriber", +] + +[[package]] +name = "test-log-core" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c26ef8b00e4d382e59f6a8ddb3cd790b3a5bb29f21a358a9a69ea2f29f13f27b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "test-log-macros" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "944ad38adcbb71eaa682c56bceeb079e4ca82b4b3edc2a0fde5cb297b77dac8d" +dependencies = [ + "syn 2.0.117", + "test-log-core", +] + +[[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", + "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 2.0.117", ] [[package]] @@ -3406,6 +5939,26 @@ dependencies = [ "time-core", ] +[[package]] +name = "tinystr" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "tinyvec" version = "1.11.0" @@ -3449,6 +6002,32 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "tokio-postgres" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a528f7d280f6d5b9cd149635c8705b0dd049754bc67d81d31fa25169a93809d3" +dependencies = [ + "async-trait", + "byteorder", + "bytes", + "fallible-iterator", + "futures-channel", + "futures-util", + "log", + "parking_lot", + "percent-encoding", + "phf 0.13.1", + "pin-project-lite", + "postgres-protocol", + "postgres-types", + "rand 0.10.1", + "socket2", + "tokio", + "tokio-util", + "whoami", +] + [[package]] name = "tokio-rustls" version = "0.26.4" @@ -3468,6 +6047,18 @@ dependencies = [ "futures-core", "pin-project-lite", "tokio", + "tokio-util", +] + +[[package]] +name = "tokio-test" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6d24790a10a7af737693a3e8f1d03faef7e6ca0cc99aae5066f533766de545" +dependencies = [ + "futures-core", + "tokio", + "tokio-stream", ] [[package]] @@ -3483,6 +6074,36 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.15+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1340ea94a5856333492c9064b02c778b191dd2c853778d9609debdcdfea3a614" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +dependencies = [ + "winnow", +] + [[package]] name = "tonic" version = "0.14.5" @@ -3491,7 +6112,7 @@ checksum = "fec7c61a0695dc1887c1b53952990f3ad2e3a31453e1f49f10e75424943a93ec" dependencies = [ "async-trait", "axum", - "base64", + "base64 0.22.1", "bytes", "h2", "http", @@ -3520,12 +6141,25 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1882ac3bf5ef12877d7ed57aad87e75154c11931c2ba7e6cde5e22d63522c734" dependencies = [ - "prettyplease", + "prettyplease 0.2.37", "proc-macro2", "quote", "syn 2.0.117", ] +[[package]] +name = "tonic-health" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4ff0636fef47afb3ec02818f5bceb4377b8abb9d6a386aeade18bd6212f8eb7" +dependencies = [ + "prost", + "tokio", + "tokio-stream", + "tonic", + "tonic-prost", +] + [[package]] name = "tonic-prost" version = "0.14.5" @@ -3543,7 +6177,7 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3144df636917574672e93d0f56d7edec49f90305749c668df5101751bb8f95a" dependencies = [ - "prettyplease", + "prettyplease 0.2.37", "proc-macro2", "prost-build", "prost-types", @@ -3554,19 +6188,65 @@ dependencies = [ ] [[package]] -name = "tower" -version = "0.5.3" +name = "tonic-types" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a875a902255423d34c1f20838ab374126db8eb41625b7947a1d54113b0b7399" +dependencies = [ + "prost", + "prost-types", + "tonic", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "indexmap", + "pin-project-lite", + "slab", + "sync_wrapper", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-http" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +checksum = "08a05a66a4fdd61cbbe0a1d755ffe0ca6aba159dd4820936a0ff8a8278245b9c" dependencies = [ - "futures-core", - "futures-util", - "indexmap", + "bitflags", + "bytes", + "http", + "http-body", + "percent-encoding", "pin-project-lite", - "slab", - "sync_wrapper", - "tokio", - "tokio-util", "tower-layer", "tower-service", "tracing", @@ -3590,6 +6270,7 @@ version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -3627,6 +6308,16 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "tracing-core", +] + [[package]] name = "tracing-subscriber" version = "0.3.23" @@ -3637,12 +6328,15 @@ dependencies = [ "nu-ansi-term", "once_cell", "regex-automata", + "serde", + "serde_json", "sharded-slab", "smallvec", "thread_local", "tracing", "tracing-core", "tracing-log", + "tracing-serde", ] [[package]] @@ -3699,12 +6393,24 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + [[package]] name = "unicase" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + [[package]] name = "unicode-ident" version = "1.0.24" @@ -3720,6 +6426,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + [[package]] name = "unicode-segmentation" version = "1.13.2" @@ -3748,18 +6460,58 @@ dependencies = [ "subtle", ] +[[package]] +name = "universal-hash" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4987bdc12753382e0bec4a65c50738ffaabc998b9cdd1f952fb5f39b0048a96" +dependencies = [ + "crypto-common 0.2.2", + "ctutils", +] + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + [[package]] name = "utf8-ranges" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcfc827f90e53a02eaef5e535ee14266c1d569214c6aa70133a624d8a3164ba" +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -3805,6 +6557,25 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -3820,6 +6591,15 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasi" +version = "0.14.7+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" +dependencies = [ + "wasip2", +] + [[package]] name = "wasip2" version = "1.0.2+wasi-0.2.9" @@ -3838,24 +6618,44 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasite" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fe902b4a6b8028a753d5424909b764ccf79b7a209eac9bf97e59cda9f71a42" +dependencies = [ + "wasi 0.14.7+wasi-0.2.4", +] + [[package]] name = "wasm-bindgen" -version = "0.2.118" +version = "0.2.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" +checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf" dependencies = [ "cfg-if", "once_cell", "rustversion", + "serde", "wasm-bindgen-macro", "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ef4c5d3d2cdf5c54f4231181768f5510842e350db025faf1f7163b1030ed928" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "wasm-bindgen-macro" -version = "0.2.118" +version = "0.2.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" +checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3863,22 +6663,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.118" +version = "0.2.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" +checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.117", + "syn 3.0.4", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.118" +version = "0.2.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" +checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e" dependencies = [ "unicode-ident", ] @@ -3917,6 +6717,25 @@ dependencies = [ "semver", ] +[[package]] +name = "web-sys" +version = "0.3.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbddc4a036f00ec4f18c83445bd3115cb306a91da554919a099d9222fe4a7f8" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webpki-roots" version = "1.0.9" @@ -3935,6 +6754,19 @@ dependencies = [ "hashbrown 0.15.5", ] +[[package]] +name = "whoami" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626c4bac6755d76ffc12cb01b2eac751db1996b9e0041de9aa02c8c211ddc82c" +dependencies = [ + "libc", + "libredox", + "objc2-system-configuration", + "wasite", + "web-sys", +] + [[package]] name = "winapi" version = "0.3.9" @@ -3951,12 +6783,42 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core", +] + [[package]] name = "windows-core" version = "0.62.2" @@ -3970,6 +6832,17 @@ dependencies = [ "windows-strings", ] +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + [[package]] name = "windows-implement" version = "0.60.2" @@ -3998,6 +6871,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core", + "windows-link", +] + [[package]] name = "windows-result" version = "0.4.1" @@ -4059,6 +6942,15 @@ dependencies = [ "windows_x86_64_msvc", ] +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -4107,6 +6999,15 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + [[package]] name = "wit-bindgen" version = "0.51.0" @@ -4136,7 +7037,7 @@ dependencies = [ "anyhow", "heck", "indexmap", - "prettyplease", + "prettyplease 0.2.37", "syn 2.0.117", "wasm-metadata", "wit-bindgen-core", @@ -4150,7 +7051,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" dependencies = [ "anyhow", - "prettyplease", + "prettyplease 0.2.37", "proc-macro2", "quote", "syn 2.0.117", @@ -4195,6 +7096,12 @@ dependencies = [ "wasmparser", ] +[[package]] +name = "writeable" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" + [[package]] name = "x25519-dalek" version = "2.0.1" @@ -4206,12 +7113,82 @@ dependencies = [ "zeroize", ] +[[package]] +name = "x509-certificate" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca9eb9a0c822c67129d5b8fcc2806c6bc4f50496b420825069a440669bcfbf7f" +dependencies = [ + "bcder", + "bytes", + "chrono", + "der", + "hex", + "pem 3.0.6", + "ring", + "signature", + "spki", + "thiserror 2.0.18", + "zeroize", +] + +[[package]] +name = "x509-parser" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "ring", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] + [[package]] name = "xxhash-rust" version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" +[[package]] +name = "yasna" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5f6765e852b9b4dc8e2a76843e4d64d1cea8e79bcde0b6901aea8e7c7f08282" +dependencies = [ + "bit-vec 0.9.1", + "time", +] + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33811428bee40dbceb6d545e95754741d17a6aef9a4849f0fd62e2ba4f412a78" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", + "synstructure 0.14.0", +] + [[package]] name = "zerocopy" version = "0.8.48" @@ -4232,6 +7209,27 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", + "synstructure 0.14.0", +] + [[package]] name = "zeroize" version = "1.9.0" @@ -4252,6 +7250,45 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "zerotrie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "zlib-rs" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112" + [[package]] name = "zmij" version = "1.0.21" diff --git a/coordinode-rs b/coordinode-rs index ff122a3..b7efa9b 160000 --- a/coordinode-rs +++ b/coordinode-rs @@ -1 +1 @@ -Subproject commit ff122a3dc17bfc14bf96336ab6cb414cb10db196 +Subproject commit b7efa9b4295e5a34ddc69cd95c2f5e3ee0804562 diff --git a/coordinode/README.md b/coordinode/README.md index 5603ba6..2dc90b1 100644 --- a/coordinode/README.md +++ b/coordinode/README.md @@ -171,8 +171,12 @@ db.cypher( read_concern="majority", ) -# Durable write, acknowledged by a majority of the cluster -db.cypher("CREATE (n:Event {t: timestamp()})", write_concern="majority") +# Writes default to a journaled majority. Weaken it explicitly per write, +# only for data that may be lost with its leader: +from coordinode import WriteConcern + +db.cypher("CREATE (n:Hit {t: timestamp()})", write_concern=1) # leader only +db.cypher("CREATE (n:Hit {t: timestamp()})", write_concern=WriteConcern(w=1, journal="memory")) # Causal read: see at least the state at raft index 42. The fence is about # which replica may answer, so it is the READ concern that has to be majority. @@ -181,12 +185,19 @@ db.cypher( after_index=42, read_concern="majority", ) + +# Write only if the node is still at the version you read. +node = db.get_node(node_id) +with db.transaction() as tx: + tx.cypher("MATCH (n) WHERE id(n) = $id SET n.seen = true", params={"id": node_id}) + tx.commit(expect={node.id: node.version}) # ABORTED / REVISION_MISMATCH if it moved +# tx.commit_ts is when the write landed: at_timestamp=tx.commit_ts reads exactly it. ``` Accepted values: - ``read_concern``: ``local`` (default) · ``majority`` · ``linearizable`` · ``snapshot``. Causal reads (``after_index`` > 0) require ``majority`` here. -- ``write_concern``: ``w0`` · ``memory`` · ``cache`` · ``w1`` (default) · ``majority`` +- ``write_concern``: ``"majority"`` (default) · a member count (``1`` leader only, ``0`` fire-and-forget) · ``WriteConcern(w, journal, timeout_ms)`` with ``journal`` one of ``journal`` (default) · ``cache`` · ``memory``. The volatile ``cache`` and ``memory`` states are accepted only with ``w`` of 0 or 1. - ``read_preference``: ``primary`` (default) · ``primary_preferred`` · ``secondary`` · ``secondary_preferred`` · ``nearest`` ## Related Packages diff --git a/coordinode/coordinode/__init__.py b/coordinode/coordinode/__init__.py index 16c2b55..17228aa 100644 --- a/coordinode/coordinode/__init__.py +++ b/coordinode/coordinode/__init__.py @@ -33,6 +33,7 @@ Transaction, TraverseResult, VectorResult, + WriteConcern, ) try: @@ -52,6 +53,8 @@ # requires the constructor, not just the shape. "MultiVector", "Path", + # How a write is acknowledged: member count or majority, and how durably. + "WriteConcern", "NodeResult", "EdgeResult", "VectorResult", diff --git a/coordinode/coordinode/client.py b/coordinode/coordinode/client.py index 3e5bc17..8d9e55c 100644 --- a/coordinode/coordinode/client.py +++ b/coordinode/coordinode/client.py @@ -9,8 +9,9 @@ import inspect import logging import re -from collections.abc import AsyncIterator, Iterator, Sequence +from collections.abc import AsyncIterator, Iterator, Mapping, Sequence from contextlib import asynccontextmanager, contextmanager, suppress +from dataclasses import dataclass from typing import Any import grpc @@ -266,9 +267,44 @@ def __init__(self, proto_node: Any) -> None: self.element_id: str = proto_node.element_id self.labels: list[str] = list(proto_node.labels) self.properties: dict[str, PyValue] = props_to_dict(proto_node.properties) + #: Timestamp of the commit that last wrote this node: the value to pass + #: in ``commit(expect=...)`` to write only if nobody changed it since. + #: ``None`` when the server did not resolve one (the wire sends zero, + #: which is never a real version). + self.version: int | None = proto_node.version or None def __repr__(self) -> str: - return f"Node(id={self.id}, element_id={self.element_id!r}, labels={self.labels}, properties={self.properties})" + return ( + f"Node(id={self.id}, element_id={self.element_id!r}, labels={self.labels}, " + f"properties={self.properties}, version={self.version})" + ) + + +_UINT32_MAX = 0xFFFF_FFFF +_UINT64_MAX = 0xFFFF_FFFF_FFFF_FFFF + + +@dataclass(frozen=True) +class WriteConcern: + """How a write is acknowledged: by how many members, holding it how durably. + + ``w`` is ``"majority"`` (the server default, survives the loss of a + minority) or a member count, the leader included: ``1`` is the leader + alone, ``0`` is fire-and-forget (the caller is not told whether the write + committed). ``journal`` is the state each counted member holds the write + in: ``"journal"`` (fsynced, the default), ``"cache"`` (RAM + NVMe overlay, + lost on power failure before the drain) or ``"memory"`` (RAM only, lost on + process crash before the drain). The volatile states are accepted only + with ``w`` of 0 or 1. ``timeout_ms`` bounds the wait for ``w``; 0 waits + without a limit, and a timed-out write is not rolled back. + + ``write_concern="majority"`` and ``write_concern=1`` are shorthands for + ``WriteConcern(w="majority")`` and ``WriteConcern(w=1)``. + """ + + w: str | int = "majority" + journal: str = "journal" + timeout_ms: int = 0 class EdgeResult: @@ -435,6 +471,10 @@ def __init__(self, client: AsyncCoordinodeClient, transaction_id: int) -> None: # writes may all be applied or none may be, and nothing on the client # can tell which. self._state = "open" + #: Timestamp (microseconds since the epoch) every write of the + #: transaction landed at; ``None`` until a commit succeeds. Pass it as + #: ``at_timestamp`` to read exactly this commit. + self.commit_ts: int | None = None # Whether the last best-effort cleanup RPC actually completed. False # after a cleanup that itself failed (both RPCs lost to the same # outage, say): the server may still hold the transaction, so an @@ -736,12 +776,21 @@ async def cypher( self._state = "open" return _rows_to_dicts(resp) - async def commit(self) -> int: + async def commit(self, *, expect: Mapping[int, int | None] | None = None) -> int: """Apply every buffered write as one unit. Returns the Raft applied index of the commit, which a later read can pass as ``after_index`` (with ``read_concern="majority"``) when it must - observe these writes. + observe these writes. The commit's timestamp is kept in + :attr:`commit_ts`: ``at_timestamp=tx.commit_ts`` reads exactly this + commit and nothing later. + + ``expect`` maps node ids to the version they must still be at + (:attr:`NodeResult.version`), or to ``None`` for a node that must not + exist. The check runs at commit, so it covers the window between the + read and this write; a mismatch refuses the whole transaction with + ABORTED, reason ``REVISION_MISMATCH``, and the expected and current + versions in the error metadata. Raises if another transaction has written the same data since this one began: conflicts are detected here, not at the statement. A rejected @@ -757,8 +806,26 @@ async def commit(self) -> int: """ from coordinode._proto.coordinode.v1.query.cypher_pb2 import ( # type: ignore[import] CommitTransactionRequest, + ExpectedNodeVersion, ) + # Validated before the open-check and the state transition: a bad + # argument is the caller's mistake, not a reason to consume the + # transaction. + expected = [] + for node_id, version in (expect or {}).items(): + if not isinstance(node_id, int) or isinstance(node_id, bool) or not 0 <= node_id <= _UINT64_MAX: + raise ValueError(f"expect keys must be node ids (non-negative integers), got {node_id!r}") + if version is None: + expected.append(ExpectedNodeVersion(node_id=node_id)) + elif isinstance(version, int) and not isinstance(version, bool) and 0 < version <= _UINT64_MAX: + expected.append(ExpectedNodeVersion(node_id=node_id, version=version)) + else: + # Zero is not a version a node can have; `None` is how to ask + # for absence. + raise ValueError( + f"expected version for node {node_id} must be a positive integer or None, got {version!r}" + ) self._require_open("commit") # Transition BEFORE the await: a concurrent operation on this handle # would otherwise pass its own open-check while the commit is in @@ -767,7 +834,7 @@ async def commit(self) -> int: self._state = "committing" try: resp = await self._client._cypher_stub.CommitTransaction( - CommitTransactionRequest(transaction_id=self._id), timeout=self._client._timeout + CommitTransactionRequest(transaction_id=self._id, expect=expected), timeout=self._client._timeout ) except grpc.RpcError as exc: if _rpc_outcome_is_ambiguous(exc): @@ -806,6 +873,7 @@ async def commit(self) -> int: self._spawn_cleanup() raise self._state = "committed" + self.commit_ts = int(resp.commit_ts) return int(resp.applied_index) async def rollback(self) -> None: @@ -1117,7 +1185,7 @@ async def cypher( params: dict[str, PyValue] | None = None, *, read_concern: str | None = None, - write_concern: str | None = None, + write_concern: WriteConcern | str | int | None = None, read_preference: str | None = None, after_index: int | None = None, at_timestamp: int | None = None, @@ -1129,10 +1197,11 @@ async def cypher( - ``read_concern``: ``"local"`` (default), ``"majority"``, ``"linearizable"``, ``"snapshot"``. Causal reads (``after_index`` > 0) require ``"majority"`` here. - - ``write_concern``: ``"w0"``, ``"memory"``, ``"cache"``, ``"w1"`` (default, leader-ack), - ``"majority"``, in rising order of durability. ``"memory"`` and ``"cache"`` acknowledge - before the write reaches Raft, so a leader crash before the background drain loses them; - reach for those only where losing recent writes is acceptable. + - ``write_concern``: a :class:`WriteConcern`, or its shorthands ``"majority"`` (the + default: journaled on a majority, survives the loss of a minority) and a member count + (``1`` = leader only, ``0`` = fire-and-forget). Weaker concerns are for writes that may + be lost with their leader; ``WriteConcern(w=1, journal="memory")`` answers before the + write reaches the log, so a leader crash before the drain loses it. - ``read_preference``: ``"primary"`` (default), ``"primary_preferred"``, ``"secondary"``, ``"secondary_preferred"``, ``"nearest"``. - ``after_index``: raft log index for causal reads, a fence. Returned rows reflect at @@ -2034,22 +2103,30 @@ def cypher( self._inner._cleanup_confirmed = False raise - def commit(self) -> int: + def commit(self, *, expect: Mapping[int, int | None] | None = None) -> int: """Apply every buffered write as one unit. See :meth:`AsyncTransaction.commit`.""" try: - return self._client._run(self._inner.commit()) # type: ignore[no-any-return] - except BaseException: + return self._client._run(self._inner.commit(expect=expect)) # type: ignore[no-any-return] + except BaseException as exc: # An interruption at the loop boundary (Ctrl-C, SystemExit) never # reaches the async handlers, so without this the handle would # read "open" (or stay parked mid-"committing") while the server # may already have applied the writes — inviting the duplicate # retry the indeterminate state exists to prevent. An outcome the # inner handler already decided (aborted, indeterminate, - # committed) is kept. - if self._inner._state in ("open", "committing"): + # committed) is kept. Only for real interruptions: an ordinary + # Exception is either settled by the inner handlers already or a + # rejected argument raised before any RPC, which leaves the + # transaction open. + if not isinstance(exc, Exception) and self._inner._state in ("open", "committing"): self._inner._state = "indeterminate" raise + @property + def commit_ts(self) -> int | None: + """Timestamp of the commit, once it succeeded. See :attr:`AsyncTransaction.commit_ts`.""" + return self._inner.commit_ts + def rollback(self) -> None: """Discard every buffered write. See :meth:`AsyncTransaction.rollback`.""" self._client._run(self._inner.rollback()) @@ -2155,7 +2232,7 @@ def cypher( params: dict[str, PyValue] | None = None, *, read_concern: str | None = None, - write_concern: str | None = None, + write_concern: WriteConcern | str | int | None = None, read_preference: str | None = None, after_index: int | None = None, at_timestamp: int | None = None, @@ -2385,15 +2462,13 @@ def health(self) -> bool: "linearizable": "READ_CONCERN_LEVEL_LINEARIZABLE", "snapshot": "READ_CONCERN_LEVEL_SNAPSHOT", } -# Durability rises W0 < MEMORY < CACHE < W1 < MAJORITY. Anything below W1 -# acknowledges before the write is replicated through Raft: a leader crash -# before the background drain loses every in-flight MEMORY or CACHE write. -_WRITE_CONCERN_MAP = { - "w0": "WRITE_CONCERN_LEVEL_W0", - "memory": "WRITE_CONCERN_LEVEL_MEMORY", - "cache": "WRITE_CONCERN_LEVEL_CACHE", - "w1": "WRITE_CONCERN_LEVEL_W1", - "majority": "WRITE_CONCERN_LEVEL_MAJORITY", +# The state each acknowledging member holds the write in. CACHE and MEMORY +# answer before the write reaches the log: a leader crash before the drain +# loses every write still in that overlay. +_JOURNAL_MAP = { + "journal": "JOURNAL_JOURNAL", + "cache": "JOURNAL_CACHE", + "memory": "JOURNAL_MEMORY", } _READ_PREFERENCE_MAP = { "primary": "READ_PREFERENCE_PRIMARY", @@ -2470,10 +2545,41 @@ def _make_read_concern(level: str | None, after_index: int | None, at_timestamp: return pb.ReadConcern(**kwargs) -def _make_write_concern(level: str) -> Any: +def _make_write_concern(concern: WriteConcern | str | int) -> Any: from coordinode._proto.coordinode.v1.replication import consistency_pb2 as pb # type: ignore[import] - return pb.WriteConcern(level=getattr(pb, _normalize_consistency_key(level, "write_concern", _WRITE_CONCERN_MAP))) + # The two shorthands name only `w`; journal and timeout keep their defaults. + if isinstance(concern, str) or (isinstance(concern, int) and not isinstance(concern, bool)): + concern = WriteConcern(w=concern) + if not isinstance(concern, WriteConcern): + raise ValueError(f"write_concern must be a WriteConcern, 'majority' or a member count; got {concern!r}") + kwargs: dict[str, Any] = { + "journal": getattr(pb, _normalize_consistency_key(concern.journal, "journal", _JOURNAL_MAP)), + } + w = concern.w + if isinstance(w, str): + if w.strip().lower() != "majority": + raise ValueError(f"invalid write_concern w {w!r}; expected 'majority' or a member count") + kwargs["mode"] = pb.WRITE_CONCERN_MODE_MAJORITY + acks = None + elif isinstance(w, int) and not isinstance(w, bool) and 0 <= w <= _UINT32_MAX: + kwargs["acks"] = w + acks = w + else: + raise ValueError(f"write_concern w must be 'majority' or a non-negative integer, got {w!r}") + # A volatile state cannot be confirmed across members, so the server + # refuses CACHE / MEMORY with MAJORITY or with more than one ack + # (INVALID_ARGUMENT). Say so here rather than a round trip later. + if kwargs["journal"] != pb.JOURNAL_JOURNAL and (acks is None or acks > 1): + raise ValueError( + f"journal={concern.journal!r} is accepted only with w=0 or w=1: a volatile write " + "cannot be confirmed across members" + ) + t = concern.timeout_ms + if not isinstance(t, int) or isinstance(t, bool) or not 0 <= t <= _UINT32_MAX: + raise ValueError(f"write_concern timeout_ms must be a non-negative 32-bit integer, got {t!r}") + kwargs["timeout_ms"] = t + return pb.WriteConcern(**kwargs) def _make_read_preference(pref: str) -> Any: diff --git a/demo/Dockerfile.jupyter b/demo/Dockerfile.jupyter index effccd9..8988dc5 100644 --- a/demo/Dockerfile.jupyter +++ b/demo/Dockerfile.jupyter @@ -24,9 +24,13 @@ RUN pip install --no-cache-dir \ langchain-openai \ langchain-community \ langgraph \ - # coordinode SDK packages (installed from mounted /sdk) - grpcio \ - grpcio-tools \ - protobuf + # coordinode SDK packages (installed from mounted /sdk). Pinned to the + # versions uv.lock resolves: install-sdk.sh generates the proto stubs into + # the mounted checkout, and gRPC stubs refuse to import under a grpcio + # older than the grpcio-tools that generated them. An unpinned, newer + # grpcio-tools here would break the host's own test runs on the same tree. + grpcio==1.83.1 \ + grpcio-tools==1.83.1 \ + protobuf==7.36.0 WORKDIR /home/jovyan/work diff --git a/demo/README.md b/demo/README.md index a769ab4..343c5e9 100644 --- a/demo/README.md +++ b/demo/README.md @@ -19,10 +19,10 @@ Interactive notebooks for LlamaIndex, LangChain, and LangGraph integrations. > alternative was filtering model-written Cypher by a session tag, which leaks > the first time the filter misses a case. `COORDINODE_AGENT_DB` moves that > file; deleting it starts the agent with no memory. -> The Docker Compose stack below pins the CoordiNode **server** image v0.5.7 by -> digest. Do not move it below that: 0.5.1 crashes its Raft core when the oplog -> rolls a segment that already exists, and a single-node stack never regains -> leadership afterwards. +> The Docker Compose stack below pins the CoordiNode **server** image v0.6.0 by +> digest. Do not move it below that: the client speaks the two-axis write +> concern 0.6 introduced, and an older server ignores it and applies its own +> default. > > The first four run in Colab with no setup. **What 0.5 Added** does not: write > concerns, read concerns and time travel are distribution and durability features, diff --git a/demo/docker-compose.yml b/demo/docker-compose.yml index 93b59a8..a9b2bb8 100644 --- a/demo/docker-compose.yml +++ b/demo/docker-compose.yml @@ -4,8 +4,8 @@ services: # COORDINODE_IMAGE at a locally built tag to try a server build before it # is published. # Digest-pinned so a re-pushed tag cannot change the demo underneath - # you; this one is 0.5.7. Override COORDINODE_IMAGE to try another. - image: ${COORDINODE_IMAGE:-ghcr.io/structured-world/coordinode@sha256:75a6242beb4cea8ab6726c842898fafc03ca9f59c6f7e36e462934a4ca64874c} + # you; this one is 0.6.0. Override COORDINODE_IMAGE to try another. + image: ${COORDINODE_IMAGE:-ghcr.io/structured-world/coordinode@sha256:f7e0ee8515868bc068f1e2f66d499a4acf5e9b7bce76bd5913bf805c7f9fd6da} container_name: demo-coordinode ports: - "127.0.0.1:37080:7080" # gRPC (native API) — localhost-only diff --git a/demo/notebooks/03_langgraph_agent.ipynb b/demo/notebooks/03_langgraph_agent.ipynb index e645b85..61f0dc0 100644 --- a/demo/notebooks/03_langgraph_agent.ipynb +++ b/demo/notebooks/03_langgraph_agent.ipynb @@ -34,7 +34,7 @@ "id": "4", "metadata": {}, "outputs": [], - "source": "import os, tempfile\n\n# A database this notebook owns, on disk so the graph survives a kernel\n# restart and re-running cells: an agent's memory that vanished every time the\n# kernel bounced would not be much of a demo.\n#\n# It is a file of its own rather than the one notebooks 00 to 02 share. Those\n# pass a seeded graph between them; this one writes and reads only the facts\n# the agent creates here, so a separate file keeps the two apart.\n#\n# That file is also what makes `query_facts` safe to hand raw Cypher: nobody\n# else writes here, so a query the model invents has nothing of anyone else's\n# to reach. Against a shared server that took a guard proving each query\n# stayed inside this run's own tag, and a text check over model-written Cypher\n# leaks the moment it misses a case. COORDINODE_ADDR is ignored for that\n# reason; notebooks 00 to 02 and 04 exercise the client/server path.\nAGENT_DB_PATH = os.environ.get(\n \"COORDINODE_AGENT_DB\",\n \"/content/coordinode-agent-demo.db\"\n if os.path.isdir(\"/content\")\n else os.path.join(tempfile.gettempdir(), \"coordinode-agent-demo.db\"),\n)\n\ntry:\n from coordinode_embedded import LocalClient\nexcept ImportError as exc:\n raise RuntimeError(\n \"coordinode-embedded is not installed, and this notebook runs on it.\\n\"\n \" pip install coordinode-embedded\\n\"\n \"In Colab, rerun the install cell above. To build it from this \"\n \"checkout instead: maturin build --manifest-path coordinode-embedded/\"\n \"Cargo.toml, then install the wheel it prints.\"\n ) from exc\n\nclient = LocalClient(AGENT_DB_PATH)\n\n# `find_related` traverses with a variable-length relationship. Ask the engine\n# for one now, so an installation too old to parse it says so here instead of\n# failing mid-conversation with a parse error that reads like the agent's\n# fault.\ntry:\n client.cypher(\"MATCH (n)-[*1..2]->(m) RETURN n LIMIT 1\")\nexcept Exception as exc:\n client.close()\n raise RuntimeError(\n \"The installed coordinode-embedded cannot parse a variable-length \"\n \"relationship, which `find_related` uses.\\n\"\n \"Upgrade it, or build the current one from this checkout:\\n\"\n \" maturin build --manifest-path coordinode-embedded/Cargo.toml\\n\"\n \"then install the wheel it prints.\"\n ) from exc\n\nprint(f\"Graph memory at {AGENT_DB_PATH}\")" + "source": "import tempfile\n\n# A database this notebook owns, on disk so the graph survives a kernel\n# restart and re-running cells: an agent's memory that vanished every time the\n# kernel bounced would not be much of a demo.\n#\n# It is a file of its own rather than the one notebooks 00 to 02 share. Those\n# pass a seeded graph between them; this one writes and reads only the facts\n# the agent creates here, so a separate file keeps the two apart.\n#\n# That file is also what makes `query_facts` safe to hand raw Cypher: nobody\n# else writes here, so a query the model invents has nothing of anyone else's\n# to reach. Against a shared server that took a guard proving each query\n# stayed inside this run's own tag, and a text check over model-written Cypher\n# leaks the moment it misses a case. COORDINODE_ADDR is ignored for that\n# reason; notebooks 00 to 02 and 04 exercise the client/server path.\nAGENT_DB_PATH = os.environ.get(\n \"COORDINODE_AGENT_DB\",\n \"/content/coordinode-agent-demo.db\"\n if os.path.isdir(\"/content\")\n else os.path.join(tempfile.gettempdir(), \"coordinode-agent-demo.db\"),\n)\n\ntry:\n from coordinode_embedded import LocalClient\nexcept ImportError as exc:\n raise RuntimeError(\n \"coordinode-embedded is not installed, and this notebook runs on it.\\n\"\n \" pip install coordinode-embedded\\n\"\n \"In Colab, rerun the install cell above. To build it from this \"\n \"checkout instead: maturin build --manifest-path coordinode-embedded/\"\n \"Cargo.toml, then install the wheel it prints.\"\n ) from exc\n\nclient = LocalClient(AGENT_DB_PATH)\n\n# `find_related` traverses with a variable-length relationship. Ask the engine\n# for one now, so an installation too old to parse it says so here instead of\n# failing mid-conversation with a parse error that reads like the agent's\n# fault.\ntry:\n client.cypher(\"MATCH (n)-[*1..2]->(m) RETURN n LIMIT 1\")\nexcept Exception as exc:\n client.close()\n raise RuntimeError(\n \"The installed coordinode-embedded cannot parse a variable-length \"\n \"relationship, which `find_related` uses.\\n\"\n \"Upgrade it, or build the current one from this checkout:\\n\"\n \" maturin build --manifest-path coordinode-embedded/Cargo.toml\\n\"\n \"then install the wheel it prints.\"\n ) from exc\n\nprint(f\"Graph memory at {AGENT_DB_PATH}\")" }, { "cell_type": "markdown", diff --git a/demo/notebooks/04_whats_new_in_0_5.ipynb b/demo/notebooks/04_whats_new_in_0_5.ipynb index 6a29d73..bf0d81e 100644 --- a/demo/notebooks/04_whats_new_in_0_5.ipynb +++ b/demo/notebooks/04_whats_new_in_0_5.ipynb @@ -3,7 +3,7 @@ { "cell_type": "markdown", "metadata": {}, - "source": "# What 0.5 Added\n\n[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/structured-world/coordinode-python/blob/main/demo/notebooks/04_whats_new_in_0_5.ipynb)\n\nThe client surface that arrived with CoordiNode 0.5, exercised end to end:\n\n| Feature | What it is for |\n|---------|----------------|\n| `create_nodes_batch` | Create many nodes in one atomic write instead of a loop of round trips |\n| `element_id` | A stable identifier that survives restarts and replication |\n| `schema_revision` | Tells you when a label's shape last changed, so caches know to refresh |\n| `write_concern` | Chooses how durable an acknowledgement is, from fire-and-forget to majority |\n| `read_concern` / `read_preference` | Chooses how fresh a read is, and which replica answers it |\n| `at_timestamp` | Reads the database as it was at a point in time |\n| `transaction()` | Commits several statements as one unit, or rolls back all of them |\n\n> **Needs a server, v0.5.5 or newer** (the release this client is tested\n> against). These are distribution and durability features, so they exist on\n> the client that talks to a CoordiNode server. Set `COORDINODE_ADDR` before\n> running. The embedded engine has no Raft and no replicas, so the cells below\n> stop with an explanation rather than pretending.\n>\n> The version floor is not covered by the connect cell's health check, which\n> exercises a different service: a server without the transaction RPCs passes\n> it and then refuses them.\n", + "source": "# What 0.5 Added\n\n[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/structured-world/coordinode-python/blob/main/demo/notebooks/04_whats_new_in_0_5.ipynb)\n\nThe client surface that arrived with CoordiNode 0.5, exercised end to end:\n\n| Feature | What it is for |\n|---------|----------------|\n| `create_nodes_batch` | Create many nodes in one atomic write instead of a loop of round trips |\n| `element_id` | A stable identifier that survives restarts and replication |\n| `schema_revision` | Tells you when a label's shape last changed, so caches know to refresh |\n| `write_concern` | Chooses how durable an acknowledgement is, from fire-and-forget to majority |\n| `read_concern` / `read_preference` | Chooses how fresh a read is, and which replica answers it |\n| `at_timestamp` | Reads the database as it was at a point in time |\n| `transaction()` | Commits several statements as one unit, or rolls back all of them |\n\n> **Needs a server, v0.6.0 or newer** (the release this client is tested\n> against; 0.6 changed the write concern to the two axes shown below). These\n> are distribution and durability features, so they exist on the client that\n> talks to a CoordiNode server. Set `COORDINODE_ADDR` before running. The\n> embedded engine has no Raft and no replicas, so the cells below stop with an\n> explanation rather than pretending.\n>\n> The version floor is not covered by the connect cell's health check, which\n> exercises a different service: a server without the transaction RPCs passes\n> it and then refuses them.", "id": "cell-00-ccfb99" }, { @@ -19,7 +19,7 @@ "execution_count": null, "metadata": {}, "outputs": [], - "source": "import importlib.util, inspect, os, shutil, subprocess, sys\n\n# (distribution on PyPI, module to import it by).\npkgs = [\n (\"coordinode\", \"coordinode\"),\n (\"nest_asyncio\", \"nest_asyncio\"),\n]\n\n# Install only what is missing. A checkout mounted into the image, or an\n# editable install, already provides these; pulling them from PyPI there\n# would shadow the very code the notebook is meant to exercise.\n#\n# find_spec raises rather than returning None when a dotted name's parent\n# package is absent, which is exactly the fresh environment this cell exists\n# for: unguarded, it would abort before installing anything.\ndef _missing(module: str) -> bool:\n try:\n return importlib.util.find_spec(module) is None\n except ModuleNotFoundError:\n return True\n\n\ndef _install(dists: list[str], upgrade: bool = False) -> None:\n \"\"\"Install into this interpreter with whatever installer it has.\n\n Colab ships pip. A uv-managed venv deliberately does not, and there\n `python -m pip` dies with \"No module named pip\" before the notebook\n reaches its first query, so fall back to uv targeting this same\n interpreter rather than uv's own default environment.\n\n The finder caches a directory listing taken before the install, so a\n package that has just appeared on disk is invisible until the caches are\n dropped. Doing it here means every caller sees what it installed.\n \"\"\"\n flags = [\"-q\", \"-U\"] if upgrade else [\"-q\"]\n if not _missing(\"pip\"):\n cmd = [sys.executable, \"-m\", \"pip\", \"install\", *flags, *dists]\n elif shutil.which(\"uv\"):\n cmd = [\"uv\", \"pip\", \"install\", *flags, \"--python\", sys.executable, *dists]\n else:\n raise RuntimeError(\n f\"Neither pip nor uv is available to install: {', '.join(dists)}\"\n )\n subprocess.run(cmd, check=True, timeout=300)\n importlib.invalidate_caches()\n\n\ndef _has_0_5_surface() -> bool:\n \"\"\"Whether the installed `coordinode` has the API this notebook calls.\n\n Every cell below is about something 0.5 added, so \"the module imports\" is\n the wrong question: a 0.4 install answers it happily and then fails four\n cells later with an AttributeError about `create_nodes_batch`, which reads\n like a broken notebook rather than an old package. Three probes cover the\n set: the batch method and the consistency arguments on `cypher`, which\n shipped together, and `transaction`, which arrived after them and so has\n to be asked about separately.\n\n Probing the surface rather than comparing `__version__` is deliberate. An\n editable install without hatch-vcs reports 0.0.0, so a version floor would\n reject exactly the checkout this notebook is meant to exercise.\n \"\"\"\n try:\n from coordinode import CoordinodeClient\n except ImportError:\n return False\n if not hasattr(CoordinodeClient, \"create_nodes_batch\"):\n return False\n if not hasattr(CoordinodeClient, \"transaction\"):\n return False\n params = inspect.signature(CoordinodeClient.cypher).parameters\n return {\"read_concern\", \"write_concern\", \"at_timestamp\"}.issubset(params)\n\n\nmissing = [dist for dist, mod in pkgs if _missing(mod)]\nif missing:\n _install(missing)\n\nif not _has_0_5_surface():\n print(\"Installed `coordinode` predates 0.5, upgrading it.\")\n _install([\"coordinode\"], upgrade=True)\n # The old module object stays in sys.modules and would keep answering\n # imports for the rest of the session, so drop it and re-probe.\n for name in [m for m in sys.modules if m == \"coordinode\" or m.startswith(\"coordinode.\")]:\n del sys.modules[name]\n if not _has_0_5_surface():\n raise RuntimeError(\n \"`coordinode` still lacks the 0.5 client surface after upgrading. \"\n \"If this environment mounts a checkout or uses an editable install, \"\n \"that source is older than 0.5: update it, or install the released \"\n \"package instead.\"\n )\n\nimport nest_asyncio\n\nnest_asyncio.apply()\n\nprint(\"Ready\")\n", + "source": "import importlib.util, inspect, os, shutil, subprocess, sys\n\n# (distribution on PyPI, module to import it by).\npkgs = [\n (\"coordinode\", \"coordinode\"),\n (\"nest_asyncio\", \"nest_asyncio\"),\n]\n\n# Install only what is missing. A checkout mounted into the image, or an\n# editable install, already provides these; pulling them from PyPI there\n# would shadow the very code the notebook is meant to exercise.\n#\n# find_spec raises rather than returning None when a dotted name's parent\n# package is absent, which is exactly the fresh environment this cell exists\n# for: unguarded, it would abort before installing anything.\ndef _missing(module: str) -> bool:\n try:\n return importlib.util.find_spec(module) is None\n except ModuleNotFoundError:\n return True\n\n\ndef _install(dists: list[str], upgrade: bool = False) -> None:\n \"\"\"Install into this interpreter with whatever installer it has.\n\n Colab ships pip. A uv-managed venv deliberately does not, and there\n `python -m pip` dies with \"No module named pip\" before the notebook\n reaches its first query, so fall back to uv targeting this same\n interpreter rather than uv's own default environment.\n\n The finder caches a directory listing taken before the install, so a\n package that has just appeared on disk is invisible until the caches are\n dropped. Doing it here means every caller sees what it installed.\n \"\"\"\n flags = [\"-q\", \"-U\"] if upgrade else [\"-q\"]\n if not _missing(\"pip\"):\n cmd = [sys.executable, \"-m\", \"pip\", \"install\", *flags, *dists]\n elif shutil.which(\"uv\"):\n cmd = [\"uv\", \"pip\", \"install\", *flags, \"--python\", sys.executable, *dists]\n else:\n raise RuntimeError(\n f\"Neither pip nor uv is available to install: {', '.join(dists)}\"\n )\n subprocess.run(cmd, check=True, timeout=300)\n importlib.invalidate_caches()\n\n\ndef _has_0_5_surface() -> bool:\n \"\"\"Whether the installed `coordinode` has the API this notebook calls.\n\n Every cell below is about something 0.5 added, so \"the module imports\" is\n the wrong question: a 0.4 install answers it happily and then fails four\n cells later with an AttributeError about `create_nodes_batch`, which reads\n like a broken notebook rather than an old package. The probes cover the\n set: the batch method and the consistency arguments on `cypher`, which\n shipped together, `transaction`, which arrived after them, and\n `WriteConcern`, the two-axis write concern the server speaks since 0.6.\n\n Probing the surface rather than comparing `__version__` is deliberate. An\n editable install without hatch-vcs reports 0.0.0, so a version floor would\n reject exactly the checkout this notebook is meant to exercise.\n \"\"\"\n try:\n from coordinode import CoordinodeClient, WriteConcern # noqa: F401\n except ImportError:\n return False\n if not hasattr(CoordinodeClient, \"create_nodes_batch\"):\n return False\n if not hasattr(CoordinodeClient, \"transaction\"):\n return False\n params = inspect.signature(CoordinodeClient.cypher).parameters\n return {\"read_concern\", \"write_concern\", \"at_timestamp\"}.issubset(params)\n\n\nmissing = [dist for dist, mod in pkgs if _missing(mod)]\nif missing:\n _install(missing)\n\nif not _has_0_5_surface():\n print(\"Installed `coordinode` predates this notebook's API, upgrading it.\")\n _install([\"coordinode\"], upgrade=True)\n # The old module object stays in sys.modules and would keep answering\n # imports for the rest of the session, so drop it and re-probe.\n for name in [m for m in sys.modules if m == \"coordinode\" or m.startswith(\"coordinode.\")]:\n del sys.modules[name]\n if not _has_0_5_surface():\n raise RuntimeError(\n \"`coordinode` still lacks the client surface this notebook uses after \"\n \"upgrading. If this environment mounts a checkout or uses an editable \"\n \"install, that source is too old: update it, or install the released \"\n \"package instead.\"\n )\n\nimport nest_asyncio\n\nnest_asyncio.apply()\n\nprint(\"Ready\")", "id": "cell-02-54b3dc" }, { @@ -38,32 +38,7 @@ "execution_count": null, "metadata": {}, "outputs": [], - "source": [ - "import os, time, uuid\n", - "\n", - "COORDINODE_ADDR = os.environ.get(\"COORDINODE_ADDR\")\n", - "client = None\n", - "\n", - "if COORDINODE_ADDR:\n", - " from coordinode import CoordinodeClient\n", - "\n", - " client = CoordinodeClient(COORDINODE_ADDR)\n", - " if not client.health():\n", - " client.close()\n", - " raise RuntimeError(f\"Health check failed for {COORDINODE_ADDR}\")\n", - " print(f\"Connected to {COORDINODE_ADDR}\")\n", - "else:\n", - " print(\n", - " \"COORDINODE_ADDR is not set, so this notebook has nothing to talk to.\\n\"\n", - " \"Start the demo stack (see demo/README.md) or point at your own server:\\n\"\n", - " ' os.environ[\"COORDINODE_ADDR\"] = \"localhost:37080\"'\n", - " )\n", - "\n", - "# Tag every node this run creates, so the cleanup at the end removes exactly\n", - "# what was added and nothing a sibling notebook left behind.\n", - "DEMO_TAG = f\"whats_new_{uuid.uuid4().hex[:8]}\"\n", - "print(f\"DEMO_TAG: {DEMO_TAG}\")\n" - ], + "source": "import time, uuid\n\nCOORDINODE_ADDR = os.environ.get(\"COORDINODE_ADDR\")\nclient = None\n\nif COORDINODE_ADDR:\n from coordinode import CoordinodeClient\n\n client = CoordinodeClient(COORDINODE_ADDR)\n if not client.health():\n client.close()\n raise RuntimeError(f\"Health check failed for {COORDINODE_ADDR}\")\n print(f\"Connected to {COORDINODE_ADDR}\")\nelse:\n print(\n \"COORDINODE_ADDR is not set, so this notebook has nothing to talk to.\\n\"\n \"Start the demo stack (see demo/README.md) or point at your own server:\\n\"\n ' os.environ[\"COORDINODE_ADDR\"] = \"localhost:37080\"'\n )\n\n# Tag every node this run creates, so the cleanup at the end removes exactly\n# what was added and nothing a sibling notebook left behind.\nDEMO_TAG = f\"whats_new_{uuid.uuid4().hex[:8]}\"\nprint(f\"DEMO_TAG: {DEMO_TAG}\")", "id": "cell-04-340937" }, { @@ -153,39 +128,13 @@ "execution_count": null, "metadata": {}, "outputs": [], - "source": [ - "if client:\n", - " labels = {l.name: l for l in client.get_labels()}\n", - " engineer = labels.get(\"Engineer\")\n", - " if engineer:\n", - " print(f\"Engineer schema_revision: {engineer.schema_revision}\")\n", - " for et in client.get_edge_types():\n", - " if et.name == \"PAIRS_WITH\":\n", - " print(f\"PAIRS_WITH schema_revision: {et.schema_revision}\")\n" - ], + "source": "if client:\n labels = {info.name: info for info in client.get_labels()}\n engineer = labels.get(\"Engineer\")\n if engineer:\n print(f\"Engineer schema_revision: {engineer.schema_revision}\")\n for et in client.get_edge_types():\n if et.name == \"PAIRS_WITH\":\n print(f\"PAIRS_WITH schema_revision: {et.schema_revision}\")", "id": "cell-10-89d6af" }, { "cell_type": "markdown", "metadata": {}, - "source": [ - "## Write concerns: how durable is \"done\"\n", - "\n", - "A write concern is the answer to \"acknowledged by whom\". They rise in\n", - "durability:\n", - "\n", - "| Concern | Acknowledged when |\n", - "|---------|-------------------|\n", - "| `w0` | The server accepted the request; nothing is guaranteed |\n", - "| `memory` | Applied in memory, before Raft |\n", - "| `cache` | Cached, still before Raft |\n", - "| `w1` | The leader has it durably (default) |\n", - "| `majority` | A majority of the cluster has it |\n", - "\n", - "`memory` and `cache` acknowledge before the write reaches Raft, so a leader\n", - "crash before the background drain loses them. They are for data you can\n", - "afford to lose, not for a speed-up on data you cannot.\n" - ], + "source": "## Write concerns: how durable is \"done\"\n\nA write concern has two independent axes. `w` says how many members must\nacknowledge: `\"majority\"` (the default, survives the loss of a minority) or a\ncount, where `1` is the leader alone and `0` is fire-and-forget. `journal` says\nwhat state each counted member holds the write in:\n\n| `journal` | Held as | Lost on |\n|-----------|---------|---------|\n| `journal` | fsynced to the log (default) | nothing short of losing the disk |\n| `cache` | RAM + NVMe overlay, before the log | power failure before the drain |\n| `memory` | RAM only, before the log | process crash before the drain |\n\n`cache` and `memory` answer before the write reaches the log, so a leader crash\nbefore the drain loses them. They are for data you can afford to lose, not a\nspeed-up on data you cannot, and they are accepted only with `w` of 0 or 1: a\nvolatile write cannot be confirmed across members.\n\n`write_concern=\"majority\"` and `write_concern=1` are shorthands; the full form\nis `WriteConcern(w=..., journal=..., timeout_ms=...)`.", "id": "cell-11-3dcc1b" }, { @@ -193,18 +142,7 @@ "execution_count": null, "metadata": {}, "outputs": [], - "source": [ - "if client:\n", - " for concern in (\"w0\", \"memory\", \"cache\", \"w1\", \"majority\"):\n", - " started = time.perf_counter()\n", - " client.cypher(\n", - " \"CREATE (:Sample {tag: $tag, concern: $concern})\",\n", - " params={\"tag\": DEMO_TAG, \"concern\": concern},\n", - " write_concern=concern,\n", - " )\n", - " elapsed_ms = (time.perf_counter() - started) * 1000\n", - " print(f\" {concern:<9} acknowledged in {elapsed_ms:6.2f} ms\")\n" - ], + "source": "if client:\n from coordinode import WriteConcern\n\n concerns = {\n \"fire-and-forget\": 0,\n \"leader, memory\": WriteConcern(w=1, journal=\"memory\"),\n \"leader, cache\": WriteConcern(w=1, journal=\"cache\"),\n \"leader, journal\": 1,\n \"majority (default)\": \"majority\",\n }\n for name, concern in concerns.items():\n started = time.perf_counter()\n client.cypher(\n \"CREATE (:Sample {tag: $tag, concern: $concern})\",\n params={\"tag\": DEMO_TAG, \"concern\": name},\n write_concern=concern,\n )\n elapsed_ms = (time.perf_counter() - started) * 1000\n print(f\" {name:<19} acknowledged in {elapsed_ms:6.2f} ms\")", "id": "cell-12-a0f9c5" }, { diff --git a/docker-compose.yml b/docker-compose.yml index a4de6d4..687c6e6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,10 +7,10 @@ version: "3.9" services: coordinode: - # The same 0.5.7 digest the demo stack and the integration job run, so the + # The same 0.6.0 digest the demo stack and the integration job run, so the # quick start does not hand a newcomer an older server than everything else # here is tested against. Pinned by digest because a tag can be re-pushed. - image: ghcr.io/structured-world/coordinode@sha256:75a6242beb4cea8ab6726c842898fafc03ca9f59c6f7e36e462934a4ca64874c + image: ghcr.io/structured-world/coordinode@sha256:f7e0ee8515868bc068f1e2f66d499a4acf5e9b7bce76bd5913bf805c7f9fd6da # Named for this repository rather than plain `coordinode`: the server # repository runs its own compose stack that claims that name, and a bare # name is global to the daemon. diff --git a/proto b/proto index b1bf1d0..41273fe 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit b1bf1d0f1b4b811b4cfe1789a93ff8aa7a54c939 +Subproject commit 41273fed20777523b0a78ae79ebdf135226df401 diff --git a/tests/integration/test_sdk.py b/tests/integration/test_sdk.py index 7760bcf..fdde749 100644 --- a/tests/integration/test_sdk.py +++ b/tests/integration/test_sdk.py @@ -22,6 +22,7 @@ TextIndexInfo, TextResult, TraverseResult, + WriteConcern, ) ADDR = os.environ.get("COORDINODE_ADDR", "localhost:7080") @@ -304,17 +305,6 @@ def test_traverse_returns_neighbours(client): client.cypher("MATCH (n:TraverseRPC {tag: $tag}) DETACH DELETE n", params={"tag": tag}) -@pytest.mark.xfail( - strict=False, - raises=AssertionError, - # strict=False: XPASS is good news (server gained inbound support), not an error. - # strict=True would break CI exactly when the server improves, which is undesirable. - # The XPASS report in pytest output is the signal to remove this marker. - # raises=AssertionError: narrows xfail to the known failure mode (empty result set → - # assertion fails). Unexpected errors (gRPC RpcError, wrong enum, etc.) are NOT covered - # and will still propagate as CI failures. - reason="CoordiNode Traverse RPC does not yet support inbound direction — server returns empty result set", -) def test_traverse_inbound_direction(client): """traverse() with direction='inbound' reaches nodes that point TO start_id.""" tag = uid() @@ -323,8 +313,8 @@ def test_traverse_inbound_direction(client): params={"tag": tag}, ) try: - # Capture both src and dst so that when the server gains inbound support - # (XPASS), the assertion verifies the *correct* node was returned, not just any node. + # Capture both src and dst so the assertion verifies the *correct* node + # was returned, not just any node. rows = client.cypher( "MATCH (src:TraverseIn {tag: $tag})-[:INBOUND_TEST]->(dst:TraverseIn {tag: $tag}) " "RETURN src AS src_id, dst AS dst_id", @@ -718,12 +708,35 @@ def test_cypher_accepts_consistency_kwargs(client): client.cypher(f"MATCH (n:{label} {{tag: $tag}}) DELETE n", params={"tag": tag}) +@pytest.mark.parametrize( + "concern", + [1, 0, WriteConcern(w=1, journal="cache"), WriteConcern(w=1, journal="memory"), WriteConcern(timeout_ms=5000)], +) +def test_weaker_and_bounded_write_concerns_are_accepted(client, concern): + """Every combination the client lets through is one the server accepts. + + Fire-and-forget (w=0) answers before the write is known to commit, so its + visibility is not asserted; the others are read back. + """ + tag = uid() + try: + client.cypher("CREATE (:WcDemo {tag: $tag})", params={"tag": tag}, write_concern=concern) + if concern != 0: + rows = client.cypher("MATCH (n:WcDemo {tag: $tag}) RETURN count(n) AS c", params={"tag": tag}) + assert rows[0]["c"] == 1 + finally: + client.cypher("MATCH (n:WcDemo {tag: $tag}) DELETE n", params={"tag": tag}) + + def test_cypher_rejects_invalid_consistency_values(client): """Invalid consistency kwargs raise ValueError before the RPC.""" with pytest.raises(ValueError, match="invalid read_concern"): client.cypher("RETURN 1", read_concern="strong") - with pytest.raises(ValueError, match="invalid write_concern"): + with pytest.raises(ValueError, match="invalid write_concern w"): client.cypher("RETURN 1", write_concern="w9") + # A volatile state on several members is refused here, as the server would. + with pytest.raises(ValueError, match="accepted only with w=0 or w=1"): + client.cypher("RETURN 1", write_concern=WriteConcern(journal="memory")) with pytest.raises(ValueError, match="invalid read_preference"): client.cypher("RETURN 1", read_preference="leader") with pytest.raises(ValueError, match="after_index must be a non-negative integer"): @@ -858,6 +871,103 @@ def test_commit_returns_an_index_a_read_can_be_fenced_on(client): client.cypher("MATCH (n:TxDemo {tag: $tag}) DELETE n", params={"tag": tag}) +def test_commit_ts_reads_exactly_that_commit(client): + """A read pinned at commit_ts sees the commit; one pinned just before it does not.""" + tag = uid() + try: + tx = client.begin_transaction() + tx.cypher("CREATE (:TxDemo {tag: $tag, name: 'Alice'})", params={"tag": tag}) + tx.commit() + assert tx.commit_ts and tx.commit_ts > 0 + + def count_at(ts): + rows = client.cypher( + "MATCH (n:TxDemo {tag: $tag}) RETURN count(n) AS c", params={"tag": tag}, at_timestamp=ts + ) + return rows[0]["c"] + + assert count_at(tx.commit_ts) == 1 + assert count_at(tx.commit_ts - 1) == 0 + finally: + client.cypher("MATCH (n:TxDemo {tag: $tag}) DELETE n", params={"tag": tag}) + + +def _error_info(exc: grpc.RpcError): + """The ErrorInfo the server attaches to a classified rejection.""" + from google.rpc import error_details_pb2, status_pb2 + + for key, value in exc.trailing_metadata() or (): + if key == "grpc-status-details-bin": + status = status_pb2.Status.FromString(value) + for detail in status.details: + info = error_details_pb2.ErrorInfo() + if detail.Unpack(info): + return info + raise AssertionError(f"no ErrorInfo on the rejection: {exc!r}") + + +def test_commit_with_the_current_version_applies(client): + tag = uid() + node = client.create_node(["TxDemo"], {"tag": tag, "name": "Alice"}) + try: + assert node.version, "the server resolves a version for a node it just wrote" + assert client.get_node(node.id).version == node.version + with client.transaction() as tx: + tx.cypher("MATCH (n:TxDemo {tag: $tag}) SET n.name = 'Bob'", params={"tag": tag}) + tx.commit(expect={node.id: node.version}) + after = client.get_node(node.id) + assert after.properties["name"] == "Bob" + # The version moves to the commit that changed the node. + assert after.version == tx.commit_ts + finally: + client.cypher("MATCH (n:TxDemo {tag: $tag}) DELETE n", params={"tag": tag}) + + +def test_commit_against_a_stale_version_is_refused_and_applies_nothing(client): + """The check runs at commit, so a write between the read and the commit loses the race cleanly.""" + tag = uid() + node = client.create_node(["TxDemo"], {"tag": tag, "name": "Alice"}) + try: + stale = node.version + client.cypher("MATCH (n:TxDemo {tag: $tag}) SET n.name = 'Carol'", params={"tag": tag}) + current = client.get_node(node.id).version + assert current != stale + + tx = client.begin_transaction() + tx.cypher("MATCH (n:TxDemo {tag: $tag}) SET n.name = 'Bob'", params={"tag": tag}) + with pytest.raises(grpc.RpcError) as info: + tx.commit(expect={node.id: stale}) + assert info.value.code() == grpc.StatusCode.ABORTED + error = _error_info(info.value) + assert error.reason == "REVISION_MISMATCH" + # The caller decides its next move from these without reading again. + assert (error.metadata["expected_version"], error.metadata["current_version"]) == (str(stale), str(current)) + assert tx.is_open is False + assert client.get_node(node.id).properties["name"] == "Carol" + finally: + client.cypher("MATCH (n:TxDemo {tag: $tag}) DELETE n", params={"tag": tag}) + + +def test_expecting_absence_of_an_existing_node_is_refused(client): + tag = uid() + node = client.create_node(["TxDemo"], {"tag": tag}) + try: + tx = client.begin_transaction() + tx.cypher("CREATE (:TxDemo {tag: $tag, extra: true})", params={"tag": tag}) + with pytest.raises(grpc.RpcError) as info: + tx.commit(expect={node.id: None}) + assert info.value.code() == grpc.StatusCode.ABORTED + error = _error_info(info.value) + assert error.reason == "REVISION_MISMATCH" + # Absence was expected, so there is no expected version to report. + assert "expected_version" not in error.metadata + assert error.metadata["current_version"] == str(node.version) + rows = client.cypher("MATCH (n:TxDemo {tag: $tag}) RETURN count(n) AS c", params={"tag": tag}) + assert rows[0]["c"] == 1, "the refused commit must apply nothing" + finally: + client.cypher("MATCH (n:TxDemo {tag: $tag}) DELETE n", params={"tag": tag}) + + def test_reuse_after_commit_raises(client): tx = client.begin_transaction() tx.commit() diff --git a/tests/unit/test_consistency_helpers.py b/tests/unit/test_consistency_helpers.py index f729199..9c4483c 100644 --- a/tests/unit/test_consistency_helpers.py +++ b/tests/unit/test_consistency_helpers.py @@ -6,6 +6,7 @@ from coordinode._proto.coordinode.v1.replication import consistency_pb2 as pb from coordinode.client import ( + WriteConcern, _make_read_concern, _make_read_preference, _make_write_concern, @@ -114,38 +115,79 @@ def test_rejects_a_level_the_server_will_refuse(self, level: str) -> None: class TestWriteConcern: + """Two independent axes: `w` (count or majority) and `journal` (state per member).""" + + def test_majority_shorthand_sets_the_mode_not_a_count(self) -> None: + wc = _make_write_concern("majority") + assert wc.WhichOneof("w") == "mode" + assert wc.mode == pb.WRITE_CONCERN_MODE_MAJORITY + assert wc.journal == pb.JOURNAL_JOURNAL + + def test_majority_is_case_insensitive(self) -> None: + assert _make_write_concern(" Majority ").mode == pb.WRITE_CONCERN_MODE_MAJORITY + + @pytest.mark.parametrize("acks", [0, 1, 3]) + def test_count_shorthand_sets_acks(self, acks: int) -> None: + """`acks: 0` must be on the wire: unset `w` means MAJORITY, not fire-and-forget.""" + wc = _make_write_concern(acks) + assert wc.WhichOneof("w") == "acks" + assert wc.acks == acks + + def test_default_writeconcern_is_journaled_majority(self) -> None: + wc = _make_write_concern(WriteConcern()) + assert (wc.WhichOneof("w"), wc.mode, wc.journal, wc.timeout_ms) == ( + "mode", + pb.WRITE_CONCERN_MODE_MAJORITY, + pb.JOURNAL_JOURNAL, + 0, + ) + @pytest.mark.parametrize( - ("level", "expected"), - [ - ("w0", pb.WRITE_CONCERN_LEVEL_W0), - ("memory", pb.WRITE_CONCERN_LEVEL_MEMORY), - ("cache", pb.WRITE_CONCERN_LEVEL_CACHE), - ("w1", pb.WRITE_CONCERN_LEVEL_W1), - ("majority", pb.WRITE_CONCERN_LEVEL_MAJORITY), - ], + ("journal", "expected"), + [("journal", pb.JOURNAL_JOURNAL), ("cache", pb.JOURNAL_CACHE), ("memory", pb.JOURNAL_MEMORY)], ) - def test_valid_levels(self, level: str, expected: int) -> None: - assert _make_write_concern(level).level == expected - - def test_every_documented_level_is_reachable(self) -> None: - """The proto orders durability W0 < MEMORY < CACHE < W1 < MAJORITY.""" - names = ("w0", "memory", "cache", "w1", "majority") - assert [_make_write_concern(n).level for n in names] == [ - pb.WRITE_CONCERN_LEVEL_W0, - pb.WRITE_CONCERN_LEVEL_MEMORY, - pb.WRITE_CONCERN_LEVEL_CACHE, - pb.WRITE_CONCERN_LEVEL_W1, - pb.WRITE_CONCERN_LEVEL_MAJORITY, - ] + def test_journal_states_on_a_single_ack(self, journal: str, expected: int) -> None: + wc = _make_write_concern(WriteConcern(w=1, journal=journal)) + assert (wc.acks, wc.journal) == (1, expected) + + def test_volatile_journal_is_allowed_fire_and_forget(self) -> None: + wc = _make_write_concern(WriteConcern(w=0, journal="memory")) + assert (wc.WhichOneof("w"), wc.acks, wc.journal) == ("acks", 0, pb.JOURNAL_MEMORY) + + @pytest.mark.parametrize("w", ["majority", 2, 5]) + @pytest.mark.parametrize("journal", ["cache", "memory"]) + def test_volatile_journal_across_members_is_refused(self, w: object, journal: str) -> None: + """The server answers INVALID_ARGUMENT: a volatile write cannot be confirmed by several members.""" + with pytest.raises(ValueError, match="accepted only with w=0 or w=1"): + _make_write_concern(WriteConcern(w=w, journal=journal)) # type: ignore[arg-type] + + def test_timeout_is_carried(self) -> None: + assert _make_write_concern(WriteConcern(timeout_ms=250)).timeout_ms == 250 + + @pytest.mark.parametrize("bad", ["w1", "w0", "", " ", "all"]) + def test_rejects_unknown_named_w(self, bad: str) -> None: + """The retired level names are not silently mapped to a guess.""" + with pytest.raises(ValueError, match="invalid write_concern w"): + _make_write_concern(bad) + + @pytest.mark.parametrize("bad", [-1, 2**32, 1.5, True]) + def test_rejects_a_count_that_is_not_a_uint32(self, bad: object) -> None: + with pytest.raises(ValueError, match="write_concern"): + _make_write_concern(WriteConcern(w=bad)) # type: ignore[arg-type] + + @pytest.mark.parametrize("bad", [None, 1.0, True, ["majority"]]) + def test_rejects_a_value_of_the_wrong_type(self, bad: object) -> None: + with pytest.raises(ValueError, match="write_concern must be a WriteConcern"): + _make_write_concern(bad) # type: ignore[arg-type] - def test_invalid_raises(self) -> None: - with pytest.raises(ValueError, match="invalid write_concern"): - _make_write_concern("w9") + def test_rejects_an_unknown_journal(self) -> None: + with pytest.raises(ValueError, match="invalid journal"): + _make_write_concern(WriteConcern(w=1, journal="disk")) - @pytest.mark.parametrize("bad", ["", " ", None, 1]) - def test_rejects_blank_or_non_string(self, bad: object) -> None: - with pytest.raises(ValueError, match="write_concern must be a non-empty string"): - _make_write_concern(bad) # type: ignore[arg-type] + @pytest.mark.parametrize("bad", [-1, 2**32, True, 1.5]) + def test_rejects_a_bad_timeout(self, bad: object) -> None: + with pytest.raises(ValueError, match="timeout_ms must be a non-negative 32-bit integer"): + _make_write_concern(WriteConcern(timeout_ms=bad)) # type: ignore[arg-type] class TestReadPreference: diff --git a/tests/unit/test_transactions.py b/tests/unit/test_transactions.py index ac610b8..26fb7b0 100644 --- a/tests/unit/test_transactions.py +++ b/tests/unit/test_transactions.py @@ -326,6 +326,69 @@ async def _inner() -> None: asyncio.run(_inner()) + def test_commit_keeps_the_commit_timestamp(self): + """The timestamp is the exact snapshot anchor of the write, so it has to reach the caller.""" + from unittest.mock import AsyncMock + + async def _inner() -> None: + client = _async_client( + CommitTransaction=AsyncMock( + return_value=cypher_pb2.CommitTransactionResponse(applied_index=7, commit_ts=1_700_000_000_000_001) + ) + ) + tx = await client.begin_transaction() + assert tx.commit_ts is None + await tx.commit() + assert tx.commit_ts == 1_700_000_000_000_001 + + asyncio.run(_inner()) + + def test_commit_sends_expected_versions(self): + """A version pins the node; None asks for the node not to exist (create-if-absent).""" + + async def _inner() -> None: + client = _async_client() + tx = await client.begin_transaction() + await tx.commit(expect={5: 123, 9: None}) + sent = client._cypher_stub.CommitTransaction.call_args.args[0] + assert [(e.node_id, e.HasField("version"), e.version) for e in sent.expect] == [ + (5, True, 123), + (9, False, 0), + ] + + asyncio.run(_inner()) + + def test_commit_without_expect_is_unconditional(self): + async def _inner() -> None: + client = _async_client() + tx = await client.begin_transaction() + await tx.commit() + assert list(client._cypher_stub.CommitTransaction.call_args.args[0].expect) == [] + + asyncio.run(_inner()) + + @pytest.mark.parametrize( + "expect", + [{5: 0}, {5: -1}, {5: True}, {5: "7"}, {-1: 3}, {True: 3}, {"5": 3}, {5: 2**64}], + ) + def test_bad_expect_is_refused_before_any_rpc_and_leaves_the_transaction_open(self, expect): + """A malformed argument is the caller's mistake, not a reason to consume the transaction. + + Zero is refused as a version because no node is at version zero; None + is the way to ask for absence. + """ + + async def _inner() -> None: + client = _async_client() + tx = await client.begin_transaction() + with pytest.raises(ValueError, match="expect"): + await tx.commit(expect=expect) + assert client._cypher_stub.CommitTransaction.await_count == 0 + assert tx.is_open is True + assert await tx.commit() == 7 + + asyncio.run(_inner()) + # ── Sync wrapper ───────────────────────────────────────────────────────────── @@ -354,6 +417,29 @@ def test_explicit_commit_returns_the_applied_index(self): assert tx.commit() == 7 assert tx.is_open is False + def test_sync_commit_passes_expect_and_exposes_the_timestamp(self): + from unittest.mock import AsyncMock + + client = _sync_client( + CommitTransaction=AsyncMock( + return_value=cypher_pb2.CommitTransactionResponse(applied_index=7, commit_ts=99) + ) + ) + tx = client.begin_transaction() + tx.commit(expect={5: 123}) + sent = client._async._cypher_stub.CommitTransaction.call_args.args[0] + assert [(e.node_id, e.version) for e in sent.expect] == [(5, 123)] + assert tx.commit_ts == 99 + + def test_sync_bad_expect_leaves_the_transaction_open(self): + """The loop-boundary guard settles only real interruptions as indeterminate, not a rejected argument.""" + client = _sync_client() + tx = client.begin_transaction() + with pytest.raises(ValueError, match="expect"): + tx.commit(expect={5: 0}) + assert tx.is_open is True + assert tx.commit() == 7 + def test_reuse_after_commit_raises(self): client = _sync_client() tx = client.begin_transaction() diff --git a/uv.lock b/uv.lock index 58c89d2..a115f94 100644 --- a/uv.lock +++ b/uv.lock @@ -586,7 +586,7 @@ provides-extras = ["dev"] [[package]] name = "coordinode-workspace" -version = "2.0.0" +version = "2.1.1" source = { virtual = "." } dependencies = [ { name = "googleapis-common-protos" }, From bb75007107d073adb03e41f23ba5b0109ebcd744 Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Thu, 24 Sep 2026 02:20:04 +0300 Subject: [PATCH 2/6] fix: leave commit_ts unset without a timestamp - A commit reply without commit_ts (a server older than 0.6) carries zero, which is never a real commit time; store None, as NodeResult.version already does, so at_timestamp=tx.commit_ts is not handed the epoch. - Notebooks 03 and 04: import os in the cell that uses it, not in the install cell that does not, so the graph-opening and connect cells rerun on their own after a kernel restart. --- coordinode/coordinode/client.py | 4 +++- demo/notebooks/03_langgraph_agent.ipynb | 4 ++-- demo/notebooks/04_whats_new_in_0_5.ipynb | 4 ++-- tests/unit/test_transactions.py | 15 +++++++++++++++ 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/coordinode/coordinode/client.py b/coordinode/coordinode/client.py index 8d9e55c..2c999bb 100644 --- a/coordinode/coordinode/client.py +++ b/coordinode/coordinode/client.py @@ -873,7 +873,9 @@ async def commit(self, *, expect: Mapping[int, int | None] | None = None) -> int self._spawn_cleanup() raise self._state = "committed" - self.commit_ts = int(resp.commit_ts) + # Zero is how the wire says "no timestamp" (a server older than 0.6); + # it is never a real commit time. + self.commit_ts = int(resp.commit_ts) or None return int(resp.applied_index) async def rollback(self) -> None: diff --git a/demo/notebooks/03_langgraph_agent.ipynb b/demo/notebooks/03_langgraph_agent.ipynb index 61f0dc0..17dae76 100644 --- a/demo/notebooks/03_langgraph_agent.ipynb +++ b/demo/notebooks/03_langgraph_agent.ipynb @@ -20,7 +20,7 @@ "id": "2", "metadata": {}, "outputs": [], - "source": "import importlib.util, os, shutil, subprocess, sys\n\n# (distribution on PyPI, module to import it by).\n# coordinode-embedded is listed unconditionally: this notebook runs the engine\n# in-process so that the graph an agent writes into is private to the run.\npkgs = [\n (\"coordinode-embedded\", \"coordinode_embedded\"),\n (\"langchain-coordinode\", \"langchain_coordinode\"),\n (\"langchain-community\", \"langchain_community\"),\n (\"langchain-openai\", \"langchain_openai\"),\n (\"langgraph\", \"langgraph\"),\n (\"nest_asyncio\", \"nest_asyncio\"),\n]\n\n# Install only what is missing. A checkout mounted into the image, or an\n# editable install, already provides these; pulling them from PyPI there\n# would shadow the very code the notebook is meant to exercise.\n#\n# find_spec raises rather than returning None when a dotted name's parent\n# package is absent, which is exactly the fresh environment this cell exists\n# for: unguarded, it would abort before installing anything.\ndef _missing(module: str) -> bool:\n try:\n return importlib.util.find_spec(module) is None\n except ModuleNotFoundError:\n return True\n\n\ndef _install(dists: list[str]) -> None:\n \"\"\"Install into this interpreter with whatever installer it has.\n\n Colab ships pip. A uv-managed venv deliberately does not, and there\n `python -m pip` dies with \"No module named pip\" before the notebook\n reaches its first query, so fall back to uv targeting this same\n interpreter rather than uv's own default environment.\n \"\"\"\n if not _missing(\"pip\"):\n cmd = [sys.executable, \"-m\", \"pip\", \"install\", \"-q\", *dists]\n elif shutil.which(\"uv\"):\n cmd = [\"uv\", \"pip\", \"install\", \"-q\", \"--python\", sys.executable, *dists]\n else:\n raise RuntimeError(\n f\"Neither pip nor uv is available to install: {', '.join(dists)}\"\n )\n subprocess.run(cmd, check=True, timeout=300)\n # The finder caches a directory listing taken before the install, so a\n # package that has just appeared on disk stays invisible without this.\n importlib.invalidate_caches()\n\n\nmissing = [dist for dist, mod in pkgs if _missing(mod)]\nif missing:\n _install(missing)\n\nimport nest_asyncio\n\nnest_asyncio.apply()\n\nprint(\"SDK installed\")" + "source": "import importlib.util, shutil, subprocess, sys\n\n# (distribution on PyPI, module to import it by).\n# coordinode-embedded is listed unconditionally: this notebook runs the engine\n# in-process so that the graph an agent writes into is private to the run.\npkgs = [\n (\"coordinode-embedded\", \"coordinode_embedded\"),\n (\"langchain-coordinode\", \"langchain_coordinode\"),\n (\"langchain-community\", \"langchain_community\"),\n (\"langchain-openai\", \"langchain_openai\"),\n (\"langgraph\", \"langgraph\"),\n (\"nest_asyncio\", \"nest_asyncio\"),\n]\n\n# Install only what is missing. A checkout mounted into the image, or an\n# editable install, already provides these; pulling them from PyPI there\n# would shadow the very code the notebook is meant to exercise.\n#\n# find_spec raises rather than returning None when a dotted name's parent\n# package is absent, which is exactly the fresh environment this cell exists\n# for: unguarded, it would abort before installing anything.\ndef _missing(module: str) -> bool:\n try:\n return importlib.util.find_spec(module) is None\n except ModuleNotFoundError:\n return True\n\n\ndef _install(dists: list[str]) -> None:\n \"\"\"Install into this interpreter with whatever installer it has.\n\n Colab ships pip. A uv-managed venv deliberately does not, and there\n `python -m pip` dies with \"No module named pip\" before the notebook\n reaches its first query, so fall back to uv targeting this same\n interpreter rather than uv's own default environment.\n \"\"\"\n if not _missing(\"pip\"):\n cmd = [sys.executable, \"-m\", \"pip\", \"install\", \"-q\", *dists]\n elif shutil.which(\"uv\"):\n cmd = [\"uv\", \"pip\", \"install\", \"-q\", \"--python\", sys.executable, *dists]\n else:\n raise RuntimeError(\n f\"Neither pip nor uv is available to install: {', '.join(dists)}\"\n )\n subprocess.run(cmd, check=True, timeout=300)\n # The finder caches a directory listing taken before the install, so a\n # package that has just appeared on disk stays invisible without this.\n importlib.invalidate_caches()\n\n\nmissing = [dist for dist, mod in pkgs if _missing(mod)]\nif missing:\n _install(missing)\n\nimport nest_asyncio\n\nnest_asyncio.apply()\n\nprint(\"SDK installed\")" }, { "cell_type": "markdown", @@ -34,7 +34,7 @@ "id": "4", "metadata": {}, "outputs": [], - "source": "import tempfile\n\n# A database this notebook owns, on disk so the graph survives a kernel\n# restart and re-running cells: an agent's memory that vanished every time the\n# kernel bounced would not be much of a demo.\n#\n# It is a file of its own rather than the one notebooks 00 to 02 share. Those\n# pass a seeded graph between them; this one writes and reads only the facts\n# the agent creates here, so a separate file keeps the two apart.\n#\n# That file is also what makes `query_facts` safe to hand raw Cypher: nobody\n# else writes here, so a query the model invents has nothing of anyone else's\n# to reach. Against a shared server that took a guard proving each query\n# stayed inside this run's own tag, and a text check over model-written Cypher\n# leaks the moment it misses a case. COORDINODE_ADDR is ignored for that\n# reason; notebooks 00 to 02 and 04 exercise the client/server path.\nAGENT_DB_PATH = os.environ.get(\n \"COORDINODE_AGENT_DB\",\n \"/content/coordinode-agent-demo.db\"\n if os.path.isdir(\"/content\")\n else os.path.join(tempfile.gettempdir(), \"coordinode-agent-demo.db\"),\n)\n\ntry:\n from coordinode_embedded import LocalClient\nexcept ImportError as exc:\n raise RuntimeError(\n \"coordinode-embedded is not installed, and this notebook runs on it.\\n\"\n \" pip install coordinode-embedded\\n\"\n \"In Colab, rerun the install cell above. To build it from this \"\n \"checkout instead: maturin build --manifest-path coordinode-embedded/\"\n \"Cargo.toml, then install the wheel it prints.\"\n ) from exc\n\nclient = LocalClient(AGENT_DB_PATH)\n\n# `find_related` traverses with a variable-length relationship. Ask the engine\n# for one now, so an installation too old to parse it says so here instead of\n# failing mid-conversation with a parse error that reads like the agent's\n# fault.\ntry:\n client.cypher(\"MATCH (n)-[*1..2]->(m) RETURN n LIMIT 1\")\nexcept Exception as exc:\n client.close()\n raise RuntimeError(\n \"The installed coordinode-embedded cannot parse a variable-length \"\n \"relationship, which `find_related` uses.\\n\"\n \"Upgrade it, or build the current one from this checkout:\\n\"\n \" maturin build --manifest-path coordinode-embedded/Cargo.toml\\n\"\n \"then install the wheel it prints.\"\n ) from exc\n\nprint(f\"Graph memory at {AGENT_DB_PATH}\")" + "source": "import os, tempfile\n\n# A database this notebook owns, on disk so the graph survives a kernel\n# restart and re-running cells: an agent's memory that vanished every time the\n# kernel bounced would not be much of a demo.\n#\n# It is a file of its own rather than the one notebooks 00 to 02 share. Those\n# pass a seeded graph between them; this one writes and reads only the facts\n# the agent creates here, so a separate file keeps the two apart.\n#\n# That file is also what makes `query_facts` safe to hand raw Cypher: nobody\n# else writes here, so a query the model invents has nothing of anyone else's\n# to reach. Against a shared server that took a guard proving each query\n# stayed inside this run's own tag, and a text check over model-written Cypher\n# leaks the moment it misses a case. COORDINODE_ADDR is ignored for that\n# reason; notebooks 00 to 02 and 04 exercise the client/server path.\nAGENT_DB_PATH = os.environ.get(\n \"COORDINODE_AGENT_DB\",\n \"/content/coordinode-agent-demo.db\"\n if os.path.isdir(\"/content\")\n else os.path.join(tempfile.gettempdir(), \"coordinode-agent-demo.db\"),\n)\n\ntry:\n from coordinode_embedded import LocalClient\nexcept ImportError as exc:\n raise RuntimeError(\n \"coordinode-embedded is not installed, and this notebook runs on it.\\n\"\n \" pip install coordinode-embedded\\n\"\n \"In Colab, rerun the install cell above. To build it from this \"\n \"checkout instead: maturin build --manifest-path coordinode-embedded/\"\n \"Cargo.toml, then install the wheel it prints.\"\n ) from exc\n\nclient = LocalClient(AGENT_DB_PATH)\n\n# `find_related` traverses with a variable-length relationship. Ask the engine\n# for one now, so an installation too old to parse it says so here instead of\n# failing mid-conversation with a parse error that reads like the agent's\n# fault.\ntry:\n client.cypher(\"MATCH (n)-[*1..2]->(m) RETURN n LIMIT 1\")\nexcept Exception as exc:\n client.close()\n raise RuntimeError(\n \"The installed coordinode-embedded cannot parse a variable-length \"\n \"relationship, which `find_related` uses.\\n\"\n \"Upgrade it, or build the current one from this checkout:\\n\"\n \" maturin build --manifest-path coordinode-embedded/Cargo.toml\\n\"\n \"then install the wheel it prints.\"\n ) from exc\n\nprint(f\"Graph memory at {AGENT_DB_PATH}\")" }, { "cell_type": "markdown", diff --git a/demo/notebooks/04_whats_new_in_0_5.ipynb b/demo/notebooks/04_whats_new_in_0_5.ipynb index bf0d81e..7133c2c 100644 --- a/demo/notebooks/04_whats_new_in_0_5.ipynb +++ b/demo/notebooks/04_whats_new_in_0_5.ipynb @@ -19,7 +19,7 @@ "execution_count": null, "metadata": {}, "outputs": [], - "source": "import importlib.util, inspect, os, shutil, subprocess, sys\n\n# (distribution on PyPI, module to import it by).\npkgs = [\n (\"coordinode\", \"coordinode\"),\n (\"nest_asyncio\", \"nest_asyncio\"),\n]\n\n# Install only what is missing. A checkout mounted into the image, or an\n# editable install, already provides these; pulling them from PyPI there\n# would shadow the very code the notebook is meant to exercise.\n#\n# find_spec raises rather than returning None when a dotted name's parent\n# package is absent, which is exactly the fresh environment this cell exists\n# for: unguarded, it would abort before installing anything.\ndef _missing(module: str) -> bool:\n try:\n return importlib.util.find_spec(module) is None\n except ModuleNotFoundError:\n return True\n\n\ndef _install(dists: list[str], upgrade: bool = False) -> None:\n \"\"\"Install into this interpreter with whatever installer it has.\n\n Colab ships pip. A uv-managed venv deliberately does not, and there\n `python -m pip` dies with \"No module named pip\" before the notebook\n reaches its first query, so fall back to uv targeting this same\n interpreter rather than uv's own default environment.\n\n The finder caches a directory listing taken before the install, so a\n package that has just appeared on disk is invisible until the caches are\n dropped. Doing it here means every caller sees what it installed.\n \"\"\"\n flags = [\"-q\", \"-U\"] if upgrade else [\"-q\"]\n if not _missing(\"pip\"):\n cmd = [sys.executable, \"-m\", \"pip\", \"install\", *flags, *dists]\n elif shutil.which(\"uv\"):\n cmd = [\"uv\", \"pip\", \"install\", *flags, \"--python\", sys.executable, *dists]\n else:\n raise RuntimeError(\n f\"Neither pip nor uv is available to install: {', '.join(dists)}\"\n )\n subprocess.run(cmd, check=True, timeout=300)\n importlib.invalidate_caches()\n\n\ndef _has_0_5_surface() -> bool:\n \"\"\"Whether the installed `coordinode` has the API this notebook calls.\n\n Every cell below is about something 0.5 added, so \"the module imports\" is\n the wrong question: a 0.4 install answers it happily and then fails four\n cells later with an AttributeError about `create_nodes_batch`, which reads\n like a broken notebook rather than an old package. The probes cover the\n set: the batch method and the consistency arguments on `cypher`, which\n shipped together, `transaction`, which arrived after them, and\n `WriteConcern`, the two-axis write concern the server speaks since 0.6.\n\n Probing the surface rather than comparing `__version__` is deliberate. An\n editable install without hatch-vcs reports 0.0.0, so a version floor would\n reject exactly the checkout this notebook is meant to exercise.\n \"\"\"\n try:\n from coordinode import CoordinodeClient, WriteConcern # noqa: F401\n except ImportError:\n return False\n if not hasattr(CoordinodeClient, \"create_nodes_batch\"):\n return False\n if not hasattr(CoordinodeClient, \"transaction\"):\n return False\n params = inspect.signature(CoordinodeClient.cypher).parameters\n return {\"read_concern\", \"write_concern\", \"at_timestamp\"}.issubset(params)\n\n\nmissing = [dist for dist, mod in pkgs if _missing(mod)]\nif missing:\n _install(missing)\n\nif not _has_0_5_surface():\n print(\"Installed `coordinode` predates this notebook's API, upgrading it.\")\n _install([\"coordinode\"], upgrade=True)\n # The old module object stays in sys.modules and would keep answering\n # imports for the rest of the session, so drop it and re-probe.\n for name in [m for m in sys.modules if m == \"coordinode\" or m.startswith(\"coordinode.\")]:\n del sys.modules[name]\n if not _has_0_5_surface():\n raise RuntimeError(\n \"`coordinode` still lacks the client surface this notebook uses after \"\n \"upgrading. If this environment mounts a checkout or uses an editable \"\n \"install, that source is too old: update it, or install the released \"\n \"package instead.\"\n )\n\nimport nest_asyncio\n\nnest_asyncio.apply()\n\nprint(\"Ready\")", + "source": "import importlib.util, inspect, shutil, subprocess, sys\n\n# (distribution on PyPI, module to import it by).\npkgs = [\n (\"coordinode\", \"coordinode\"),\n (\"nest_asyncio\", \"nest_asyncio\"),\n]\n\n# Install only what is missing. A checkout mounted into the image, or an\n# editable install, already provides these; pulling them from PyPI there\n# would shadow the very code the notebook is meant to exercise.\n#\n# find_spec raises rather than returning None when a dotted name's parent\n# package is absent, which is exactly the fresh environment this cell exists\n# for: unguarded, it would abort before installing anything.\ndef _missing(module: str) -> bool:\n try:\n return importlib.util.find_spec(module) is None\n except ModuleNotFoundError:\n return True\n\n\ndef _install(dists: list[str], upgrade: bool = False) -> None:\n \"\"\"Install into this interpreter with whatever installer it has.\n\n Colab ships pip. A uv-managed venv deliberately does not, and there\n `python -m pip` dies with \"No module named pip\" before the notebook\n reaches its first query, so fall back to uv targeting this same\n interpreter rather than uv's own default environment.\n\n The finder caches a directory listing taken before the install, so a\n package that has just appeared on disk is invisible until the caches are\n dropped. Doing it here means every caller sees what it installed.\n \"\"\"\n flags = [\"-q\", \"-U\"] if upgrade else [\"-q\"]\n if not _missing(\"pip\"):\n cmd = [sys.executable, \"-m\", \"pip\", \"install\", *flags, *dists]\n elif shutil.which(\"uv\"):\n cmd = [\"uv\", \"pip\", \"install\", *flags, \"--python\", sys.executable, *dists]\n else:\n raise RuntimeError(\n f\"Neither pip nor uv is available to install: {', '.join(dists)}\"\n )\n subprocess.run(cmd, check=True, timeout=300)\n importlib.invalidate_caches()\n\n\ndef _has_0_5_surface() -> bool:\n \"\"\"Whether the installed `coordinode` has the API this notebook calls.\n\n Every cell below is about something 0.5 added, so \"the module imports\" is\n the wrong question: a 0.4 install answers it happily and then fails four\n cells later with an AttributeError about `create_nodes_batch`, which reads\n like a broken notebook rather than an old package. The probes cover the\n set: the batch method and the consistency arguments on `cypher`, which\n shipped together, `transaction`, which arrived after them, and\n `WriteConcern`, the two-axis write concern the server speaks since 0.6.\n\n Probing the surface rather than comparing `__version__` is deliberate. An\n editable install without hatch-vcs reports 0.0.0, so a version floor would\n reject exactly the checkout this notebook is meant to exercise.\n \"\"\"\n try:\n from coordinode import CoordinodeClient, WriteConcern # noqa: F401\n except ImportError:\n return False\n if not hasattr(CoordinodeClient, \"create_nodes_batch\"):\n return False\n if not hasattr(CoordinodeClient, \"transaction\"):\n return False\n params = inspect.signature(CoordinodeClient.cypher).parameters\n return {\"read_concern\", \"write_concern\", \"at_timestamp\"}.issubset(params)\n\n\nmissing = [dist for dist, mod in pkgs if _missing(mod)]\nif missing:\n _install(missing)\n\nif not _has_0_5_surface():\n print(\"Installed `coordinode` predates this notebook's API, upgrading it.\")\n _install([\"coordinode\"], upgrade=True)\n # The old module object stays in sys.modules and would keep answering\n # imports for the rest of the session, so drop it and re-probe.\n for name in [m for m in sys.modules if m == \"coordinode\" or m.startswith(\"coordinode.\")]:\n del sys.modules[name]\n if not _has_0_5_surface():\n raise RuntimeError(\n \"`coordinode` still lacks the client surface this notebook uses after \"\n \"upgrading. If this environment mounts a checkout or uses an editable \"\n \"install, that source is too old: update it, or install the released \"\n \"package instead.\"\n )\n\nimport nest_asyncio\n\nnest_asyncio.apply()\n\nprint(\"Ready\")", "id": "cell-02-54b3dc" }, { @@ -38,7 +38,7 @@ "execution_count": null, "metadata": {}, "outputs": [], - "source": "import time, uuid\n\nCOORDINODE_ADDR = os.environ.get(\"COORDINODE_ADDR\")\nclient = None\n\nif COORDINODE_ADDR:\n from coordinode import CoordinodeClient\n\n client = CoordinodeClient(COORDINODE_ADDR)\n if not client.health():\n client.close()\n raise RuntimeError(f\"Health check failed for {COORDINODE_ADDR}\")\n print(f\"Connected to {COORDINODE_ADDR}\")\nelse:\n print(\n \"COORDINODE_ADDR is not set, so this notebook has nothing to talk to.\\n\"\n \"Start the demo stack (see demo/README.md) or point at your own server:\\n\"\n ' os.environ[\"COORDINODE_ADDR\"] = \"localhost:37080\"'\n )\n\n# Tag every node this run creates, so the cleanup at the end removes exactly\n# what was added and nothing a sibling notebook left behind.\nDEMO_TAG = f\"whats_new_{uuid.uuid4().hex[:8]}\"\nprint(f\"DEMO_TAG: {DEMO_TAG}\")", + "source": "import os, time, uuid\n\nCOORDINODE_ADDR = os.environ.get(\"COORDINODE_ADDR\")\nclient = None\n\nif COORDINODE_ADDR:\n from coordinode import CoordinodeClient\n\n client = CoordinodeClient(COORDINODE_ADDR)\n if not client.health():\n client.close()\n raise RuntimeError(f\"Health check failed for {COORDINODE_ADDR}\")\n print(f\"Connected to {COORDINODE_ADDR}\")\nelse:\n print(\n \"COORDINODE_ADDR is not set, so this notebook has nothing to talk to.\\n\"\n \"Start the demo stack (see demo/README.md) or point at your own server:\\n\"\n ' os.environ[\"COORDINODE_ADDR\"] = \"localhost:37080\"'\n )\n\n# Tag every node this run creates, so the cleanup at the end removes exactly\n# what was added and nothing a sibling notebook left behind.\nDEMO_TAG = f\"whats_new_{uuid.uuid4().hex[:8]}\"\nprint(f\"DEMO_TAG: {DEMO_TAG}\")", "id": "cell-04-340937" }, { diff --git a/tests/unit/test_transactions.py b/tests/unit/test_transactions.py index 26fb7b0..d0c76b9 100644 --- a/tests/unit/test_transactions.py +++ b/tests/unit/test_transactions.py @@ -343,6 +343,21 @@ async def _inner() -> None: asyncio.run(_inner()) + def test_a_commit_without_a_timestamp_leaves_commit_ts_unset(self): + """Zero is how the wire says "no timestamp" (a server older than 0.6). + + Storing it would hand `at_timestamp=tx.commit_ts` a value that reads + as the epoch and is refused as not positive. + """ + + async def _inner() -> None: + client = _async_client() # the default reply carries no commit_ts + tx = await client.begin_transaction() + assert await tx.commit() == 7 + assert tx.commit_ts is None + + asyncio.run(_inner()) + def test_commit_sends_expected_versions(self): """A version pins the node; None asks for the node not to exist (create-if-absent).""" From 38d18f2d83203e5b37cf76927741045760199343 Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Thu, 24 Sep 2026 02:35:38 +0300 Subject: [PATCH 3/6] ci: install third-party packages from wheels only CI and the release job ran plain `uv sync` / `uv run`, so a dependency without a wheel for a runner's Python would be built from its source distribution, running its setup script. --no-build was dropped earlier because it broke the release PR: that PR bumps the version in pyproject.toml but leaves uv.lock on the old one, and re-resolving a stale lock needs builds of this workspace's own packages (their version is dynamic). - release-please.yml refreshes uv.lock on the release branch whenever the release PR is created or updated, and pushes it with the bot token. release-please's own TOML updater cannot do it: its JSONPath filter does not match the workspace entry by name (checked against its GenericToml updater on the real lock). The refresh changes exactly the workspace version line. - ci.yml and release.yml run uv sync / uv run with --locked --no-build: the exact locked set, third-party from wheels only, while uv still builds the three workspace packages from a matching lock. A pull request that changes dependencies now has to commit the lock. - The release build uses `uv build --no-build` instead of `python -m build`, so the build backend is wheel-only too; the pypa `build` dev dependency goes with it. - The embedded CI check installs its wheels with --no-build and finds the wheel with find instead of parsing ls. --- .github/workflows/ci.yml | 58 +++++++++++----------------- .github/workflows/release-please.yml | 46 ++++++++++++++++++++++ .github/workflows/release.yml | 10 +++-- pyproject.toml | 1 - uv.lock | 25 ------------ 5 files changed, 75 insertions(+), 65 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 60181fb..daf1e3c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,35 +16,18 @@ jobs: with: python-version: "3.11" - # Plain `uv sync`, deliberately, on two counts. + # --locked: install exactly what uv.lock pins, and fail if the lock no + # longer matches pyproject.toml; a pull request that changes + # dependencies commits the regenerated lock with them. The release PR + # gets its lock refreshed by release-please.yml, so it passes too. # - # Resolving fresh is wanted on the pull requests where it actually - # happens, which is not all of them. `uv sync` follows the committed lock - # while that lock satisfies pyproject.toml, so an ordinary pull request - # installs the locked versions. When a pull request moves dependency - # metadata without regenerating the lock, uv resolves the new set and - # these jobs exercise it. `--locked` would instead refuse to run and - # report that the lock needs updating, which says nothing about whether - # the new version works. - # - # The two flags tried here also break the release, and for one shared - # reason: the release bumps the version in pyproject.toml and leaves - # uv.lock naming the old one, so uv has to re-resolve. - # - # --locked fails on exactly that mismatch, reporting that the - # lockfile needs to be updated. - # --no-build fails because re-resolving means building this - # workspace's own three packages, which are source rather - # than wheels: "Building source distributions for - # coordinode is disabled". - # - # Verified on the release branch with a cold cache: both fail there, and - # both pass on that same branch once uv.lock carries the new version. So - # restoring either one means first making the release regenerate the - # lock, and even then it gives up the fresh resolution described above. - - run: uv sync - - run: uv run ruff check coordinode/ langchain-coordinode/ llama-index-coordinode/ tests/ - - run: uv run ruff format --check coordinode/ langchain-coordinode/ llama-index-coordinode/ tests/ + # --no-build: third-party packages come from wheels only, so no setup + # script from a source distribution runs here. uv still builds this + # workspace's own three packages from a matching lock; only resolving a + # stale one would need those builds, and --locked refuses that first. + - run: uv sync --locked --no-build + - run: uv run --locked --no-build ruff check coordinode/ langchain-coordinode/ llama-index-coordinode/ tests/ + - run: uv run --locked --no-build ruff format --check coordinode/ langchain-coordinode/ llama-index-coordinode/ tests/ test: name: Test (Python ${{ matrix.python-version }}) @@ -62,14 +45,15 @@ jobs: with: python-version: ${{ matrix.python-version }} + # --locked --no-build: see the lint job. - name: Install dependencies - run: uv sync --all-packages + run: uv sync --all-packages --locked --no-build - name: Generate proto stubs - run: uv run make proto + run: uv run --all-packages --locked --no-build make proto - name: Unit tests - run: uv run pytest tests/unit/ -v + run: uv run --all-packages --locked --no-build pytest tests/unit/ -v build-embedded: name: Build embedded (CI check) @@ -106,13 +90,14 @@ jobs: # the wheel goes to ./dist/ (workspace-relative), not /tmp/. run: | set -euo pipefail - WHL=$(ls dist/coordinode_embedded-*.whl | head -1) + WHL=$(find dist -name 'coordinode_embedded-*.whl' -print -quit) test -n "$WHL" uv venv --python 3.12 /tmp/test-venv # Pinned to the versions uv.lock already resolves for the workspace, # so this throwaway venv tests against the same stack as the other # jobs instead of whatever is newest on PyPI that morning. - uv pip install --python /tmp/test-venv/bin/python "$WHL" \ + # Wheels only (--no-build), as everywhere else in this workflow. + uv pip install --no-build --python /tmp/test-venv/bin/python "$WHL" \ numpy==2.4.6 pytest==9.1.1 pytest-timeout==2.4.0 # Every test file that needs the extension belongs here: the main # `test` job skips them all for want of a wheel, so a file left out @@ -171,12 +156,13 @@ jobs: echo "Error: coordinode did not become healthy after 150s" >&2 exit 1 + # --locked --no-build: see the lint job. - name: Install + generate proto run: | - uv sync --all-packages - uv run make proto + uv sync --all-packages --locked --no-build + uv run --all-packages --locked --no-build make proto - name: Integration tests env: COORDINODE_ADDR: "localhost:7080" - run: uv run pytest tests/integration/ -v --timeout=30 + run: uv run --all-packages --locked --no-build pytest tests/integration/ -v --timeout=30 diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 28ddd98..76cfadc 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -26,3 +26,49 @@ jobs: token: ${{ steps.app-token.outputs.token }} config-file: release-please-config.json manifest-file: .release-please-manifest.json + + # The release PR bumps the workspace version in pyproject.toml, and + # uv.lock records that version too. Left stale, every `uv sync --locked` + # in CI refuses the branch, and without --locked uv re-resolves, which + # builds source distributions. release-please cannot edit the lock + # itself: its TOML updater cannot select the workspace entry by name. + # So the lock is refreshed here, on the release branch, whenever the + # release PR was created or updated. Pushed with the bot token so CI + # runs on the new commit. + - name: Check out the release branch + if: steps.rp.outputs.pr + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + ref: ${{ fromJSON(steps.rp.outputs.pr).headBranchName }} + token: ${{ steps.app-token.outputs.token }} + # hatch-vcs derives the member versions from tags while locking. + fetch-depth: 0 + + - uses: astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4 + if: steps.rp.outputs.pr + with: + python-version: "3.11" + + - name: Sync uv.lock with the release version + if: steps.rp.outputs.pr + # Locking reads the workspace members' metadata, and their version is + # dynamic (hatch-vcs), so uv has to build these three packages of this + # repository; --no-build refuses exactly that. Third-party packages + # keep their locked versions, and the CI run this push triggers + # installs them with `--locked --no-build`, failing if any of them + # would need a source build. + run: uv lock # NOSONAR + - name: Commit the refreshed lock + if: steps.rp.outputs.pr + env: + BRANCH: ${{ fromJSON(steps.rp.outputs.pr).headBranchName }} + run: | + if git diff --quiet -- uv.lock; then + echo "uv.lock already matches the release version" + exit 0 + fi + git config user.name "sw-release-bot[bot]" + git config user.email "255865126+sw-release-bot[bot]@users.noreply.github.com" + git add uv.lock + git commit -m "chore: sync uv.lock with the release version" + git push origin "HEAD:$BRANCH" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9054b65..3bb2533 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -38,19 +38,23 @@ jobs: with: python-version: "3.11" + # --locked --no-build: install exactly the locked set, third-party + # packages from wheels only (see ci.yml). - name: Install build tools - run: uv sync + run: uv sync --locked --no-build - name: Generate proto stubs (coordinode only) if: matrix.package.name == 'coordinode' - run: uv run make proto + run: uv run --locked --no-build make proto - name: Build wheel + sdist working-directory: ${{ matrix.package.path }} run: | # Force hatch-vcs / setuptools_scm to use the tag version, not dirty-tree dev version export SETUPTOOLS_SCM_PRETEND_VERSION="${GITHUB_REF_NAME#v}" - uv run python -m build + # Builds this package; --no-build keeps its build backend + # (hatchling, hatch-vcs) to wheels, so nothing else's setup runs. + uv build --no-build --out-dir dist - name: Upload dist artifact uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 diff --git a/pyproject.toml b/pyproject.toml index db476ff..2455e41 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,6 @@ dev = [ "coordinode", "langchain-coordinode", "llama-index-graph-stores-coordinode", - "build>=1.2", "grpcio-tools>=1.83.1", "ipykernel>=7.3.0", "langchain>=1.0", diff --git a/uv.lock b/uv.lock index a115f94..691bc96 100644 --- a/uv.lock +++ b/uv.lock @@ -260,20 +260,6 @@ css = [ { name = "tinycss2" }, ] -[[package]] -name = "build" -version = "1.6.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "os_name == 'nt'" }, - { name = "packaging" }, - { name = "pyproject-hooks" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/4d/b7/1db48a9ce2984842c8c886432ec8a2719613322e868a966ba82a28862f25/build-1.6.0.tar.gz", hash = "sha256:bd2c8afc603e7a2e0ce70e2ea85f0a6d02043bafbd307f5bada0f98669eca5af", size = 113825, upload-time = "2026-08-27T21:01:16.458Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/e5/aa1e81b21aea0ce0ba435311837a37d4cb936e7461f9fecac08580073ba9/build-1.6.0-py3-none-any.whl", hash = "sha256:f7aaf1ebbb79178a02ba248bb524f2176b256017e17e8e4bd4289c7b38cc2bad", size = 31187, upload-time = "2026-08-27T21:01:14.957Z" }, -] - [[package]] name = "certifi" version = "2026.7.22" @@ -594,7 +580,6 @@ dependencies = [ [package.dev-dependencies] dev = [ - { name = "build" }, { name = "coordinode" }, { name = "grpcio-tools" }, { name = "ipykernel" }, @@ -619,7 +604,6 @@ requires-dist = [{ name = "googleapis-common-protos", specifier = ">=1.74.0" }] [package.metadata.requires-dev] dev = [ - { name = "build", specifier = ">=1.2" }, { name = "coordinode", editable = "coordinode" }, { name = "grpcio-tools", specifier = ">=1.83.1" }, { name = "ipykernel", specifier = ">=7.3.0" }, @@ -2839,15 +2823,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" }, ] -[[package]] -name = "pyproject-hooks" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/82/28175b2414effca1cdac8dc99f76d660e7a4fb0ceefa4b4ab8f5f6742925/pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8", size = 19228, upload-time = "2024-09-29T09:24:13.293Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913", size = 10216, upload-time = "2024-09-29T09:24:11.978Z" }, -] - [[package]] name = "pytest" version = "9.1.1" From d38d672a262018a257dcc1eb69e0df843320e0fe Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Thu, 24 Sep 2026 02:44:35 +0300 Subject: [PATCH 4/6] build: require the uv release the --no-build steps rely on CI and the release pass --no-build and rely on uv still building this workspace's own packages. Older uv applied the flag to them as well and refused to install or build them, while setup-uv installs the latest release, so the floor is stated in pyproject.toml, where uv enforces it. Verified on 0.12.12 locally and 0.12.18 in CI. --- pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 2455e41..4dda059 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,10 @@ dependencies = [ [tool.uv] package = false # virtual root — not installed as a package +# CI and the release run `--no-build`, relying on uv exempting this +# workspace's own packages from it; older uv applied it to them too and +# refused to install or build them. Verified on 0.12.12 and 0.12.18. +required-version = ">=0.12.12" [tool.uv.workspace] members = [ From a53bfbbeb5cc3347227985222738e9f779bf5d8c Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Thu, 24 Sep 2026 04:59:06 +0300 Subject: [PATCH 5/6] fix: refuse an expect argument that is not a mapping commit(expect=...) read a falsy non-mapping such as [] or 0 as "no condition" and committed unconditionally, so a caller's mistake applied the writes instead of failing; a list of pairs crashed with AttributeError. Only None now means no condition, and anything else that is not a mapping raises ValueError before any RPC, leaving the transaction open. --- coordinode/coordinode/client.py | 5 +++++ tests/unit/test_transactions.py | 20 ++++++++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/coordinode/coordinode/client.py b/coordinode/coordinode/client.py index 2c999bb..6e27ab9 100644 --- a/coordinode/coordinode/client.py +++ b/coordinode/coordinode/client.py @@ -812,6 +812,11 @@ async def commit(self, *, expect: Mapping[int, int | None] | None = None) -> int # Validated before the open-check and the state transition: a bad # argument is the caller's mistake, not a reason to consume the # transaction. + # Only None means "no condition". Anything else that is not a mapping + # is refused rather than read as none: a falsy [] or 0 would otherwise + # turn a conditional commit into an unconditional one. + if expect is not None and not isinstance(expect, Mapping): + raise ValueError(f"expect must be a mapping of node ids to versions, got {expect!r}") expected = [] for node_id, version in (expect or {}).items(): if not isinstance(node_id, int) or isinstance(node_id, bool) or not 0 <= node_id <= _UINT64_MAX: diff --git a/tests/unit/test_transactions.py b/tests/unit/test_transactions.py index d0c76b9..382912a 100644 --- a/tests/unit/test_transactions.py +++ b/tests/unit/test_transactions.py @@ -384,13 +384,29 @@ async def _inner() -> None: @pytest.mark.parametrize( "expect", - [{5: 0}, {5: -1}, {5: True}, {5: "7"}, {-1: 3}, {True: 3}, {"5": 3}, {5: 2**64}], + [ + {5: 0}, + {5: -1}, + {5: True}, + {5: "7"}, + {-1: 3}, + {True: 3}, + {"5": 3}, + {5: 2**64}, + # Not a mapping at all. The falsy ones matter most: treated as + # "no condition", they would commit unconditionally. + [], + 0, + "", + [(5, 3)], + ], ) def test_bad_expect_is_refused_before_any_rpc_and_leaves_the_transaction_open(self, expect): """A malformed argument is the caller's mistake, not a reason to consume the transaction. Zero is refused as a version because no node is at version zero; None - is the way to ask for absence. + is the way to ask for absence. A value that is not a mapping is refused + rather than read as "no condition", which would apply the writes. """ async def _inner() -> None: From 80364c6645d1bb1fe9f311524ac8060471f498f9 Mon Sep 17 00:00:00 2001 From: Dmitry Prudnikov Date: Thu, 24 Sep 2026 09:25:33 +0300 Subject: [PATCH 6/6] fix: keep the conditions of a mapping that reads as false The expect loop still chose its fallback with (expect or {}), so a Mapping whose __len__ reports zero while carrying entries was replaced by an empty one and the commit went out without the caller's precondition. The fallback now keys on expect being None, the same rule the argument check applies. Covered by a regression test with such a mapping, which failed before the change. --- coordinode/coordinode/client.py | 2 +- tests/unit/test_transactions.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/coordinode/coordinode/client.py b/coordinode/coordinode/client.py index 6e27ab9..d0d3224 100644 --- a/coordinode/coordinode/client.py +++ b/coordinode/coordinode/client.py @@ -818,7 +818,7 @@ async def commit(self, *, expect: Mapping[int, int | None] | None = None) -> int if expect is not None and not isinstance(expect, Mapping): raise ValueError(f"expect must be a mapping of node ids to versions, got {expect!r}") expected = [] - for node_id, version in (expect or {}).items(): + for node_id, version in expect.items() if expect is not None else (): if not isinstance(node_id, int) or isinstance(node_id, bool) or not 0 <= node_id <= _UINT64_MAX: raise ValueError(f"expect keys must be node ids (non-negative integers), got {node_id!r}") if version is None: diff --git a/tests/unit/test_transactions.py b/tests/unit/test_transactions.py index 382912a..a5cc6b9 100644 --- a/tests/unit/test_transactions.py +++ b/tests/unit/test_transactions.py @@ -373,6 +373,37 @@ async def _inner() -> None: asyncio.run(_inner()) + def test_commit_keeps_the_conditions_of_a_mapping_that_reads_as_false(self): + """Only None drops the condition, never the mapping's truthiness. + + A Mapping whose __len__ reports zero still carries entries. Choosing + the fallback by truthiness would replace it with an empty one and + commit without the precondition the caller asked for. + """ + from collections.abc import Mapping + + class _Falsy(Mapping): + def __init__(self, data): + self._data = data + + def __getitem__(self, key): + return self._data[key] + + def __iter__(self): + return iter(self._data) + + def __len__(self): + return 0 + + async def _inner() -> None: + client = _async_client() + tx = await client.begin_transaction() + await tx.commit(expect=_Falsy({5: 123})) + sent = client._cypher_stub.CommitTransaction.call_args.args[0] + assert [(e.node_id, e.version) for e in sent.expect] == [(5, 123)] + + asyncio.run(_inner()) + def test_commit_without_expect_is_unconditional(self): async def _inner() -> None: client = _async_client()