diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000000000000000000000000000000000000..91a30776e69ee2e9060230242c3e62aeaf6793bf --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,123 @@ +name: rust + +on: + push: + branches: [ main, rust-rewrite ] + paths: + - 'crates/**' + - 'Cargo.toml' + - 'Cargo.lock' + - 'rust-toolchain.toml' + - 'tests/parity/**' + - 'Makefile' + - '.github/workflows/rust.yml' + pull_request: + paths: + - 'crates/**' + - 'Cargo.toml' + - 'Cargo.lock' + - 'rust-toolchain.toml' + - 'tests/parity/**' + - 'Makefile' + - '.github/workflows/rust.yml' + schedule: + # Nightly parity run at 07:17 UTC (weekdays only). Phase 0 allows failure. + - cron: '17 7 * * 1-5' + +concurrency: + group: rust-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + name: test (ubuntu) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install stable toolchain + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + - name: Cache cargo registry + build + uses: Swatinem/rust-cache@v2 + - name: cargo fmt --check + run: cargo fmt --all -- --check + - name: cargo clippy + run: cargo clippy --workspace -- -D warnings + - name: cargo test + run: cargo test --workspace + + wheels: + name: wheels (${{ matrix.target }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + target: x86_64-unknown-linux-gnu + maturin-target: x86_64 + - os: macos-14 + target: aarch64-apple-darwin + maturin-target: aarch64-apple-darwin + - os: macos-13 + target: x86_64-apple-darwin + maturin-target: x86_64-apple-darwin + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Build wheel + uses: PyO3/maturin-action@v1 + with: + command: build + args: --release -m crates/headroom-py/pyproject.toml --out dist + target: ${{ matrix.maturin-target }} + - name: Upload wheel artifact + uses: actions/upload-artifact@v4 + with: + name: wheels-${{ matrix.target }} + path: dist/*.whl + + audit: + name: audit + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - name: Install cargo-audit + cargo-deny + run: | + cargo install --locked cargo-audit || true + cargo install --locked cargo-deny || true + - name: cargo audit (soft-fail) + continue-on-error: true + run: cargo audit + - name: cargo deny check licenses + continue-on-error: true + run: cargo deny check licenses + + parity-nightly: + name: parity (nightly, allowed to fail during Phase 0) + if: github.event_name == 'schedule' + runs-on: ubuntu-latest + continue-on-error: true + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + - uses: Swatinem/rust-cache@v2 + - name: Install deps + run: | + python -m venv .venv + source .venv/bin/activate + pip install --upgrade pip + pip install maturin + pip install -e . + - name: Run parity harness + run: | + source .venv/bin/activate + make test-parity diff --git a/.gitignore b/.gitignore index 4b03031f6ef5b31f6f2e16c12698044f2122cc7e..51e79e0dbd94193f18ab8d2535bffea6b9531ee7 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,12 @@ scripts/* !scripts/repro_codex_replay.py !scripts/fixtures/ !scripts/fixtures/*.json +!scripts/record_fixtures.py + +# Rust / Cargo build artifacts +/target/ +**/target/ +Cargo.lock.bak # Swift SDK (separate repo) swift/ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000000000000000000000000000000000000..1254becd21d1cbf67feb6fcd584494c490ae31fd --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1797 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +dependencies = [ + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cc" +version = "1.2.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "clap" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[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 = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "headroom-core" +version = "0.1.0" +dependencies = [ + "bytes", + "serde", + "serde_json", + "thiserror 1.0.69", + "tracing", +] + +[[package]] +name = "headroom-parity" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "headroom-core", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "headroom-proxy" +version = "0.1.0" +dependencies = [ + "axum", + "headroom-core", + "reqwest", + "serde", + "serde_json", + "tokio", + "tower", + "tracing", +] + +[[package]] +name = "headroom-py" +version = "0.1.0" +dependencies = [ + "headroom-core", + "pyo3", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indoc" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "iri-string" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.185" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mio" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pyo3" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f402062616ab18202ae8319da13fa4279883a2b8a9d9f83f20dbade813ce1884" +dependencies = [ + "cfg-if", + "indoc", + "libc", + "memoffset", + "once_cell", + "portable-atomic", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b14b5775b5ff446dd1056212d778012cbe8a0fbffd368029fd9e25b514479c38" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ab5bcf04a2cdcbb50c7d6105de943f543f9ed92af55818fd17b660390fc8636" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fd24d897903a9e6d80b968368a34e1525aeb719d568dba8b3d4bfa5dc67d453" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c011a03ba1e50152b4b394b479826cad97e7a21eb52df179cd91ac411cbfbe" +dependencies = [ + "heck", + "proc-macro2", + "pyo3-build-config", + "quote", + "syn", +] + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +dependencies = [ + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +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 = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[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", +] + +[[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 = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64", + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustls" +version = "0.23.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c2c118cb077cca2822033836dfb1b975355dfb784b5e8da48f7b6c5db74e60e" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "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 = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +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", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.52.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +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", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unindent" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3" + +[[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_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "yoke" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..878fcdd9002091ff0aa75723efdfaddb3dca180f --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,39 @@ +[workspace] +resolver = "2" +members = [ + "crates/headroom-core", + "crates/headroom-proxy", + "crates/headroom-py", + "crates/headroom-parity", +] +# headroom-py is a Python extension module — it must be built via maturin, not +# plain cargo (the "extension-module" feature tells pyo3 not to link libpython, +# which is required for `import` to work). `cargo build --workspace` without +# explicit members skips it; `cargo test --workspace` still runs its tests +# because pyo3 can dynamically link here for the cdylib used by tests. +default-members = [ + "crates/headroom-core", + "crates/headroom-proxy", + "crates/headroom-parity", +] + +[workspace.package] +edition = "2021" +rust-version = "1.78" +license = "Apache-2.0" +repository = "https://github.com/chopratejas/headroom" +authors = ["Headroom Maintainers"] + +[workspace.dependencies] +serde = { version = "1", features = ["derive"] } +serde_json = "1" +bytes = "1" +thiserror = "1" +tracing = "0.1" +anyhow = "1" +clap = { version = "4", features = ["derive"] } +tokio = { version = "1", features = ["macros", "rt-multi-thread", "signal"] } +axum = "0.7" +tower = "0.5" +reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } +pyo3 = "0.22" diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..ef02b25598ae6df0cee5c4ca25f955a2477ead84 --- /dev/null +++ b/Makefile @@ -0,0 +1,58 @@ +# Headroom Rust build targets. `just` is not installed on dev boxes; this +# Makefile is the source of truth and is mirrored by .github/workflows/rust.yml. + +SHELL := /bin/bash +CARGO ?= cargo +MATURIN ?= maturin +PYTHON ?= python3 +FIXTURES ?= tests/parity/fixtures + +.PHONY: help test test-parity bench build-proxy build-wheel fmt fmt-check lint clippy clean + +help: + @echo "Headroom Rust targets:" + @echo " make test - cargo test --workspace" + @echo " make test-parity - maturin develop + parity-run against fixtures" + @echo " make bench - cargo bench --workspace" + @echo " make build-proxy - release build + strip headroom-proxy, print size" + @echo " make build-wheel - release wheel for headroom-py" + @echo " make fmt - cargo fmt --all" + @echo " make fmt-check - cargo fmt --all -- --check" + @echo " make lint - cargo clippy --workspace -- -D warnings" + @echo " make clean - cargo clean" + +test: + $(CARGO) test --workspace + +test-parity: + @if [ -z "$$VIRTUAL_ENV" ]; then \ + echo "error: activate a venv first (e.g. source .venv/bin/activate)"; \ + exit 1; \ + fi + $(MATURIN) develop -m crates/headroom-py/pyproject.toml + $(CARGO) run -p headroom-parity -- run --fixtures $(FIXTURES) + +bench: + $(CARGO) bench --workspace + +build-proxy: + $(CARGO) build --release -p headroom-proxy + @BIN=target/release/headroom-proxy; \ + if command -v strip >/dev/null 2>&1; then strip "$$BIN" || true; fi; \ + SIZE=$$(wc -c < "$$BIN"); \ + printf 'headroom-proxy: %s bytes (%.1f MiB)\n' "$$SIZE" "$$(echo "$$SIZE / 1048576" | bc -l)" + +build-wheel: + $(MATURIN) build --release -m crates/headroom-py/pyproject.toml + +fmt: + $(CARGO) fmt --all + +fmt-check: + $(CARGO) fmt --all -- --check + +clippy lint: + $(CARGO) clippy --workspace -- -D warnings + +clean: + $(CARGO) clean diff --git a/RUST_DEV.md b/RUST_DEV.md new file mode 100644 index 0000000000000000000000000000000000000000..9be6e3f5aa2ee0254b0f33d5e3fee72ce9c2d6c1 --- /dev/null +++ b/RUST_DEV.md @@ -0,0 +1,142 @@ +# Headroom Rust Rewrite — Developer Guide + +This document covers the Rust port of Headroom. It is the only new top-level +doc created in Phase 0; longer-form design/plan writeups live elsewhere and +are not versioned in this repo. + +## Workspace layout + +``` +Cargo.toml # workspace root +rust-toolchain.toml # pins stable rustc with rustfmt+clippy +crates/ + headroom-core/ # library: shared types + transform trait surface + headroom-proxy/ # binary: axum /healthz (Phase 2 grows this) + headroom-py/ # PyO3 cdylib exposing `headroom._core` + headroom-parity/ # lib + `parity-run` CLI for Python parity tests +tests/parity/ + fixtures//*.json # recorded Python outputs (Phase 1 ports match) + recorder.py # Python-side fixture recorder +scripts/record_fixtures.py # entry point for running the recorder +``` + +`cargo build --workspace` builds every crate. `default-members` drops +`headroom-py` from `cargo run`/bare-`cargo test` flows so that `cargo test +--workspace` does not try to execute the PyO3 cdylib standalone (it can't +find `libpython` without a Python interpreter hosting it). + +## Common commands + +`just` is not installed on dev boxes here; a `Makefile` at the repo root +exposes the same targets: + +| Target | What it does | +| --- | --- | +| `make test` | `cargo test --workspace` | +| `make test-parity` | Builds `headroom-py` via maturin, runs `parity-run run` | +| `make bench` | `cargo bench --workspace` | +| `make build-proxy` | Release-builds `headroom-proxy`, strips, prints size | +| `make build-wheel` | `maturin build --release -m crates/headroom-py/pyproject.toml` | +| `make fmt` | `cargo fmt --all` | +| `make lint` | `cargo fmt --check` + `cargo clippy --workspace -- -D warnings` | + +## Running the proxy + +```bash +cargo run -p headroom-proxy +# or +make build-proxy +./target/release/headroom-proxy +# then +curl -s http://127.0.0.1:8787/healthz +# => {"ok":true} +``` + +## Maturin + Python wiring + +`headroom-py` is a PyO3 cdylib that exposes `headroom._core` in Python. The +`extension-module` feature is opt-in so plain `cargo build --workspace` does +not try to link against `libpython` on systems that don't have it. + +### First-time setup (clean venv recommended) + +```bash +python3.11 -m venv /tmp/hr-rust-venv +source /tmp/hr-rust-venv/bin/activate +pip install maturin +cd crates/headroom-py +maturin develop # editable dev build, installs headroom._core +cd /tmp # IMPORTANT: step out of the repo root first +python -c "from headroom._core import hello; print(hello())" +# => headroom-core +``` + +> Why `cd /tmp`? The repo root also contains the Python `headroom/` package. +> Running the smoke import from the repo root makes Python resolve `headroom` +> to `./headroom/__init__.py` (the full SDK, which pulls in heavy deps) instead +> of the lightweight namespace package installed by maturin. Tests should +> either run outside the repo root, or ensure `headroom` is installed into +> the same venv (then the maturin-installed `_core.so` lands alongside it and +> both imports resolve). + +### Release wheels + +```bash +make build-wheel +# wheels land under target/wheels/ +``` + +CI (`.github/workflows/rust.yml`) builds linux-x86_64, macos-arm64, and +macos-x86_64 wheels via `PyO3/maturin-action` and uploads them as artifacts. + +## Parity harness + +`crates/headroom-parity` owns the Rust-vs-Python oracle: + +- JSON fixtures under `tests/parity/fixtures//` (schema: + `{ transform, input, config, output, recorded_at, input_sha256 }`). +- `TransformComparator` trait — one impl per transform. Phase 0 stubs return + `Err(...)`; the harness flags those as `Skipped`, not panics. +- `parity-run` CLI: `cargo run -p headroom-parity -- run [--only TRANSFORM]`. +- Unit tests in `crates/headroom-parity/src/lib.rs` include a **negative + test** (`harness_reports_diff_for_divergent_comparator`) proving the + harness detects mismatched output before any real port lands. + +### Recording fresh fixtures + +```bash +source .venv/bin/activate # the main Python SDK venv +python scripts/record_fixtures.py # uses tests/parity/recorder.py +ls tests/parity/fixtures/*/ | sort | uniq -c +``` + +The recorder monkey-patches the in-process transform classes (see +`record_all()` in `tests/parity/recorder.py`). It does **not** modify any +file under `headroom/`. + +## Phase 0 Blockers + +These are known limitations for Phase 0. They are tracked here so Phase 1 +doesn't rediscover them. + +- **`cache_aligner` fixtures**: `CacheAligner.apply()` takes + `(messages, tokenizer, **kwargs)` — a `Tokenizer` is provider-specific and + its cheapest `NoopTokenCounter` / `TiktokenTokenCounter` construction still + requires pulling `headroom.providers.*` which imports the full observability + stack (opentelemetry, etc). The recorder records `cache_aligner` only if a + usable tokenizer is cheaply available; otherwise it logs a blocker and + skips. See `recorder.py::_build_cache_aligner_tokenizer`. +- **`ccr` is not a single class**: The repo has `CCRToolInjector`, + `CCRResponseHandler`, `CCRToolCall`, `CCRToolResult` etc. rather than a + single `CCR` class. The recorder targets the encoder-style entry point + most analogous to the Rust port (`CCRToolInjector.inject_tool` and + `CCRResponseHandler.parse_response`). If Phase 1 wants a different split + it should update `recorder.py::record_all` accordingly. +- **Pre-commit hook noise**: `scripts/sync-plugin-versions.py` mutates + `.claude-plugin/marketplace.json`, `.github/plugin/marketplace.json`, and + `plugins/headroom-agent-hooks/**/plugin.json` on every commit. Those + changes are harmless but each commit in Phase 0 picks them up. Phase 1 + does not need to do anything special — just let the hook run. +- **`rust-toolchain.toml`** pins `channel = "stable"` rather than a specific + version so CI picks up the same toolchain the local box uses. Tighten to a + pinned version (e.g. `1.78`) once the port stabilizes. diff --git a/crates/headroom-core/Cargo.toml b/crates/headroom-core/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..366a13872890e1422c954e66f4c24f6691cdda5f --- /dev/null +++ b/crates/headroom-core/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "headroom-core" +version = "0.1.0" +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true +description = "Core Headroom types and compression transform traits (Rust)." + +[dependencies] +serde = { workspace = true } +serde_json = { workspace = true } +bytes = { workspace = true } +thiserror = { workspace = true } +tracing = { workspace = true } diff --git a/crates/headroom-core/src/lib.rs b/crates/headroom-core/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..baac3bad5f6fa2ddc0660401b3b5350330d16aa8 --- /dev/null +++ b/crates/headroom-core/src/lib.rs @@ -0,0 +1,21 @@ +//! headroom-core: foundation crate for the Rust port of Headroom. +//! +//! Phase 0: only exposes stubs. No algorithm implementations yet. + +pub mod transforms; + +/// Identity stub used by downstream crates and the Python binding to verify +/// linkage end-to-end. +pub fn hello() -> &'static str { + "headroom-core" +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn hello_returns_crate_name() { + assert_eq!(hello(), "headroom-core"); + } +} diff --git a/crates/headroom-core/src/transforms/mod.rs b/crates/headroom-core/src/transforms/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..137b04b24a36392d41de12665541af5fca7984f6 --- /dev/null +++ b/crates/headroom-core/src/transforms/mod.rs @@ -0,0 +1,2 @@ +//! Transform trait namespace. Deliberately empty in Phase 0 — ports land in +//! Phase 1 (log_compressor, diff_compressor, cache_aligner, tokenizer, ccr). diff --git a/crates/headroom-parity/Cargo.toml b/crates/headroom-parity/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..b105814de26b5ee83537a348c80770216e317113 --- /dev/null +++ b/crates/headroom-parity/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "headroom-parity" +version = "0.1.0" +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true +description = "Rust-vs-Python parity harness for the Headroom port." + +[lib] +path = "src/lib.rs" + +[[bin]] +name = "parity-run" +path = "src/bin/parity_run.rs" + +[dependencies] +serde = { workspace = true } +serde_json = { workspace = true } +anyhow = { workspace = true } +clap = { workspace = true } +thiserror = { workspace = true } +headroom-core = { path = "../headroom-core" } +# NOTE: Phase 0 does not invoke Python from Rust. Phase 1 adds `pyo3` with +# `auto-initialize` here so comparators can call into the installed +# `headroom` package directly. diff --git a/crates/headroom-parity/src/bin/parity_run.rs b/crates/headroom-parity/src/bin/parity_run.rs new file mode 100644 index 0000000000000000000000000000000000000000..713f1dd485c58dd449823bb3a85b550376983a4d --- /dev/null +++ b/crates/headroom-parity/src/bin/parity_run.rs @@ -0,0 +1,78 @@ +//! `parity-run` CLI: drive the parity harness from the command line. + +use anyhow::Result; +use clap::{Parser, Subcommand}; +use headroom_parity::{builtin_comparators, run_comparator}; +use std::path::PathBuf; + +#[derive(Parser, Debug)] +#[command( + name = "parity-run", + about = "Run Headroom Rust-vs-Python parity checks" +)] +struct Cli { + #[command(subcommand)] + cmd: Cmd, +} + +#[derive(Subcommand, Debug)] +enum Cmd { + /// Run all built-in comparators against fixtures under --fixtures. + Run { + #[arg(long, default_value = "tests/parity/fixtures")] + fixtures: PathBuf, + /// Only run this comparator (by transform name). + #[arg(long)] + only: Option, + }, + /// List the transforms the harness knows about. + List, +} + +fn main() -> Result<()> { + let cli = Cli::parse(); + match cli.cmd { + Cmd::List => { + for c in builtin_comparators() { + println!("{}", c.name()); + } + Ok(()) + } + Cmd::Run { fixtures, only } => { + let mut any_diffs = false; + for comparator in builtin_comparators() { + if let Some(ref filt) = only { + if filt != comparator.name() { + continue; + } + } + let report = run_comparator(&fixtures, comparator.as_ref())?; + println!( + "[{:<16}] total={} matched={} skipped={} diffed={}", + comparator.name(), + report.total(), + report.matched, + report.skipped.len(), + report.diffed.len() + ); + for (path, reason) in &report.skipped { + println!(" skipped {}: {}", path.display(), reason); + } + for (path, expected, actual) in &report.diffed { + any_diffs = true; + println!(" DIFF {}", path.display()); + println!(" expected: {}", first_line(expected)); + println!(" actual : {}", first_line(actual)); + } + } + if any_diffs { + std::process::exit(1); + } + Ok(()) + } + } +} + +fn first_line(s: &str) -> String { + s.lines().next().unwrap_or("").to_string() +} diff --git a/crates/headroom-parity/src/lib.rs b/crates/headroom-parity/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..cb4e885504fa310d46495702312fb6150cb7c190 --- /dev/null +++ b/crates/headroom-parity/src/lib.rs @@ -0,0 +1,308 @@ +//! Parity harness: load JSON fixtures recorded from the Python implementation, +//! run the Rust port, and compare outputs. +//! +//! Phase 0: the per-transform comparators are stubs (`todo!()`), but the +//! harness wiring (fixture loading, dispatch, diff reporting) is real and +//! covered by a negative test. + +use anyhow::{bail, Context, Result}; +use serde::{Deserialize, Serialize}; +use std::fs; +use std::path::{Path, PathBuf}; + +/// Recorded fixture schema. Matches `tests/parity/recorder.py`. +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct Fixture { + pub transform: String, + pub input: serde_json::Value, + #[serde(default)] + pub config: serde_json::Value, + pub output: serde_json::Value, + #[serde(default)] + pub recorded_at: String, + #[serde(default)] + pub input_sha256: String, +} + +/// Outcome of comparing a recorded fixture against the current Rust impl. +#[derive(Debug, Clone)] +pub enum ComparisonOutcome { + Match, + Diff { expected: String, actual: String }, + Skipped { reason: String }, +} + +/// Trait implemented by transform-specific comparators. A comparator receives +/// the fixture's input and config and produces a JSON value to compare against +/// `fixture.output`. +pub trait TransformComparator { + fn name(&self) -> &str; + fn run( + &self, + input: &serde_json::Value, + config: &serde_json::Value, + ) -> Result; +} + +/// Compare a single fixture against a comparator and return an outcome. +pub fn compare_fixture( + comparator: &dyn TransformComparator, + fixture: &Fixture, +) -> Result { + let actual = match comparator.run(&fixture.input, &fixture.config) { + Ok(v) => v, + Err(e) => { + return Ok(ComparisonOutcome::Skipped { + reason: format!("comparator error: {e}"), + }) + } + }; + if actual == fixture.output { + Ok(ComparisonOutcome::Match) + } else { + Ok(ComparisonOutcome::Diff { + expected: serde_json::to_string_pretty(&fixture.output)?, + actual: serde_json::to_string_pretty(&actual)?, + }) + } +} + +/// Load every `*.json` fixture under `dir//`. +pub fn load_fixtures_for(dir: &Path, transform: &str) -> Result> { + let root = dir.join(transform); + if !root.exists() { + return Ok(Vec::new()); + } + let mut out = Vec::new(); + for entry in fs::read_dir(&root).with_context(|| format!("reading {}", root.display()))? { + let entry = entry?; + let path = entry.path(); + if path.extension().and_then(|s| s.to_str()) != Some("json") { + continue; + } + let bytes = fs::read(&path).with_context(|| format!("reading {}", path.display()))?; + let fixture: Fixture = serde_json::from_slice(&bytes) + .with_context(|| format!("parsing fixture {}", path.display()))?; + if fixture.transform != transform { + bail!( + "fixture {} declares transform={} but lives under {}", + path.display(), + fixture.transform, + transform + ); + } + out.push((path, fixture)); + } + Ok(out) +} + +/// Aggregate report of one comparator run. +#[derive(Debug, Default)] +pub struct Report { + pub matched: usize, + pub diffed: Vec<(PathBuf, String, String)>, + pub skipped: Vec<(PathBuf, String)>, +} + +impl Report { + pub fn total(&self) -> usize { + self.matched + self.diffed.len() + self.skipped.len() + } + pub fn is_clean(&self) -> bool { + self.diffed.is_empty() + } +} + +/// Run a comparator over every fixture under `dir//` and return a +/// report. Propagates IO/parse errors but never panics on comparator errors — +/// those become `Skipped` entries. +pub fn run_comparator(dir: &Path, comparator: &dyn TransformComparator) -> Result { + let mut report = Report::default(); + let fixtures = load_fixtures_for(dir, comparator.name())?; + for (path, fixture) in fixtures { + match compare_fixture(comparator, &fixture)? { + ComparisonOutcome::Match => report.matched += 1, + ComparisonOutcome::Diff { expected, actual } => { + report.diffed.push((path, expected, actual)); + } + ComparisonOutcome::Skipped { reason } => { + report.skipped.push((path, reason)); + } + } + } + Ok(report) +} + +// --- Built-in comparator stubs --------------------------------------------- +// +// Phase 1 will replace `todo!()` bodies with real Rust ports. Until then they +// return `Err`, causing the harness to mark fixtures as `Skipped` instead of +// panicking. The parity-run binary wires them up so the CLI works today. + +macro_rules! stub_comparator { + ($ty:ident, $name:literal) => { + pub struct $ty; + impl TransformComparator for $ty { + fn name(&self) -> &str { + $name + } + fn run( + &self, + _input: &serde_json::Value, + _config: &serde_json::Value, + ) -> Result { + anyhow::bail!(concat!("comparator ", $name, " not implemented (Phase 0)")) + } + } + }; +} + +stub_comparator!(LogCompressorComparator, "log_compressor"); +stub_comparator!(DiffCompressorComparator, "diff_compressor"); +stub_comparator!(CacheAlignerComparator, "cache_aligner"); +stub_comparator!(TokenizerComparator, "tokenizer"); +stub_comparator!(CcrComparator, "ccr"); + +/// Every built-in comparator, in a stable order. +pub fn builtin_comparators() -> Vec> { + vec![ + Box::new(LogCompressorComparator), + Box::new(DiffCompressorComparator), + Box::new(CacheAlignerComparator), + Box::new(TokenizerComparator), + Box::new(CcrComparator), + ] +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::Write; + + /// A fake comparator that always returns "rust-output" regardless of + /// input. Paired with a fixture whose `output` is "python-output", this + /// proves the harness reports diffs correctly. + struct FakeDivergent; + impl TransformComparator for FakeDivergent { + fn name(&self) -> &str { + "fake_divergent" + } + fn run( + &self, + _input: &serde_json::Value, + _config: &serde_json::Value, + ) -> Result { + Ok(serde_json::json!("rust-output")) + } + } + + struct FakeAgreeing; + impl TransformComparator for FakeAgreeing { + fn name(&self) -> &str { + "fake_agreeing" + } + fn run( + &self, + _input: &serde_json::Value, + _config: &serde_json::Value, + ) -> Result { + Ok(serde_json::json!("python-output")) + } + } + + fn write_fixture(dir: &Path, transform: &str, name: &str, output: serde_json::Value) { + let sub = dir.join(transform); + fs::create_dir_all(&sub).unwrap(); + let fixture = Fixture { + transform: transform.to_string(), + input: serde_json::json!("hello"), + config: serde_json::json!({}), + output, + recorded_at: "2026-04-23T00:00:00Z".to_string(), + input_sha256: "deadbeef".to_string(), + }; + let mut f = fs::File::create(sub.join(format!("{name}.json"))).unwrap(); + f.write_all(&serde_json::to_vec_pretty(&fixture).unwrap()) + .unwrap(); + } + + #[test] + fn harness_reports_diff_for_divergent_comparator() { + let tmp = tempdir(); + write_fixture( + tmp.path(), + "fake_divergent", + "case1", + serde_json::json!("python-output"), + ); + let report = run_comparator(tmp.path(), &FakeDivergent).unwrap(); + assert_eq!(report.total(), 1); + assert_eq!(report.matched, 0); + assert_eq!(report.diffed.len(), 1); + assert!(!report.is_clean()); + let (_, expected, actual) = &report.diffed[0]; + assert!(expected.contains("python-output")); + assert!(actual.contains("rust-output")); + } + + #[test] + fn harness_reports_match_for_agreeing_comparator() { + let tmp = tempdir(); + write_fixture( + tmp.path(), + "fake_agreeing", + "case1", + serde_json::json!("python-output"), + ); + let report = run_comparator(tmp.path(), &FakeAgreeing).unwrap(); + assert_eq!(report.matched, 1); + assert!(report.is_clean()); + } + + #[test] + fn missing_transform_dir_yields_empty_report() { + let tmp = tempdir(); + let report = run_comparator(tmp.path(), &FakeAgreeing).unwrap(); + assert_eq!(report.total(), 0); + } + + #[test] + fn stub_comparators_skip_rather_than_panic() { + let tmp = tempdir(); + write_fixture( + tmp.path(), + "log_compressor", + "case1", + serde_json::json!({"compressed": "x"}), + ); + let report = run_comparator(tmp.path(), &LogCompressorComparator).unwrap(); + assert_eq!(report.skipped.len(), 1); + assert_eq!(report.matched, 0); + } + + /// Minimal tempdir helper to avoid a dev-dependency on `tempfile`. + struct TempDir(PathBuf); + impl TempDir { + fn path(&self) -> &Path { + &self.0 + } + } + impl Drop for TempDir { + fn drop(&mut self) { + let _ = fs::remove_dir_all(&self.0); + } + } + fn tempdir() -> TempDir { + use std::time::{SystemTime, UNIX_EPOCH}; + let nanos = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_nanos(); + let p = std::env::temp_dir().join(format!( + "headroom-parity-{nanos}-{:?}", + std::thread::current().id() + )); + fs::create_dir_all(&p).unwrap(); + TempDir(p) + } +} diff --git a/crates/headroom-proxy/Cargo.toml b/crates/headroom-proxy/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..d475ec365c6bc202c9b812a620c41dfda46b73d1 --- /dev/null +++ b/crates/headroom-proxy/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "headroom-proxy" +version = "0.1.0" +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true +description = "Headroom compression proxy (Rust, axum)." + +[[bin]] +name = "headroom-proxy" +path = "src/main.rs" + +[dependencies] +axum = { workspace = true } +tokio = { workspace = true } +tower = { workspace = true } +tracing = { workspace = true } +reqwest = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +headroom-core = { path = "../headroom-core" } + +[dev-dependencies] +tower = { workspace = true, features = ["util"] } diff --git a/crates/headroom-proxy/src/main.rs b/crates/headroom-proxy/src/main.rs new file mode 100644 index 0000000000000000000000000000000000000000..4cce526471a8451966f439757022294899e04ef7 --- /dev/null +++ b/crates/headroom-proxy/src/main.rs @@ -0,0 +1,57 @@ +//! headroom-proxy: Rust proxy binary (Phase 0 scaffolding). +//! +//! Currently exposes only `/healthz`. Provider routes land in Phase 2. + +use axum::{routing::get, Json, Router}; +use serde_json::{json, Value}; +use std::net::SocketAddr; + +async fn healthz() -> Json { + Json(json!({ "ok": true })) +} + +fn app() -> Router { + Router::new().route("/healthz", get(healthz)) +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + tracing_subscriber_init(); + let addr: SocketAddr = "127.0.0.1:8787".parse()?; + tracing::info!(%addr, crate_hello = headroom_core::hello(), "starting headroom-proxy"); + let listener = tokio::net::TcpListener::bind(addr).await?; + axum::serve(listener, app()).await?; + Ok(()) +} + +fn tracing_subscriber_init() { + // Minimal no-op initializer so we don't pull tracing-subscriber in Phase 0. + // Replace with tracing-subscriber in Phase 2 when richer logging is needed. +} + +#[cfg(test)] +mod tests { + use super::app; + use axum::body::Body; + use axum::http::{Request, StatusCode}; + use tower::ServiceExt; + + #[tokio::test] + async fn healthz_returns_ok() { + let response = app() + .oneshot( + Request::builder() + .uri("/healthz") + .body(Body::empty()) + .unwrap(), + ) + .await + .unwrap(); + assert_eq!(response.status(), StatusCode::OK); + let bytes = axum::body::to_bytes(response.into_body(), 1024) + .await + .unwrap(); + let value: serde_json::Value = serde_json::from_slice(&bytes).unwrap(); + assert_eq!(value, serde_json::json!({"ok": true})); + } +} diff --git a/crates/headroom-py/Cargo.toml b/crates/headroom-py/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..9b014aaabd7db7693facf207dd028ee853be68d7 --- /dev/null +++ b/crates/headroom-py/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "headroom-py" +version = "0.1.0" +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true +description = "PyO3 bindings exposing headroom-core to Python as headroom._core." + +[lib] +name = "_core" +crate-type = ["cdylib"] +# Disable the default test harness — Rust `cargo test` can't run a cdylib that +# links against libpython without LD/DYLD setup. Tests for this crate happen +# on the Python side (via `maturin develop` + pytest/smoke). +test = false +doctest = false +bench = false + +[features] +default = [] +extension-module = ["pyo3/extension-module"] + +[dependencies] +headroom-core = { path = "../headroom-core" } +pyo3 = { workspace = true } diff --git a/crates/headroom-py/pyproject.toml b/crates/headroom-py/pyproject.toml new file mode 100644 index 0000000000000000000000000000000000000000..320bed4a22a5766424de2db84338cd7b259404c4 --- /dev/null +++ b/crates/headroom-py/pyproject.toml @@ -0,0 +1,21 @@ +[build-system] +requires = ["maturin>=1.5,<2.0"] +build-backend = "maturin" + +[project] +name = "headroom-core-py" +version = "0.1.0" +description = "Python bindings to the Rust headroom-core crate." +requires-python = ">=3.10" +license = { text = "Apache-2.0" } +authors = [{ name = "Headroom Maintainers" }] + +[tool.maturin] +# Build as a submodule of the `headroom` namespace so it becomes +# `from headroom._core import hello`. +module-name = "headroom._core" +# Keep python source layout out of this directory; maturin will install the +# compiled module into an existing `headroom` package in site-packages (or +# the active venv's overlay created by `maturin develop`). +python-source = "python" +features = ["extension-module"] diff --git a/crates/headroom-py/python/headroom/__init__.py b/crates/headroom-py/python/headroom/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/crates/headroom-py/src/lib.rs b/crates/headroom-py/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..70a693c98b34636ddd109c48049342ac69c9fcca --- /dev/null +++ b/crates/headroom-py/src/lib.rs @@ -0,0 +1,15 @@ +//! PyO3 bindings for headroom-core. Exposed to Python as `headroom._core`. + +use pyo3::prelude::*; + +/// Return the identity string from headroom-core. +#[pyfunction] +fn hello() -> &'static str { + headroom_core::hello() +} + +#[pymodule] +fn _core(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_function(wrap_pyfunction!(hello, m)?)?; + Ok(()) +} diff --git a/deny.toml b/deny.toml new file mode 100644 index 0000000000000000000000000000000000000000..de50674e29a6c9d8b155b9caef99f5391663f350 --- /dev/null +++ b/deny.toml @@ -0,0 +1,32 @@ +# cargo-deny configuration for the Rust workspace. Intentionally permissive +# during Phase 0 — tighten before Phase 2 goes to production. + +[graph] +all-features = false + +[licenses] +version = 2 +allow = [ + "MIT", + "Apache-2.0", + "Apache-2.0 WITH LLVM-exception", + "BSD-2-Clause", + "BSD-3-Clause", + "ISC", + "Unicode-3.0", + "Unicode-DFS-2016", + "CC0-1.0", + "Zlib", + "0BSD", + "MPL-2.0", +] +confidence-threshold = 0.8 +exceptions = [] + +[bans] +multiple-versions = "allow" +wildcards = "allow" + +[sources] +unknown-registry = "warn" +unknown-git = "warn" diff --git a/docs/spec/022-rust-migration.md b/docs/spec/022-rust-migration.md new file mode 100644 index 0000000000000000000000000000000000000000..dadc03089674e75e1d99dc1ce54b827b45562b4b --- /dev/null +++ b/docs/spec/022-rust-migration.md @@ -0,0 +1,98 @@ +# 022. Rust Migration + +**Status:** in-progress (Stage 0 complete) + +Headroom is evolving from a pure-Python proxy into a Rust engine with a Python SDK layer. This document describes the motivation, target architecture, and phased execution plan. It is the source of truth for the migration; the Discord announcement and contributor docs are derived from it. + +## Why Rust + +Three forces push us toward Rust: + +1. **Latency.** Every LLM request flows through compression transforms on the hot path. Rust runs that math 2–10× faster than Python, with negligible interpreter overhead and near-instant cold starts. +2. **Deployment.** A Rust proxy is a single static binary (~10 MB). No Python interpreter, no pip, no wheel matrix, no model downloads at startup. It drops cleanly into containers, serverless runtimes, or bare hosts. +3. **Non-Python consumers.** Integrations like the TypeScript OpenClaw plugin talk to Headroom over HTTP. A faster proxy makes every downstream client faster without changing any client code. + +The Python code is not being thrown away. Rust lives alongside Python in the same repository, the HTTP contract stays stable, and existing users see no breaking changes. + +## Target architecture + +Two new artifacts, built over time: + +- **`headroom-proxy`** — a standalone Rust binary that speaks the existing Headroom HTTP API and contains native implementations of the proxy's hot-path logic (routing, streaming, compression, telemetry). This is the deployable artifact. +- **`headroom-core`** — a Rust library with the compression transforms (CCR, log compressor, diff compressor, tokenizer, code compressor, content router, etc.). Consumed by `headroom-proxy` directly; optionally exposed to Python via a PyO3 binding if/when embedded SDK use warrants it. + +Both live in a Cargo workspace under `crates/` at the repo root. They are built, tested, and released together with the Python package. + +## Migration strategy + +**Trunk-based, no long-lived branch.** Every Rust change ships as a small PR to `main`. Rust and Python live side by side. CI enforces parity on every PR — if the Rust port diverges from the Python reference, the build fails. + +**Proxy-first, not transforms-first.** We build the Rust proxy binary as the primary deliverable, starting from a pure HTTP passthrough that forwards upstream to the existing Python proxy. Native Rust implementations of individual routes then replace passthroughs one at a time, gated by feature flags. This lets us ship a deployable Rust binary on day one and iterate without modifying Python internals. + +**Feature-flagged cutover.** Each native Rust route is toggled by config. Default is passthrough-to-Python until a route has been shadow-tested and validated. Rollback is a flag flip, not a redeploy. + +**Parity by shadow traffic.** Recorded input/output fixtures give us a unit-test-level parity check. Shadow mode — running both proxies on live traffic and diffing outputs — gives us the real validation gate before any cutover. + +## Stages + +### Stage 0 — Foundation ✅ + +Cargo workspace with four crates (`headroom-core`, `headroom-proxy`, `headroom-py`, `headroom-parity`), CI, build tooling (Makefile, GitHub Actions), and a parity test harness seeded with 125 recorded fixtures across 5 leaf transforms. No production behavior changed. + +### Stage 1 — Rust proxy as passthrough + +`headroom-proxy` accepts requests on the same HTTP contract as the Python proxy and forwards everything upstream to Python. No transforms yet, no intelligence. The point is a deployable binary that can run in front of the existing stack with zero risk. + +### Stage 2 — First native route + +Replace the passthrough for `/v1/chat/completions` (OpenAI) with a native Rust implementation: Rust transforms, direct provider call, streamed response. Feature-flagged. Python proxy handles everything else unchanged. + +### Stage 3 — Shadow mode validation + +Run both proxies on real traffic. Diff outputs with tolerance for chunk timing (SSE). Gate: one week of ≤ 0.1% content divergence before flipping the flag for real. + +### Stage 4 — Provider expansion + +Anthropic, Google, Cohere, Mistral, Bedrock, others. Each provider goes through its own shadow period before cutover. + +### Stage 5 — Code-aware transforms + +Port `code_compressor` (tree-sitter, already Rust-native upstream), `content_router`, `content_detector`. These are larger transforms but have no ML dependencies. + +### Stage 6 — Storage layer + +SQLite (`rusqlite` + `sqlite-vec`), HNSW vector index (`instant-distance`), graph store (`neo4rs`). Feature-flagged backend for the memory subsystem. + +### Stage 7 — ONNX migration + +Remove the torch-dependent LLMLingua compressor. Convert remaining ML models (SmartCrusher, IntelligentContext, memory embedders) to ONNX with fixed opset. Run via the `ort` crate. Remove `torch`, `transformers`, `sentence-transformers`, `llmlingua` from runtime dependencies. + +### Stage 8 — Retire the Python proxy + +Delete `headroom/proxy/server.py` and the Python HTTP routes. The Python package (`import headroom`) continues to exist for SDK users and integration adapters (LangChain, Agno, MCP, Strands), which talk HTTP and don't care which proxy implementation is behind the endpoint. + +## What does not change + +- `pip install headroom` continues to work. +- The HTTP API contract is preserved. +- LangChain, Agno, MCP, Strands integrations keep working unchanged. +- The CLI, dashboard, eval framework, examples, and documentation site are all unaffected. +- Python contributions remain welcome for integrations, tooling, examples, and any code paths not yet ported. + +## Contributing + +- **Python contributors** do not need to learn Rust. CI will flag a PR if a Python change diverges from a Rust-ported counterpart; in that case either update both implementations or disable the feature flag for the affected route. +- **Rust contributors** should read `RUST_DEV.md` for workspace setup, then pick a transform or proxy route from the open issues. + +## Risks and open questions + +- **ONNX export parity** for embedding models: numerical reproducibility must be validated per model before cutover. Some models may resist clean export and require keeping a Python inference path behind PyO3 as a fallback. +- **LLMLingua removal** is a feature removal visible to users relying on it; deprecation timing will be announced on Discord before Stage 7 begins. +- **PyO3 binding scope**: we may ultimately not need a PyO3-exposed `headroom._core` at all, if existing Python SDK users are happy with the HTTP contract. Decision deferred to Stage 8. + +## References + +- `RUST_DEV.md` — developer setup and workspace reference +- `crates/` — Rust sources +- `tests/parity/` — fixtures and parity harness +- `Makefile` — `make test`, `make test-parity`, `make build-proxy`, `make build-wheel`, `make fmt`, `make lint` diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000000000000000000000000000000000000..85f36062a19af87c331c34b45a166a79d5270a06 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "stable" +components = ["rustfmt", "clippy"] +profile = "minimal" diff --git a/scripts/record_fixtures.py b/scripts/record_fixtures.py new file mode 100644 index 0000000000000000000000000000000000000000..8259670e505bab5dbe6e0031ce07a88d98478068 --- /dev/null +++ b/scripts/record_fixtures.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +"""Entry point for recording Rust-vs-Python parity fixtures. + +Usage: + python scripts/record_fixtures.py + +This driver: + 1. Monkey-patches the Phase-1 transform classes via + `tests.parity.recorder.record_all()`. + 2. Runs a small deterministic synthetic workload (no network, no real LLM + calls) that hits each transform at least 20 times with varied inputs. + 3. Prints a summary and exits non-zero if any transform was blocked. +""" + +from __future__ import annotations + +import logging +import sys +from pathlib import Path + +# Make `tests/parity/recorder.py` importable without installing it. +_REPO_ROOT = Path(__file__).resolve().parent.parent +sys.path.insert(0, str(_REPO_ROOT)) + +from tests.parity.recorder import record_all, run_default_workload # noqa: E402 + + +def main() -> int: + logging.basicConfig(level=logging.INFO, format="%(levelname)s %(name)s: %(message)s") + log = logging.getLogger("record_fixtures") + + statuses = record_all() + log.info("patch status:") + blocked = [] + for name, status in statuses.items(): + log.info(" %-16s %s", name, status) + if status.startswith("blocked:"): + blocked.append((name, status)) + + counts = run_default_workload() + log.info("fixture counts:") + shortfall = [] + for name, n in counts.items(): + log.info(" %-16s %d", name, n) + if n < 20: + shortfall.append((name, n)) + + if blocked: + log.error("blocked transforms: %s", blocked) + if shortfall: + log.error("transforms with <20 fixtures: %s", shortfall) + + # Exit non-zero if anything was wholly blocked; short recordings are + # a soft warning. + return 1 if blocked else 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/parity/fixtures/cache_aligner/0225460359a992d9.json b/tests/parity/fixtures/cache_aligner/0225460359a992d9.json new file mode 100644 index 0000000000000000000000000000000000000000..085a4ae085616bf38a35aa5bd7aad5f9b2af9e5b --- /dev/null +++ b/tests/parity/fixtures/cache_aligner/0225460359a992d9.json @@ -0,0 +1,64 @@ +{ + "config": { + "collapse_blank_lines": true, + "date_patterns": [ + "Current [Dd]ate:?\\s*\\d{4}-\\d{2}-\\d{2}", + "Today is \\w+,?\\s+\\w+ \\d+", + "Today's date:?\\s*\\d{4}-\\d{2}-\\d{2}", + "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}" + ], + "detection_tiers": [ + "regex" + ], + "dynamic_tail_separator": "\n\n---\n[Dynamic Context]\n", + "enabled": false, + "entropy_threshold": 0.7, + "extra_dynamic_labels": [], + "normalize_whitespace": true, + "use_dynamic_detector": true + }, + "input": [ + { + "content": "You are a helpful assistant. The date is 2026-04-23. Request id 12.", + "role": "system" + }, + { + "content": "Question number 12: what is 2+2?", + "role": "user" + } + ], + "input_sha256": "0225460359a992d95ef38cd7cec1d3e564d3271d289620aec5c55ed795659827", + "output": { + "cache_metrics": { + "prefix_changed": true, + "previous_hash": "7f4a75c96f08adfe", + "stable_prefix_bytes": 57, + "stable_prefix_hash": "d6c0c0f0a1603e63", + "stable_prefix_tokens_est": 15 + }, + "diff_artifact": null, + "markers_inserted": [ + "stable_prefix_hash:d6c0c0f0a1603e63" + ], + "messages": [ + { + "content": "You are a helpful assistant. The date is . Request id 12.\n\n---\n[Dynamic Context]\n2026-04-23", + "role": "system" + }, + { + "content": "Question number 12: what is 2+2?", + "role": "user" + } + ], + "timing": {}, + "tokens_after": 51, + "tokens_before": 47, + "transforms_applied": [ + "cache_align" + ], + "warnings": [], + "waste_signals": null + }, + "recorded_at": "2026-04-23T23:51:37.783792+00:00", + "transform": "cache_aligner" +} diff --git a/tests/parity/fixtures/cache_aligner/231ab237501e106b.json b/tests/parity/fixtures/cache_aligner/231ab237501e106b.json new file mode 100644 index 0000000000000000000000000000000000000000..88137ec2a8863e3cba9254946c8a6250944ad0ea --- /dev/null +++ b/tests/parity/fixtures/cache_aligner/231ab237501e106b.json @@ -0,0 +1,64 @@ +{ + "config": { + "collapse_blank_lines": true, + "date_patterns": [ + "Current [Dd]ate:?\\s*\\d{4}-\\d{2}-\\d{2}", + "Today is \\w+,?\\s+\\w+ \\d+", + "Today's date:?\\s*\\d{4}-\\d{2}-\\d{2}", + "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}" + ], + "detection_tiers": [ + "regex" + ], + "dynamic_tail_separator": "\n\n---\n[Dynamic Context]\n", + "enabled": false, + "entropy_threshold": 0.7, + "extra_dynamic_labels": [], + "normalize_whitespace": true, + "use_dynamic_detector": true + }, + "input": [ + { + "content": "You are a helpful assistant. The date is 2026-04-23. Request id 8.", + "role": "system" + }, + { + "content": "Question number 8: what is 2+2?", + "role": "user" + } + ], + "input_sha256": "231ab237501e106b7c4ef2b8aa3842c31abf201f4f2b8c36166d2ae786f24f83", + "output": { + "cache_metrics": { + "prefix_changed": true, + "previous_hash": "ae6baf456ddfbff6", + "stable_prefix_bytes": 56, + "stable_prefix_hash": "f2be38f2730cd5ec", + "stable_prefix_tokens_est": 15 + }, + "diff_artifact": null, + "markers_inserted": [ + "stable_prefix_hash:f2be38f2730cd5ec" + ], + "messages": [ + { + "content": "You are a helpful assistant. The date is . Request id 8.\n\n---\n[Dynamic Context]\n2026-04-23", + "role": "system" + }, + { + "content": "Question number 8: what is 2+2?", + "role": "user" + } + ], + "timing": {}, + "tokens_after": 51, + "tokens_before": 47, + "transforms_applied": [ + "cache_align" + ], + "warnings": [], + "waste_signals": null + }, + "recorded_at": "2026-04-23T23:51:37.781924+00:00", + "transform": "cache_aligner" +} diff --git a/tests/parity/fixtures/cache_aligner/25683f67cbb74ece.json b/tests/parity/fixtures/cache_aligner/25683f67cbb74ece.json new file mode 100644 index 0000000000000000000000000000000000000000..28ff3ae373d4811cf0ca5ce8f7442d0b9a7811e7 --- /dev/null +++ b/tests/parity/fixtures/cache_aligner/25683f67cbb74ece.json @@ -0,0 +1,64 @@ +{ + "config": { + "collapse_blank_lines": true, + "date_patterns": [ + "Current [Dd]ate:?\\s*\\d{4}-\\d{2}-\\d{2}", + "Today is \\w+,?\\s+\\w+ \\d+", + "Today's date:?\\s*\\d{4}-\\d{2}-\\d{2}", + "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}" + ], + "detection_tiers": [ + "regex" + ], + "dynamic_tail_separator": "\n\n---\n[Dynamic Context]\n", + "enabled": false, + "entropy_threshold": 0.7, + "extra_dynamic_labels": [], + "normalize_whitespace": true, + "use_dynamic_detector": true + }, + "input": [ + { + "content": "You are a helpful assistant. The date is 2026-04-23. Request id 2.", + "role": "system" + }, + { + "content": "Question number 2: what is 2+2?", + "role": "user" + } + ], + "input_sha256": "25683f67cbb74ecea3018e0cbf0855b05b32c41db0ad584e38af49227d9626ef", + "output": { + "cache_metrics": { + "prefix_changed": true, + "previous_hash": "62429dcfa8ef6f9a", + "stable_prefix_bytes": 56, + "stable_prefix_hash": "b903dee7e3bdfda5", + "stable_prefix_tokens_est": 15 + }, + "diff_artifact": null, + "markers_inserted": [ + "stable_prefix_hash:b903dee7e3bdfda5" + ], + "messages": [ + { + "content": "You are a helpful assistant. The date is . Request id 2.\n\n---\n[Dynamic Context]\n2026-04-23", + "role": "system" + }, + { + "content": "Question number 2: what is 2+2?", + "role": "user" + } + ], + "timing": {}, + "tokens_after": 51, + "tokens_before": 47, + "transforms_applied": [ + "cache_align" + ], + "warnings": [], + "waste_signals": null + }, + "recorded_at": "2026-04-23T23:51:37.777851+00:00", + "transform": "cache_aligner" +} diff --git a/tests/parity/fixtures/cache_aligner/31eb80356eeeb30d.json b/tests/parity/fixtures/cache_aligner/31eb80356eeeb30d.json new file mode 100644 index 0000000000000000000000000000000000000000..20e6cc69e82ceee39949426c2f7b7e779f9f58cd --- /dev/null +++ b/tests/parity/fixtures/cache_aligner/31eb80356eeeb30d.json @@ -0,0 +1,64 @@ +{ + "config": { + "collapse_blank_lines": true, + "date_patterns": [ + "Current [Dd]ate:?\\s*\\d{4}-\\d{2}-\\d{2}", + "Today is \\w+,?\\s+\\w+ \\d+", + "Today's date:?\\s*\\d{4}-\\d{2}-\\d{2}", + "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}" + ], + "detection_tiers": [ + "regex" + ], + "dynamic_tail_separator": "\n\n---\n[Dynamic Context]\n", + "enabled": false, + "entropy_threshold": 0.7, + "extra_dynamic_labels": [], + "normalize_whitespace": true, + "use_dynamic_detector": true + }, + "input": [ + { + "content": "You are a helpful assistant. The date is 2026-04-23. Request id 0.", + "role": "system" + }, + { + "content": "Question number 0: what is 2+2?", + "role": "user" + } + ], + "input_sha256": "31eb80356eeeb30df589cbd6000601f1815e5c5731c98009c6e6488d60847aa8", + "output": { + "cache_metrics": { + "prefix_changed": false, + "previous_hash": null, + "stable_prefix_bytes": 56, + "stable_prefix_hash": "a6f9a6e9b16b87f8", + "stable_prefix_tokens_est": 15 + }, + "diff_artifact": null, + "markers_inserted": [ + "stable_prefix_hash:a6f9a6e9b16b87f8" + ], + "messages": [ + { + "content": "You are a helpful assistant. The date is . Request id 0.\n\n---\n[Dynamic Context]\n2026-04-23", + "role": "system" + }, + { + "content": "Question number 0: what is 2+2?", + "role": "user" + } + ], + "timing": {}, + "tokens_after": 51, + "tokens_before": 47, + "transforms_applied": [ + "cache_align" + ], + "warnings": [], + "waste_signals": null + }, + "recorded_at": "2026-04-23T23:51:37.772878+00:00", + "transform": "cache_aligner" +} diff --git a/tests/parity/fixtures/cache_aligner/336311b995b063d5.json b/tests/parity/fixtures/cache_aligner/336311b995b063d5.json new file mode 100644 index 0000000000000000000000000000000000000000..22f81becfd70cc8c38ad8d1fdb437af45cde47de --- /dev/null +++ b/tests/parity/fixtures/cache_aligner/336311b995b063d5.json @@ -0,0 +1,64 @@ +{ + "config": { + "collapse_blank_lines": true, + "date_patterns": [ + "Current [Dd]ate:?\\s*\\d{4}-\\d{2}-\\d{2}", + "Today is \\w+,?\\s+\\w+ \\d+", + "Today's date:?\\s*\\d{4}-\\d{2}-\\d{2}", + "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}" + ], + "detection_tiers": [ + "regex" + ], + "dynamic_tail_separator": "\n\n---\n[Dynamic Context]\n", + "enabled": false, + "entropy_threshold": 0.7, + "extra_dynamic_labels": [], + "normalize_whitespace": true, + "use_dynamic_detector": true + }, + "input": [ + { + "content": "You are a helpful assistant. The date is 2026-04-23. Request id 18.", + "role": "system" + }, + { + "content": "Question number 18: what is 2+2?", + "role": "user" + } + ], + "input_sha256": "336311b995b063d547dd0bc4b1505a6d92b546810989c807a45a9b9adbd8726b", + "output": { + "cache_metrics": { + "prefix_changed": true, + "previous_hash": "3448a68808670799", + "stable_prefix_bytes": 57, + "stable_prefix_hash": "5d2cc8038b3e2eff", + "stable_prefix_tokens_est": 15 + }, + "diff_artifact": null, + "markers_inserted": [ + "stable_prefix_hash:5d2cc8038b3e2eff" + ], + "messages": [ + { + "content": "You are a helpful assistant. The date is . Request id 18.\n\n---\n[Dynamic Context]\n2026-04-23", + "role": "system" + }, + { + "content": "Question number 18: what is 2+2?", + "role": "user" + } + ], + "timing": {}, + "tokens_after": 51, + "tokens_before": 47, + "transforms_applied": [ + "cache_align" + ], + "warnings": [], + "waste_signals": null + }, + "recorded_at": "2026-04-23T23:51:37.797368+00:00", + "transform": "cache_aligner" +} diff --git a/tests/parity/fixtures/cache_aligner/475e76a9f99c29ab.json b/tests/parity/fixtures/cache_aligner/475e76a9f99c29ab.json new file mode 100644 index 0000000000000000000000000000000000000000..6112c300538225d0508ea34cdaf05428d85017b2 --- /dev/null +++ b/tests/parity/fixtures/cache_aligner/475e76a9f99c29ab.json @@ -0,0 +1,64 @@ +{ + "config": { + "collapse_blank_lines": true, + "date_patterns": [ + "Current [Dd]ate:?\\s*\\d{4}-\\d{2}-\\d{2}", + "Today is \\w+,?\\s+\\w+ \\d+", + "Today's date:?\\s*\\d{4}-\\d{2}-\\d{2}", + "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}" + ], + "detection_tiers": [ + "regex" + ], + "dynamic_tail_separator": "\n\n---\n[Dynamic Context]\n", + "enabled": false, + "entropy_threshold": 0.7, + "extra_dynamic_labels": [], + "normalize_whitespace": true, + "use_dynamic_detector": true + }, + "input": [ + { + "content": "You are a helpful assistant. The date is 2026-04-23. Request id 5.", + "role": "system" + }, + { + "content": "Question number 5: what is 2+2?", + "role": "user" + } + ], + "input_sha256": "475e76a9f99c29abbaae11133063a5c9d56e923ba7641576fc8af781f8c90231", + "output": { + "cache_metrics": { + "prefix_changed": true, + "previous_hash": "a81110326e885546", + "stable_prefix_bytes": 56, + "stable_prefix_hash": "d28bc9575c97a6b5", + "stable_prefix_tokens_est": 15 + }, + "diff_artifact": null, + "markers_inserted": [ + "stable_prefix_hash:d28bc9575c97a6b5" + ], + "messages": [ + { + "content": "You are a helpful assistant. The date is . Request id 5.\n\n---\n[Dynamic Context]\n2026-04-23", + "role": "system" + }, + { + "content": "Question number 5: what is 2+2?", + "role": "user" + } + ], + "timing": {}, + "tokens_after": 51, + "tokens_before": 47, + "transforms_applied": [ + "cache_align" + ], + "warnings": [], + "waste_signals": null + }, + "recorded_at": "2026-04-23T23:51:37.780357+00:00", + "transform": "cache_aligner" +} diff --git a/tests/parity/fixtures/cache_aligner/4b171a7f9af6e2d5.json b/tests/parity/fixtures/cache_aligner/4b171a7f9af6e2d5.json new file mode 100644 index 0000000000000000000000000000000000000000..036e455b060b17529322e420ea06de9e942a0141 --- /dev/null +++ b/tests/parity/fixtures/cache_aligner/4b171a7f9af6e2d5.json @@ -0,0 +1,64 @@ +{ + "config": { + "collapse_blank_lines": true, + "date_patterns": [ + "Current [Dd]ate:?\\s*\\d{4}-\\d{2}-\\d{2}", + "Today is \\w+,?\\s+\\w+ \\d+", + "Today's date:?\\s*\\d{4}-\\d{2}-\\d{2}", + "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}" + ], + "detection_tiers": [ + "regex" + ], + "dynamic_tail_separator": "\n\n---\n[Dynamic Context]\n", + "enabled": false, + "entropy_threshold": 0.7, + "extra_dynamic_labels": [], + "normalize_whitespace": true, + "use_dynamic_detector": true + }, + "input": [ + { + "content": "You are a helpful assistant. The date is 2026-04-23. Request id 13.", + "role": "system" + }, + { + "content": "Question number 13: what is 2+2?", + "role": "user" + } + ], + "input_sha256": "4b171a7f9af6e2d55e4d1ac374e9b87e2842455a358aa3628ae533ebb401e5c8", + "output": { + "cache_metrics": { + "prefix_changed": true, + "previous_hash": "d6c0c0f0a1603e63", + "stable_prefix_bytes": 57, + "stable_prefix_hash": "36ca37871b1a19b3", + "stable_prefix_tokens_est": 15 + }, + "diff_artifact": null, + "markers_inserted": [ + "stable_prefix_hash:36ca37871b1a19b3" + ], + "messages": [ + { + "content": "You are a helpful assistant. The date is . Request id 13.\n\n---\n[Dynamic Context]\n2026-04-23", + "role": "system" + }, + { + "content": "Question number 13: what is 2+2?", + "role": "user" + } + ], + "timing": {}, + "tokens_after": 51, + "tokens_before": 47, + "transforms_applied": [ + "cache_align" + ], + "warnings": [], + "waste_signals": null + }, + "recorded_at": "2026-04-23T23:51:37.784256+00:00", + "transform": "cache_aligner" +} diff --git a/tests/parity/fixtures/cache_aligner/541daf83f5a1fda9.json b/tests/parity/fixtures/cache_aligner/541daf83f5a1fda9.json new file mode 100644 index 0000000000000000000000000000000000000000..917ad45fa953da186858b00728dff48f90a966e9 --- /dev/null +++ b/tests/parity/fixtures/cache_aligner/541daf83f5a1fda9.json @@ -0,0 +1,64 @@ +{ + "config": { + "collapse_blank_lines": true, + "date_patterns": [ + "Current [Dd]ate:?\\s*\\d{4}-\\d{2}-\\d{2}", + "Today is \\w+,?\\s+\\w+ \\d+", + "Today's date:?\\s*\\d{4}-\\d{2}-\\d{2}", + "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}" + ], + "detection_tiers": [ + "regex" + ], + "dynamic_tail_separator": "\n\n---\n[Dynamic Context]\n", + "enabled": false, + "entropy_threshold": 0.7, + "extra_dynamic_labels": [], + "normalize_whitespace": true, + "use_dynamic_detector": true + }, + "input": [ + { + "content": "You are a helpful assistant. The date is 2026-04-23. Request id 4.", + "role": "system" + }, + { + "content": "Question number 4: what is 2+2?", + "role": "user" + } + ], + "input_sha256": "541daf83f5a1fda96f7548a5ccd77471ed9f65f18c96b0d0e739672249b50558", + "output": { + "cache_metrics": { + "prefix_changed": true, + "previous_hash": "387e30c8b8e09c1b", + "stable_prefix_bytes": 56, + "stable_prefix_hash": "a81110326e885546", + "stable_prefix_tokens_est": 15 + }, + "diff_artifact": null, + "markers_inserted": [ + "stable_prefix_hash:a81110326e885546" + ], + "messages": [ + { + "content": "You are a helpful assistant. The date is . Request id 4.\n\n---\n[Dynamic Context]\n2026-04-23", + "role": "system" + }, + { + "content": "Question number 4: what is 2+2?", + "role": "user" + } + ], + "timing": {}, + "tokens_after": 51, + "tokens_before": 47, + "transforms_applied": [ + "cache_align" + ], + "warnings": [], + "waste_signals": null + }, + "recorded_at": "2026-04-23T23:51:37.779840+00:00", + "transform": "cache_aligner" +} diff --git a/tests/parity/fixtures/cache_aligner/6502f683439de052.json b/tests/parity/fixtures/cache_aligner/6502f683439de052.json new file mode 100644 index 0000000000000000000000000000000000000000..bf118c8afb969b917ce0bd25e89cae5c6a577405 --- /dev/null +++ b/tests/parity/fixtures/cache_aligner/6502f683439de052.json @@ -0,0 +1,64 @@ +{ + "config": { + "collapse_blank_lines": true, + "date_patterns": [ + "Current [Dd]ate:?\\s*\\d{4}-\\d{2}-\\d{2}", + "Today is \\w+,?\\s+\\w+ \\d+", + "Today's date:?\\s*\\d{4}-\\d{2}-\\d{2}", + "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}" + ], + "detection_tiers": [ + "regex" + ], + "dynamic_tail_separator": "\n\n---\n[Dynamic Context]\n", + "enabled": false, + "entropy_threshold": 0.7, + "extra_dynamic_labels": [], + "normalize_whitespace": true, + "use_dynamic_detector": true + }, + "input": [ + { + "content": "You are a helpful assistant. The date is 2026-04-23. Request id 7.", + "role": "system" + }, + { + "content": "Question number 7: what is 2+2?", + "role": "user" + } + ], + "input_sha256": "6502f683439de0524e87b14e56fe9ac81dadfec1136fc1b7029511272648d34e", + "output": { + "cache_metrics": { + "prefix_changed": true, + "previous_hash": "370f22d8bec65ce9", + "stable_prefix_bytes": 56, + "stable_prefix_hash": "ae6baf456ddfbff6", + "stable_prefix_tokens_est": 15 + }, + "diff_artifact": null, + "markers_inserted": [ + "stable_prefix_hash:ae6baf456ddfbff6" + ], + "messages": [ + { + "content": "You are a helpful assistant. The date is . Request id 7.\n\n---\n[Dynamic Context]\n2026-04-23", + "role": "system" + }, + { + "content": "Question number 7: what is 2+2?", + "role": "user" + } + ], + "timing": {}, + "tokens_after": 51, + "tokens_before": 47, + "transforms_applied": [ + "cache_align" + ], + "warnings": [], + "waste_signals": null + }, + "recorded_at": "2026-04-23T23:51:37.781367+00:00", + "transform": "cache_aligner" +} diff --git a/tests/parity/fixtures/cache_aligner/7b556192a56bca94.json b/tests/parity/fixtures/cache_aligner/7b556192a56bca94.json new file mode 100644 index 0000000000000000000000000000000000000000..b6efefc773acdcab588b66d269bb934b8797e542 --- /dev/null +++ b/tests/parity/fixtures/cache_aligner/7b556192a56bca94.json @@ -0,0 +1,64 @@ +{ + "config": { + "collapse_blank_lines": true, + "date_patterns": [ + "Current [Dd]ate:?\\s*\\d{4}-\\d{2}-\\d{2}", + "Today is \\w+,?\\s+\\w+ \\d+", + "Today's date:?\\s*\\d{4}-\\d{2}-\\d{2}", + "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}" + ], + "detection_tiers": [ + "regex" + ], + "dynamic_tail_separator": "\n\n---\n[Dynamic Context]\n", + "enabled": false, + "entropy_threshold": 0.7, + "extra_dynamic_labels": [], + "normalize_whitespace": true, + "use_dynamic_detector": true + }, + "input": [ + { + "content": "You are a helpful assistant. The date is 2026-04-23. Request id 17.", + "role": "system" + }, + { + "content": "Question number 17: what is 2+2?", + "role": "user" + } + ], + "input_sha256": "7b556192a56bca9484db5a8aa7198e1fb9484d134385950a5186780e6e2dd5f4", + "output": { + "cache_metrics": { + "prefix_changed": true, + "previous_hash": "a63698f1393aaf8d", + "stable_prefix_bytes": 57, + "stable_prefix_hash": "3448a68808670799", + "stable_prefix_tokens_est": 15 + }, + "diff_artifact": null, + "markers_inserted": [ + "stable_prefix_hash:3448a68808670799" + ], + "messages": [ + { + "content": "You are a helpful assistant. The date is . Request id 17.\n\n---\n[Dynamic Context]\n2026-04-23", + "role": "system" + }, + { + "content": "Question number 17: what is 2+2?", + "role": "user" + } + ], + "timing": {}, + "tokens_after": 51, + "tokens_before": 47, + "transforms_applied": [ + "cache_align" + ], + "warnings": [], + "waste_signals": null + }, + "recorded_at": "2026-04-23T23:51:37.795172+00:00", + "transform": "cache_aligner" +} diff --git a/tests/parity/fixtures/cache_aligner/8822af1cf9a14254.json b/tests/parity/fixtures/cache_aligner/8822af1cf9a14254.json new file mode 100644 index 0000000000000000000000000000000000000000..ab9ddac4a90d1055e6ae024cdc6744fb6f8f780b --- /dev/null +++ b/tests/parity/fixtures/cache_aligner/8822af1cf9a14254.json @@ -0,0 +1,64 @@ +{ + "config": { + "collapse_blank_lines": true, + "date_patterns": [ + "Current [Dd]ate:?\\s*\\d{4}-\\d{2}-\\d{2}", + "Today is \\w+,?\\s+\\w+ \\d+", + "Today's date:?\\s*\\d{4}-\\d{2}-\\d{2}", + "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}" + ], + "detection_tiers": [ + "regex" + ], + "dynamic_tail_separator": "\n\n---\n[Dynamic Context]\n", + "enabled": false, + "entropy_threshold": 0.7, + "extra_dynamic_labels": [], + "normalize_whitespace": true, + "use_dynamic_detector": true + }, + "input": [ + { + "content": "You are a helpful assistant. The date is 2026-04-23. Request id 9.", + "role": "system" + }, + { + "content": "Question number 9: what is 2+2?", + "role": "user" + } + ], + "input_sha256": "8822af1cf9a14254ca88e13a955b89703e56b89f4889b62c0c74f69b753fae55", + "output": { + "cache_metrics": { + "prefix_changed": true, + "previous_hash": "f2be38f2730cd5ec", + "stable_prefix_bytes": 56, + "stable_prefix_hash": "f0eb6d1b95c08f62", + "stable_prefix_tokens_est": 15 + }, + "diff_artifact": null, + "markers_inserted": [ + "stable_prefix_hash:f0eb6d1b95c08f62" + ], + "messages": [ + { + "content": "You are a helpful assistant. The date is . Request id 9.\n\n---\n[Dynamic Context]\n2026-04-23", + "role": "system" + }, + { + "content": "Question number 9: what is 2+2?", + "role": "user" + } + ], + "timing": {}, + "tokens_after": 51, + "tokens_before": 47, + "transforms_applied": [ + "cache_align" + ], + "warnings": [], + "waste_signals": null + }, + "recorded_at": "2026-04-23T23:51:37.782389+00:00", + "transform": "cache_aligner" +} diff --git a/tests/parity/fixtures/cache_aligner/92b25557cfbfa8db.json b/tests/parity/fixtures/cache_aligner/92b25557cfbfa8db.json new file mode 100644 index 0000000000000000000000000000000000000000..260f8017751bbb6e8bd05aca1ef88ecb26916764 --- /dev/null +++ b/tests/parity/fixtures/cache_aligner/92b25557cfbfa8db.json @@ -0,0 +1,64 @@ +{ + "config": { + "collapse_blank_lines": true, + "date_patterns": [ + "Current [Dd]ate:?\\s*\\d{4}-\\d{2}-\\d{2}", + "Today is \\w+,?\\s+\\w+ \\d+", + "Today's date:?\\s*\\d{4}-\\d{2}-\\d{2}", + "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}" + ], + "detection_tiers": [ + "regex" + ], + "dynamic_tail_separator": "\n\n---\n[Dynamic Context]\n", + "enabled": false, + "entropy_threshold": 0.7, + "extra_dynamic_labels": [], + "normalize_whitespace": true, + "use_dynamic_detector": true + }, + "input": [ + { + "content": "You are a helpful assistant. The date is 2026-04-23. Request id 14.", + "role": "system" + }, + { + "content": "Question number 14: what is 2+2?", + "role": "user" + } + ], + "input_sha256": "92b25557cfbfa8dba4b21abe9c4de4e6fde501cdf4a6b37556e45fcd32a852eb", + "output": { + "cache_metrics": { + "prefix_changed": true, + "previous_hash": "36ca37871b1a19b3", + "stable_prefix_bytes": 57, + "stable_prefix_hash": "2847e8fa28fb37d6", + "stable_prefix_tokens_est": 15 + }, + "diff_artifact": null, + "markers_inserted": [ + "stable_prefix_hash:2847e8fa28fb37d6" + ], + "messages": [ + { + "content": "You are a helpful assistant. The date is . Request id 14.\n\n---\n[Dynamic Context]\n2026-04-23", + "role": "system" + }, + { + "content": "Question number 14: what is 2+2?", + "role": "user" + } + ], + "timing": {}, + "tokens_after": 51, + "tokens_before": 47, + "transforms_applied": [ + "cache_align" + ], + "warnings": [], + "waste_signals": null + }, + "recorded_at": "2026-04-23T23:51:37.784722+00:00", + "transform": "cache_aligner" +} diff --git a/tests/parity/fixtures/cache_aligner/acda633b1733c6e6.json b/tests/parity/fixtures/cache_aligner/acda633b1733c6e6.json new file mode 100644 index 0000000000000000000000000000000000000000..7cbf8134bf457aafe34c125b3ec3a43ab2d7afe9 --- /dev/null +++ b/tests/parity/fixtures/cache_aligner/acda633b1733c6e6.json @@ -0,0 +1,64 @@ +{ + "config": { + "collapse_blank_lines": true, + "date_patterns": [ + "Current [Dd]ate:?\\s*\\d{4}-\\d{2}-\\d{2}", + "Today is \\w+,?\\s+\\w+ \\d+", + "Today's date:?\\s*\\d{4}-\\d{2}-\\d{2}", + "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}" + ], + "detection_tiers": [ + "regex" + ], + "dynamic_tail_separator": "\n\n---\n[Dynamic Context]\n", + "enabled": false, + "entropy_threshold": 0.7, + "extra_dynamic_labels": [], + "normalize_whitespace": true, + "use_dynamic_detector": true + }, + "input": [ + { + "content": "You are a helpful assistant. The date is 2026-04-23. Request id 1.", + "role": "system" + }, + { + "content": "Question number 1: what is 2+2?", + "role": "user" + } + ], + "input_sha256": "acda633b1733c6e6e9b99cac3085118df5907266d9175c2ccb69ccadbe200d5a", + "output": { + "cache_metrics": { + "prefix_changed": true, + "previous_hash": "a6f9a6e9b16b87f8", + "stable_prefix_bytes": 56, + "stable_prefix_hash": "62429dcfa8ef6f9a", + "stable_prefix_tokens_est": 15 + }, + "diff_artifact": null, + "markers_inserted": [ + "stable_prefix_hash:62429dcfa8ef6f9a" + ], + "messages": [ + { + "content": "You are a helpful assistant. The date is . Request id 1.\n\n---\n[Dynamic Context]\n2026-04-23", + "role": "system" + }, + { + "content": "Question number 1: what is 2+2?", + "role": "user" + } + ], + "timing": {}, + "tokens_after": 51, + "tokens_before": 47, + "transforms_applied": [ + "cache_align" + ], + "warnings": [], + "waste_signals": null + }, + "recorded_at": "2026-04-23T23:51:37.776323+00:00", + "transform": "cache_aligner" +} diff --git a/tests/parity/fixtures/cache_aligner/c2563e51d50a4da6.json b/tests/parity/fixtures/cache_aligner/c2563e51d50a4da6.json new file mode 100644 index 0000000000000000000000000000000000000000..f1359f8a2800309ee7313479aca1048ed9677d35 --- /dev/null +++ b/tests/parity/fixtures/cache_aligner/c2563e51d50a4da6.json @@ -0,0 +1,64 @@ +{ + "config": { + "collapse_blank_lines": true, + "date_patterns": [ + "Current [Dd]ate:?\\s*\\d{4}-\\d{2}-\\d{2}", + "Today is \\w+,?\\s+\\w+ \\d+", + "Today's date:?\\s*\\d{4}-\\d{2}-\\d{2}", + "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}" + ], + "detection_tiers": [ + "regex" + ], + "dynamic_tail_separator": "\n\n---\n[Dynamic Context]\n", + "enabled": false, + "entropy_threshold": 0.7, + "extra_dynamic_labels": [], + "normalize_whitespace": true, + "use_dynamic_detector": true + }, + "input": [ + { + "content": "You are a helpful assistant. The date is 2026-04-23. Request id 19.", + "role": "system" + }, + { + "content": "Question number 19: what is 2+2?", + "role": "user" + } + ], + "input_sha256": "c2563e51d50a4da6d315ced09771ec955cc0dd833897cc1a4f924703a154cafb", + "output": { + "cache_metrics": { + "prefix_changed": true, + "previous_hash": "5d2cc8038b3e2eff", + "stable_prefix_bytes": 57, + "stable_prefix_hash": "d37929b1256f7860", + "stable_prefix_tokens_est": 15 + }, + "diff_artifact": null, + "markers_inserted": [ + "stable_prefix_hash:d37929b1256f7860" + ], + "messages": [ + { + "content": "You are a helpful assistant. The date is . Request id 19.\n\n---\n[Dynamic Context]\n2026-04-23", + "role": "system" + }, + { + "content": "Question number 19: what is 2+2?", + "role": "user" + } + ], + "timing": {}, + "tokens_after": 51, + "tokens_before": 47, + "transforms_applied": [ + "cache_align" + ], + "warnings": [], + "waste_signals": null + }, + "recorded_at": "2026-04-23T23:51:37.799586+00:00", + "transform": "cache_aligner" +} diff --git a/tests/parity/fixtures/cache_aligner/c43df917ec5d7d4c.json b/tests/parity/fixtures/cache_aligner/c43df917ec5d7d4c.json new file mode 100644 index 0000000000000000000000000000000000000000..41bad60c082957bb3bd31e7107f7666df51df28e --- /dev/null +++ b/tests/parity/fixtures/cache_aligner/c43df917ec5d7d4c.json @@ -0,0 +1,64 @@ +{ + "config": { + "collapse_blank_lines": true, + "date_patterns": [ + "Current [Dd]ate:?\\s*\\d{4}-\\d{2}-\\d{2}", + "Today is \\w+,?\\s+\\w+ \\d+", + "Today's date:?\\s*\\d{4}-\\d{2}-\\d{2}", + "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}" + ], + "detection_tiers": [ + "regex" + ], + "dynamic_tail_separator": "\n\n---\n[Dynamic Context]\n", + "enabled": false, + "entropy_threshold": 0.7, + "extra_dynamic_labels": [], + "normalize_whitespace": true, + "use_dynamic_detector": true + }, + "input": [ + { + "content": "You are a helpful assistant. The date is 2026-04-23. Request id 11.", + "role": "system" + }, + { + "content": "Question number 11: what is 2+2?", + "role": "user" + } + ], + "input_sha256": "c43df917ec5d7d4c5d95dcddee64ef6260c9b98b00c967330e8fe62b20b4b0a1", + "output": { + "cache_metrics": { + "prefix_changed": true, + "previous_hash": "c8cd901c29a6e141", + "stable_prefix_bytes": 57, + "stable_prefix_hash": "7f4a75c96f08adfe", + "stable_prefix_tokens_est": 15 + }, + "diff_artifact": null, + "markers_inserted": [ + "stable_prefix_hash:7f4a75c96f08adfe" + ], + "messages": [ + { + "content": "You are a helpful assistant. The date is . Request id 11.\n\n---\n[Dynamic Context]\n2026-04-23", + "role": "system" + }, + { + "content": "Question number 11: what is 2+2?", + "role": "user" + } + ], + "timing": {}, + "tokens_after": 51, + "tokens_before": 47, + "transforms_applied": [ + "cache_align" + ], + "warnings": [], + "waste_signals": null + }, + "recorded_at": "2026-04-23T23:51:37.783317+00:00", + "transform": "cache_aligner" +} diff --git a/tests/parity/fixtures/cache_aligner/c5ec19381964f8ef.json b/tests/parity/fixtures/cache_aligner/c5ec19381964f8ef.json new file mode 100644 index 0000000000000000000000000000000000000000..81d249a38ee9ac272c828cf93ecf5ea86c3141e8 --- /dev/null +++ b/tests/parity/fixtures/cache_aligner/c5ec19381964f8ef.json @@ -0,0 +1,64 @@ +{ + "config": { + "collapse_blank_lines": true, + "date_patterns": [ + "Current [Dd]ate:?\\s*\\d{4}-\\d{2}-\\d{2}", + "Today is \\w+,?\\s+\\w+ \\d+", + "Today's date:?\\s*\\d{4}-\\d{2}-\\d{2}", + "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}" + ], + "detection_tiers": [ + "regex" + ], + "dynamic_tail_separator": "\n\n---\n[Dynamic Context]\n", + "enabled": false, + "entropy_threshold": 0.7, + "extra_dynamic_labels": [], + "normalize_whitespace": true, + "use_dynamic_detector": true + }, + "input": [ + { + "content": "You are a helpful assistant. The date is 2026-04-23. Request id 15.", + "role": "system" + }, + { + "content": "Question number 15: what is 2+2?", + "role": "user" + } + ], + "input_sha256": "c5ec19381964f8ef5a08d27435e71db7160c00a8980a78a97843832af3cf1190", + "output": { + "cache_metrics": { + "prefix_changed": true, + "previous_hash": "2847e8fa28fb37d6", + "stable_prefix_bytes": 57, + "stable_prefix_hash": "8a75d8139c10abcf", + "stable_prefix_tokens_est": 15 + }, + "diff_artifact": null, + "markers_inserted": [ + "stable_prefix_hash:8a75d8139c10abcf" + ], + "messages": [ + { + "content": "You are a helpful assistant. The date is . Request id 15.\n\n---\n[Dynamic Context]\n2026-04-23", + "role": "system" + }, + { + "content": "Question number 15: what is 2+2?", + "role": "user" + } + ], + "timing": {}, + "tokens_after": 51, + "tokens_before": 47, + "transforms_applied": [ + "cache_align" + ], + "warnings": [], + "waste_signals": null + }, + "recorded_at": "2026-04-23T23:51:37.787416+00:00", + "transform": "cache_aligner" +} diff --git a/tests/parity/fixtures/cache_aligner/e7a901cbf00a645f.json b/tests/parity/fixtures/cache_aligner/e7a901cbf00a645f.json new file mode 100644 index 0000000000000000000000000000000000000000..5f3bde4c5f7416aeb1297fff3d0de64b10f3b10f --- /dev/null +++ b/tests/parity/fixtures/cache_aligner/e7a901cbf00a645f.json @@ -0,0 +1,64 @@ +{ + "config": { + "collapse_blank_lines": true, + "date_patterns": [ + "Current [Dd]ate:?\\s*\\d{4}-\\d{2}-\\d{2}", + "Today is \\w+,?\\s+\\w+ \\d+", + "Today's date:?\\s*\\d{4}-\\d{2}-\\d{2}", + "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}" + ], + "detection_tiers": [ + "regex" + ], + "dynamic_tail_separator": "\n\n---\n[Dynamic Context]\n", + "enabled": false, + "entropy_threshold": 0.7, + "extra_dynamic_labels": [], + "normalize_whitespace": true, + "use_dynamic_detector": true + }, + "input": [ + { + "content": "You are a helpful assistant. The date is 2026-04-23. Request id 3.", + "role": "system" + }, + { + "content": "Question number 3: what is 2+2?", + "role": "user" + } + ], + "input_sha256": "e7a901cbf00a645fb14d9c0d0fec1cff05a1e54ce124f51497461605a5d5e5ba", + "output": { + "cache_metrics": { + "prefix_changed": true, + "previous_hash": "b903dee7e3bdfda5", + "stable_prefix_bytes": 56, + "stable_prefix_hash": "387e30c8b8e09c1b", + "stable_prefix_tokens_est": 15 + }, + "diff_artifact": null, + "markers_inserted": [ + "stable_prefix_hash:387e30c8b8e09c1b" + ], + "messages": [ + { + "content": "You are a helpful assistant. The date is . Request id 3.\n\n---\n[Dynamic Context]\n2026-04-23", + "role": "system" + }, + { + "content": "Question number 3: what is 2+2?", + "role": "user" + } + ], + "timing": {}, + "tokens_after": 51, + "tokens_before": 47, + "transforms_applied": [ + "cache_align" + ], + "warnings": [], + "waste_signals": null + }, + "recorded_at": "2026-04-23T23:51:37.779295+00:00", + "transform": "cache_aligner" +} diff --git a/tests/parity/fixtures/cache_aligner/ed9c7e7ca8f0ada6.json b/tests/parity/fixtures/cache_aligner/ed9c7e7ca8f0ada6.json new file mode 100644 index 0000000000000000000000000000000000000000..5ebd3b69944644204d5fc200cb84a5d161055a3c --- /dev/null +++ b/tests/parity/fixtures/cache_aligner/ed9c7e7ca8f0ada6.json @@ -0,0 +1,64 @@ +{ + "config": { + "collapse_blank_lines": true, + "date_patterns": [ + "Current [Dd]ate:?\\s*\\d{4}-\\d{2}-\\d{2}", + "Today is \\w+,?\\s+\\w+ \\d+", + "Today's date:?\\s*\\d{4}-\\d{2}-\\d{2}", + "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}" + ], + "detection_tiers": [ + "regex" + ], + "dynamic_tail_separator": "\n\n---\n[Dynamic Context]\n", + "enabled": false, + "entropy_threshold": 0.7, + "extra_dynamic_labels": [], + "normalize_whitespace": true, + "use_dynamic_detector": true + }, + "input": [ + { + "content": "You are a helpful assistant. The date is 2026-04-23. Request id 10.", + "role": "system" + }, + { + "content": "Question number 10: what is 2+2?", + "role": "user" + } + ], + "input_sha256": "ed9c7e7ca8f0ada6b54b1b96ae23e960e89fa6a897a5b48b3be1493ccb84afff", + "output": { + "cache_metrics": { + "prefix_changed": true, + "previous_hash": "f0eb6d1b95c08f62", + "stable_prefix_bytes": 57, + "stable_prefix_hash": "c8cd901c29a6e141", + "stable_prefix_tokens_est": 15 + }, + "diff_artifact": null, + "markers_inserted": [ + "stable_prefix_hash:c8cd901c29a6e141" + ], + "messages": [ + { + "content": "You are a helpful assistant. The date is . Request id 10.\n\n---\n[Dynamic Context]\n2026-04-23", + "role": "system" + }, + { + "content": "Question number 10: what is 2+2?", + "role": "user" + } + ], + "timing": {}, + "tokens_after": 51, + "tokens_before": 47, + "transforms_applied": [ + "cache_align" + ], + "warnings": [], + "waste_signals": null + }, + "recorded_at": "2026-04-23T23:51:37.782857+00:00", + "transform": "cache_aligner" +} diff --git a/tests/parity/fixtures/cache_aligner/efe495ffb2ff9922.json b/tests/parity/fixtures/cache_aligner/efe495ffb2ff9922.json new file mode 100644 index 0000000000000000000000000000000000000000..feee75a53691211c7bd325966d4dfbc1e3838abd --- /dev/null +++ b/tests/parity/fixtures/cache_aligner/efe495ffb2ff9922.json @@ -0,0 +1,64 @@ +{ + "config": { + "collapse_blank_lines": true, + "date_patterns": [ + "Current [Dd]ate:?\\s*\\d{4}-\\d{2}-\\d{2}", + "Today is \\w+,?\\s+\\w+ \\d+", + "Today's date:?\\s*\\d{4}-\\d{2}-\\d{2}", + "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}" + ], + "detection_tiers": [ + "regex" + ], + "dynamic_tail_separator": "\n\n---\n[Dynamic Context]\n", + "enabled": false, + "entropy_threshold": 0.7, + "extra_dynamic_labels": [], + "normalize_whitespace": true, + "use_dynamic_detector": true + }, + "input": [ + { + "content": "You are a helpful assistant. The date is 2026-04-23. Request id 6.", + "role": "system" + }, + { + "content": "Question number 6: what is 2+2?", + "role": "user" + } + ], + "input_sha256": "efe495ffb2ff99227362d743a53d36d48d178f9e704443aad491795a8ff9ae39", + "output": { + "cache_metrics": { + "prefix_changed": true, + "previous_hash": "d28bc9575c97a6b5", + "stable_prefix_bytes": 56, + "stable_prefix_hash": "370f22d8bec65ce9", + "stable_prefix_tokens_est": 15 + }, + "diff_artifact": null, + "markers_inserted": [ + "stable_prefix_hash:370f22d8bec65ce9" + ], + "messages": [ + { + "content": "You are a helpful assistant. The date is . Request id 6.\n\n---\n[Dynamic Context]\n2026-04-23", + "role": "system" + }, + { + "content": "Question number 6: what is 2+2?", + "role": "user" + } + ], + "timing": {}, + "tokens_after": 51, + "tokens_before": 47, + "transforms_applied": [ + "cache_align" + ], + "warnings": [], + "waste_signals": null + }, + "recorded_at": "2026-04-23T23:51:37.780871+00:00", + "transform": "cache_aligner" +} diff --git a/tests/parity/fixtures/cache_aligner/f2b50b3cf76fe6a1.json b/tests/parity/fixtures/cache_aligner/f2b50b3cf76fe6a1.json new file mode 100644 index 0000000000000000000000000000000000000000..d80afa6efc0e565ce47f82a34e27e0265784fa79 --- /dev/null +++ b/tests/parity/fixtures/cache_aligner/f2b50b3cf76fe6a1.json @@ -0,0 +1,64 @@ +{ + "config": { + "collapse_blank_lines": true, + "date_patterns": [ + "Current [Dd]ate:?\\s*\\d{4}-\\d{2}-\\d{2}", + "Today is \\w+,?\\s+\\w+ \\d+", + "Today's date:?\\s*\\d{4}-\\d{2}-\\d{2}", + "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}" + ], + "detection_tiers": [ + "regex" + ], + "dynamic_tail_separator": "\n\n---\n[Dynamic Context]\n", + "enabled": false, + "entropy_threshold": 0.7, + "extra_dynamic_labels": [], + "normalize_whitespace": true, + "use_dynamic_detector": true + }, + "input": [ + { + "content": "You are a helpful assistant. The date is 2026-04-23. Request id 16.", + "role": "system" + }, + { + "content": "Question number 16: what is 2+2?", + "role": "user" + } + ], + "input_sha256": "f2b50b3cf76fe6a197f251c3745e78a172846277ca7665b97cd913d6ee243f79", + "output": { + "cache_metrics": { + "prefix_changed": true, + "previous_hash": "8a75d8139c10abcf", + "stable_prefix_bytes": 57, + "stable_prefix_hash": "a63698f1393aaf8d", + "stable_prefix_tokens_est": 15 + }, + "diff_artifact": null, + "markers_inserted": [ + "stable_prefix_hash:a63698f1393aaf8d" + ], + "messages": [ + { + "content": "You are a helpful assistant. The date is . Request id 16.\n\n---\n[Dynamic Context]\n2026-04-23", + "role": "system" + }, + { + "content": "Question number 16: what is 2+2?", + "role": "user" + } + ], + "timing": {}, + "tokens_after": 51, + "tokens_before": 47, + "transforms_applied": [ + "cache_align" + ], + "warnings": [], + "waste_signals": null + }, + "recorded_at": "2026-04-23T23:51:37.792923+00:00", + "transform": "cache_aligner" +} diff --git a/tests/parity/fixtures/ccr/15887705e46d70a3.json b/tests/parity/fixtures/ccr/15887705e46d70a3.json new file mode 100644 index 0000000000000000000000000000000000000000..8337968fb000f654a8ea3ccd20a2c427ed48a0dd --- /dev/null +++ b/tests/parity/fixtures/ccr/15887705e46d70a3.json @@ -0,0 +1,41 @@ +{ + "config": {}, + "input": [ + { + "description": "desc 16", + "name": "other_tool_16" + } + ], + "input_sha256": "15887705e46d70a33029342fc15f4a3259031c01e2317008f4683833798d78d9", + "output": [ + [ + { + "description": "desc 16", + "name": "other_tool_16" + }, + { + "description": "Retrieve original uncompressed content that was compressed to save tokens. Use this when you need more data than what's shown in compressed tool results. The hash is provided in compression markers like [N items compressed... hash=abc123].", + "input_schema": { + "properties": { + "hash": { + "description": "Hash key from the compression marker (e.g., 'abc123' from hash=abc123)", + "type": "string" + }, + "query": { + "description": "Optional search query to filter results. If provided, only returns items matching the query. If omitted, returns all original items.", + "type": "string" + } + }, + "required": [ + "hash" + ], + "type": "object" + }, + "name": "headroom_retrieve" + } + ], + true + ], + "recorded_at": "2026-04-23T23:51:37.805953+00:00", + "transform": "ccr" +} diff --git a/tests/parity/fixtures/ccr/1e13216df83b1b24.json b/tests/parity/fixtures/ccr/1e13216df83b1b24.json new file mode 100644 index 0000000000000000000000000000000000000000..da517d2f813abc625f1ba7b734b532eb5fe459f0 --- /dev/null +++ b/tests/parity/fixtures/ccr/1e13216df83b1b24.json @@ -0,0 +1,41 @@ +{ + "config": {}, + "input": [ + { + "description": "desc 22", + "name": "other_tool_22" + } + ], + "input_sha256": "1e13216df83b1b242e602e061752778811c4303a567abe718681754dc0e7ff1e", + "output": [ + [ + { + "description": "desc 22", + "name": "other_tool_22" + }, + { + "description": "Retrieve original uncompressed content that was compressed to save tokens. Use this when you need more data than what's shown in compressed tool results. The hash is provided in compression markers like [N items compressed... hash=abc123].", + "input_schema": { + "properties": { + "hash": { + "description": "Hash key from the compression marker (e.g., 'abc123' from hash=abc123)", + "type": "string" + }, + "query": { + "description": "Optional search query to filter results. If provided, only returns items matching the query. If omitted, returns all original items.", + "type": "string" + } + }, + "required": [ + "hash" + ], + "type": "object" + }, + "name": "headroom_retrieve" + } + ], + true + ], + "recorded_at": "2026-04-23T23:51:37.806891+00:00", + "transform": "ccr" +} diff --git a/tests/parity/fixtures/ccr/22e61aa4e1839aaf.json b/tests/parity/fixtures/ccr/22e61aa4e1839aaf.json new file mode 100644 index 0000000000000000000000000000000000000000..799a4fa6ce5d997cf6a1a6e29d9ccd476e5801a1 --- /dev/null +++ b/tests/parity/fixtures/ccr/22e61aa4e1839aaf.json @@ -0,0 +1,44 @@ +{ + "config": {}, + "input": [ + { + "description": "desc 17", + "name": "other_tool_17" + } + ], + "input_sha256": "22e61aa4e1839aaf69514cbdbdc4ac615fe1e436fe87c5de75d860a94762ff3a", + "output": [ + [ + { + "description": "desc 17", + "name": "other_tool_17" + }, + { + "function": { + "description": "Retrieve original uncompressed content that was compressed to save tokens. Use this when you need more data than what's shown in compressed tool results. The hash is provided in compression markers like [N items compressed... hash=abc123].", + "name": "headroom_retrieve", + "parameters": { + "properties": { + "hash": { + "description": "Hash key from the compression marker (e.g., 'abc123' from hash=abc123)", + "type": "string" + }, + "query": { + "description": "Optional search query to filter results. If provided, only returns items matching the query. If omitted, returns all original items.", + "type": "string" + } + }, + "required": [ + "hash" + ], + "type": "object" + } + }, + "type": "function" + } + ], + true + ], + "recorded_at": "2026-04-23T23:51:37.806121+00:00", + "transform": "ccr" +} diff --git a/tests/parity/fixtures/ccr/2670d5319d6ff242.json b/tests/parity/fixtures/ccr/2670d5319d6ff242.json new file mode 100644 index 0000000000000000000000000000000000000000..d279a728539b8789ea70883edda6541f2c3292fe --- /dev/null +++ b/tests/parity/fixtures/ccr/2670d5319d6ff242.json @@ -0,0 +1,44 @@ +{ + "config": {}, + "input": [ + { + "description": "desc 19", + "name": "other_tool_19" + } + ], + "input_sha256": "2670d5319d6ff242031fc022d473f8b670ff943b47b70440f84e15655ca7dcd0", + "output": [ + [ + { + "description": "desc 19", + "name": "other_tool_19" + }, + { + "function": { + "description": "Retrieve original uncompressed content that was compressed to save tokens. Use this when you need more data than what's shown in compressed tool results. The hash is provided in compression markers like [N items compressed... hash=abc123].", + "name": "headroom_retrieve", + "parameters": { + "properties": { + "hash": { + "description": "Hash key from the compression marker (e.g., 'abc123' from hash=abc123)", + "type": "string" + }, + "query": { + "description": "Optional search query to filter results. If provided, only returns items matching the query. If omitted, returns all original items.", + "type": "string" + } + }, + "required": [ + "hash" + ], + "type": "object" + } + }, + "type": "function" + } + ], + true + ], + "recorded_at": "2026-04-23T23:51:37.806448+00:00", + "transform": "ccr" +} diff --git a/tests/parity/fixtures/ccr/2c7824a6bc69ae89.json b/tests/parity/fixtures/ccr/2c7824a6bc69ae89.json new file mode 100644 index 0000000000000000000000000000000000000000..821ab3f05c03f6f0671220e7cb16c42a3a5b6af0 --- /dev/null +++ b/tests/parity/fixtures/ccr/2c7824a6bc69ae89.json @@ -0,0 +1,41 @@ +{ + "config": {}, + "input": [ + { + "description": "desc 20", + "name": "other_tool_20" + } + ], + "input_sha256": "2c7824a6bc69ae8935944b28a879d0adf7f10d7d9fdd7cf0e74775389cc48944", + "output": [ + [ + { + "description": "desc 20", + "name": "other_tool_20" + }, + { + "description": "Retrieve original uncompressed content that was compressed to save tokens. Use this when you need more data than what's shown in compressed tool results. The hash is provided in compression markers like [N items compressed... hash=abc123].", + "input_schema": { + "properties": { + "hash": { + "description": "Hash key from the compression marker (e.g., 'abc123' from hash=abc123)", + "type": "string" + }, + "query": { + "description": "Optional search query to filter results. If provided, only returns items matching the query. If omitted, returns all original items.", + "type": "string" + } + }, + "required": [ + "hash" + ], + "type": "object" + }, + "name": "headroom_retrieve" + } + ], + true + ], + "recorded_at": "2026-04-23T23:51:37.806597+00:00", + "transform": "ccr" +} diff --git a/tests/parity/fixtures/ccr/3d8773cdc4d3493c.json b/tests/parity/fixtures/ccr/3d8773cdc4d3493c.json new file mode 100644 index 0000000000000000000000000000000000000000..ba4048c60b23dd0e776988126c26550a9d10c276 --- /dev/null +++ b/tests/parity/fixtures/ccr/3d8773cdc4d3493c.json @@ -0,0 +1,44 @@ +{ + "config": {}, + "input": [ + { + "description": "desc 23", + "name": "other_tool_23" + } + ], + "input_sha256": "3d8773cdc4d3493c7df439805446ac7a09f75942495ccc985e6959c67521917f", + "output": [ + [ + { + "description": "desc 23", + "name": "other_tool_23" + }, + { + "function": { + "description": "Retrieve original uncompressed content that was compressed to save tokens. Use this when you need more data than what's shown in compressed tool results. The hash is provided in compression markers like [N items compressed... hash=abc123].", + "name": "headroom_retrieve", + "parameters": { + "properties": { + "hash": { + "description": "Hash key from the compression marker (e.g., 'abc123' from hash=abc123)", + "type": "string" + }, + "query": { + "description": "Optional search query to filter results. If provided, only returns items matching the query. If omitted, returns all original items.", + "type": "string" + } + }, + "required": [ + "hash" + ], + "type": "object" + } + }, + "type": "function" + } + ], + true + ], + "recorded_at": "2026-04-23T23:51:37.807121+00:00", + "transform": "ccr" +} diff --git a/tests/parity/fixtures/ccr/3de30e123dc02906.json b/tests/parity/fixtures/ccr/3de30e123dc02906.json new file mode 100644 index 0000000000000000000000000000000000000000..f3061e1ade9cc0aa2e486447d0d59efeca36ca7d --- /dev/null +++ b/tests/parity/fixtures/ccr/3de30e123dc02906.json @@ -0,0 +1,44 @@ +{ + "config": {}, + "input": [ + { + "description": "desc 11", + "name": "other_tool_11" + } + ], + "input_sha256": "3de30e123dc02906deb06ba4ff4146bf31802302ed4ea40e636b45041ba1b661", + "output": [ + [ + { + "description": "desc 11", + "name": "other_tool_11" + }, + { + "function": { + "description": "Retrieve original uncompressed content that was compressed to save tokens. Use this when you need more data than what's shown in compressed tool results. The hash is provided in compression markers like [N items compressed... hash=abc123].", + "name": "headroom_retrieve", + "parameters": { + "properties": { + "hash": { + "description": "Hash key from the compression marker (e.g., 'abc123' from hash=abc123)", + "type": "string" + }, + "query": { + "description": "Optional search query to filter results. If provided, only returns items matching the query. If omitted, returns all original items.", + "type": "string" + } + }, + "required": [ + "hash" + ], + "type": "object" + } + }, + "type": "function" + } + ], + true + ], + "recorded_at": "2026-04-23T23:51:37.803681+00:00", + "transform": "ccr" +} diff --git a/tests/parity/fixtures/ccr/7d7e15a8b88f7f62.json b/tests/parity/fixtures/ccr/7d7e15a8b88f7f62.json new file mode 100644 index 0000000000000000000000000000000000000000..55c90144761cb001ad80c0e921067d4fd76be97c --- /dev/null +++ b/tests/parity/fixtures/ccr/7d7e15a8b88f7f62.json @@ -0,0 +1,44 @@ +{ + "config": {}, + "input": [ + { + "description": "desc 3", + "name": "other_tool_3" + } + ], + "input_sha256": "7d7e15a8b88f7f62634f22e3040c14dff81ed35cee498af81f150b6cbc1a55bd", + "output": [ + [ + { + "description": "desc 3", + "name": "other_tool_3" + }, + { + "function": { + "description": "Retrieve original uncompressed content that was compressed to save tokens. Use this when you need more data than what's shown in compressed tool results. The hash is provided in compression markers like [N items compressed... hash=abc123].", + "name": "headroom_retrieve", + "parameters": { + "properties": { + "hash": { + "description": "Hash key from the compression marker (e.g., 'abc123' from hash=abc123)", + "type": "string" + }, + "query": { + "description": "Optional search query to filter results. If provided, only returns items matching the query. If omitted, returns all original items.", + "type": "string" + } + }, + "required": [ + "hash" + ], + "type": "object" + } + }, + "type": "function" + } + ], + true + ], + "recorded_at": "2026-04-23T23:51:37.802244+00:00", + "transform": "ccr" +} diff --git a/tests/parity/fixtures/ccr/7f7179505206626f.json b/tests/parity/fixtures/ccr/7f7179505206626f.json new file mode 100644 index 0000000000000000000000000000000000000000..af22715b0ddc321fbb55bd17a1b5c6350d2d8a20 --- /dev/null +++ b/tests/parity/fixtures/ccr/7f7179505206626f.json @@ -0,0 +1,44 @@ +{ + "config": {}, + "input": [ + { + "description": "desc 1", + "name": "other_tool_1" + } + ], + "input_sha256": "7f7179505206626f8793122805abad646557b8e23915db1a8f2ed8fb4154e47e", + "output": [ + [ + { + "description": "desc 1", + "name": "other_tool_1" + }, + { + "function": { + "description": "Retrieve original uncompressed content that was compressed to save tokens. Use this when you need more data than what's shown in compressed tool results. The hash is provided in compression markers like [N items compressed... hash=abc123].", + "name": "headroom_retrieve", + "parameters": { + "properties": { + "hash": { + "description": "Hash key from the compression marker (e.g., 'abc123' from hash=abc123)", + "type": "string" + }, + "query": { + "description": "Optional search query to filter results. If provided, only returns items matching the query. If omitted, returns all original items.", + "type": "string" + } + }, + "required": [ + "hash" + ], + "type": "object" + } + }, + "type": "function" + } + ], + true + ], + "recorded_at": "2026-04-23T23:51:37.801863+00:00", + "transform": "ccr" +} diff --git a/tests/parity/fixtures/ccr/8bf62db276326e98.json b/tests/parity/fixtures/ccr/8bf62db276326e98.json new file mode 100644 index 0000000000000000000000000000000000000000..dae86e2d138dbc9c33f25ec2ae0cf79b48c5c0e6 --- /dev/null +++ b/tests/parity/fixtures/ccr/8bf62db276326e98.json @@ -0,0 +1,44 @@ +{ + "config": {}, + "input": [ + { + "description": "desc 21", + "name": "other_tool_21" + } + ], + "input_sha256": "8bf62db276326e98b3d49ae9c24e4eb75e58393fe706d13da72b2fb5efc76f01", + "output": [ + [ + { + "description": "desc 21", + "name": "other_tool_21" + }, + { + "function": { + "description": "Retrieve original uncompressed content that was compressed to save tokens. Use this when you need more data than what's shown in compressed tool results. The hash is provided in compression markers like [N items compressed... hash=abc123].", + "name": "headroom_retrieve", + "parameters": { + "properties": { + "hash": { + "description": "Hash key from the compression marker (e.g., 'abc123' from hash=abc123)", + "type": "string" + }, + "query": { + "description": "Optional search query to filter results. If provided, only returns items matching the query. If omitted, returns all original items.", + "type": "string" + } + }, + "required": [ + "hash" + ], + "type": "object" + } + }, + "type": "function" + } + ], + true + ], + "recorded_at": "2026-04-23T23:51:37.806740+00:00", + "transform": "ccr" +} diff --git a/tests/parity/fixtures/ccr/8c605629cab1b723.json b/tests/parity/fixtures/ccr/8c605629cab1b723.json new file mode 100644 index 0000000000000000000000000000000000000000..e2f82e9a03fecab12202965cd0d34cacdf6359a6 --- /dev/null +++ b/tests/parity/fixtures/ccr/8c605629cab1b723.json @@ -0,0 +1,41 @@ +{ + "config": {}, + "input": [ + { + "description": "desc 4", + "name": "other_tool_4" + } + ], + "input_sha256": "8c605629cab1b7231506f35b0bcbcfbaa831984804968851eaf58829121f52ec", + "output": [ + [ + { + "description": "desc 4", + "name": "other_tool_4" + }, + { + "description": "Retrieve original uncompressed content that was compressed to save tokens. Use this when you need more data than what's shown in compressed tool results. The hash is provided in compression markers like [N items compressed... hash=abc123].", + "input_schema": { + "properties": { + "hash": { + "description": "Hash key from the compression marker (e.g., 'abc123' from hash=abc123)", + "type": "string" + }, + "query": { + "description": "Optional search query to filter results. If provided, only returns items matching the query. If omitted, returns all original items.", + "type": "string" + } + }, + "required": [ + "hash" + ], + "type": "object" + }, + "name": "headroom_retrieve" + } + ], + true + ], + "recorded_at": "2026-04-23T23:51:37.802480+00:00", + "transform": "ccr" +} diff --git a/tests/parity/fixtures/ccr/936e54516eae8e15.json b/tests/parity/fixtures/ccr/936e54516eae8e15.json new file mode 100644 index 0000000000000000000000000000000000000000..723fd6084e04a43d678339cae6218e3f55d95f0d --- /dev/null +++ b/tests/parity/fixtures/ccr/936e54516eae8e15.json @@ -0,0 +1,41 @@ +{ + "config": {}, + "input": [ + { + "description": "desc 6", + "name": "other_tool_6" + } + ], + "input_sha256": "936e54516eae8e15c9d4bf975a3471613e66fa2077b69f37fe6d47e862c3662c", + "output": [ + [ + { + "description": "desc 6", + "name": "other_tool_6" + }, + { + "description": "Retrieve original uncompressed content that was compressed to save tokens. Use this when you need more data than what's shown in compressed tool results. The hash is provided in compression markers like [N items compressed... hash=abc123].", + "input_schema": { + "properties": { + "hash": { + "description": "Hash key from the compression marker (e.g., 'abc123' from hash=abc123)", + "type": "string" + }, + "query": { + "description": "Optional search query to filter results. If provided, only returns items matching the query. If omitted, returns all original items.", + "type": "string" + } + }, + "required": [ + "hash" + ], + "type": "object" + }, + "name": "headroom_retrieve" + } + ], + true + ], + "recorded_at": "2026-04-23T23:51:37.802838+00:00", + "transform": "ccr" +} diff --git a/tests/parity/fixtures/ccr/93b84c8742a4a013.json b/tests/parity/fixtures/ccr/93b84c8742a4a013.json new file mode 100644 index 0000000000000000000000000000000000000000..df313f4411ab6dcb9ce4be69a67cc349b51196a1 --- /dev/null +++ b/tests/parity/fixtures/ccr/93b84c8742a4a013.json @@ -0,0 +1,41 @@ +{ + "config": {}, + "input": [ + { + "description": "desc 18", + "name": "other_tool_18" + } + ], + "input_sha256": "93b84c8742a4a013bba557f2a803d751a140d8e349be31871897696132a65664", + "output": [ + [ + { + "description": "desc 18", + "name": "other_tool_18" + }, + { + "description": "Retrieve original uncompressed content that was compressed to save tokens. Use this when you need more data than what's shown in compressed tool results. The hash is provided in compression markers like [N items compressed... hash=abc123].", + "input_schema": { + "properties": { + "hash": { + "description": "Hash key from the compression marker (e.g., 'abc123' from hash=abc123)", + "type": "string" + }, + "query": { + "description": "Optional search query to filter results. If provided, only returns items matching the query. If omitted, returns all original items.", + "type": "string" + } + }, + "required": [ + "hash" + ], + "type": "object" + }, + "name": "headroom_retrieve" + } + ], + true + ], + "recorded_at": "2026-04-23T23:51:37.806284+00:00", + "transform": "ccr" +} diff --git a/tests/parity/fixtures/ccr/a4395ce692509f23.json b/tests/parity/fixtures/ccr/a4395ce692509f23.json new file mode 100644 index 0000000000000000000000000000000000000000..71b7546aed1196e8f223ead63d5a365d26a3d5fe --- /dev/null +++ b/tests/parity/fixtures/ccr/a4395ce692509f23.json @@ -0,0 +1,41 @@ +{ + "config": {}, + "input": [ + { + "description": "desc 8", + "name": "other_tool_8" + } + ], + "input_sha256": "a4395ce692509f2395abe00668a974fc2551593da1e4a1997c315feec62d609a", + "output": [ + [ + { + "description": "desc 8", + "name": "other_tool_8" + }, + { + "description": "Retrieve original uncompressed content that was compressed to save tokens. Use this when you need more data than what's shown in compressed tool results. The hash is provided in compression markers like [N items compressed... hash=abc123].", + "input_schema": { + "properties": { + "hash": { + "description": "Hash key from the compression marker (e.g., 'abc123' from hash=abc123)", + "type": "string" + }, + "query": { + "description": "Optional search query to filter results. If provided, only returns items matching the query. If omitted, returns all original items.", + "type": "string" + } + }, + "required": [ + "hash" + ], + "type": "object" + }, + "name": "headroom_retrieve" + } + ], + true + ], + "recorded_at": "2026-04-23T23:51:37.803184+00:00", + "transform": "ccr" +} diff --git a/tests/parity/fixtures/ccr/a960a03905b2305d.json b/tests/parity/fixtures/ccr/a960a03905b2305d.json new file mode 100644 index 0000000000000000000000000000000000000000..acb76f0b16be587c07c6e978342a002d77a0f98e --- /dev/null +++ b/tests/parity/fixtures/ccr/a960a03905b2305d.json @@ -0,0 +1,41 @@ +{ + "config": {}, + "input": [ + { + "description": "desc 2", + "name": "other_tool_2" + } + ], + "input_sha256": "a960a03905b2305decb2cc44a143d7e5af8879ec54b7b31eb98d139af150cf45", + "output": [ + [ + { + "description": "desc 2", + "name": "other_tool_2" + }, + { + "description": "Retrieve original uncompressed content that was compressed to save tokens. Use this when you need more data than what's shown in compressed tool results. The hash is provided in compression markers like [N items compressed... hash=abc123].", + "input_schema": { + "properties": { + "hash": { + "description": "Hash key from the compression marker (e.g., 'abc123' from hash=abc123)", + "type": "string" + }, + "query": { + "description": "Optional search query to filter results. If provided, only returns items matching the query. If omitted, returns all original items.", + "type": "string" + } + }, + "required": [ + "hash" + ], + "type": "object" + }, + "name": "headroom_retrieve" + } + ], + true + ], + "recorded_at": "2026-04-23T23:51:37.802061+00:00", + "transform": "ccr" +} diff --git a/tests/parity/fixtures/ccr/a9a5944d23fe44c0.json b/tests/parity/fixtures/ccr/a9a5944d23fe44c0.json new file mode 100644 index 0000000000000000000000000000000000000000..f4c0ea3510bc88f58ec708320259741ecd45bdaf --- /dev/null +++ b/tests/parity/fixtures/ccr/a9a5944d23fe44c0.json @@ -0,0 +1,44 @@ +{ + "config": {}, + "input": [ + { + "description": "desc 5", + "name": "other_tool_5" + } + ], + "input_sha256": "a9a5944d23fe44c03c2c77f1329c70e0c0d619fe4ce5da7fdd9c475d8e00b96a", + "output": [ + [ + { + "description": "desc 5", + "name": "other_tool_5" + }, + { + "function": { + "description": "Retrieve original uncompressed content that was compressed to save tokens. Use this when you need more data than what's shown in compressed tool results. The hash is provided in compression markers like [N items compressed... hash=abc123].", + "name": "headroom_retrieve", + "parameters": { + "properties": { + "hash": { + "description": "Hash key from the compression marker (e.g., 'abc123' from hash=abc123)", + "type": "string" + }, + "query": { + "description": "Optional search query to filter results. If provided, only returns items matching the query. If omitted, returns all original items.", + "type": "string" + } + }, + "required": [ + "hash" + ], + "type": "object" + } + }, + "type": "function" + } + ], + true + ], + "recorded_at": "2026-04-23T23:51:37.802669+00:00", + "transform": "ccr" +} diff --git a/tests/parity/fixtures/ccr/aae9481c92191cd1.json b/tests/parity/fixtures/ccr/aae9481c92191cd1.json new file mode 100644 index 0000000000000000000000000000000000000000..36100109ff378ef37db764bd89094d784c36e049 --- /dev/null +++ b/tests/parity/fixtures/ccr/aae9481c92191cd1.json @@ -0,0 +1,44 @@ +{ + "config": {}, + "input": [ + { + "description": "desc 13", + "name": "other_tool_13" + } + ], + "input_sha256": "aae9481c92191cd197de2f114ec033d9b1623a05a763f234225480ec2aa37ec1", + "output": [ + [ + { + "description": "desc 13", + "name": "other_tool_13" + }, + { + "function": { + "description": "Retrieve original uncompressed content that was compressed to save tokens. Use this when you need more data than what's shown in compressed tool results. The hash is provided in compression markers like [N items compressed... hash=abc123].", + "name": "headroom_retrieve", + "parameters": { + "properties": { + "hash": { + "description": "Hash key from the compression marker (e.g., 'abc123' from hash=abc123)", + "type": "string" + }, + "query": { + "description": "Optional search query to filter results. If provided, only returns items matching the query. If omitted, returns all original items.", + "type": "string" + } + }, + "required": [ + "hash" + ], + "type": "object" + } + }, + "type": "function" + } + ], + true + ], + "recorded_at": "2026-04-23T23:51:37.805176+00:00", + "transform": "ccr" +} diff --git a/tests/parity/fixtures/ccr/b993d838f6a7aba0.json b/tests/parity/fixtures/ccr/b993d838f6a7aba0.json new file mode 100644 index 0000000000000000000000000000000000000000..0612aa00e468afb5d1eb2b07378031d7f324f985 --- /dev/null +++ b/tests/parity/fixtures/ccr/b993d838f6a7aba0.json @@ -0,0 +1,44 @@ +{ + "config": {}, + "input": [ + { + "description": "desc 7", + "name": "other_tool_7" + } + ], + "input_sha256": "b993d838f6a7aba083e8564f106357d30640dcfd0dc327b01e9b5f26f85f4129", + "output": [ + [ + { + "description": "desc 7", + "name": "other_tool_7" + }, + { + "function": { + "description": "Retrieve original uncompressed content that was compressed to save tokens. Use this when you need more data than what's shown in compressed tool results. The hash is provided in compression markers like [N items compressed... hash=abc123].", + "name": "headroom_retrieve", + "parameters": { + "properties": { + "hash": { + "description": "Hash key from the compression marker (e.g., 'abc123' from hash=abc123)", + "type": "string" + }, + "query": { + "description": "Optional search query to filter results. If provided, only returns items matching the query. If omitted, returns all original items.", + "type": "string" + } + }, + "required": [ + "hash" + ], + "type": "object" + } + }, + "type": "function" + } + ], + true + ], + "recorded_at": "2026-04-23T23:51:37.803014+00:00", + "transform": "ccr" +} diff --git a/tests/parity/fixtures/ccr/c171cef065b43a3e.json b/tests/parity/fixtures/ccr/c171cef065b43a3e.json new file mode 100644 index 0000000000000000000000000000000000000000..0e4190edc2d5cb8e21ea8307056d6c077c261cd5 --- /dev/null +++ b/tests/parity/fixtures/ccr/c171cef065b43a3e.json @@ -0,0 +1,41 @@ +{ + "config": {}, + "input": [ + { + "description": "desc 24", + "name": "other_tool_24" + } + ], + "input_sha256": "c171cef065b43a3e16a97bdc84ba0e562c2195876b6281914cb81b72445da332", + "output": [ + [ + { + "description": "desc 24", + "name": "other_tool_24" + }, + { + "description": "Retrieve original uncompressed content that was compressed to save tokens. Use this when you need more data than what's shown in compressed tool results. The hash is provided in compression markers like [N items compressed... hash=abc123].", + "input_schema": { + "properties": { + "hash": { + "description": "Hash key from the compression marker (e.g., 'abc123' from hash=abc123)", + "type": "string" + }, + "query": { + "description": "Optional search query to filter results. If provided, only returns items matching the query. If omitted, returns all original items.", + "type": "string" + } + }, + "required": [ + "hash" + ], + "type": "object" + }, + "name": "headroom_retrieve" + } + ], + true + ], + "recorded_at": "2026-04-23T23:51:37.807275+00:00", + "transform": "ccr" +} diff --git a/tests/parity/fixtures/ccr/d22dc7c618d06703.json b/tests/parity/fixtures/ccr/d22dc7c618d06703.json new file mode 100644 index 0000000000000000000000000000000000000000..12ef0fd5055879d81370acb3cdee0f97972afca7 --- /dev/null +++ b/tests/parity/fixtures/ccr/d22dc7c618d06703.json @@ -0,0 +1,41 @@ +{ + "config": {}, + "input": [ + { + "description": "desc 12", + "name": "other_tool_12" + } + ], + "input_sha256": "d22dc7c618d06703b9be20a3e39352da3d17ce60039b8a90b5c9293eb277acf6", + "output": [ + [ + { + "description": "desc 12", + "name": "other_tool_12" + }, + { + "description": "Retrieve original uncompressed content that was compressed to save tokens. Use this when you need more data than what's shown in compressed tool results. The hash is provided in compression markers like [N items compressed... hash=abc123].", + "input_schema": { + "properties": { + "hash": { + "description": "Hash key from the compression marker (e.g., 'abc123' from hash=abc123)", + "type": "string" + }, + "query": { + "description": "Optional search query to filter results. If provided, only returns items matching the query. If omitted, returns all original items.", + "type": "string" + } + }, + "required": [ + "hash" + ], + "type": "object" + }, + "name": "headroom_retrieve" + } + ], + true + ], + "recorded_at": "2026-04-23T23:51:37.803865+00:00", + "transform": "ccr" +} diff --git a/tests/parity/fixtures/ccr/d266ff10965e9e45.json b/tests/parity/fixtures/ccr/d266ff10965e9e45.json new file mode 100644 index 0000000000000000000000000000000000000000..3b58b4f6d6da98ab8ae9abed7552e7eda4f9941e --- /dev/null +++ b/tests/parity/fixtures/ccr/d266ff10965e9e45.json @@ -0,0 +1,41 @@ +{ + "config": {}, + "input": [ + { + "description": "desc 0", + "name": "other_tool_0" + } + ], + "input_sha256": "d266ff10965e9e45f7a464dc376554f36e73ad61b2a69d9dca09d9c750836093", + "output": [ + [ + { + "description": "desc 0", + "name": "other_tool_0" + }, + { + "description": "Retrieve original uncompressed content that was compressed to save tokens. Use this when you need more data than what's shown in compressed tool results. The hash is provided in compression markers like [N items compressed... hash=abc123].", + "input_schema": { + "properties": { + "hash": { + "description": "Hash key from the compression marker (e.g., 'abc123' from hash=abc123)", + "type": "string" + }, + "query": { + "description": "Optional search query to filter results. If provided, only returns items matching the query. If omitted, returns all original items.", + "type": "string" + } + }, + "required": [ + "hash" + ], + "type": "object" + }, + "name": "headroom_retrieve" + } + ], + true + ], + "recorded_at": "2026-04-23T23:51:37.801596+00:00", + "transform": "ccr" +} diff --git a/tests/parity/fixtures/ccr/e240be82ab1c494b.json b/tests/parity/fixtures/ccr/e240be82ab1c494b.json new file mode 100644 index 0000000000000000000000000000000000000000..172627683b98638a9fdb9abc2d4658e96c7b2d66 --- /dev/null +++ b/tests/parity/fixtures/ccr/e240be82ab1c494b.json @@ -0,0 +1,41 @@ +{ + "config": {}, + "input": [ + { + "description": "desc 14", + "name": "other_tool_14" + } + ], + "input_sha256": "e240be82ab1c494ba39d3d4200640e34865f4ae214b72bb7c4ae099b17d8806f", + "output": [ + [ + { + "description": "desc 14", + "name": "other_tool_14" + }, + { + "description": "Retrieve original uncompressed content that was compressed to save tokens. Use this when you need more data than what's shown in compressed tool results. The hash is provided in compression markers like [N items compressed... hash=abc123].", + "input_schema": { + "properties": { + "hash": { + "description": "Hash key from the compression marker (e.g., 'abc123' from hash=abc123)", + "type": "string" + }, + "query": { + "description": "Optional search query to filter results. If provided, only returns items matching the query. If omitted, returns all original items.", + "type": "string" + } + }, + "required": [ + "hash" + ], + "type": "object" + }, + "name": "headroom_retrieve" + } + ], + true + ], + "recorded_at": "2026-04-23T23:51:37.805539+00:00", + "transform": "ccr" +} diff --git a/tests/parity/fixtures/ccr/f76cad36ebb6cf4e.json b/tests/parity/fixtures/ccr/f76cad36ebb6cf4e.json new file mode 100644 index 0000000000000000000000000000000000000000..e8f0d672fbe541abd30455b4ecdc7eef1d4495ff --- /dev/null +++ b/tests/parity/fixtures/ccr/f76cad36ebb6cf4e.json @@ -0,0 +1,44 @@ +{ + "config": {}, + "input": [ + { + "description": "desc 15", + "name": "other_tool_15" + } + ], + "input_sha256": "f76cad36ebb6cf4e20617a5603457c90084eff584b81ea3214cc6d5c59784cf9", + "output": [ + [ + { + "description": "desc 15", + "name": "other_tool_15" + }, + { + "function": { + "description": "Retrieve original uncompressed content that was compressed to save tokens. Use this when you need more data than what's shown in compressed tool results. The hash is provided in compression markers like [N items compressed... hash=abc123].", + "name": "headroom_retrieve", + "parameters": { + "properties": { + "hash": { + "description": "Hash key from the compression marker (e.g., 'abc123' from hash=abc123)", + "type": "string" + }, + "query": { + "description": "Optional search query to filter results. If provided, only returns items matching the query. If omitted, returns all original items.", + "type": "string" + } + }, + "required": [ + "hash" + ], + "type": "object" + } + }, + "type": "function" + } + ], + true + ], + "recorded_at": "2026-04-23T23:51:37.805767+00:00", + "transform": "ccr" +} diff --git a/tests/parity/fixtures/ccr/f7f1e1ad4d5f25c5.json b/tests/parity/fixtures/ccr/f7f1e1ad4d5f25c5.json new file mode 100644 index 0000000000000000000000000000000000000000..e8147419d0dbd270687742104fcb51479a23b1cd --- /dev/null +++ b/tests/parity/fixtures/ccr/f7f1e1ad4d5f25c5.json @@ -0,0 +1,41 @@ +{ + "config": {}, + "input": [ + { + "description": "desc 10", + "name": "other_tool_10" + } + ], + "input_sha256": "f7f1e1ad4d5f25c506d156919dc300b087a500deef17cfb97e7021f57f6db3af", + "output": [ + [ + { + "description": "desc 10", + "name": "other_tool_10" + }, + { + "description": "Retrieve original uncompressed content that was compressed to save tokens. Use this when you need more data than what's shown in compressed tool results. The hash is provided in compression markers like [N items compressed... hash=abc123].", + "input_schema": { + "properties": { + "hash": { + "description": "Hash key from the compression marker (e.g., 'abc123' from hash=abc123)", + "type": "string" + }, + "query": { + "description": "Optional search query to filter results. If provided, only returns items matching the query. If omitted, returns all original items.", + "type": "string" + } + }, + "required": [ + "hash" + ], + "type": "object" + }, + "name": "headroom_retrieve" + } + ], + true + ], + "recorded_at": "2026-04-23T23:51:37.803519+00:00", + "transform": "ccr" +} diff --git a/tests/parity/fixtures/ccr/fd10707d5dc2526b.json b/tests/parity/fixtures/ccr/fd10707d5dc2526b.json new file mode 100644 index 0000000000000000000000000000000000000000..c14381056650029a632e8ffd53593ebdf763a62b --- /dev/null +++ b/tests/parity/fixtures/ccr/fd10707d5dc2526b.json @@ -0,0 +1,44 @@ +{ + "config": {}, + "input": [ + { + "description": "desc 9", + "name": "other_tool_9" + } + ], + "input_sha256": "fd10707d5dc2526ba85fc276a771f799bccbd3fe1d81aab1208aee8a2cf14411", + "output": [ + [ + { + "description": "desc 9", + "name": "other_tool_9" + }, + { + "function": { + "description": "Retrieve original uncompressed content that was compressed to save tokens. Use this when you need more data than what's shown in compressed tool results. The hash is provided in compression markers like [N items compressed... hash=abc123].", + "name": "headroom_retrieve", + "parameters": { + "properties": { + "hash": { + "description": "Hash key from the compression marker (e.g., 'abc123' from hash=abc123)", + "type": "string" + }, + "query": { + "description": "Optional search query to filter results. If provided, only returns items matching the query. If omitted, returns all original items.", + "type": "string" + } + }, + "required": [ + "hash" + ], + "type": "object" + } + }, + "type": "function" + } + ], + true + ], + "recorded_at": "2026-04-23T23:51:37.803338+00:00", + "transform": "ccr" +} diff --git a/tests/parity/fixtures/diff_compressor/0cf5e189bd42fab1.json b/tests/parity/fixtures/diff_compressor/0cf5e189bd42fab1.json new file mode 100644 index 0000000000000000000000000000000000000000..a570067ac4bf07c344f6fbd87c777562eae2c057 --- /dev/null +++ b/tests/parity/fixtures/diff_compressor/0cf5e189bd42fab1.json @@ -0,0 +1,26 @@ +{ + "config": { + "always_keep_additions": true, + "always_keep_deletions": true, + "enable_ccr": true, + "max_context_lines": 2, + "max_files": 20, + "max_hunks_per_file": 10, + "min_lines_for_ccr": 50 + }, + "input": "diff --git a/file_0.py b/file_0.py\n--- a/file_0.py\n+++ b/file_0.py\n@@ -1,10 +1,12 @@\n context_0_0\n context_1_0\n context_2_0\n context_3_0\n context_4_0\n-removed_0_0\n-removed_1_0\n-removed_2_0\n+added_0_0\n+added_1_0\n+added_2_0\n+added_3_0\n+added_4_0\n tail_0_0\n tail_1_0\n tail_2_0\n tail_3_0\n tail_4_0\ndiff --git a/file_1.py b/file_1.py\n--- a/file_1.py\n+++ b/file_1.py\n@@ -1,10 +1,12 @@\n context_0_1\n context_1_1\n context_2_1\n context_3_1\n context_4_1\n-removed_0_1\n-removed_1_1\n-removed_2_1\n+added_0_1\n+added_1_1\n+added_2_1\n+added_3_1\n+added_4_1\n tail_0_1\n tail_1_1\n tail_2_1\n tail_3_1\n tail_4_1\ndiff --git a/file_2.py b/file_2.py\n--- a/file_2.py\n+++ b/file_2.py\n@@ -1,10 +1,12 @@\n context_0_2\n context_1_2\n context_2_2\n context_3_2\n context_4_2\n-removed_0_2\n-removed_1_2\n-removed_2_2\n+added_0_2\n+added_1_2\n+added_2_2\n+added_3_2\n+added_4_2\n tail_0_2\n tail_1_2\n tail_2_2\n tail_3_2\n tail_4_2\ndiff --git a/file_3.py b/file_3.py\n--- a/file_3.py\n+++ b/file_3.py\n@@ -1,10 +1,12 @@\n context_0_3\n context_1_3\n context_2_3\n context_3_3\n context_4_3\n-removed_0_3\n-removed_1_3\n-removed_2_3\n+added_0_3\n+added_1_3\n+added_2_3\n+added_3_3\n+added_4_3\n tail_0_3\n tail_1_3\n tail_2_3\n tail_3_3\n tail_4_3\ndiff --git a/file_4.py b/file_4.py\n--- a/file_4.py\n+++ b/file_4.py\n@@ -1,10 +1,12 @@\n context_0_4\n context_1_4\n context_2_4\n context_3_4\n context_4_4\n-removed_0_4\n-removed_1_4\n-removed_2_4\n+added_0_4\n+added_1_4\n+added_2_4\n+added_3_4\n+added_4_4\n tail_0_4\n tail_1_4\n tail_2_4\n tail_3_4\n tail_4_4\ndiff --git a/file_5.py b/file_5.py\n--- a/file_5.py\n+++ b/file_5.py\n@@ -1,10 +1,12 @@\n context_0_5\n context_1_5\n context_2_5\n context_3_5\n context_4_5\n-removed_0_5\n-removed_1_5\n-removed_2_5\n+added_0_5\n+added_1_5\n+added_2_5\n+added_3_5\n+added_4_5\n tail_0_5\n tail_1_5\n tail_2_5\n tail_3_5\n tail_4_5\ndiff --git a/file_6.py b/file_6.py\n--- a/file_6.py\n+++ b/file_6.py\n@@ -1,10 +1,12 @@\n context_0_6\n context_1_6\n context_2_6\n context_3_6\n context_4_6\n-removed_0_6\n-removed_1_6\n-removed_2_6\n+added_0_6\n+added_1_6\n+added_2_6\n+added_3_6\n+added_4_6\n tail_0_6\n tail_1_6\n tail_2_6\n tail_3_6\n tail_4_6\ndiff --git a/file_7.py b/file_7.py\n--- a/file_7.py\n+++ b/file_7.py\n@@ -1,10 +1,12 @@\n context_0_7\n context_1_7\n context_2_7\n context_3_7\n context_4_7\n-removed_0_7\n-removed_1_7\n-removed_2_7\n+added_0_7\n+added_1_7\n+added_2_7\n+added_3_7\n+added_4_7\n tail_0_7\n tail_1_7\n tail_2_7\n tail_3_7\n tail_4_7\n# variant 1", + "input_sha256": "0cf5e189bd42fab1313c9ded283effee957d096eb1a8e1aa05fab8224644307f", + "output": { + "additions": 40, + "cache_key": "4ced183e23d56ca575d6ff00", + "compressed": "diff --git a/file_0.py b/file_0.py\n--- a/file_0.py\n+++ b/file_0.py\n@@ -1,10 +1,12 @@\n context_3_0\n context_4_0\n-removed_0_0\n-removed_1_0\n-removed_2_0\n+added_0_0\n+added_1_0\n+added_2_0\n+added_3_0\n+added_4_0\n tail_0_0\n tail_1_0\ndiff --git a/file_1.py b/file_1.py\n--- a/file_1.py\n+++ b/file_1.py\n@@ -1,10 +1,12 @@\n context_3_1\n context_4_1\n-removed_0_1\n-removed_1_1\n-removed_2_1\n+added_0_1\n+added_1_1\n+added_2_1\n+added_3_1\n+added_4_1\n tail_0_1\n tail_1_1\ndiff --git a/file_2.py b/file_2.py\n--- a/file_2.py\n+++ b/file_2.py\n@@ -1,10 +1,12 @@\n context_3_2\n context_4_2\n-removed_0_2\n-removed_1_2\n-removed_2_2\n+added_0_2\n+added_1_2\n+added_2_2\n+added_3_2\n+added_4_2\n tail_0_2\n tail_1_2\ndiff --git a/file_3.py b/file_3.py\n--- a/file_3.py\n+++ b/file_3.py\n@@ -1,10 +1,12 @@\n context_3_3\n context_4_3\n-removed_0_3\n-removed_1_3\n-removed_2_3\n+added_0_3\n+added_1_3\n+added_2_3\n+added_3_3\n+added_4_3\n tail_0_3\n tail_1_3\ndiff --git a/file_4.py b/file_4.py\n--- a/file_4.py\n+++ b/file_4.py\n@@ -1,10 +1,12 @@\n context_3_4\n context_4_4\n-removed_0_4\n-removed_1_4\n-removed_2_4\n+added_0_4\n+added_1_4\n+added_2_4\n+added_3_4\n+added_4_4\n tail_0_4\n tail_1_4\ndiff --git a/file_5.py b/file_5.py\n--- a/file_5.py\n+++ b/file_5.py\n@@ -1,10 +1,12 @@\n context_3_5\n context_4_5\n-removed_0_5\n-removed_1_5\n-removed_2_5\n+added_0_5\n+added_1_5\n+added_2_5\n+added_3_5\n+added_4_5\n tail_0_5\n tail_1_5\ndiff --git a/file_6.py b/file_6.py\n--- a/file_6.py\n+++ b/file_6.py\n@@ -1,10 +1,12 @@\n context_3_6\n context_4_6\n-removed_0_6\n-removed_1_6\n-removed_2_6\n+added_0_6\n+added_1_6\n+added_2_6\n+added_3_6\n+added_4_6\n tail_0_6\n tail_1_6\ndiff --git a/file_7.py b/file_7.py\n--- a/file_7.py\n+++ b/file_7.py\n@@ -1,10 +1,12 @@\n context_3_7\n context_4_7\n-removed_0_7\n-removed_1_7\n-removed_2_7\n+added_0_7\n+added_1_7\n+added_2_7\n+added_3_7\n+added_4_7\n tail_0_7\n tail_1_7\n[8 files changed, +40 -24 lines]\n[177 lines compressed to 129. Retrieve full diff: hash=4ced183e23d56ca575d6ff00]", + "compressed_line_count": 129, + "deletions": 24, + "files_affected": 8, + "hunks_kept": 8, + "hunks_removed": 0, + "original_line_count": 177 + }, + "recorded_at": "2026-04-23T23:51:37.351451+00:00", + "transform": "diff_compressor" +} diff --git a/tests/parity/fixtures/diff_compressor/10f0106f40cbe2fa.json b/tests/parity/fixtures/diff_compressor/10f0106f40cbe2fa.json new file mode 100644 index 0000000000000000000000000000000000000000..bfe15dd8beba88cb1502e791c2b7b7f319eea4ce --- /dev/null +++ b/tests/parity/fixtures/diff_compressor/10f0106f40cbe2fa.json @@ -0,0 +1,26 @@ +{ + "config": { + "always_keep_additions": true, + "always_keep_deletions": true, + "enable_ccr": true, + "max_context_lines": 2, + "max_files": 20, + "max_hunks_per_file": 10, + "min_lines_for_ccr": 50 + }, + "input": "diff --git a/src/main.rs b/src/main.rs\n--- a/src/main.rs\n+++ b/src/main.rs\n@@ -10,7 +10,7 @@\n unchanged_line_0\n unchanged_line_1\n unchanged_line_2\n unchanged_line_3\n unchanged_line_4\n- let x = 1;\n+ let x = 2;\n unchanged_after_0\n unchanged_after_1\n unchanged_after_2\n unchanged_after_3\n unchanged_after_4\n# variant 0", + "input_sha256": "10f0106f40cbe2faa4471f44c8469acc2a7cd9cf36ba23f4c895f365cf85b298", + "output": { + "additions": 0, + "cache_key": null, + "compressed": "diff --git a/src/main.rs b/src/main.rs\n--- a/src/main.rs\n+++ b/src/main.rs\n@@ -10,7 +10,7 @@\n unchanged_line_0\n unchanged_line_1\n unchanged_line_2\n unchanged_line_3\n unchanged_line_4\n- let x = 1;\n+ let x = 2;\n unchanged_after_0\n unchanged_after_1\n unchanged_after_2\n unchanged_after_3\n unchanged_after_4\n# variant 0", + "compressed_line_count": 17, + "deletions": 0, + "files_affected": 0, + "hunks_kept": 0, + "hunks_removed": 0, + "original_line_count": 17 + }, + "recorded_at": "2026-04-23T23:51:37.349228+00:00", + "transform": "diff_compressor" +} diff --git a/tests/parity/fixtures/diff_compressor/1a4e35c597c7a001.json b/tests/parity/fixtures/diff_compressor/1a4e35c597c7a001.json new file mode 100644 index 0000000000000000000000000000000000000000..6eaf416a9b029c14e1e890322b04ae75ab0b466d --- /dev/null +++ b/tests/parity/fixtures/diff_compressor/1a4e35c597c7a001.json @@ -0,0 +1,26 @@ +{ + "config": { + "always_keep_additions": true, + "always_keep_deletions": true, + "enable_ccr": true, + "max_context_lines": 2, + "max_files": 20, + "max_hunks_per_file": 10, + "min_lines_for_ccr": 50 + }, + "input": "diff --git a/file_0.py b/file_0.py\n--- a/file_0.py\n+++ b/file_0.py\n@@ -1,10 +1,12 @@\n context_0_0\n context_1_0\n context_2_0\n context_3_0\n context_4_0\n-removed_0_0\n-removed_1_0\n-removed_2_0\n+added_0_0\n+added_1_0\n+added_2_0\n+added_3_0\n+added_4_0\n tail_0_0\n tail_1_0\n tail_2_0\n tail_3_0\n tail_4_0\ndiff --git a/file_1.py b/file_1.py\n--- a/file_1.py\n+++ b/file_1.py\n@@ -1,10 +1,12 @@\n context_0_1\n context_1_1\n context_2_1\n context_3_1\n context_4_1\n-removed_0_1\n-removed_1_1\n-removed_2_1\n+added_0_1\n+added_1_1\n+added_2_1\n+added_3_1\n+added_4_1\n tail_0_1\n tail_1_1\n tail_2_1\n tail_3_1\n tail_4_1\ndiff --git a/file_2.py b/file_2.py\n--- a/file_2.py\n+++ b/file_2.py\n@@ -1,10 +1,12 @@\n context_0_2\n context_1_2\n context_2_2\n context_3_2\n context_4_2\n-removed_0_2\n-removed_1_2\n-removed_2_2\n+added_0_2\n+added_1_2\n+added_2_2\n+added_3_2\n+added_4_2\n tail_0_2\n tail_1_2\n tail_2_2\n tail_3_2\n tail_4_2\ndiff --git a/file_3.py b/file_3.py\n--- a/file_3.py\n+++ b/file_3.py\n@@ -1,10 +1,12 @@\n context_0_3\n context_1_3\n context_2_3\n context_3_3\n context_4_3\n-removed_0_3\n-removed_1_3\n-removed_2_3\n+added_0_3\n+added_1_3\n+added_2_3\n+added_3_3\n+added_4_3\n tail_0_3\n tail_1_3\n tail_2_3\n tail_3_3\n tail_4_3\ndiff --git a/file_4.py b/file_4.py\n--- a/file_4.py\n+++ b/file_4.py\n@@ -1,10 +1,12 @@\n context_0_4\n context_1_4\n context_2_4\n context_3_4\n context_4_4\n-removed_0_4\n-removed_1_4\n-removed_2_4\n+added_0_4\n+added_1_4\n+added_2_4\n+added_3_4\n+added_4_4\n tail_0_4\n tail_1_4\n tail_2_4\n tail_3_4\n tail_4_4\ndiff --git a/file_5.py b/file_5.py\n--- a/file_5.py\n+++ b/file_5.py\n@@ -1,10 +1,12 @@\n context_0_5\n context_1_5\n context_2_5\n context_3_5\n context_4_5\n-removed_0_5\n-removed_1_5\n-removed_2_5\n+added_0_5\n+added_1_5\n+added_2_5\n+added_3_5\n+added_4_5\n tail_0_5\n tail_1_5\n tail_2_5\n tail_3_5\n tail_4_5\ndiff --git a/file_6.py b/file_6.py\n--- a/file_6.py\n+++ b/file_6.py\n@@ -1,10 +1,12 @@\n context_0_6\n context_1_6\n context_2_6\n context_3_6\n context_4_6\n-removed_0_6\n-removed_1_6\n-removed_2_6\n+added_0_6\n+added_1_6\n+added_2_6\n+added_3_6\n+added_4_6\n tail_0_6\n tail_1_6\n tail_2_6\n tail_3_6\n tail_4_6\ndiff --git a/file_7.py b/file_7.py\n--- a/file_7.py\n+++ b/file_7.py\n@@ -1,10 +1,12 @@\n context_0_7\n context_1_7\n context_2_7\n context_3_7\n context_4_7\n-removed_0_7\n-removed_1_7\n-removed_2_7\n+added_0_7\n+added_1_7\n+added_2_7\n+added_3_7\n+added_4_7\n tail_0_7\n tail_1_7\n tail_2_7\n tail_3_7\n tail_4_7\n# variant 2", + "input_sha256": "1a4e35c597c7a001858425c04cf7083cec38ab5966126b16e58cfc616d497d93", + "output": { + "additions": 40, + "cache_key": "11ee99638b134ba374157005", + "compressed": "diff --git a/file_0.py b/file_0.py\n--- a/file_0.py\n+++ b/file_0.py\n@@ -1,10 +1,12 @@\n context_3_0\n context_4_0\n-removed_0_0\n-removed_1_0\n-removed_2_0\n+added_0_0\n+added_1_0\n+added_2_0\n+added_3_0\n+added_4_0\n tail_0_0\n tail_1_0\ndiff --git a/file_1.py b/file_1.py\n--- a/file_1.py\n+++ b/file_1.py\n@@ -1,10 +1,12 @@\n context_3_1\n context_4_1\n-removed_0_1\n-removed_1_1\n-removed_2_1\n+added_0_1\n+added_1_1\n+added_2_1\n+added_3_1\n+added_4_1\n tail_0_1\n tail_1_1\ndiff --git a/file_2.py b/file_2.py\n--- a/file_2.py\n+++ b/file_2.py\n@@ -1,10 +1,12 @@\n context_3_2\n context_4_2\n-removed_0_2\n-removed_1_2\n-removed_2_2\n+added_0_2\n+added_1_2\n+added_2_2\n+added_3_2\n+added_4_2\n tail_0_2\n tail_1_2\ndiff --git a/file_3.py b/file_3.py\n--- a/file_3.py\n+++ b/file_3.py\n@@ -1,10 +1,12 @@\n context_3_3\n context_4_3\n-removed_0_3\n-removed_1_3\n-removed_2_3\n+added_0_3\n+added_1_3\n+added_2_3\n+added_3_3\n+added_4_3\n tail_0_3\n tail_1_3\ndiff --git a/file_4.py b/file_4.py\n--- a/file_4.py\n+++ b/file_4.py\n@@ -1,10 +1,12 @@\n context_3_4\n context_4_4\n-removed_0_4\n-removed_1_4\n-removed_2_4\n+added_0_4\n+added_1_4\n+added_2_4\n+added_3_4\n+added_4_4\n tail_0_4\n tail_1_4\ndiff --git a/file_5.py b/file_5.py\n--- a/file_5.py\n+++ b/file_5.py\n@@ -1,10 +1,12 @@\n context_3_5\n context_4_5\n-removed_0_5\n-removed_1_5\n-removed_2_5\n+added_0_5\n+added_1_5\n+added_2_5\n+added_3_5\n+added_4_5\n tail_0_5\n tail_1_5\ndiff --git a/file_6.py b/file_6.py\n--- a/file_6.py\n+++ b/file_6.py\n@@ -1,10 +1,12 @@\n context_3_6\n context_4_6\n-removed_0_6\n-removed_1_6\n-removed_2_6\n+added_0_6\n+added_1_6\n+added_2_6\n+added_3_6\n+added_4_6\n tail_0_6\n tail_1_6\ndiff --git a/file_7.py b/file_7.py\n--- a/file_7.py\n+++ b/file_7.py\n@@ -1,10 +1,12 @@\n context_3_7\n context_4_7\n-removed_0_7\n-removed_1_7\n-removed_2_7\n+added_0_7\n+added_1_7\n+added_2_7\n+added_3_7\n+added_4_7\n tail_0_7\n tail_1_7\n[8 files changed, +40 -24 lines]\n[177 lines compressed to 129. Retrieve full diff: hash=11ee99638b134ba374157005]", + "compressed_line_count": 129, + "deletions": 24, + "files_affected": 8, + "hunks_kept": 8, + "hunks_removed": 0, + "original_line_count": 177 + }, + "recorded_at": "2026-04-23T23:51:37.352262+00:00", + "transform": "diff_compressor" +} diff --git a/tests/parity/fixtures/diff_compressor/1bdfa041108f0aad.json b/tests/parity/fixtures/diff_compressor/1bdfa041108f0aad.json new file mode 100644 index 0000000000000000000000000000000000000000..f382cdd4552169633dd912f5c2e41cedc331ff23 --- /dev/null +++ b/tests/parity/fixtures/diff_compressor/1bdfa041108f0aad.json @@ -0,0 +1,26 @@ +{ + "config": { + "always_keep_additions": true, + "always_keep_deletions": true, + "enable_ccr": true, + "max_context_lines": 2, + "max_files": 20, + "max_hunks_per_file": 10, + "min_lines_for_ccr": 50 + }, + "input": "diff --git a/src/main.rs b/src/main.rs\n--- a/src/main.rs\n+++ b/src/main.rs\n@@ -10,7 +10,7 @@\n unchanged_line_0\n unchanged_line_1\n unchanged_line_2\n unchanged_line_3\n unchanged_line_4\n- let x = 1;\n+ let x = 2;\n unchanged_after_0\n unchanged_after_1\n unchanged_after_2\n unchanged_after_3\n unchanged_after_4\n# variant 2", + "input_sha256": "1bdfa041108f0aad443277ec10aab9ff265608f50ff3f58c156b7fee476727d6", + "output": { + "additions": 0, + "cache_key": null, + "compressed": "diff --git a/src/main.rs b/src/main.rs\n--- a/src/main.rs\n+++ b/src/main.rs\n@@ -10,7 +10,7 @@\n unchanged_line_0\n unchanged_line_1\n unchanged_line_2\n unchanged_line_3\n unchanged_line_4\n- let x = 1;\n+ let x = 2;\n unchanged_after_0\n unchanged_after_1\n unchanged_after_2\n unchanged_after_3\n unchanged_after_4\n# variant 2", + "compressed_line_count": 17, + "deletions": 0, + "files_affected": 0, + "hunks_kept": 0, + "hunks_removed": 0, + "original_line_count": 17 + }, + "recorded_at": "2026-04-23T23:51:37.349572+00:00", + "transform": "diff_compressor" +} diff --git a/tests/parity/fixtures/diff_compressor/1eeb1f7ae64c7744.json b/tests/parity/fixtures/diff_compressor/1eeb1f7ae64c7744.json new file mode 100644 index 0000000000000000000000000000000000000000..953987cb13502120cc925b594efe88276226d6b1 --- /dev/null +++ b/tests/parity/fixtures/diff_compressor/1eeb1f7ae64c7744.json @@ -0,0 +1,26 @@ +{ + "config": { + "always_keep_additions": true, + "always_keep_deletions": true, + "enable_ccr": true, + "max_context_lines": 2, + "max_files": 20, + "max_hunks_per_file": 10, + "min_lines_for_ccr": 50 + }, + "input": "diff --git a/a.py b/a.py\n--- a/a.py\n+++ b/a.py\n@@ -1,3 +1,3 @@\n-x = 1\n+x = 2\n y = 3\n z = 4\n# variant 0", + "input_sha256": "1eeb1f7ae64c77446687a9b56b0ba9712856faaf1993dfec6bd13135d7003552", + "output": { + "additions": 0, + "cache_key": null, + "compressed": "diff --git a/a.py b/a.py\n--- a/a.py\n+++ b/a.py\n@@ -1,3 +1,3 @@\n-x = 1\n+x = 2\n y = 3\n z = 4\n# variant 0", + "compressed_line_count": 9, + "deletions": 0, + "files_affected": 0, + "hunks_kept": 0, + "hunks_removed": 0, + "original_line_count": 9 + }, + "recorded_at": "2026-04-23T23:51:37.347500+00:00", + "transform": "diff_compressor" +} diff --git a/tests/parity/fixtures/diff_compressor/2c07a365f52d6fa6.json b/tests/parity/fixtures/diff_compressor/2c07a365f52d6fa6.json new file mode 100644 index 0000000000000000000000000000000000000000..c224f62774488f3dc8df0e112e44a7fae22eb7f1 --- /dev/null +++ b/tests/parity/fixtures/diff_compressor/2c07a365f52d6fa6.json @@ -0,0 +1,26 @@ +{ + "config": { + "always_keep_additions": true, + "always_keep_deletions": true, + "enable_ccr": true, + "max_context_lines": 2, + "max_files": 20, + "max_hunks_per_file": 10, + "min_lines_for_ccr": 50 + }, + "input": "diff --git a/src/main.rs b/src/main.rs\n--- a/src/main.rs\n+++ b/src/main.rs\n@@ -10,7 +10,7 @@\n unchanged_line_0\n unchanged_line_1\n unchanged_line_2\n unchanged_line_3\n unchanged_line_4\n- let x = 1;\n+ let x = 2;\n unchanged_after_0\n unchanged_after_1\n unchanged_after_2\n unchanged_after_3\n unchanged_after_4\n# variant 5", + "input_sha256": "2c07a365f52d6fa6911a8c6c2ab691f2a800538898a2bb0df048d7025a60a79e", + "output": { + "additions": 0, + "cache_key": null, + "compressed": "diff --git a/src/main.rs b/src/main.rs\n--- a/src/main.rs\n+++ b/src/main.rs\n@@ -10,7 +10,7 @@\n unchanged_line_0\n unchanged_line_1\n unchanged_line_2\n unchanged_line_3\n unchanged_line_4\n- let x = 1;\n+ let x = 2;\n unchanged_after_0\n unchanged_after_1\n unchanged_after_2\n unchanged_after_3\n unchanged_after_4\n# variant 5", + "compressed_line_count": 17, + "deletions": 0, + "files_affected": 0, + "hunks_kept": 0, + "hunks_removed": 0, + "original_line_count": 17 + }, + "recorded_at": "2026-04-23T23:51:37.350101+00:00", + "transform": "diff_compressor" +} diff --git a/tests/parity/fixtures/diff_compressor/2cd095b132f5da3f.json b/tests/parity/fixtures/diff_compressor/2cd095b132f5da3f.json new file mode 100644 index 0000000000000000000000000000000000000000..3925ccef486c5a029330cee4d5a657586d02773d --- /dev/null +++ b/tests/parity/fixtures/diff_compressor/2cd095b132f5da3f.json @@ -0,0 +1,26 @@ +{ + "config": { + "always_keep_additions": true, + "always_keep_deletions": true, + "enable_ccr": true, + "max_context_lines": 2, + "max_files": 20, + "max_hunks_per_file": 10, + "min_lines_for_ccr": 50 + }, + "input": "diff --git a/new.py b/new.py\nnew file mode 100644\n--- /dev/null\n+++ b/new.py\n@@ -0,0 +1,4 @@\n+def hello():\n+ return 'world'\n+\n+x = hello()\n# variant 0", + "input_sha256": "2cd095b132f5da3f90c8f101e192724f40e4d23fa8ad720c287a72d7944ea589", + "output": { + "additions": 0, + "cache_key": null, + "compressed": "diff --git a/new.py b/new.py\nnew file mode 100644\n--- /dev/null\n+++ b/new.py\n@@ -0,0 +1,4 @@\n+def hello():\n+ return 'world'\n+\n+x = hello()\n# variant 0", + "compressed_line_count": 10, + "deletions": 0, + "files_affected": 0, + "hunks_kept": 0, + "hunks_removed": 0, + "original_line_count": 10 + }, + "recorded_at": "2026-04-23T23:51:37.353113+00:00", + "transform": "diff_compressor" +} diff --git a/tests/parity/fixtures/diff_compressor/433b2d25303645ed.json b/tests/parity/fixtures/diff_compressor/433b2d25303645ed.json new file mode 100644 index 0000000000000000000000000000000000000000..347c922398709937d52a76db5fe9ab650a8d9e9c --- /dev/null +++ b/tests/parity/fixtures/diff_compressor/433b2d25303645ed.json @@ -0,0 +1,26 @@ +{ + "config": { + "always_keep_additions": true, + "always_keep_deletions": true, + "enable_ccr": true, + "max_context_lines": 2, + "max_files": 20, + "max_hunks_per_file": 10, + "min_lines_for_ccr": 50 + }, + "input": "diff --git a/a.py b/a.py\n--- a/a.py\n+++ b/a.py\n@@ -1,3 +1,3 @@\n-x = 1\n+x = 2\n y = 3\n z = 4\n# variant 2", + "input_sha256": "433b2d25303645edb0104d51e2c4f677cf0509703a7755adb44071b2251f479a", + "output": { + "additions": 0, + "cache_key": null, + "compressed": "diff --git a/a.py b/a.py\n--- a/a.py\n+++ b/a.py\n@@ -1,3 +1,3 @@\n-x = 1\n+x = 2\n y = 3\n z = 4\n# variant 2", + "compressed_line_count": 9, + "deletions": 0, + "files_affected": 0, + "hunks_kept": 0, + "hunks_removed": 0, + "original_line_count": 9 + }, + "recorded_at": "2026-04-23T23:51:37.347987+00:00", + "transform": "diff_compressor" +} diff --git a/tests/parity/fixtures/diff_compressor/4844a5f9605b8425.json b/tests/parity/fixtures/diff_compressor/4844a5f9605b8425.json new file mode 100644 index 0000000000000000000000000000000000000000..106ae3d0d3f2570ec508ab2dc3c7f11eecde3bde --- /dev/null +++ b/tests/parity/fixtures/diff_compressor/4844a5f9605b8425.json @@ -0,0 +1,26 @@ +{ + "config": { + "always_keep_additions": true, + "always_keep_deletions": true, + "enable_ccr": true, + "max_context_lines": 2, + "max_files": 20, + "max_hunks_per_file": 10, + "min_lines_for_ccr": 50 + }, + "input": "diff --git a/new.py b/new.py\nnew file mode 100644\n--- /dev/null\n+++ b/new.py\n@@ -0,0 +1,4 @@\n+def hello():\n+ return 'world'\n+\n+x = hello()\n# variant 2", + "input_sha256": "4844a5f9605b84255e51b1e4b434ee7e8a93311eaac6704b146c1176b97d1d0a", + "output": { + "additions": 0, + "cache_key": null, + "compressed": "diff --git a/new.py b/new.py\nnew file mode 100644\n--- /dev/null\n+++ b/new.py\n@@ -0,0 +1,4 @@\n+def hello():\n+ return 'world'\n+\n+x = hello()\n# variant 2", + "compressed_line_count": 10, + "deletions": 0, + "files_affected": 0, + "hunks_kept": 0, + "hunks_removed": 0, + "original_line_count": 10 + }, + "recorded_at": "2026-04-23T23:51:37.353749+00:00", + "transform": "diff_compressor" +} diff --git a/tests/parity/fixtures/diff_compressor/649fd65ad3511191.json b/tests/parity/fixtures/diff_compressor/649fd65ad3511191.json new file mode 100644 index 0000000000000000000000000000000000000000..97a79313631719305d395dda12cb3d7574ec4f95 --- /dev/null +++ b/tests/parity/fixtures/diff_compressor/649fd65ad3511191.json @@ -0,0 +1,26 @@ +{ + "config": { + "always_keep_additions": true, + "always_keep_deletions": true, + "enable_ccr": true, + "max_context_lines": 2, + "max_files": 20, + "max_hunks_per_file": 10, + "min_lines_for_ccr": 50 + }, + "input": "diff --git a/new.py b/new.py\nnew file mode 100644\n--- /dev/null\n+++ b/new.py\n@@ -0,0 +1,4 @@\n+def hello():\n+ return 'world'\n+\n+x = hello()\n# variant 1", + "input_sha256": "649fd65ad35111911ad4d54fa694eac28d9bcbcc9f9fc99e10329f5bbcf20035", + "output": { + "additions": 0, + "cache_key": null, + "compressed": "diff --git a/new.py b/new.py\nnew file mode 100644\n--- /dev/null\n+++ b/new.py\n@@ -0,0 +1,4 @@\n+def hello():\n+ return 'world'\n+\n+x = hello()\n# variant 1", + "compressed_line_count": 10, + "deletions": 0, + "files_affected": 0, + "hunks_kept": 0, + "hunks_removed": 0, + "original_line_count": 10 + }, + "recorded_at": "2026-04-23T23:51:37.353544+00:00", + "transform": "diff_compressor" +} diff --git a/tests/parity/fixtures/diff_compressor/9b9a03215a7198bf.json b/tests/parity/fixtures/diff_compressor/9b9a03215a7198bf.json new file mode 100644 index 0000000000000000000000000000000000000000..3d71ff5a6eba350df7f9e130b0fde875ffcc92fe --- /dev/null +++ b/tests/parity/fixtures/diff_compressor/9b9a03215a7198bf.json @@ -0,0 +1,26 @@ +{ + "config": { + "always_keep_additions": true, + "always_keep_deletions": true, + "enable_ccr": true, + "max_context_lines": 2, + "max_files": 20, + "max_hunks_per_file": 10, + "min_lines_for_ccr": 50 + }, + "input": "diff --git a/a.py b/a.py\n--- a/a.py\n+++ b/a.py\n@@ -1,3 +1,3 @@\n-x = 1\n+x = 2\n y = 3\n z = 4\n# variant 6", + "input_sha256": "9b9a03215a7198bfe469acdb7207e518354b4d5e0d2b2ed1edd958a0c22184b9", + "output": { + "additions": 0, + "cache_key": null, + "compressed": "diff --git a/a.py b/a.py\n--- a/a.py\n+++ b/a.py\n@@ -1,3 +1,3 @@\n-x = 1\n+x = 2\n y = 3\n z = 4\n# variant 6", + "compressed_line_count": 9, + "deletions": 0, + "files_affected": 0, + "hunks_kept": 0, + "hunks_removed": 0, + "original_line_count": 9 + }, + "recorded_at": "2026-04-23T23:51:37.349054+00:00", + "transform": "diff_compressor" +} diff --git a/tests/parity/fixtures/diff_compressor/b0b83d16366edfed.json b/tests/parity/fixtures/diff_compressor/b0b83d16366edfed.json new file mode 100644 index 0000000000000000000000000000000000000000..1e057660f6d7de68940688af36f6b0c64da5cdbc --- /dev/null +++ b/tests/parity/fixtures/diff_compressor/b0b83d16366edfed.json @@ -0,0 +1,26 @@ +{ + "config": { + "always_keep_additions": true, + "always_keep_deletions": true, + "enable_ccr": true, + "max_context_lines": 2, + "max_files": 20, + "max_hunks_per_file": 10, + "min_lines_for_ccr": 50 + }, + "input": "diff --git a/a.py b/a.py\n--- a/a.py\n+++ b/a.py\n@@ -1,3 +1,3 @@\n-x = 1\n+x = 2\n y = 3\n z = 4\n# variant 4", + "input_sha256": "b0b83d16366edfed6f05a8f452615d7fa78f0b7a3da5f00435084b5c85de199f", + "output": { + "additions": 0, + "cache_key": null, + "compressed": "diff --git a/a.py b/a.py\n--- a/a.py\n+++ b/a.py\n@@ -1,3 +1,3 @@\n-x = 1\n+x = 2\n y = 3\n z = 4\n# variant 4", + "compressed_line_count": 9, + "deletions": 0, + "files_affected": 0, + "hunks_kept": 0, + "hunks_removed": 0, + "original_line_count": 9 + }, + "recorded_at": "2026-04-23T23:51:37.348655+00:00", + "transform": "diff_compressor" +} diff --git a/tests/parity/fixtures/diff_compressor/b605bd89e234cbff.json b/tests/parity/fixtures/diff_compressor/b605bd89e234cbff.json new file mode 100644 index 0000000000000000000000000000000000000000..a3f5196725d4bf638b5fc9278ff48f0b62332365 --- /dev/null +++ b/tests/parity/fixtures/diff_compressor/b605bd89e234cbff.json @@ -0,0 +1,26 @@ +{ + "config": { + "always_keep_additions": true, + "always_keep_deletions": true, + "enable_ccr": true, + "max_context_lines": 2, + "max_files": 20, + "max_hunks_per_file": 10, + "min_lines_for_ccr": 50 + }, + "input": "diff --git a/src/main.rs b/src/main.rs\n--- a/src/main.rs\n+++ b/src/main.rs\n@@ -10,7 +10,7 @@\n unchanged_line_0\n unchanged_line_1\n unchanged_line_2\n unchanged_line_3\n unchanged_line_4\n- let x = 1;\n+ let x = 2;\n unchanged_after_0\n unchanged_after_1\n unchanged_after_2\n unchanged_after_3\n unchanged_after_4\n# variant 3", + "input_sha256": "b605bd89e234cbff8b72d508c791f9ba0d263e929c3f0ad1263431486a8080f0", + "output": { + "additions": 0, + "cache_key": null, + "compressed": "diff --git a/src/main.rs b/src/main.rs\n--- a/src/main.rs\n+++ b/src/main.rs\n@@ -10,7 +10,7 @@\n unchanged_line_0\n unchanged_line_1\n unchanged_line_2\n unchanged_line_3\n unchanged_line_4\n- let x = 1;\n+ let x = 2;\n unchanged_after_0\n unchanged_after_1\n unchanged_after_2\n unchanged_after_3\n unchanged_after_4\n# variant 3", + "compressed_line_count": 17, + "deletions": 0, + "files_affected": 0, + "hunks_kept": 0, + "hunks_removed": 0, + "original_line_count": 17 + }, + "recorded_at": "2026-04-23T23:51:37.349729+00:00", + "transform": "diff_compressor" +} diff --git a/tests/parity/fixtures/diff_compressor/c6b6d5c9fd7f1ad2.json b/tests/parity/fixtures/diff_compressor/c6b6d5c9fd7f1ad2.json new file mode 100644 index 0000000000000000000000000000000000000000..224a5575cf4eb21d0cb1bbfae1abc139abed623d --- /dev/null +++ b/tests/parity/fixtures/diff_compressor/c6b6d5c9fd7f1ad2.json @@ -0,0 +1,26 @@ +{ + "config": { + "always_keep_additions": true, + "always_keep_deletions": true, + "enable_ccr": true, + "max_context_lines": 2, + "max_files": 20, + "max_hunks_per_file": 10, + "min_lines_for_ccr": 50 + }, + "input": "diff --git a/src/main.rs b/src/main.rs\n--- a/src/main.rs\n+++ b/src/main.rs\n@@ -10,7 +10,7 @@\n unchanged_line_0\n unchanged_line_1\n unchanged_line_2\n unchanged_line_3\n unchanged_line_4\n- let x = 1;\n+ let x = 2;\n unchanged_after_0\n unchanged_after_1\n unchanged_after_2\n unchanged_after_3\n unchanged_after_4\n# variant 4", + "input_sha256": "c6b6d5c9fd7f1ad260a76dc351d49219e2bbc94807e9a7f22f3fd0dabf2a45da", + "output": { + "additions": 0, + "cache_key": null, + "compressed": "diff --git a/src/main.rs b/src/main.rs\n--- a/src/main.rs\n+++ b/src/main.rs\n@@ -10,7 +10,7 @@\n unchanged_line_0\n unchanged_line_1\n unchanged_line_2\n unchanged_line_3\n unchanged_line_4\n- let x = 1;\n+ let x = 2;\n unchanged_after_0\n unchanged_after_1\n unchanged_after_2\n unchanged_after_3\n unchanged_after_4\n# variant 4", + "compressed_line_count": 17, + "deletions": 0, + "files_affected": 0, + "hunks_kept": 0, + "hunks_removed": 0, + "original_line_count": 17 + }, + "recorded_at": "2026-04-23T23:51:37.349941+00:00", + "transform": "diff_compressor" +} diff --git a/tests/parity/fixtures/diff_compressor/c72662c635d1defa.json b/tests/parity/fixtures/diff_compressor/c72662c635d1defa.json new file mode 100644 index 0000000000000000000000000000000000000000..dd16d9c231a8a240f0086849a2daf801324ef127 --- /dev/null +++ b/tests/parity/fixtures/diff_compressor/c72662c635d1defa.json @@ -0,0 +1,26 @@ +{ + "config": { + "always_keep_additions": true, + "always_keep_deletions": true, + "enable_ccr": true, + "max_context_lines": 2, + "max_files": 20, + "max_hunks_per_file": 10, + "min_lines_for_ccr": 50 + }, + "input": "diff --git a/src/main.rs b/src/main.rs\n--- a/src/main.rs\n+++ b/src/main.rs\n@@ -10,7 +10,7 @@\n unchanged_line_0\n unchanged_line_1\n unchanged_line_2\n unchanged_line_3\n unchanged_line_4\n- let x = 1;\n+ let x = 2;\n unchanged_after_0\n unchanged_after_1\n unchanged_after_2\n unchanged_after_3\n unchanged_after_4\n# variant 1", + "input_sha256": "c72662c635d1defae45f9cdd261f3ddd8fe62c6a5feee19470609bb84b2d561d", + "output": { + "additions": 0, + "cache_key": null, + "compressed": "diff --git a/src/main.rs b/src/main.rs\n--- a/src/main.rs\n+++ b/src/main.rs\n@@ -10,7 +10,7 @@\n unchanged_line_0\n unchanged_line_1\n unchanged_line_2\n unchanged_line_3\n unchanged_line_4\n- let x = 1;\n+ let x = 2;\n unchanged_after_0\n unchanged_after_1\n unchanged_after_2\n unchanged_after_3\n unchanged_after_4\n# variant 1", + "compressed_line_count": 17, + "deletions": 0, + "files_affected": 0, + "hunks_kept": 0, + "hunks_removed": 0, + "original_line_count": 17 + }, + "recorded_at": "2026-04-23T23:51:37.349404+00:00", + "transform": "diff_compressor" +} diff --git a/tests/parity/fixtures/diff_compressor/d0ba5cae330722be.json b/tests/parity/fixtures/diff_compressor/d0ba5cae330722be.json new file mode 100644 index 0000000000000000000000000000000000000000..fae8cbbb00b6f9b653aed6f944f2a3249e00b786 --- /dev/null +++ b/tests/parity/fixtures/diff_compressor/d0ba5cae330722be.json @@ -0,0 +1,26 @@ +{ + "config": { + "always_keep_additions": true, + "always_keep_deletions": true, + "enable_ccr": true, + "max_context_lines": 2, + "max_files": 20, + "max_hunks_per_file": 10, + "min_lines_for_ccr": 50 + }, + "input": "diff --git a/a.py b/a.py\n--- a/a.py\n+++ b/a.py\n@@ -1,3 +1,3 @@\n-x = 1\n+x = 2\n y = 3\n z = 4\n# variant 5", + "input_sha256": "d0ba5cae330722be827e591da5644c445cf646db7b4c76a51473cda1165140ba", + "output": { + "additions": 0, + "cache_key": null, + "compressed": "diff --git a/a.py b/a.py\n--- a/a.py\n+++ b/a.py\n@@ -1,3 +1,3 @@\n-x = 1\n+x = 2\n y = 3\n z = 4\n# variant 5", + "compressed_line_count": 9, + "deletions": 0, + "files_affected": 0, + "hunks_kept": 0, + "hunks_removed": 0, + "original_line_count": 9 + }, + "recorded_at": "2026-04-23T23:51:37.348881+00:00", + "transform": "diff_compressor" +} diff --git a/tests/parity/fixtures/diff_compressor/d0e62d2f1493bd72.json b/tests/parity/fixtures/diff_compressor/d0e62d2f1493bd72.json new file mode 100644 index 0000000000000000000000000000000000000000..e2f0ea98c35204df1d8806ec709866dfe3b52533 --- /dev/null +++ b/tests/parity/fixtures/diff_compressor/d0e62d2f1493bd72.json @@ -0,0 +1,26 @@ +{ + "config": { + "always_keep_additions": true, + "always_keep_deletions": true, + "enable_ccr": true, + "max_context_lines": 2, + "max_files": 20, + "max_hunks_per_file": 10, + "min_lines_for_ccr": 50 + }, + "input": "diff --git a/a.py b/a.py\n--- a/a.py\n+++ b/a.py\n@@ -1,3 +1,3 @@\n-x = 1\n+x = 2\n y = 3\n z = 4\n# variant 1", + "input_sha256": "d0e62d2f1493bd7273d2837a335a50507835f0aa4cf2b52aa6be131dfbe24001", + "output": { + "additions": 0, + "cache_key": null, + "compressed": "diff --git a/a.py b/a.py\n--- a/a.py\n+++ b/a.py\n@@ -1,3 +1,3 @@\n-x = 1\n+x = 2\n y = 3\n z = 4\n# variant 1", + "compressed_line_count": 9, + "deletions": 0, + "files_affected": 0, + "hunks_kept": 0, + "hunks_removed": 0, + "original_line_count": 9 + }, + "recorded_at": "2026-04-23T23:51:37.347801+00:00", + "transform": "diff_compressor" +} diff --git a/tests/parity/fixtures/diff_compressor/d4ed44a6fc667358.json b/tests/parity/fixtures/diff_compressor/d4ed44a6fc667358.json new file mode 100644 index 0000000000000000000000000000000000000000..c0cd754d5a3e3b6395e48a95ec67d1afebfa25ba --- /dev/null +++ b/tests/parity/fixtures/diff_compressor/d4ed44a6fc667358.json @@ -0,0 +1,26 @@ +{ + "config": { + "always_keep_additions": true, + "always_keep_deletions": true, + "enable_ccr": true, + "max_context_lines": 2, + "max_files": 20, + "max_hunks_per_file": 10, + "min_lines_for_ccr": 50 + }, + "input": "diff --git a/file_0.py b/file_0.py\n--- a/file_0.py\n+++ b/file_0.py\n@@ -1,10 +1,12 @@\n context_0_0\n context_1_0\n context_2_0\n context_3_0\n context_4_0\n-removed_0_0\n-removed_1_0\n-removed_2_0\n+added_0_0\n+added_1_0\n+added_2_0\n+added_3_0\n+added_4_0\n tail_0_0\n tail_1_0\n tail_2_0\n tail_3_0\n tail_4_0\ndiff --git a/file_1.py b/file_1.py\n--- a/file_1.py\n+++ b/file_1.py\n@@ -1,10 +1,12 @@\n context_0_1\n context_1_1\n context_2_1\n context_3_1\n context_4_1\n-removed_0_1\n-removed_1_1\n-removed_2_1\n+added_0_1\n+added_1_1\n+added_2_1\n+added_3_1\n+added_4_1\n tail_0_1\n tail_1_1\n tail_2_1\n tail_3_1\n tail_4_1\ndiff --git a/file_2.py b/file_2.py\n--- a/file_2.py\n+++ b/file_2.py\n@@ -1,10 +1,12 @@\n context_0_2\n context_1_2\n context_2_2\n context_3_2\n context_4_2\n-removed_0_2\n-removed_1_2\n-removed_2_2\n+added_0_2\n+added_1_2\n+added_2_2\n+added_3_2\n+added_4_2\n tail_0_2\n tail_1_2\n tail_2_2\n tail_3_2\n tail_4_2\ndiff --git a/file_3.py b/file_3.py\n--- a/file_3.py\n+++ b/file_3.py\n@@ -1,10 +1,12 @@\n context_0_3\n context_1_3\n context_2_3\n context_3_3\n context_4_3\n-removed_0_3\n-removed_1_3\n-removed_2_3\n+added_0_3\n+added_1_3\n+added_2_3\n+added_3_3\n+added_4_3\n tail_0_3\n tail_1_3\n tail_2_3\n tail_3_3\n tail_4_3\ndiff --git a/file_4.py b/file_4.py\n--- a/file_4.py\n+++ b/file_4.py\n@@ -1,10 +1,12 @@\n context_0_4\n context_1_4\n context_2_4\n context_3_4\n context_4_4\n-removed_0_4\n-removed_1_4\n-removed_2_4\n+added_0_4\n+added_1_4\n+added_2_4\n+added_3_4\n+added_4_4\n tail_0_4\n tail_1_4\n tail_2_4\n tail_3_4\n tail_4_4\ndiff --git a/file_5.py b/file_5.py\n--- a/file_5.py\n+++ b/file_5.py\n@@ -1,10 +1,12 @@\n context_0_5\n context_1_5\n context_2_5\n context_3_5\n context_4_5\n-removed_0_5\n-removed_1_5\n-removed_2_5\n+added_0_5\n+added_1_5\n+added_2_5\n+added_3_5\n+added_4_5\n tail_0_5\n tail_1_5\n tail_2_5\n tail_3_5\n tail_4_5\ndiff --git a/file_6.py b/file_6.py\n--- a/file_6.py\n+++ b/file_6.py\n@@ -1,10 +1,12 @@\n context_0_6\n context_1_6\n context_2_6\n context_3_6\n context_4_6\n-removed_0_6\n-removed_1_6\n-removed_2_6\n+added_0_6\n+added_1_6\n+added_2_6\n+added_3_6\n+added_4_6\n tail_0_6\n tail_1_6\n tail_2_6\n tail_3_6\n tail_4_6\ndiff --git a/file_7.py b/file_7.py\n--- a/file_7.py\n+++ b/file_7.py\n@@ -1,10 +1,12 @@\n context_0_7\n context_1_7\n context_2_7\n context_3_7\n context_4_7\n-removed_0_7\n-removed_1_7\n-removed_2_7\n+added_0_7\n+added_1_7\n+added_2_7\n+added_3_7\n+added_4_7\n tail_0_7\n tail_1_7\n tail_2_7\n tail_3_7\n tail_4_7\n# variant 3", + "input_sha256": "d4ed44a6fc66735876898acb5e3b2c54f97e72033bb71eda006acdbbfb8a2e96", + "output": { + "additions": 40, + "cache_key": "706080f4af877cb632f87e1d", + "compressed": "diff --git a/file_0.py b/file_0.py\n--- a/file_0.py\n+++ b/file_0.py\n@@ -1,10 +1,12 @@\n context_3_0\n context_4_0\n-removed_0_0\n-removed_1_0\n-removed_2_0\n+added_0_0\n+added_1_0\n+added_2_0\n+added_3_0\n+added_4_0\n tail_0_0\n tail_1_0\ndiff --git a/file_1.py b/file_1.py\n--- a/file_1.py\n+++ b/file_1.py\n@@ -1,10 +1,12 @@\n context_3_1\n context_4_1\n-removed_0_1\n-removed_1_1\n-removed_2_1\n+added_0_1\n+added_1_1\n+added_2_1\n+added_3_1\n+added_4_1\n tail_0_1\n tail_1_1\ndiff --git a/file_2.py b/file_2.py\n--- a/file_2.py\n+++ b/file_2.py\n@@ -1,10 +1,12 @@\n context_3_2\n context_4_2\n-removed_0_2\n-removed_1_2\n-removed_2_2\n+added_0_2\n+added_1_2\n+added_2_2\n+added_3_2\n+added_4_2\n tail_0_2\n tail_1_2\ndiff --git a/file_3.py b/file_3.py\n--- a/file_3.py\n+++ b/file_3.py\n@@ -1,10 +1,12 @@\n context_3_3\n context_4_3\n-removed_0_3\n-removed_1_3\n-removed_2_3\n+added_0_3\n+added_1_3\n+added_2_3\n+added_3_3\n+added_4_3\n tail_0_3\n tail_1_3\ndiff --git a/file_4.py b/file_4.py\n--- a/file_4.py\n+++ b/file_4.py\n@@ -1,10 +1,12 @@\n context_3_4\n context_4_4\n-removed_0_4\n-removed_1_4\n-removed_2_4\n+added_0_4\n+added_1_4\n+added_2_4\n+added_3_4\n+added_4_4\n tail_0_4\n tail_1_4\ndiff --git a/file_5.py b/file_5.py\n--- a/file_5.py\n+++ b/file_5.py\n@@ -1,10 +1,12 @@\n context_3_5\n context_4_5\n-removed_0_5\n-removed_1_5\n-removed_2_5\n+added_0_5\n+added_1_5\n+added_2_5\n+added_3_5\n+added_4_5\n tail_0_5\n tail_1_5\ndiff --git a/file_6.py b/file_6.py\n--- a/file_6.py\n+++ b/file_6.py\n@@ -1,10 +1,12 @@\n context_3_6\n context_4_6\n-removed_0_6\n-removed_1_6\n-removed_2_6\n+added_0_6\n+added_1_6\n+added_2_6\n+added_3_6\n+added_4_6\n tail_0_6\n tail_1_6\ndiff --git a/file_7.py b/file_7.py\n--- a/file_7.py\n+++ b/file_7.py\n@@ -1,10 +1,12 @@\n context_3_7\n context_4_7\n-removed_0_7\n-removed_1_7\n-removed_2_7\n+added_0_7\n+added_1_7\n+added_2_7\n+added_3_7\n+added_4_7\n tail_0_7\n tail_1_7\n[8 files changed, +40 -24 lines]\n[177 lines compressed to 129. Retrieve full diff: hash=706080f4af877cb632f87e1d]", + "compressed_line_count": 129, + "deletions": 24, + "files_affected": 8, + "hunks_kept": 8, + "hunks_removed": 0, + "original_line_count": 177 + }, + "recorded_at": "2026-04-23T23:51:37.352883+00:00", + "transform": "diff_compressor" +} diff --git a/tests/parity/fixtures/diff_compressor/e9c505e42b2b2edc.json b/tests/parity/fixtures/diff_compressor/e9c505e42b2b2edc.json new file mode 100644 index 0000000000000000000000000000000000000000..5378ee7846525b99782ca6a2e4af27489cecc2f2 --- /dev/null +++ b/tests/parity/fixtures/diff_compressor/e9c505e42b2b2edc.json @@ -0,0 +1,26 @@ +{ + "config": { + "always_keep_additions": true, + "always_keep_deletions": true, + "enable_ccr": true, + "max_context_lines": 2, + "max_files": 20, + "max_hunks_per_file": 10, + "min_lines_for_ccr": 50 + }, + "input": "diff --git a/a.py b/a.py\n--- a/a.py\n+++ b/a.py\n@@ -1,3 +1,3 @@\n-x = 1\n+x = 2\n y = 3\n z = 4\n# variant 3", + "input_sha256": "e9c505e42b2b2edccb70c13aaee3c5d22cd07ac26449846db49d8aafb8ea538b", + "output": { + "additions": 0, + "cache_key": null, + "compressed": "diff --git a/a.py b/a.py\n--- a/a.py\n+++ b/a.py\n@@ -1,3 +1,3 @@\n-x = 1\n+x = 2\n y = 3\n z = 4\n# variant 3", + "compressed_line_count": 9, + "deletions": 0, + "files_affected": 0, + "hunks_kept": 0, + "hunks_removed": 0, + "original_line_count": 9 + }, + "recorded_at": "2026-04-23T23:51:37.348162+00:00", + "transform": "diff_compressor" +} diff --git a/tests/parity/fixtures/diff_compressor/ea99ab1ea7449acf.json b/tests/parity/fixtures/diff_compressor/ea99ab1ea7449acf.json new file mode 100644 index 0000000000000000000000000000000000000000..a16dcee733e71d0fd48fd7393ee9be42d8e83deb --- /dev/null +++ b/tests/parity/fixtures/diff_compressor/ea99ab1ea7449acf.json @@ -0,0 +1,26 @@ +{ + "config": { + "always_keep_additions": true, + "always_keep_deletions": true, + "enable_ccr": true, + "max_context_lines": 2, + "max_files": 20, + "max_hunks_per_file": 10, + "min_lines_for_ccr": 50 + }, + "input": "diff --git a/file_0.py b/file_0.py\n--- a/file_0.py\n+++ b/file_0.py\n@@ -1,10 +1,12 @@\n context_0_0\n context_1_0\n context_2_0\n context_3_0\n context_4_0\n-removed_0_0\n-removed_1_0\n-removed_2_0\n+added_0_0\n+added_1_0\n+added_2_0\n+added_3_0\n+added_4_0\n tail_0_0\n tail_1_0\n tail_2_0\n tail_3_0\n tail_4_0\ndiff --git a/file_1.py b/file_1.py\n--- a/file_1.py\n+++ b/file_1.py\n@@ -1,10 +1,12 @@\n context_0_1\n context_1_1\n context_2_1\n context_3_1\n context_4_1\n-removed_0_1\n-removed_1_1\n-removed_2_1\n+added_0_1\n+added_1_1\n+added_2_1\n+added_3_1\n+added_4_1\n tail_0_1\n tail_1_1\n tail_2_1\n tail_3_1\n tail_4_1\ndiff --git a/file_2.py b/file_2.py\n--- a/file_2.py\n+++ b/file_2.py\n@@ -1,10 +1,12 @@\n context_0_2\n context_1_2\n context_2_2\n context_3_2\n context_4_2\n-removed_0_2\n-removed_1_2\n-removed_2_2\n+added_0_2\n+added_1_2\n+added_2_2\n+added_3_2\n+added_4_2\n tail_0_2\n tail_1_2\n tail_2_2\n tail_3_2\n tail_4_2\ndiff --git a/file_3.py b/file_3.py\n--- a/file_3.py\n+++ b/file_3.py\n@@ -1,10 +1,12 @@\n context_0_3\n context_1_3\n context_2_3\n context_3_3\n context_4_3\n-removed_0_3\n-removed_1_3\n-removed_2_3\n+added_0_3\n+added_1_3\n+added_2_3\n+added_3_3\n+added_4_3\n tail_0_3\n tail_1_3\n tail_2_3\n tail_3_3\n tail_4_3\ndiff --git a/file_4.py b/file_4.py\n--- a/file_4.py\n+++ b/file_4.py\n@@ -1,10 +1,12 @@\n context_0_4\n context_1_4\n context_2_4\n context_3_4\n context_4_4\n-removed_0_4\n-removed_1_4\n-removed_2_4\n+added_0_4\n+added_1_4\n+added_2_4\n+added_3_4\n+added_4_4\n tail_0_4\n tail_1_4\n tail_2_4\n tail_3_4\n tail_4_4\ndiff --git a/file_5.py b/file_5.py\n--- a/file_5.py\n+++ b/file_5.py\n@@ -1,10 +1,12 @@\n context_0_5\n context_1_5\n context_2_5\n context_3_5\n context_4_5\n-removed_0_5\n-removed_1_5\n-removed_2_5\n+added_0_5\n+added_1_5\n+added_2_5\n+added_3_5\n+added_4_5\n tail_0_5\n tail_1_5\n tail_2_5\n tail_3_5\n tail_4_5\ndiff --git a/file_6.py b/file_6.py\n--- a/file_6.py\n+++ b/file_6.py\n@@ -1,10 +1,12 @@\n context_0_6\n context_1_6\n context_2_6\n context_3_6\n context_4_6\n-removed_0_6\n-removed_1_6\n-removed_2_6\n+added_0_6\n+added_1_6\n+added_2_6\n+added_3_6\n+added_4_6\n tail_0_6\n tail_1_6\n tail_2_6\n tail_3_6\n tail_4_6\ndiff --git a/file_7.py b/file_7.py\n--- a/file_7.py\n+++ b/file_7.py\n@@ -1,10 +1,12 @@\n context_0_7\n context_1_7\n context_2_7\n context_3_7\n context_4_7\n-removed_0_7\n-removed_1_7\n-removed_2_7\n+added_0_7\n+added_1_7\n+added_2_7\n+added_3_7\n+added_4_7\n tail_0_7\n tail_1_7\n tail_2_7\n tail_3_7\n tail_4_7\n# variant 0", + "input_sha256": "ea99ab1ea7449acf7762b3127df1287e352717d8036a6f1b818c7cb1b2edb527", + "output": { + "additions": 40, + "cache_key": "416e7f5d3ae0f718cc2609eb", + "compressed": "diff --git a/file_0.py b/file_0.py\n--- a/file_0.py\n+++ b/file_0.py\n@@ -1,10 +1,12 @@\n context_3_0\n context_4_0\n-removed_0_0\n-removed_1_0\n-removed_2_0\n+added_0_0\n+added_1_0\n+added_2_0\n+added_3_0\n+added_4_0\n tail_0_0\n tail_1_0\ndiff --git a/file_1.py b/file_1.py\n--- a/file_1.py\n+++ b/file_1.py\n@@ -1,10 +1,12 @@\n context_3_1\n context_4_1\n-removed_0_1\n-removed_1_1\n-removed_2_1\n+added_0_1\n+added_1_1\n+added_2_1\n+added_3_1\n+added_4_1\n tail_0_1\n tail_1_1\ndiff --git a/file_2.py b/file_2.py\n--- a/file_2.py\n+++ b/file_2.py\n@@ -1,10 +1,12 @@\n context_3_2\n context_4_2\n-removed_0_2\n-removed_1_2\n-removed_2_2\n+added_0_2\n+added_1_2\n+added_2_2\n+added_3_2\n+added_4_2\n tail_0_2\n tail_1_2\ndiff --git a/file_3.py b/file_3.py\n--- a/file_3.py\n+++ b/file_3.py\n@@ -1,10 +1,12 @@\n context_3_3\n context_4_3\n-removed_0_3\n-removed_1_3\n-removed_2_3\n+added_0_3\n+added_1_3\n+added_2_3\n+added_3_3\n+added_4_3\n tail_0_3\n tail_1_3\ndiff --git a/file_4.py b/file_4.py\n--- a/file_4.py\n+++ b/file_4.py\n@@ -1,10 +1,12 @@\n context_3_4\n context_4_4\n-removed_0_4\n-removed_1_4\n-removed_2_4\n+added_0_4\n+added_1_4\n+added_2_4\n+added_3_4\n+added_4_4\n tail_0_4\n tail_1_4\ndiff --git a/file_5.py b/file_5.py\n--- a/file_5.py\n+++ b/file_5.py\n@@ -1,10 +1,12 @@\n context_3_5\n context_4_5\n-removed_0_5\n-removed_1_5\n-removed_2_5\n+added_0_5\n+added_1_5\n+added_2_5\n+added_3_5\n+added_4_5\n tail_0_5\n tail_1_5\ndiff --git a/file_6.py b/file_6.py\n--- a/file_6.py\n+++ b/file_6.py\n@@ -1,10 +1,12 @@\n context_3_6\n context_4_6\n-removed_0_6\n-removed_1_6\n-removed_2_6\n+added_0_6\n+added_1_6\n+added_2_6\n+added_3_6\n+added_4_6\n tail_0_6\n tail_1_6\ndiff --git a/file_7.py b/file_7.py\n--- a/file_7.py\n+++ b/file_7.py\n@@ -1,10 +1,12 @@\n context_3_7\n context_4_7\n-removed_0_7\n-removed_1_7\n-removed_2_7\n+added_0_7\n+added_1_7\n+added_2_7\n+added_3_7\n+added_4_7\n tail_0_7\n tail_1_7\n[8 files changed, +40 -24 lines]\n[177 lines compressed to 129. Retrieve full diff: hash=416e7f5d3ae0f718cc2609eb]", + "compressed_line_count": 129, + "deletions": 24, + "files_affected": 8, + "hunks_kept": 8, + "hunks_removed": 0, + "original_line_count": 177 + }, + "recorded_at": "2026-04-23T23:51:37.350758+00:00", + "transform": "diff_compressor" +} diff --git a/tests/parity/fixtures/log_compressor/012c7bbeb96d95e9.json b/tests/parity/fixtures/log_compressor/012c7bbeb96d95e9.json new file mode 100644 index 0000000000000000000000000000000000000000..fad905d7ae0b8445370becec72f94b5b6e711e9a --- /dev/null +++ b/tests/parity/fixtures/log_compressor/012c7bbeb96d95e9.json @@ -0,0 +1,30 @@ +{ + "config": { + "dedupe_warnings": true, + "enable_ccr": true, + "error_context_lines": 3, + "keep_first_error": true, + "keep_last_error": true, + "keep_summary_lines": true, + "max_errors": 10, + "max_stack_traces": 3, + "max_total_lines": 100, + "max_warnings": 5, + "min_lines_for_ccr": 50, + "stack_trace_max_lines": 20 + }, + "input": "PASSED test_foo\nPASSED test_bar\nFAILED test_baz\nassert 1 == 2\n# variant 1", + "input_sha256": "012c7bbeb96d95e99a7f94c87e528f7141cde31e3ad846bb5782c3435ea964f1", + "output": { + "cache_key": null, + "compressed": "PASSED test_foo\nPASSED test_bar\nFAILED test_baz\nassert 1 == 2\n# variant 1", + "compressed_line_count": 5, + "compression_ratio": 1.0, + "format_detected": "generic", + "original": "PASSED test_foo\nPASSED test_bar\nFAILED test_baz\nassert 1 == 2\n# variant 1", + "original_line_count": 5, + "stats": {} + }, + "recorded_at": "2026-04-23T23:51:37.252755+00:00", + "transform": "log_compressor" +} diff --git a/tests/parity/fixtures/log_compressor/1fbaf88e12686704.json b/tests/parity/fixtures/log_compressor/1fbaf88e12686704.json new file mode 100644 index 0000000000000000000000000000000000000000..66b934207987951debd3bb51d95696db9bbb23a4 --- /dev/null +++ b/tests/parity/fixtures/log_compressor/1fbaf88e12686704.json @@ -0,0 +1,30 @@ +{ + "config": { + "dedupe_warnings": true, + "enable_ccr": true, + "error_context_lines": 3, + "keep_first_error": true, + "keep_last_error": true, + "keep_summary_lines": true, + "max_errors": 10, + "max_stack_traces": 3, + "max_total_lines": 100, + "max_warnings": 5, + "min_lines_for_ccr": 50, + "stack_trace_max_lines": 20 + }, + "input": "npm WARN deprecated foo@1.0.0: use bar\nadded 0 packages in 3s\nadded 1 packages in 3s\nadded 2 packages in 3s\nadded 3 packages in 3s\nadded 4 packages in 3s\nnpm ERR! code ERESOLVE\nnpm ERR! ERESOLVE unable to resolve dependency tree\nnpm ERR! While resolving: project@1.0.0\nnpm ERR! Found: react@17.0.2\n# variant 7", + "input_sha256": "1fbaf88e12686704fed85212e2cdd60d8da4a50265a9ecbc1b8f48e684ac9c9e", + "output": { + "cache_key": null, + "compressed": "npm WARN deprecated foo@1.0.0: use bar\nadded 0 packages in 3s\nadded 1 packages in 3s\nadded 2 packages in 3s\nadded 3 packages in 3s\nadded 4 packages in 3s\nnpm ERR! code ERESOLVE\nnpm ERR! ERESOLVE unable to resolve dependency tree\nnpm ERR! While resolving: project@1.0.0\nnpm ERR! Found: react@17.0.2\n# variant 7", + "compressed_line_count": 11, + "compression_ratio": 1.0, + "format_detected": "generic", + "original": "npm WARN deprecated foo@1.0.0: use bar\nadded 0 packages in 3s\nadded 1 packages in 3s\nadded 2 packages in 3s\nadded 3 packages in 3s\nadded 4 packages in 3s\nnpm ERR! code ERESOLVE\nnpm ERR! ERESOLVE unable to resolve dependency tree\nnpm ERR! While resolving: project@1.0.0\nnpm ERR! Found: react@17.0.2\n# variant 7", + "original_line_count": 11, + "stats": {} + }, + "recorded_at": "2026-04-23T23:51:37.255799+00:00", + "transform": "log_compressor" +} diff --git a/tests/parity/fixtures/log_compressor/23112ae8ae41ba59.json b/tests/parity/fixtures/log_compressor/23112ae8ae41ba59.json new file mode 100644 index 0000000000000000000000000000000000000000..b0e6e9e214170848b81592993cad02275d4a35f3 --- /dev/null +++ b/tests/parity/fixtures/log_compressor/23112ae8ae41ba59.json @@ -0,0 +1,30 @@ +{ + "config": { + "dedupe_warnings": true, + "enable_ccr": true, + "error_context_lines": 3, + "keep_first_error": true, + "keep_last_error": true, + "keep_summary_lines": true, + "max_errors": 10, + "max_stack_traces": 3, + "max_total_lines": 100, + "max_warnings": 5, + "min_lines_for_ccr": 50, + "stack_trace_max_lines": 20 + }, + "input": " Compiling crate_0 v0.1.0\n Compiling crate_1 v0.1.1\n Compiling crate_2 v0.1.2\n Compiling crate_3 v0.1.3\n Compiling crate_4 v0.1.4\n Compiling crate_5 v0.1.5\n Compiling crate_6 v0.1.6\n Compiling crate_7 v0.1.7\n Compiling crate_8 v0.1.8\n Compiling crate_9 v0.1.9\nerror[E0308]: mismatched types\n --> src/lib.rs:42:9\n |\n42 | return x;\n | ^^^^^^^^^ expected `i32`, found `u64`\nerror: aborting due to previous error\n# variant 8", + "input_sha256": "23112ae8ae41ba59238aaa673c4f26987898e1f99c772683128f1fb2f1396089", + "output": { + "cache_key": null, + "compressed": " Compiling crate_0 v0.1.0\n Compiling crate_1 v0.1.1\n Compiling crate_2 v0.1.2\n Compiling crate_3 v0.1.3\n Compiling crate_4 v0.1.4\n Compiling crate_5 v0.1.5\n Compiling crate_6 v0.1.6\n Compiling crate_7 v0.1.7\n Compiling crate_8 v0.1.8\n Compiling crate_9 v0.1.9\nerror[E0308]: mismatched types\n --> src/lib.rs:42:9\n |\n42 | return x;\n | ^^^^^^^^^ expected `i32`, found `u64`\nerror: aborting due to previous error\n# variant 8", + "compressed_line_count": 17, + "compression_ratio": 1.0, + "format_detected": "generic", + "original": " Compiling crate_0 v0.1.0\n Compiling crate_1 v0.1.1\n Compiling crate_2 v0.1.2\n Compiling crate_3 v0.1.3\n Compiling crate_4 v0.1.4\n Compiling crate_5 v0.1.5\n Compiling crate_6 v0.1.6\n Compiling crate_7 v0.1.7\n Compiling crate_8 v0.1.8\n Compiling crate_9 v0.1.9\nerror[E0308]: mismatched types\n --> src/lib.rs:42:9\n |\n42 | return x;\n | ^^^^^^^^^ expected `i32`, found `u64`\nerror: aborting due to previous error\n# variant 8", + "original_line_count": 17, + "stats": {} + }, + "recorded_at": "2026-04-23T23:51:37.256267+00:00", + "transform": "log_compressor" +} diff --git a/tests/parity/fixtures/log_compressor/305987ea77c385e6.json b/tests/parity/fixtures/log_compressor/305987ea77c385e6.json new file mode 100644 index 0000000000000000000000000000000000000000..5b0cf29d761ea2ef6cba29b6bbb90e7a97456844 --- /dev/null +++ b/tests/parity/fixtures/log_compressor/305987ea77c385e6.json @@ -0,0 +1,30 @@ +{ + "config": { + "dedupe_warnings": true, + "enable_ccr": true, + "error_context_lines": 3, + "keep_first_error": true, + "keep_last_error": true, + "keep_summary_lines": true, + "max_errors": 10, + "max_stack_traces": 3, + "max_total_lines": 100, + "max_warnings": 5, + "min_lines_for_ccr": 50, + "stack_trace_max_lines": 20 + }, + "input": "INFO iteration 1\nERROR error 1\nWARN warn 1\nINFO done 1\n# variant 15", + "input_sha256": "305987ea77c385e6d7118462dbb2c64e28056e67cf4d3068fd4f857af59073c9", + "output": { + "cache_key": null, + "compressed": "INFO iteration 1\nERROR error 1\nWARN warn 1\nINFO done 1\n# variant 15", + "compressed_line_count": 5, + "compression_ratio": 1.0, + "format_detected": "generic", + "original": "INFO iteration 1\nERROR error 1\nWARN warn 1\nINFO done 1\n# variant 15", + "original_line_count": 5, + "stats": {} + }, + "recorded_at": "2026-04-23T23:51:37.345690+00:00", + "transform": "log_compressor" +} diff --git a/tests/parity/fixtures/log_compressor/349121068ecbb26e.json b/tests/parity/fixtures/log_compressor/349121068ecbb26e.json new file mode 100644 index 0000000000000000000000000000000000000000..7a1af5cbc41978698be6d9066a7a91de224f2a16 --- /dev/null +++ b/tests/parity/fixtures/log_compressor/349121068ecbb26e.json @@ -0,0 +1,30 @@ +{ + "config": { + "dedupe_warnings": true, + "enable_ccr": true, + "error_context_lines": 3, + "keep_first_error": true, + "keep_last_error": true, + "keep_summary_lines": true, + "max_errors": 10, + "max_stack_traces": 3, + "max_total_lines": 100, + "max_warnings": 5, + "min_lines_for_ccr": 50, + "stack_trace_max_lines": 20 + }, + "input": "INFO iteration 2\nERROR error 2\nWARN warn 2\nINFO done 2\n# variant 16", + "input_sha256": "349121068ecbb26ee92c4cd4b7ce11970b9bc5fec1f17c93857106d21ae180da", + "output": { + "cache_key": null, + "compressed": "INFO iteration 2\nERROR error 2\nWARN warn 2\nINFO done 2\n# variant 16", + "compressed_line_count": 5, + "compression_ratio": 1.0, + "format_detected": "generic", + "original": "INFO iteration 2\nERROR error 2\nWARN warn 2\nINFO done 2\n# variant 16", + "original_line_count": 5, + "stats": {} + }, + "recorded_at": "2026-04-23T23:51:37.346245+00:00", + "transform": "log_compressor" +} diff --git a/tests/parity/fixtures/log_compressor/3bc015edc0a36387.json b/tests/parity/fixtures/log_compressor/3bc015edc0a36387.json new file mode 100644 index 0000000000000000000000000000000000000000..37d506b09cc60596e69396b979c603b500c93834 --- /dev/null +++ b/tests/parity/fixtures/log_compressor/3bc015edc0a36387.json @@ -0,0 +1,37 @@ +{ + "config": { + "dedupe_warnings": true, + "enable_ccr": true, + "error_context_lines": 3, + "keep_first_error": true, + "keep_last_error": true, + "keep_summary_lines": true, + "max_errors": 10, + "max_stack_traces": 3, + "max_total_lines": 100, + "max_warnings": 5, + "min_lines_for_ccr": 50, + "stack_trace_max_lines": 20 + }, + "input": "line 0: INFO processing request\nline 1: INFO processing request\nline 2: INFO processing request\nline 3: INFO processing request\nline 4: INFO processing request\nline 5: INFO processing request\nline 6: INFO processing request\nline 7: INFO processing request\nline 8: INFO processing request\nline 9: INFO processing request\nline 10: INFO processing request\nline 11: INFO processing request\nline 12: INFO processing request\nline 13: INFO processing request\nline 14: INFO processing request\nline 15: INFO processing request\nline 16: INFO processing request\nline 17: INFO processing request\nline 18: INFO processing request\nline 19: INFO processing request\nline 20: INFO processing request\nline 21: INFO processing request\nline 22: INFO processing request\nline 23: INFO processing request\nline 24: INFO processing request\nline 25: INFO processing request\nline 26: INFO processing request\nline 27: INFO processing request\nline 28: INFO processing request\nline 29: INFO processing request\nline 30: INFO processing request\nline 31: INFO processing request\nline 32: INFO processing request\nline 33: INFO processing request\nline 34: INFO processing request\nline 35: INFO processing request\nline 36: INFO processing request\nline 37: INFO processing request\nline 38: INFO processing request\nline 39: INFO processing request\nline 40: INFO processing request\nline 41: INFO processing request\nline 42: INFO processing request\nline 43: INFO processing request\nline 44: INFO processing request\nline 45: INFO processing request\nline 46: INFO processing request\nline 47: INFO processing request\nline 48: INFO processing request\nline 49: INFO processing request\nline 50: INFO processing request\nline 51: INFO processing request\nline 52: INFO processing request\nline 53: INFO processing request\nline 54: INFO processing request\nline 55: INFO processing request\nline 56: INFO processing request\nline 57: INFO processing request\nline 58: INFO processing request\nline 59: INFO processing request\nline 60: INFO processing request\nline 61: INFO processing request\nline 62: INFO processing request\nline 63: INFO processing request\nline 64: INFO processing request\nline 65: INFO processing request\nline 66: INFO processing request\nline 67: INFO processing request\nline 68: INFO processing request\nline 69: INFO processing request\nline 70: INFO processing request\nline 71: INFO processing request\nline 72: INFO processing request\nline 73: INFO processing request\nline 74: INFO processing request\nline 75: INFO processing request\nline 76: INFO processing request\nline 77: INFO processing request\nline 78: INFO processing request\nline 79: INFO processing request\nline 80: INFO processing request\nline 81: INFO processing request\nline 82: INFO processing request\nline 83: INFO processing request\nline 84: INFO processing request\nline 85: INFO processing request\nline 86: INFO processing request\nline 87: INFO processing request\nline 88: INFO processing request\nline 89: INFO processing request\nline 90: INFO processing request\nline 91: INFO processing request\nline 92: INFO processing request\nline 93: INFO processing request\nline 94: INFO processing request\nline 95: INFO processing request\nline 96: INFO processing request\nline 97: INFO processing request\nline 98: INFO processing request\nline 99: INFO processing request\nline 100: INFO processing request\nline 101: INFO processing request\nline 102: INFO processing request\nline 103: INFO processing request\nline 104: INFO processing request\nline 105: INFO processing request\nline 106: INFO processing request\nline 107: INFO processing request\nline 108: INFO processing request\nline 109: INFO processing request\nline 110: INFO processing request\nline 111: INFO processing request\nline 112: INFO processing request\nline 113: INFO processing request\nline 114: INFO processing request\nline 115: INFO processing request\nline 116: INFO processing request\nline 117: INFO processing request\nline 118: INFO processing request\nline 119: INFO processing request\nline 120: INFO processing request\nline 121: INFO processing request\nline 122: INFO processing request\nline 123: INFO processing request\nline 124: INFO processing request\nline 125: INFO processing request\nline 126: INFO processing request\nline 127: INFO processing request\nline 128: INFO processing request\nline 129: INFO processing request\nline 130: INFO processing request\nline 131: INFO processing request\nline 132: INFO processing request\nline 133: INFO processing request\nline 134: INFO processing request\nline 135: INFO processing request\nline 136: INFO processing request\nline 137: INFO processing request\nline 138: INFO processing request\nline 139: INFO processing request\nline 140: INFO processing request\nline 141: INFO processing request\nline 142: INFO processing request\nline 143: INFO processing request\nline 144: INFO processing request\nline 145: INFO processing request\nline 146: INFO processing request\nline 147: INFO processing request\nline 148: INFO processing request\nline 149: INFO processing request\nline 150: INFO processing request\nline 151: INFO processing request\nline 152: INFO processing request\nline 153: INFO processing request\nline 154: INFO processing request\nline 155: INFO processing request\nline 156: INFO processing request\nline 157: INFO processing request\nline 158: INFO processing request\nline 159: INFO processing request\nline 160: INFO processing request\nline 161: INFO processing request\nline 162: INFO processing request\nline 163: INFO processing request\nline 164: INFO processing request\nline 165: INFO processing request\nline 166: INFO processing request\nline 167: INFO processing request\nline 168: INFO processing request\nline 169: INFO processing request\nline 170: INFO processing request\nline 171: INFO processing request\nline 172: INFO processing request\nline 173: INFO processing request\nline 174: INFO processing request\nline 175: INFO processing request\nline 176: INFO processing request\nline 177: INFO processing request\nline 178: INFO processing request\nline 179: INFO processing request\nline 180: INFO processing request\nline 181: INFO processing request\nline 182: INFO processing request\nline 183: INFO processing request\nline 184: INFO processing request\nline 185: INFO processing request\nline 186: INFO processing request\nline 187: INFO processing request\nline 188: INFO processing request\nline 189: INFO processing request\nline 190: INFO processing request\nline 191: INFO processing request\nline 192: INFO processing request\nline 193: INFO processing request\nline 194: INFO processing request\nline 195: INFO processing request\nline 196: INFO processing request\nline 197: INFO processing request\nline 198: INFO processing request\nline 199: INFO processing request\nline 200: INFO processing request\nline 201: INFO processing request\nline 202: INFO processing request\nline 203: INFO processing request\nline 204: INFO processing request\nline 205: INFO processing request\nline 206: INFO processing request\nline 207: INFO processing request\nline 208: INFO processing request\nline 209: INFO processing request\nline 210: INFO processing request\nline 211: INFO processing request\nline 212: INFO processing request\nline 213: INFO processing request\nline 214: INFO processing request\nline 215: INFO processing request\nline 216: INFO processing request\nline 217: INFO processing request\nline 218: INFO processing request\nline 219: INFO processing request\nline 220: INFO processing request\nline 221: INFO processing request\nline 222: INFO processing request\nline 223: INFO processing request\nline 224: INFO processing request\nline 225: INFO processing request\nline 226: INFO processing request\nline 227: INFO processing request\nline 228: INFO processing request\nline 229: INFO processing request\nline 230: INFO processing request\nline 231: INFO processing request\nline 232: INFO processing request\nline 233: INFO processing request\nline 234: INFO processing request\nline 235: INFO processing request\nline 236: INFO processing request\nline 237: INFO processing request\nline 238: INFO processing request\nline 239: INFO processing request\nline 240: INFO processing request\nline 241: INFO processing request\nline 242: INFO processing request\nline 243: INFO processing request\nline 244: INFO processing request\nline 245: INFO processing request\nline 246: INFO processing request\nline 247: INFO processing request\nline 248: INFO processing request\nline 249: INFO processing request\nline 250: INFO processing request\nline 251: INFO processing request\nline 252: INFO processing request\nline 253: INFO processing request\nline 254: INFO processing request\nline 255: INFO processing request\nline 256: INFO processing request\nline 257: INFO processing request\nline 258: INFO processing request\nline 259: INFO processing request\nline 260: INFO processing request\nline 261: INFO processing request\nline 262: INFO processing request\nline 263: INFO processing request\nline 264: INFO processing request\nline 265: INFO processing request\nline 266: INFO processing request\nline 267: INFO processing request\nline 268: INFO processing request\nline 269: INFO processing request\nline 270: INFO processing request\nline 271: INFO processing request\nline 272: INFO processing request\nline 273: INFO processing request\nline 274: INFO processing request\nline 275: INFO processing request\nline 276: INFO processing request\nline 277: INFO processing request\nline 278: INFO processing request\nline 279: INFO processing request\nline 280: INFO processing request\nline 281: INFO processing request\nline 282: INFO processing request\nline 283: INFO processing request\nline 284: INFO processing request\nline 285: INFO processing request\nline 286: INFO processing request\nline 287: INFO processing request\nline 288: INFO processing request\nline 289: INFO processing request\nline 290: INFO processing request\nline 291: INFO processing request\nline 292: INFO processing request\nline 293: INFO processing request\nline 294: INFO processing request\nline 295: INFO processing request\nline 296: INFO processing request\nline 297: INFO processing request\nline 298: INFO processing request\nline 299: INFO processing request\nERROR something broke at step 42\nTraceback (most recent call last):\n File \"a.py\", line 5\nRuntimeError: boom\n# variant 13", + "input_sha256": "3bc015edc0a36387194d075139845c120a308afb314bae436ab454906b431d84", + "output": { + "cache_key": "80a6e711dbe62fc84e7b2b56", + "compressed": "line 297: INFO processing request\nline 298: INFO processing request\nline 299: INFO processing request\nERROR something broke at step 42\nTraceback (most recent call last):\n File \"a.py\", line 5\nRuntimeError: boom\n# variant 13\n[297 lines omitted: 1 ERROR, 300 INFO]\n[305 lines compressed to 8. Retrieve more: hash=80a6e711dbe62fc84e7b2b56]", + "compressed_line_count": 8, + "compression_ratio": 0.025658603466849474, + "format_detected": "generic", + "original": "line 0: INFO processing request\nline 1: INFO processing request\nline 2: INFO processing request\nline 3: INFO processing request\nline 4: INFO processing request\nline 5: INFO processing request\nline 6: INFO processing request\nline 7: INFO processing request\nline 8: INFO processing request\nline 9: INFO processing request\nline 10: INFO processing request\nline 11: INFO processing request\nline 12: INFO processing request\nline 13: INFO processing request\nline 14: INFO processing request\nline 15: INFO processing request\nline 16: INFO processing request\nline 17: INFO processing request\nline 18: INFO processing request\nline 19: INFO processing request\nline 20: INFO processing request\nline 21: INFO processing request\nline 22: INFO processing request\nline 23: INFO processing request\nline 24: INFO processing request\nline 25: INFO processing request\nline 26: INFO processing request\nline 27: INFO processing request\nline 28: INFO processing request\nline 29: INFO processing request\nline 30: INFO processing request\nline 31: INFO processing request\nline 32: INFO processing request\nline 33: INFO processing request\nline 34: INFO processing request\nline 35: INFO processing request\nline 36: INFO processing request\nline 37: INFO processing request\nline 38: INFO processing request\nline 39: INFO processing request\nline 40: INFO processing request\nline 41: INFO processing request\nline 42: INFO processing request\nline 43: INFO processing request\nline 44: INFO processing request\nline 45: INFO processing request\nline 46: INFO processing request\nline 47: INFO processing request\nline 48: INFO processing request\nline 49: INFO processing request\nline 50: INFO processing request\nline 51: INFO processing request\nline 52: INFO processing request\nline 53: INFO processing request\nline 54: INFO processing request\nline 55: INFO processing request\nline 56: INFO processing request\nline 57: INFO processing request\nline 58: INFO processing request\nline 59: INFO processing request\nline 60: INFO processing request\nline 61: INFO processing request\nline 62: INFO processing request\nline 63: INFO processing request\nline 64: INFO processing request\nline 65: INFO processing request\nline 66: INFO processing request\nline 67: INFO processing request\nline 68: INFO processing request\nline 69: INFO processing request\nline 70: INFO processing request\nline 71: INFO processing request\nline 72: INFO processing request\nline 73: INFO processing request\nline 74: INFO processing request\nline 75: INFO processing request\nline 76: INFO processing request\nline 77: INFO processing request\nline 78: INFO processing request\nline 79: INFO processing request\nline 80: INFO processing request\nline 81: INFO processing request\nline 82: INFO processing request\nline 83: INFO processing request\nline 84: INFO processing request\nline 85: INFO processing request\nline 86: INFO processing request\nline 87: INFO processing request\nline 88: INFO processing request\nline 89: INFO processing request\nline 90: INFO processing request\nline 91: INFO processing request\nline 92: INFO processing request\nline 93: INFO processing request\nline 94: INFO processing request\nline 95: INFO processing request\nline 96: INFO processing request\nline 97: INFO processing request\nline 98: INFO processing request\nline 99: INFO processing request\nline 100: INFO processing request\nline 101: INFO processing request\nline 102: INFO processing request\nline 103: INFO processing request\nline 104: INFO processing request\nline 105: INFO processing request\nline 106: INFO processing request\nline 107: INFO processing request\nline 108: INFO processing request\nline 109: INFO processing request\nline 110: INFO processing request\nline 111: INFO processing request\nline 112: INFO processing request\nline 113: INFO processing request\nline 114: INFO processing request\nline 115: INFO processing request\nline 116: INFO processing request\nline 117: INFO processing request\nline 118: INFO processing request\nline 119: INFO processing request\nline 120: INFO processing request\nline 121: INFO processing request\nline 122: INFO processing request\nline 123: INFO processing request\nline 124: INFO processing request\nline 125: INFO processing request\nline 126: INFO processing request\nline 127: INFO processing request\nline 128: INFO processing request\nline 129: INFO processing request\nline 130: INFO processing request\nline 131: INFO processing request\nline 132: INFO processing request\nline 133: INFO processing request\nline 134: INFO processing request\nline 135: INFO processing request\nline 136: INFO processing request\nline 137: INFO processing request\nline 138: INFO processing request\nline 139: INFO processing request\nline 140: INFO processing request\nline 141: INFO processing request\nline 142: INFO processing request\nline 143: INFO processing request\nline 144: INFO processing request\nline 145: INFO processing request\nline 146: INFO processing request\nline 147: INFO processing request\nline 148: INFO processing request\nline 149: INFO processing request\nline 150: INFO processing request\nline 151: INFO processing request\nline 152: INFO processing request\nline 153: INFO processing request\nline 154: INFO processing request\nline 155: INFO processing request\nline 156: INFO processing request\nline 157: INFO processing request\nline 158: INFO processing request\nline 159: INFO processing request\nline 160: INFO processing request\nline 161: INFO processing request\nline 162: INFO processing request\nline 163: INFO processing request\nline 164: INFO processing request\nline 165: INFO processing request\nline 166: INFO processing request\nline 167: INFO processing request\nline 168: INFO processing request\nline 169: INFO processing request\nline 170: INFO processing request\nline 171: INFO processing request\nline 172: INFO processing request\nline 173: INFO processing request\nline 174: INFO processing request\nline 175: INFO processing request\nline 176: INFO processing request\nline 177: INFO processing request\nline 178: INFO processing request\nline 179: INFO processing request\nline 180: INFO processing request\nline 181: INFO processing request\nline 182: INFO processing request\nline 183: INFO processing request\nline 184: INFO processing request\nline 185: INFO processing request\nline 186: INFO processing request\nline 187: INFO processing request\nline 188: INFO processing request\nline 189: INFO processing request\nline 190: INFO processing request\nline 191: INFO processing request\nline 192: INFO processing request\nline 193: INFO processing request\nline 194: INFO processing request\nline 195: INFO processing request\nline 196: INFO processing request\nline 197: INFO processing request\nline 198: INFO processing request\nline 199: INFO processing request\nline 200: INFO processing request\nline 201: INFO processing request\nline 202: INFO processing request\nline 203: INFO processing request\nline 204: INFO processing request\nline 205: INFO processing request\nline 206: INFO processing request\nline 207: INFO processing request\nline 208: INFO processing request\nline 209: INFO processing request\nline 210: INFO processing request\nline 211: INFO processing request\nline 212: INFO processing request\nline 213: INFO processing request\nline 214: INFO processing request\nline 215: INFO processing request\nline 216: INFO processing request\nline 217: INFO processing request\nline 218: INFO processing request\nline 219: INFO processing request\nline 220: INFO processing request\nline 221: INFO processing request\nline 222: INFO processing request\nline 223: INFO processing request\nline 224: INFO processing request\nline 225: INFO processing request\nline 226: INFO processing request\nline 227: INFO processing request\nline 228: INFO processing request\nline 229: INFO processing request\nline 230: INFO processing request\nline 231: INFO processing request\nline 232: INFO processing request\nline 233: INFO processing request\nline 234: INFO processing request\nline 235: INFO processing request\nline 236: INFO processing request\nline 237: INFO processing request\nline 238: INFO processing request\nline 239: INFO processing request\nline 240: INFO processing request\nline 241: INFO processing request\nline 242: INFO processing request\nline 243: INFO processing request\nline 244: INFO processing request\nline 245: INFO processing request\nline 246: INFO processing request\nline 247: INFO processing request\nline 248: INFO processing request\nline 249: INFO processing request\nline 250: INFO processing request\nline 251: INFO processing request\nline 252: INFO processing request\nline 253: INFO processing request\nline 254: INFO processing request\nline 255: INFO processing request\nline 256: INFO processing request\nline 257: INFO processing request\nline 258: INFO processing request\nline 259: INFO processing request\nline 260: INFO processing request\nline 261: INFO processing request\nline 262: INFO processing request\nline 263: INFO processing request\nline 264: INFO processing request\nline 265: INFO processing request\nline 266: INFO processing request\nline 267: INFO processing request\nline 268: INFO processing request\nline 269: INFO processing request\nline 270: INFO processing request\nline 271: INFO processing request\nline 272: INFO processing request\nline 273: INFO processing request\nline 274: INFO processing request\nline 275: INFO processing request\nline 276: INFO processing request\nline 277: INFO processing request\nline 278: INFO processing request\nline 279: INFO processing request\nline 280: INFO processing request\nline 281: INFO processing request\nline 282: INFO processing request\nline 283: INFO processing request\nline 284: INFO processing request\nline 285: INFO processing request\nline 286: INFO processing request\nline 287: INFO processing request\nline 288: INFO processing request\nline 289: INFO processing request\nline 290: INFO processing request\nline 291: INFO processing request\nline 292: INFO processing request\nline 293: INFO processing request\nline 294: INFO processing request\nline 295: INFO processing request\nline 296: INFO processing request\nline 297: INFO processing request\nline 298: INFO processing request\nline 299: INFO processing request\nERROR something broke at step 42\nTraceback (most recent call last):\n File \"a.py\", line 5\nRuntimeError: boom\n# variant 13", + "original_line_count": 305, + "stats": { + "errors": 1, + "fails": 0, + "info": 300, + "selected": 8, + "total": 305, + "warnings": 0 + } + }, + "recorded_at": "2026-04-23T23:51:37.345069+00:00", + "transform": "log_compressor" +} diff --git a/tests/parity/fixtures/log_compressor/3bf3f8bd70c80dad.json b/tests/parity/fixtures/log_compressor/3bf3f8bd70c80dad.json new file mode 100644 index 0000000000000000000000000000000000000000..da7815905a5376cbdba52e728847941c06583764 --- /dev/null +++ b/tests/parity/fixtures/log_compressor/3bf3f8bd70c80dad.json @@ -0,0 +1,30 @@ +{ + "config": { + "dedupe_warnings": true, + "enable_ccr": true, + "error_context_lines": 3, + "keep_first_error": true, + "keep_last_error": true, + "keep_summary_lines": true, + "max_errors": 10, + "max_stack_traces": 3, + "max_total_lines": 100, + "max_warnings": 5, + "min_lines_for_ccr": 50, + "stack_trace_max_lines": 20 + }, + "input": "make: *** [Makefile:12: all] Error 2\ngcc -c foo.c -o foo.o\nfoo.c:5:3: error: 'undeclared' undeclared\n# variant 12", + "input_sha256": "3bf3f8bd70c80dadc9abe939665c927c9a51b4b7f0b330a513c19224240c15ec", + "output": { + "cache_key": null, + "compressed": "make: *** [Makefile:12: all] Error 2\ngcc -c foo.c -o foo.o\nfoo.c:5:3: error: 'undeclared' undeclared\n# variant 12", + "compressed_line_count": 4, + "compression_ratio": 1.0, + "format_detected": "generic", + "original": "make: *** [Makefile:12: all] Error 2\ngcc -c foo.c -o foo.o\nfoo.c:5:3: error: 'undeclared' undeclared\n# variant 12", + "original_line_count": 4, + "stats": {} + }, + "recorded_at": "2026-04-23T23:51:37.258511+00:00", + "transform": "log_compressor" +} diff --git a/tests/parity/fixtures/log_compressor/5533a8264a04dd61.json b/tests/parity/fixtures/log_compressor/5533a8264a04dd61.json new file mode 100644 index 0000000000000000000000000000000000000000..efb9b4bf7ce29af23b5a91515dcade52aab8ae3d --- /dev/null +++ b/tests/parity/fixtures/log_compressor/5533a8264a04dd61.json @@ -0,0 +1,30 @@ +{ + "config": { + "dedupe_warnings": true, + "enable_ccr": true, + "error_context_lines": 3, + "keep_first_error": true, + "keep_last_error": true, + "keep_summary_lines": true, + "max_errors": 10, + "max_stack_traces": 3, + "max_total_lines": 100, + "max_warnings": 5, + "min_lines_for_ccr": 50, + "stack_trace_max_lines": 20 + }, + "input": "INFO iteration 5\nERROR error 5\nWARN warn 5\nINFO done 5\n# variant 19", + "input_sha256": "5533a8264a04dd6194f99fc8be8120acc72acaa3dddad9933397a7adc224f3f1", + "output": { + "cache_key": null, + "compressed": "INFO iteration 5\nERROR error 5\nWARN warn 5\nINFO done 5\n# variant 19", + "compressed_line_count": 5, + "compression_ratio": 1.0, + "format_detected": "generic", + "original": "INFO iteration 5\nERROR error 5\nWARN warn 5\nINFO done 5\n# variant 19", + "original_line_count": 5, + "stats": {} + }, + "recorded_at": "2026-04-23T23:51:37.347250+00:00", + "transform": "log_compressor" +} diff --git a/tests/parity/fixtures/log_compressor/566491aacda8af92.json b/tests/parity/fixtures/log_compressor/566491aacda8af92.json new file mode 100644 index 0000000000000000000000000000000000000000..5f49b2a29b4409285515b41795c0ce06743868c7 --- /dev/null +++ b/tests/parity/fixtures/log_compressor/566491aacda8af92.json @@ -0,0 +1,30 @@ +{ + "config": { + "dedupe_warnings": true, + "enable_ccr": true, + "error_context_lines": 3, + "keep_first_error": true, + "keep_last_error": true, + "keep_summary_lines": true, + "max_errors": 10, + "max_stack_traces": 3, + "max_total_lines": 100, + "max_warnings": 5, + "min_lines_for_ccr": 50, + "stack_trace_max_lines": 20 + }, + "input": "INFO iteration 3\nERROR error 3\nWARN warn 3\nINFO done 3\n# variant 17", + "input_sha256": "566491aacda8af92442765e7b21619e3cf7be1a1a6ef4539c80941bcb142e346", + "output": { + "cache_key": null, + "compressed": "INFO iteration 3\nERROR error 3\nWARN warn 3\nINFO done 3\n# variant 17", + "compressed_line_count": 5, + "compression_ratio": 1.0, + "format_detected": "generic", + "original": "INFO iteration 3\nERROR error 3\nWARN warn 3\nINFO done 3\n# variant 17", + "original_line_count": 5, + "stats": {} + }, + "recorded_at": "2026-04-23T23:51:37.346815+00:00", + "transform": "log_compressor" +} diff --git a/tests/parity/fixtures/log_compressor/629d38929748b41e.json b/tests/parity/fixtures/log_compressor/629d38929748b41e.json new file mode 100644 index 0000000000000000000000000000000000000000..f842b53e35cfcdd32d4c60cd4e0b3c674e6fad08 --- /dev/null +++ b/tests/parity/fixtures/log_compressor/629d38929748b41e.json @@ -0,0 +1,30 @@ +{ + "config": { + "dedupe_warnings": true, + "enable_ccr": true, + "error_context_lines": 3, + "keep_first_error": true, + "keep_last_error": true, + "keep_summary_lines": true, + "max_errors": 10, + "max_stack_traces": 3, + "max_total_lines": 100, + "max_warnings": 5, + "min_lines_for_ccr": 50, + "stack_trace_max_lines": 20 + }, + "input": " Compiling crate_0 v0.1.0\n Compiling crate_1 v0.1.1\n Compiling crate_2 v0.1.2\n Compiling crate_3 v0.1.3\n Compiling crate_4 v0.1.4\n Compiling crate_5 v0.1.5\n Compiling crate_6 v0.1.6\n Compiling crate_7 v0.1.7\n Compiling crate_8 v0.1.8\n Compiling crate_9 v0.1.9\nerror[E0308]: mismatched types\n --> src/lib.rs:42:9\n |\n42 | return x;\n | ^^^^^^^^^ expected `i32`, found `u64`\nerror: aborting due to previous error\n# variant 9", + "input_sha256": "629d38929748b41e8eeca7db86ba54846b958552dc0b94c3a99757e9cde72df2", + "output": { + "cache_key": null, + "compressed": " Compiling crate_0 v0.1.0\n Compiling crate_1 v0.1.1\n Compiling crate_2 v0.1.2\n Compiling crate_3 v0.1.3\n Compiling crate_4 v0.1.4\n Compiling crate_5 v0.1.5\n Compiling crate_6 v0.1.6\n Compiling crate_7 v0.1.7\n Compiling crate_8 v0.1.8\n Compiling crate_9 v0.1.9\nerror[E0308]: mismatched types\n --> src/lib.rs:42:9\n |\n42 | return x;\n | ^^^^^^^^^ expected `i32`, found `u64`\nerror: aborting due to previous error\n# variant 9", + "compressed_line_count": 17, + "compression_ratio": 1.0, + "format_detected": "generic", + "original": " Compiling crate_0 v0.1.0\n Compiling crate_1 v0.1.1\n Compiling crate_2 v0.1.2\n Compiling crate_3 v0.1.3\n Compiling crate_4 v0.1.4\n Compiling crate_5 v0.1.5\n Compiling crate_6 v0.1.6\n Compiling crate_7 v0.1.7\n Compiling crate_8 v0.1.8\n Compiling crate_9 v0.1.9\nerror[E0308]: mismatched types\n --> src/lib.rs:42:9\n |\n42 | return x;\n | ^^^^^^^^^ expected `i32`, found `u64`\nerror: aborting due to previous error\n# variant 9", + "original_line_count": 17, + "stats": {} + }, + "recorded_at": "2026-04-23T23:51:37.256543+00:00", + "transform": "log_compressor" +} diff --git a/tests/parity/fixtures/log_compressor/65bd6228add102ac.json b/tests/parity/fixtures/log_compressor/65bd6228add102ac.json new file mode 100644 index 0000000000000000000000000000000000000000..e9b9f45d9513cea680801975819aaac29743a284 --- /dev/null +++ b/tests/parity/fixtures/log_compressor/65bd6228add102ac.json @@ -0,0 +1,30 @@ +{ + "config": { + "dedupe_warnings": true, + "enable_ccr": true, + "error_context_lines": 3, + "keep_first_error": true, + "keep_last_error": true, + "keep_summary_lines": true, + "max_errors": 10, + "max_stack_traces": 3, + "max_total_lines": 100, + "max_warnings": 5, + "min_lines_for_ccr": 50, + "stack_trace_max_lines": 20 + }, + "input": "INFO iteration 0\nERROR error 0\nWARN warn 0\nINFO done 0\n# variant 14", + "input_sha256": "65bd6228add102acdba36b90e87b1babc54ca9076563d38c74cdc4803eff6dad", + "output": { + "cache_key": null, + "compressed": "INFO iteration 0\nERROR error 0\nWARN warn 0\nINFO done 0\n# variant 14", + "compressed_line_count": 5, + "compression_ratio": 1.0, + "format_detected": "generic", + "original": "INFO iteration 0\nERROR error 0\nWARN warn 0\nINFO done 0\n# variant 14", + "original_line_count": 5, + "stats": {} + }, + "recorded_at": "2026-04-23T23:51:37.345475+00:00", + "transform": "log_compressor" +} diff --git a/tests/parity/fixtures/log_compressor/79b59341f8159b3e.json b/tests/parity/fixtures/log_compressor/79b59341f8159b3e.json new file mode 100644 index 0000000000000000000000000000000000000000..03263742a969b500370f9f0f12e00fc25a79a8a7 --- /dev/null +++ b/tests/parity/fixtures/log_compressor/79b59341f8159b3e.json @@ -0,0 +1,30 @@ +{ + "config": { + "dedupe_warnings": true, + "enable_ccr": true, + "error_context_lines": 3, + "keep_first_error": true, + "keep_last_error": true, + "keep_summary_lines": true, + "max_errors": 10, + "max_stack_traces": 3, + "max_total_lines": 100, + "max_warnings": 5, + "min_lines_for_ccr": 50, + "stack_trace_max_lines": 20 + }, + "input": "npm WARN deprecated foo@1.0.0: use bar\nadded 0 packages in 3s\nadded 1 packages in 3s\nadded 2 packages in 3s\nadded 3 packages in 3s\nadded 4 packages in 3s\nnpm ERR! code ERESOLVE\nnpm ERR! ERESOLVE unable to resolve dependency tree\nnpm ERR! While resolving: project@1.0.0\nnpm ERR! Found: react@17.0.2\n# variant 5", + "input_sha256": "79b59341f8159b3ea568443e6e767b411b80211975d248cfc1f3d35b3d486fdc", + "output": { + "cache_key": null, + "compressed": "npm WARN deprecated foo@1.0.0: use bar\nadded 0 packages in 3s\nadded 1 packages in 3s\nadded 2 packages in 3s\nadded 3 packages in 3s\nadded 4 packages in 3s\nnpm ERR! code ERESOLVE\nnpm ERR! ERESOLVE unable to resolve dependency tree\nnpm ERR! While resolving: project@1.0.0\nnpm ERR! Found: react@17.0.2\n# variant 5", + "compressed_line_count": 11, + "compression_ratio": 1.0, + "format_detected": "generic", + "original": "npm WARN deprecated foo@1.0.0: use bar\nadded 0 packages in 3s\nadded 1 packages in 3s\nadded 2 packages in 3s\nadded 3 packages in 3s\nadded 4 packages in 3s\nnpm ERR! code ERESOLVE\nnpm ERR! ERESOLVE unable to resolve dependency tree\nnpm ERR! While resolving: project@1.0.0\nnpm ERR! Found: react@17.0.2\n# variant 5", + "original_line_count": 11, + "stats": {} + }, + "recorded_at": "2026-04-23T23:51:37.254799+00:00", + "transform": "log_compressor" +} diff --git a/tests/parity/fixtures/log_compressor/7cd0d60fe9f4aad2.json b/tests/parity/fixtures/log_compressor/7cd0d60fe9f4aad2.json new file mode 100644 index 0000000000000000000000000000000000000000..ecb9b28ab29bc552755218a579ad376361fb6157 --- /dev/null +++ b/tests/parity/fixtures/log_compressor/7cd0d60fe9f4aad2.json @@ -0,0 +1,30 @@ +{ + "config": { + "dedupe_warnings": true, + "enable_ccr": true, + "error_context_lines": 3, + "keep_first_error": true, + "keep_last_error": true, + "keep_summary_lines": true, + "max_errors": 10, + "max_stack_traces": 3, + "max_total_lines": 100, + "max_warnings": 5, + "min_lines_for_ccr": 50, + "stack_trace_max_lines": 20 + }, + "input": "============================= test session starts ==============================\ncollected 42 items\ntests/test_mod_0.py::test_case PASSED [0%]\ntests/test_mod_1.py::test_case PASSED [2%]\ntests/test_mod_2.py::test_case PASSED [4%]\ntests/test_mod_3.py::test_case PASSED [6%]\ntests/test_mod_4.py::test_case PASSED [8%]\ntests/test_mod_5.py::test_case PASSED [10%]\ntests/test_mod_6.py::test_case PASSED [12%]\ntests/test_mod_7.py::test_case PASSED [14%]\ntests/test_mod_8.py::test_case PASSED [16%]\ntests/test_mod_9.py::test_case PASSED [18%]\ntests/test_mod_10.py::test_case PASSED [20%]\ntests/test_mod_11.py::test_case PASSED [22%]\ntests/test_mod_12.py::test_case PASSED [24%]\ntests/test_mod_13.py::test_case PASSED [26%]\ntests/test_mod_14.py::test_case PASSED [28%]\ntests/test_mod_15.py::test_case PASSED [30%]\ntests/test_mod_16.py::test_case PASSED [32%]\ntests/test_mod_17.py::test_case PASSED [34%]\ntests/test_mod_18.py::test_case PASSED [36%]\ntests/test_mod_19.py::test_case PASSED [38%]\ntests/test_mod_20.py::test_case PASSED [40%]\ntests/test_mod_21.py::test_case PASSED [42%]\ntests/test_mod_22.py::test_case PASSED [44%]\ntests/test_mod_23.py::test_case PASSED [46%]\ntests/test_mod_24.py::test_case PASSED [48%]\ntests/test_mod_25.py::test_bad FAILED\n=================================== FAILURES ===================================\n___________________________________ test_bad ___________________________________\n def test_bad():\n> assert compute(1, 2) == 4\nE assert 3 == 4\ntests/test_mod_25.py:17: AssertionError\n=========================== short test summary info ============================\nFAILED tests/test_mod_25.py::test_bad\n1 failed, 25 passed in 0.42s\n# variant 2", + "input_sha256": "7cd0d60fe9f4aad2453c28a3614668bed7b5bdfc007074f8b98672b6fbb27622", + "output": { + "cache_key": null, + "compressed": "============================= test session starts ==============================\ncollected 42 items\ntests/test_mod_0.py::test_case PASSED [0%]\ntests/test_mod_1.py::test_case PASSED [2%]\ntests/test_mod_2.py::test_case PASSED [4%]\ntests/test_mod_3.py::test_case PASSED [6%]\ntests/test_mod_4.py::test_case PASSED [8%]\ntests/test_mod_5.py::test_case PASSED [10%]\ntests/test_mod_6.py::test_case PASSED [12%]\ntests/test_mod_7.py::test_case PASSED [14%]\ntests/test_mod_8.py::test_case PASSED [16%]\ntests/test_mod_9.py::test_case PASSED [18%]\ntests/test_mod_10.py::test_case PASSED [20%]\ntests/test_mod_11.py::test_case PASSED [22%]\ntests/test_mod_12.py::test_case PASSED [24%]\ntests/test_mod_13.py::test_case PASSED [26%]\ntests/test_mod_14.py::test_case PASSED [28%]\ntests/test_mod_15.py::test_case PASSED [30%]\ntests/test_mod_16.py::test_case PASSED [32%]\ntests/test_mod_17.py::test_case PASSED [34%]\ntests/test_mod_18.py::test_case PASSED [36%]\ntests/test_mod_19.py::test_case PASSED [38%]\ntests/test_mod_20.py::test_case PASSED [40%]\ntests/test_mod_21.py::test_case PASSED [42%]\ntests/test_mod_22.py::test_case PASSED [44%]\ntests/test_mod_23.py::test_case PASSED [46%]\ntests/test_mod_24.py::test_case PASSED [48%]\ntests/test_mod_25.py::test_bad FAILED\n=================================== FAILURES ===================================\n___________________________________ test_bad ___________________________________\n def test_bad():\n> assert compute(1, 2) == 4\nE assert 3 == 4\ntests/test_mod_25.py:17: AssertionError\n=========================== short test summary info ============================\nFAILED tests/test_mod_25.py::test_bad\n1 failed, 25 passed in 0.42s\n# variant 2", + "compressed_line_count": 38, + "compression_ratio": 1.0, + "format_detected": "generic", + "original": "============================= test session starts ==============================\ncollected 42 items\ntests/test_mod_0.py::test_case PASSED [0%]\ntests/test_mod_1.py::test_case PASSED [2%]\ntests/test_mod_2.py::test_case PASSED [4%]\ntests/test_mod_3.py::test_case PASSED [6%]\ntests/test_mod_4.py::test_case PASSED [8%]\ntests/test_mod_5.py::test_case PASSED [10%]\ntests/test_mod_6.py::test_case PASSED [12%]\ntests/test_mod_7.py::test_case PASSED [14%]\ntests/test_mod_8.py::test_case PASSED [16%]\ntests/test_mod_9.py::test_case PASSED [18%]\ntests/test_mod_10.py::test_case PASSED [20%]\ntests/test_mod_11.py::test_case PASSED [22%]\ntests/test_mod_12.py::test_case PASSED [24%]\ntests/test_mod_13.py::test_case PASSED [26%]\ntests/test_mod_14.py::test_case PASSED [28%]\ntests/test_mod_15.py::test_case PASSED [30%]\ntests/test_mod_16.py::test_case PASSED [32%]\ntests/test_mod_17.py::test_case PASSED [34%]\ntests/test_mod_18.py::test_case PASSED [36%]\ntests/test_mod_19.py::test_case PASSED [38%]\ntests/test_mod_20.py::test_case PASSED [40%]\ntests/test_mod_21.py::test_case PASSED [42%]\ntests/test_mod_22.py::test_case PASSED [44%]\ntests/test_mod_23.py::test_case PASSED [46%]\ntests/test_mod_24.py::test_case PASSED [48%]\ntests/test_mod_25.py::test_bad FAILED\n=================================== FAILURES ===================================\n___________________________________ test_bad ___________________________________\n def test_bad():\n> assert compute(1, 2) == 4\nE assert 3 == 4\ntests/test_mod_25.py:17: AssertionError\n=========================== short test summary info ============================\nFAILED tests/test_mod_25.py::test_bad\n1 failed, 25 passed in 0.42s\n# variant 2", + "original_line_count": 38, + "stats": {} + }, + "recorded_at": "2026-04-23T23:51:37.253497+00:00", + "transform": "log_compressor" +} diff --git a/tests/parity/fixtures/log_compressor/9655ffc91d79aa47.json b/tests/parity/fixtures/log_compressor/9655ffc91d79aa47.json new file mode 100644 index 0000000000000000000000000000000000000000..91cb9ee48e23bf8595f6f65c3b3a49de558c1a2c --- /dev/null +++ b/tests/parity/fixtures/log_compressor/9655ffc91d79aa47.json @@ -0,0 +1,30 @@ +{ + "config": { + "dedupe_warnings": true, + "enable_ccr": true, + "error_context_lines": 3, + "keep_first_error": true, + "keep_last_error": true, + "keep_summary_lines": true, + "max_errors": 10, + "max_stack_traces": 3, + "max_total_lines": 100, + "max_warnings": 5, + "min_lines_for_ccr": 50, + "stack_trace_max_lines": 20 + }, + "input": "============================= test session starts ==============================\ncollected 42 items\ntests/test_mod_0.py::test_case PASSED [0%]\ntests/test_mod_1.py::test_case PASSED [2%]\ntests/test_mod_2.py::test_case PASSED [4%]\ntests/test_mod_3.py::test_case PASSED [6%]\ntests/test_mod_4.py::test_case PASSED [8%]\ntests/test_mod_5.py::test_case PASSED [10%]\ntests/test_mod_6.py::test_case PASSED [12%]\ntests/test_mod_7.py::test_case PASSED [14%]\ntests/test_mod_8.py::test_case PASSED [16%]\ntests/test_mod_9.py::test_case PASSED [18%]\ntests/test_mod_10.py::test_case PASSED [20%]\ntests/test_mod_11.py::test_case PASSED [22%]\ntests/test_mod_12.py::test_case PASSED [24%]\ntests/test_mod_13.py::test_case PASSED [26%]\ntests/test_mod_14.py::test_case PASSED [28%]\ntests/test_mod_15.py::test_case PASSED [30%]\ntests/test_mod_16.py::test_case PASSED [32%]\ntests/test_mod_17.py::test_case PASSED [34%]\ntests/test_mod_18.py::test_case PASSED [36%]\ntests/test_mod_19.py::test_case PASSED [38%]\ntests/test_mod_20.py::test_case PASSED [40%]\ntests/test_mod_21.py::test_case PASSED [42%]\ntests/test_mod_22.py::test_case PASSED [44%]\ntests/test_mod_23.py::test_case PASSED [46%]\ntests/test_mod_24.py::test_case PASSED [48%]\ntests/test_mod_25.py::test_bad FAILED\n=================================== FAILURES ===================================\n___________________________________ test_bad ___________________________________\n def test_bad():\n> assert compute(1, 2) == 4\nE assert 3 == 4\ntests/test_mod_25.py:17: AssertionError\n=========================== short test summary info ============================\nFAILED tests/test_mod_25.py::test_bad\n1 failed, 25 passed in 0.42s\n# variant 4", + "input_sha256": "9655ffc91d79aa478fdc3cf70717f2475ff6adfb8454ccdacceaf34eb0d008c2", + "output": { + "cache_key": null, + "compressed": "============================= test session starts ==============================\ncollected 42 items\ntests/test_mod_0.py::test_case PASSED [0%]\ntests/test_mod_1.py::test_case PASSED [2%]\ntests/test_mod_2.py::test_case PASSED [4%]\ntests/test_mod_3.py::test_case PASSED [6%]\ntests/test_mod_4.py::test_case PASSED [8%]\ntests/test_mod_5.py::test_case PASSED [10%]\ntests/test_mod_6.py::test_case PASSED [12%]\ntests/test_mod_7.py::test_case PASSED [14%]\ntests/test_mod_8.py::test_case PASSED [16%]\ntests/test_mod_9.py::test_case PASSED [18%]\ntests/test_mod_10.py::test_case PASSED [20%]\ntests/test_mod_11.py::test_case PASSED [22%]\ntests/test_mod_12.py::test_case PASSED [24%]\ntests/test_mod_13.py::test_case PASSED [26%]\ntests/test_mod_14.py::test_case PASSED [28%]\ntests/test_mod_15.py::test_case PASSED [30%]\ntests/test_mod_16.py::test_case PASSED [32%]\ntests/test_mod_17.py::test_case PASSED [34%]\ntests/test_mod_18.py::test_case PASSED [36%]\ntests/test_mod_19.py::test_case PASSED [38%]\ntests/test_mod_20.py::test_case PASSED [40%]\ntests/test_mod_21.py::test_case PASSED [42%]\ntests/test_mod_22.py::test_case PASSED [44%]\ntests/test_mod_23.py::test_case PASSED [46%]\ntests/test_mod_24.py::test_case PASSED [48%]\ntests/test_mod_25.py::test_bad FAILED\n=================================== FAILURES ===================================\n___________________________________ test_bad ___________________________________\n def test_bad():\n> assert compute(1, 2) == 4\nE assert 3 == 4\ntests/test_mod_25.py:17: AssertionError\n=========================== short test summary info ============================\nFAILED tests/test_mod_25.py::test_bad\n1 failed, 25 passed in 0.42s\n# variant 4", + "compressed_line_count": 38, + "compression_ratio": 1.0, + "format_detected": "generic", + "original": "============================= test session starts ==============================\ncollected 42 items\ntests/test_mod_0.py::test_case PASSED [0%]\ntests/test_mod_1.py::test_case PASSED [2%]\ntests/test_mod_2.py::test_case PASSED [4%]\ntests/test_mod_3.py::test_case PASSED [6%]\ntests/test_mod_4.py::test_case PASSED [8%]\ntests/test_mod_5.py::test_case PASSED [10%]\ntests/test_mod_6.py::test_case PASSED [12%]\ntests/test_mod_7.py::test_case PASSED [14%]\ntests/test_mod_8.py::test_case PASSED [16%]\ntests/test_mod_9.py::test_case PASSED [18%]\ntests/test_mod_10.py::test_case PASSED [20%]\ntests/test_mod_11.py::test_case PASSED [22%]\ntests/test_mod_12.py::test_case PASSED [24%]\ntests/test_mod_13.py::test_case PASSED [26%]\ntests/test_mod_14.py::test_case PASSED [28%]\ntests/test_mod_15.py::test_case PASSED [30%]\ntests/test_mod_16.py::test_case PASSED [32%]\ntests/test_mod_17.py::test_case PASSED [34%]\ntests/test_mod_18.py::test_case PASSED [36%]\ntests/test_mod_19.py::test_case PASSED [38%]\ntests/test_mod_20.py::test_case PASSED [40%]\ntests/test_mod_21.py::test_case PASSED [42%]\ntests/test_mod_22.py::test_case PASSED [44%]\ntests/test_mod_23.py::test_case PASSED [46%]\ntests/test_mod_24.py::test_case PASSED [48%]\ntests/test_mod_25.py::test_bad FAILED\n=================================== FAILURES ===================================\n___________________________________ test_bad ___________________________________\n def test_bad():\n> assert compute(1, 2) == 4\nE assert 3 == 4\ntests/test_mod_25.py:17: AssertionError\n=========================== short test summary info ============================\nFAILED tests/test_mod_25.py::test_bad\n1 failed, 25 passed in 0.42s\n# variant 4", + "original_line_count": 38, + "stats": {} + }, + "recorded_at": "2026-04-23T23:51:37.254373+00:00", + "transform": "log_compressor" +} diff --git a/tests/parity/fixtures/log_compressor/aa433e41aafed34e.json b/tests/parity/fixtures/log_compressor/aa433e41aafed34e.json new file mode 100644 index 0000000000000000000000000000000000000000..d2b12dec57a5ae941d3c4b5561f52232c8dd4a18 --- /dev/null +++ b/tests/parity/fixtures/log_compressor/aa433e41aafed34e.json @@ -0,0 +1,30 @@ +{ + "config": { + "dedupe_warnings": true, + "enable_ccr": true, + "error_context_lines": 3, + "keep_first_error": true, + "keep_last_error": true, + "keep_summary_lines": true, + "max_errors": 10, + "max_stack_traces": 3, + "max_total_lines": 100, + "max_warnings": 5, + "min_lines_for_ccr": 50, + "stack_trace_max_lines": 20 + }, + "input": "INFO iteration 4\nERROR error 4\nWARN warn 4\nINFO done 4\n# variant 18", + "input_sha256": "aa433e41aafed34e9f7c67e921a8768a88cfd16217311e70fead739b8812af60", + "output": { + "cache_key": null, + "compressed": "INFO iteration 4\nERROR error 4\nWARN warn 4\nINFO done 4\n# variant 18", + "compressed_line_count": 5, + "compression_ratio": 1.0, + "format_detected": "generic", + "original": "INFO iteration 4\nERROR error 4\nWARN warn 4\nINFO done 4\n# variant 18", + "original_line_count": 5, + "stats": {} + }, + "recorded_at": "2026-04-23T23:51:37.347060+00:00", + "transform": "log_compressor" +} diff --git a/tests/parity/fixtures/log_compressor/c472d986fe3b3192.json b/tests/parity/fixtures/log_compressor/c472d986fe3b3192.json new file mode 100644 index 0000000000000000000000000000000000000000..cae00106df527502bcecdd81f4f34254c5b110c6 --- /dev/null +++ b/tests/parity/fixtures/log_compressor/c472d986fe3b3192.json @@ -0,0 +1,30 @@ +{ + "config": { + "dedupe_warnings": true, + "enable_ccr": true, + "error_context_lines": 3, + "keep_first_error": true, + "keep_last_error": true, + "keep_summary_lines": true, + "max_errors": 10, + "max_stack_traces": 3, + "max_total_lines": 100, + "max_warnings": 5, + "min_lines_for_ccr": 50, + "stack_trace_max_lines": 20 + }, + "input": " Compiling crate_0 v0.1.0\n Compiling crate_1 v0.1.1\n Compiling crate_2 v0.1.2\n Compiling crate_3 v0.1.3\n Compiling crate_4 v0.1.4\n Compiling crate_5 v0.1.5\n Compiling crate_6 v0.1.6\n Compiling crate_7 v0.1.7\n Compiling crate_8 v0.1.8\n Compiling crate_9 v0.1.9\nerror[E0308]: mismatched types\n --> src/lib.rs:42:9\n |\n42 | return x;\n | ^^^^^^^^^ expected `i32`, found `u64`\nerror: aborting due to previous error\n# variant 11", + "input_sha256": "c472d986fe3b3192fea01354cb32873707a5b553bec4881f59da030cdd8fae3d", + "output": { + "cache_key": null, + "compressed": " Compiling crate_0 v0.1.0\n Compiling crate_1 v0.1.1\n Compiling crate_2 v0.1.2\n Compiling crate_3 v0.1.3\n Compiling crate_4 v0.1.4\n Compiling crate_5 v0.1.5\n Compiling crate_6 v0.1.6\n Compiling crate_7 v0.1.7\n Compiling crate_8 v0.1.8\n Compiling crate_9 v0.1.9\nerror[E0308]: mismatched types\n --> src/lib.rs:42:9\n |\n42 | return x;\n | ^^^^^^^^^ expected `i32`, found `u64`\nerror: aborting due to previous error\n# variant 11", + "compressed_line_count": 17, + "compression_ratio": 1.0, + "format_detected": "generic", + "original": " Compiling crate_0 v0.1.0\n Compiling crate_1 v0.1.1\n Compiling crate_2 v0.1.2\n Compiling crate_3 v0.1.3\n Compiling crate_4 v0.1.4\n Compiling crate_5 v0.1.5\n Compiling crate_6 v0.1.6\n Compiling crate_7 v0.1.7\n Compiling crate_8 v0.1.8\n Compiling crate_9 v0.1.9\nerror[E0308]: mismatched types\n --> src/lib.rs:42:9\n |\n42 | return x;\n | ^^^^^^^^^ expected `i32`, found `u64`\nerror: aborting due to previous error\n# variant 11", + "original_line_count": 17, + "stats": {} + }, + "recorded_at": "2026-04-23T23:51:37.257910+00:00", + "transform": "log_compressor" +} diff --git a/tests/parity/fixtures/log_compressor/cbf376c989bdc9be.json b/tests/parity/fixtures/log_compressor/cbf376c989bdc9be.json new file mode 100644 index 0000000000000000000000000000000000000000..7d7c083a38e874751f90aabf5f1ae1ef71a809f3 --- /dev/null +++ b/tests/parity/fixtures/log_compressor/cbf376c989bdc9be.json @@ -0,0 +1,30 @@ +{ + "config": { + "dedupe_warnings": true, + "enable_ccr": true, + "error_context_lines": 3, + "keep_first_error": true, + "keep_last_error": true, + "keep_summary_lines": true, + "max_errors": 10, + "max_stack_traces": 3, + "max_total_lines": 100, + "max_warnings": 5, + "min_lines_for_ccr": 50, + "stack_trace_max_lines": 20 + }, + "input": "INFO starting\nERROR database connection failed\nINFO shutting down\n# variant 0", + "input_sha256": "cbf376c989bdc9beb2fca8015ffbd81780e4c1e334c9a6f3d3c14a7bcb2519a6", + "output": { + "cache_key": null, + "compressed": "INFO starting\nERROR database connection failed\nINFO shutting down\n# variant 0", + "compressed_line_count": 4, + "compression_ratio": 1.0, + "format_detected": "generic", + "original": "INFO starting\nERROR database connection failed\nINFO shutting down\n# variant 0", + "original_line_count": 4, + "stats": {} + }, + "recorded_at": "2026-04-23T23:51:37.251951+00:00", + "transform": "log_compressor" +} diff --git a/tests/parity/fixtures/log_compressor/d01455afa162c961.json b/tests/parity/fixtures/log_compressor/d01455afa162c961.json new file mode 100644 index 0000000000000000000000000000000000000000..1d3402ba35bf881fe9f15b2d5136261fa6120ff6 --- /dev/null +++ b/tests/parity/fixtures/log_compressor/d01455afa162c961.json @@ -0,0 +1,30 @@ +{ + "config": { + "dedupe_warnings": true, + "enable_ccr": true, + "error_context_lines": 3, + "keep_first_error": true, + "keep_last_error": true, + "keep_summary_lines": true, + "max_errors": 10, + "max_stack_traces": 3, + "max_total_lines": 100, + "max_warnings": 5, + "min_lines_for_ccr": 50, + "stack_trace_max_lines": 20 + }, + "input": "npm WARN deprecated foo@1.0.0: use bar\nadded 0 packages in 3s\nadded 1 packages in 3s\nadded 2 packages in 3s\nadded 3 packages in 3s\nadded 4 packages in 3s\nnpm ERR! code ERESOLVE\nnpm ERR! ERESOLVE unable to resolve dependency tree\nnpm ERR! While resolving: project@1.0.0\nnpm ERR! Found: react@17.0.2\n# variant 6", + "input_sha256": "d01455afa162c9610ec624acd978ce0ce24c8e6bf8a7a66a18f2948120be3776", + "output": { + "cache_key": null, + "compressed": "npm WARN deprecated foo@1.0.0: use bar\nadded 0 packages in 3s\nadded 1 packages in 3s\nadded 2 packages in 3s\nadded 3 packages in 3s\nadded 4 packages in 3s\nnpm ERR! code ERESOLVE\nnpm ERR! ERESOLVE unable to resolve dependency tree\nnpm ERR! While resolving: project@1.0.0\nnpm ERR! Found: react@17.0.2\n# variant 6", + "compressed_line_count": 11, + "compression_ratio": 1.0, + "format_detected": "generic", + "original": "npm WARN deprecated foo@1.0.0: use bar\nadded 0 packages in 3s\nadded 1 packages in 3s\nadded 2 packages in 3s\nadded 3 packages in 3s\nadded 4 packages in 3s\nnpm ERR! code ERESOLVE\nnpm ERR! ERESOLVE unable to resolve dependency tree\nnpm ERR! While resolving: project@1.0.0\nnpm ERR! Found: react@17.0.2\n# variant 6", + "original_line_count": 11, + "stats": {} + }, + "recorded_at": "2026-04-23T23:51:37.255226+00:00", + "transform": "log_compressor" +} diff --git a/tests/parity/fixtures/log_compressor/d7e1352bc6d3df4d.json b/tests/parity/fixtures/log_compressor/d7e1352bc6d3df4d.json new file mode 100644 index 0000000000000000000000000000000000000000..f75cd98592f2daa7b501369f048e2d507678cc95 --- /dev/null +++ b/tests/parity/fixtures/log_compressor/d7e1352bc6d3df4d.json @@ -0,0 +1,30 @@ +{ + "config": { + "dedupe_warnings": true, + "enable_ccr": true, + "error_context_lines": 3, + "keep_first_error": true, + "keep_last_error": true, + "keep_summary_lines": true, + "max_errors": 10, + "max_stack_traces": 3, + "max_total_lines": 100, + "max_warnings": 5, + "min_lines_for_ccr": 50, + "stack_trace_max_lines": 20 + }, + "input": "============================= test session starts ==============================\ncollected 42 items\ntests/test_mod_0.py::test_case PASSED [0%]\ntests/test_mod_1.py::test_case PASSED [2%]\ntests/test_mod_2.py::test_case PASSED [4%]\ntests/test_mod_3.py::test_case PASSED [6%]\ntests/test_mod_4.py::test_case PASSED [8%]\ntests/test_mod_5.py::test_case PASSED [10%]\ntests/test_mod_6.py::test_case PASSED [12%]\ntests/test_mod_7.py::test_case PASSED [14%]\ntests/test_mod_8.py::test_case PASSED [16%]\ntests/test_mod_9.py::test_case PASSED [18%]\ntests/test_mod_10.py::test_case PASSED [20%]\ntests/test_mod_11.py::test_case PASSED [22%]\ntests/test_mod_12.py::test_case PASSED [24%]\ntests/test_mod_13.py::test_case PASSED [26%]\ntests/test_mod_14.py::test_case PASSED [28%]\ntests/test_mod_15.py::test_case PASSED [30%]\ntests/test_mod_16.py::test_case PASSED [32%]\ntests/test_mod_17.py::test_case PASSED [34%]\ntests/test_mod_18.py::test_case PASSED [36%]\ntests/test_mod_19.py::test_case PASSED [38%]\ntests/test_mod_20.py::test_case PASSED [40%]\ntests/test_mod_21.py::test_case PASSED [42%]\ntests/test_mod_22.py::test_case PASSED [44%]\ntests/test_mod_23.py::test_case PASSED [46%]\ntests/test_mod_24.py::test_case PASSED [48%]\ntests/test_mod_25.py::test_bad FAILED\n=================================== FAILURES ===================================\n___________________________________ test_bad ___________________________________\n def test_bad():\n> assert compute(1, 2) == 4\nE assert 3 == 4\ntests/test_mod_25.py:17: AssertionError\n=========================== short test summary info ============================\nFAILED tests/test_mod_25.py::test_bad\n1 failed, 25 passed in 0.42s\n# variant 3", + "input_sha256": "d7e1352bc6d3df4d4bcc4a3ef69c8ac377c8d1383397c12e6f58804eb1cc888e", + "output": { + "cache_key": null, + "compressed": "============================= test session starts ==============================\ncollected 42 items\ntests/test_mod_0.py::test_case PASSED [0%]\ntests/test_mod_1.py::test_case PASSED [2%]\ntests/test_mod_2.py::test_case PASSED [4%]\ntests/test_mod_3.py::test_case PASSED [6%]\ntests/test_mod_4.py::test_case PASSED [8%]\ntests/test_mod_5.py::test_case PASSED [10%]\ntests/test_mod_6.py::test_case PASSED [12%]\ntests/test_mod_7.py::test_case PASSED [14%]\ntests/test_mod_8.py::test_case PASSED [16%]\ntests/test_mod_9.py::test_case PASSED [18%]\ntests/test_mod_10.py::test_case PASSED [20%]\ntests/test_mod_11.py::test_case PASSED [22%]\ntests/test_mod_12.py::test_case PASSED [24%]\ntests/test_mod_13.py::test_case PASSED [26%]\ntests/test_mod_14.py::test_case PASSED [28%]\ntests/test_mod_15.py::test_case PASSED [30%]\ntests/test_mod_16.py::test_case PASSED [32%]\ntests/test_mod_17.py::test_case PASSED [34%]\ntests/test_mod_18.py::test_case PASSED [36%]\ntests/test_mod_19.py::test_case PASSED [38%]\ntests/test_mod_20.py::test_case PASSED [40%]\ntests/test_mod_21.py::test_case PASSED [42%]\ntests/test_mod_22.py::test_case PASSED [44%]\ntests/test_mod_23.py::test_case PASSED [46%]\ntests/test_mod_24.py::test_case PASSED [48%]\ntests/test_mod_25.py::test_bad FAILED\n=================================== FAILURES ===================================\n___________________________________ test_bad ___________________________________\n def test_bad():\n> assert compute(1, 2) == 4\nE assert 3 == 4\ntests/test_mod_25.py:17: AssertionError\n=========================== short test summary info ============================\nFAILED tests/test_mod_25.py::test_bad\n1 failed, 25 passed in 0.42s\n# variant 3", + "compressed_line_count": 38, + "compression_ratio": 1.0, + "format_detected": "generic", + "original": "============================= test session starts ==============================\ncollected 42 items\ntests/test_mod_0.py::test_case PASSED [0%]\ntests/test_mod_1.py::test_case PASSED [2%]\ntests/test_mod_2.py::test_case PASSED [4%]\ntests/test_mod_3.py::test_case PASSED [6%]\ntests/test_mod_4.py::test_case PASSED [8%]\ntests/test_mod_5.py::test_case PASSED [10%]\ntests/test_mod_6.py::test_case PASSED [12%]\ntests/test_mod_7.py::test_case PASSED [14%]\ntests/test_mod_8.py::test_case PASSED [16%]\ntests/test_mod_9.py::test_case PASSED [18%]\ntests/test_mod_10.py::test_case PASSED [20%]\ntests/test_mod_11.py::test_case PASSED [22%]\ntests/test_mod_12.py::test_case PASSED [24%]\ntests/test_mod_13.py::test_case PASSED [26%]\ntests/test_mod_14.py::test_case PASSED [28%]\ntests/test_mod_15.py::test_case PASSED [30%]\ntests/test_mod_16.py::test_case PASSED [32%]\ntests/test_mod_17.py::test_case PASSED [34%]\ntests/test_mod_18.py::test_case PASSED [36%]\ntests/test_mod_19.py::test_case PASSED [38%]\ntests/test_mod_20.py::test_case PASSED [40%]\ntests/test_mod_21.py::test_case PASSED [42%]\ntests/test_mod_22.py::test_case PASSED [44%]\ntests/test_mod_23.py::test_case PASSED [46%]\ntests/test_mod_24.py::test_case PASSED [48%]\ntests/test_mod_25.py::test_bad FAILED\n=================================== FAILURES ===================================\n___________________________________ test_bad ___________________________________\n def test_bad():\n> assert compute(1, 2) == 4\nE assert 3 == 4\ntests/test_mod_25.py:17: AssertionError\n=========================== short test summary info ============================\nFAILED tests/test_mod_25.py::test_bad\n1 failed, 25 passed in 0.42s\n# variant 3", + "original_line_count": 38, + "stats": {} + }, + "recorded_at": "2026-04-23T23:51:37.253988+00:00", + "transform": "log_compressor" +} diff --git a/tests/parity/fixtures/log_compressor/ef61ab0e0029aa96.json b/tests/parity/fixtures/log_compressor/ef61ab0e0029aa96.json new file mode 100644 index 0000000000000000000000000000000000000000..36c681c34b6d1997655c1f54bbe8390f8fe827e2 --- /dev/null +++ b/tests/parity/fixtures/log_compressor/ef61ab0e0029aa96.json @@ -0,0 +1,30 @@ +{ + "config": { + "dedupe_warnings": true, + "enable_ccr": true, + "error_context_lines": 3, + "keep_first_error": true, + "keep_last_error": true, + "keep_summary_lines": true, + "max_errors": 10, + "max_stack_traces": 3, + "max_total_lines": 100, + "max_warnings": 5, + "min_lines_for_ccr": 50, + "stack_trace_max_lines": 20 + }, + "input": " Compiling crate_0 v0.1.0\n Compiling crate_1 v0.1.1\n Compiling crate_2 v0.1.2\n Compiling crate_3 v0.1.3\n Compiling crate_4 v0.1.4\n Compiling crate_5 v0.1.5\n Compiling crate_6 v0.1.6\n Compiling crate_7 v0.1.7\n Compiling crate_8 v0.1.8\n Compiling crate_9 v0.1.9\nerror[E0308]: mismatched types\n --> src/lib.rs:42:9\n |\n42 | return x;\n | ^^^^^^^^^ expected `i32`, found `u64`\nerror: aborting due to previous error\n# variant 10", + "input_sha256": "ef61ab0e0029aa96e629a78e7b74365c5980161db14d6b67ccdde2a9fb2855f7", + "output": { + "cache_key": null, + "compressed": " Compiling crate_0 v0.1.0\n Compiling crate_1 v0.1.1\n Compiling crate_2 v0.1.2\n Compiling crate_3 v0.1.3\n Compiling crate_4 v0.1.4\n Compiling crate_5 v0.1.5\n Compiling crate_6 v0.1.6\n Compiling crate_7 v0.1.7\n Compiling crate_8 v0.1.8\n Compiling crate_9 v0.1.9\nerror[E0308]: mismatched types\n --> src/lib.rs:42:9\n |\n42 | return x;\n | ^^^^^^^^^ expected `i32`, found `u64`\nerror: aborting due to previous error\n# variant 10", + "compressed_line_count": 17, + "compression_ratio": 1.0, + "format_detected": "generic", + "original": " Compiling crate_0 v0.1.0\n Compiling crate_1 v0.1.1\n Compiling crate_2 v0.1.2\n Compiling crate_3 v0.1.3\n Compiling crate_4 v0.1.4\n Compiling crate_5 v0.1.5\n Compiling crate_6 v0.1.6\n Compiling crate_7 v0.1.7\n Compiling crate_8 v0.1.8\n Compiling crate_9 v0.1.9\nerror[E0308]: mismatched types\n --> src/lib.rs:42:9\n |\n42 | return x;\n | ^^^^^^^^^ expected `i32`, found `u64`\nerror: aborting due to previous error\n# variant 10", + "original_line_count": 17, + "stats": {} + }, + "recorded_at": "2026-04-23T23:51:37.256925+00:00", + "transform": "log_compressor" +} diff --git a/tests/parity/fixtures/tokenizer/0c8d2e2560344a24.json b/tests/parity/fixtures/tokenizer/0c8d2e2560344a24.json new file mode 100644 index 0000000000000000000000000000000000000000..e6a8bec056c9e7deed4d0a637883dbb1ecd754c2 --- /dev/null +++ b/tests/parity/fixtures/tokenizer/0c8d2e2560344a24.json @@ -0,0 +1,8 @@ +{ + "config": {}, + "input": "a\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\n\n[[variant 0]]", + "input_sha256": "0c8d2e2560344a2407b63cfa9e2fc156a9af1abc2298f3a3a8074b7bbc241644", + "output": 605, + "recorded_at": "2026-04-23T23:51:37.613894+00:00", + "transform": "tokenizer" +} diff --git a/tests/parity/fixtures/tokenizer/19456eb333a094de.json b/tests/parity/fixtures/tokenizer/19456eb333a094de.json new file mode 100644 index 0000000000000000000000000000000000000000..a73ef6dbd8a5d843972e18eba91d01e2ddd29e28 --- /dev/null +++ b/tests/parity/fixtures/tokenizer/19456eb333a094de.json @@ -0,0 +1,8 @@ +{ + "config": {}, + "input": "You are a helpful assistant. The date is . Request id 0.", + "input_sha256": "19456eb333a094ded597cf903b044bdb2c2476892212455d739f21d2ef445975", + "output": 15, + "recorded_at": "2026-04-23T23:51:37.772009+00:00", + "transform": "tokenizer" +} diff --git a/tests/parity/fixtures/tokenizer/1c75164f0c558b4d.json b/tests/parity/fixtures/tokenizer/1c75164f0c558b4d.json new file mode 100644 index 0000000000000000000000000000000000000000..be6959366d63205fbd1af1577b7c74cd748b4082 --- /dev/null +++ b/tests/parity/fixtures/tokenizer/1c75164f0c558b4d.json @@ -0,0 +1,8 @@ +{ + "config": {}, + "input": "The quick brown fox jumps over the lazy dog.\n[[variant 1]]", + "input_sha256": "1c75164f0c558b4dd9b7209d97562eea5033e160d6eda10509b90ba5362bc899", + "output": 15, + "recorded_at": "2026-04-23T23:51:37.614605+00:00", + "transform": "tokenizer" +} diff --git a/tests/parity/fixtures/tokenizer/21e56c0d138797d6.json b/tests/parity/fixtures/tokenizer/21e56c0d138797d6.json new file mode 100644 index 0000000000000000000000000000000000000000..334a4561f75b9d9378afaa73d3c8b917f39e55d9 --- /dev/null +++ b/tests/parity/fixtures/tokenizer/21e56c0d138797d6.json @@ -0,0 +1,8 @@ +{ + "config": {}, + "input": "You are a helpful assistant. The date is . Request id 13.", + "input_sha256": "21e56c0d138797d637254383ab691bf55a5fc3cbd82f506b6af6a98d9fb2933c", + "output": 15, + "recorded_at": "2026-04-23T23:51:37.784035+00:00", + "transform": "tokenizer" +} diff --git a/tests/parity/fixtures/tokenizer/28837bb1444083d6.json b/tests/parity/fixtures/tokenizer/28837bb1444083d6.json new file mode 100644 index 0000000000000000000000000000000000000000..2c0098082e601fdba1a32617791aacdc6105249e --- /dev/null +++ b/tests/parity/fixtures/tokenizer/28837bb1444083d6.json @@ -0,0 +1,8 @@ +{ + "config": {}, + "input": "You are a helpful assistant. The date is . Request id 14.", + "input_sha256": "28837bb1444083d68a8983f8160d7b7fe7d9d676bd1309b769d3a9db3d1bb4b2", + "output": 15, + "recorded_at": "2026-04-23T23:51:37.784494+00:00", + "transform": "tokenizer" +} diff --git a/tests/parity/fixtures/tokenizer/29a04bdec859732f.json b/tests/parity/fixtures/tokenizer/29a04bdec859732f.json new file mode 100644 index 0000000000000000000000000000000000000000..2e4f07490b0d9c02b56297755e8a7ee8175b2875 --- /dev/null +++ b/tests/parity/fixtures/tokenizer/29a04bdec859732f.json @@ -0,0 +1,8 @@ +{ + "config": {}, + "input": "You are a helpful assistant. The date is . Request id 11.", + "input_sha256": "29a04bdec859732f1be7cf7e65846f363825f2e66c3b36c31a8dacd58a74187f", + "output": 15, + "recorded_at": "2026-04-23T23:51:37.783101+00:00", + "transform": "tokenizer" +} diff --git a/tests/parity/fixtures/tokenizer/2c20d38a59b31925.json b/tests/parity/fixtures/tokenizer/2c20d38a59b31925.json new file mode 100644 index 0000000000000000000000000000000000000000..478f7f861e6bfa63c66c2475562581df6c6e9c23 --- /dev/null +++ b/tests/parity/fixtures/tokenizer/2c20d38a59b31925.json @@ -0,0 +1,8 @@ +{ + "config": {}, + "input": "You are a helpful assistant. The date is . Request id 19.", + "input_sha256": "2c20d38a59b31925debc74b0a3799adf52191fca3195f23ad61acdef52f4d801", + "output": 15, + "recorded_at": "2026-04-23T23:51:37.798471+00:00", + "transform": "tokenizer" +} diff --git a/tests/parity/fixtures/tokenizer/3058a9dd4d4590a7.json b/tests/parity/fixtures/tokenizer/3058a9dd4d4590a7.json new file mode 100644 index 0000000000000000000000000000000000000000..a36d55df46c6f1298b2ac1fc681de01d089c64c5 --- /dev/null +++ b/tests/parity/fixtures/tokenizer/3058a9dd4d4590a7.json @@ -0,0 +1,8 @@ +{ + "config": {}, + "input": "The quick brown fox jumps over the lazy dog.\n[[variant 0]]", + "input_sha256": "3058a9dd4d4590a77097827da2340ff5ca2a598c82c377c47b43f58fa37ce883", + "output": 15, + "recorded_at": "2026-04-23T23:51:37.612361+00:00", + "transform": "tokenizer" +} diff --git a/tests/parity/fixtures/tokenizer/3658f2a54c201dcb.json b/tests/parity/fixtures/tokenizer/3658f2a54c201dcb.json new file mode 100644 index 0000000000000000000000000000000000000000..6a105c468a378c9a9e9285b12dbc3fd74b8e9cf2 --- /dev/null +++ b/tests/parity/fixtures/tokenizer/3658f2a54c201dcb.json @@ -0,0 +1,8 @@ +{ + "config": {}, + "input": "You are a helpful assistant. The date is . Request id 8.", + "input_sha256": "3658f2a54c201dcb0979e15e47892e7b000cbc1410be9d6f907be5743365b59d", + "output": 15, + "recorded_at": "2026-04-23T23:51:37.781692+00:00", + "transform": "tokenizer" +} diff --git a/tests/parity/fixtures/tokenizer/3ae287cf54e20572.json b/tests/parity/fixtures/tokenizer/3ae287cf54e20572.json new file mode 100644 index 0000000000000000000000000000000000000000..4194dcdfc7df46e16c21969a09e7f6c8aad72bb8 --- /dev/null +++ b/tests/parity/fixtures/tokenizer/3ae287cf54e20572.json @@ -0,0 +1,8 @@ +{ + "config": {}, + "input": "You are a helpful assistant. The date is . Request id 4.", + "input_sha256": "3ae287cf54e2057269a47f61db0d62ab27113eea8773aedb96d06c424334d880", + "output": 15, + "recorded_at": "2026-04-23T23:51:37.779582+00:00", + "transform": "tokenizer" +} diff --git a/tests/parity/fixtures/tokenizer/3e27d2a91d7c8ef3.json b/tests/parity/fixtures/tokenizer/3e27d2a91d7c8ef3.json new file mode 100644 index 0000000000000000000000000000000000000000..ab51df5a1cbec113fd648126999e2602e5446ac9 --- /dev/null +++ b/tests/parity/fixtures/tokenizer/3e27d2a91d7c8ef3.json @@ -0,0 +1,8 @@ +{ + "config": {}, + "input": "\ud83d\ude80 unicode \u2728 emoji \u4e2d\u6587 \u0440\u0443\u0441\u0441\u043a\u0438\u0439\n[[variant 1]]", + "input_sha256": "3e27d2a91d7c8ef3f140f4a7beedf90e875cdb61e223516b17bf78d3e1b671dc", + "output": 14, + "recorded_at": "2026-04-23T23:51:37.616864+00:00", + "transform": "tokenizer" +} diff --git a/tests/parity/fixtures/tokenizer/43a16be994276c2a.json b/tests/parity/fixtures/tokenizer/43a16be994276c2a.json new file mode 100644 index 0000000000000000000000000000000000000000..31681710a91e9ab8354375b03057c4c05799c455 --- /dev/null +++ b/tests/parity/fixtures/tokenizer/43a16be994276c2a.json @@ -0,0 +1,8 @@ +{ + "config": {}, + "input": "\ud83d\ude80 unicode \u2728 emoji \u4e2d\u6587 \u0440\u0443\u0441\u0441\u043a\u0438\u0439\n[[variant 0]]", + "input_sha256": "43a16be994276c2a8baa0fcaefcdade676b3b42512233218a2dd42a850d96db8", + "output": 14, + "recorded_at": "2026-04-23T23:51:37.614234+00:00", + "transform": "tokenizer" +} diff --git a/tests/parity/fixtures/tokenizer/4a3533a2e42bbc61.json b/tests/parity/fixtures/tokenizer/4a3533a2e42bbc61.json new file mode 100644 index 0000000000000000000000000000000000000000..02c50144f3457b6628413aa5553302bd0c55302c --- /dev/null +++ b/tests/parity/fixtures/tokenizer/4a3533a2e42bbc61.json @@ -0,0 +1,8 @@ +{ + "config": {}, + "input": "You are a helpful assistant. The date is . Request id 10.", + "input_sha256": "4a3533a2e42bbc61aa5fe7bc2a4581d2ad26938d5c6628ffeb292f5960792e16", + "output": 15, + "recorded_at": "2026-04-23T23:51:37.782638+00:00", + "transform": "tokenizer" +} diff --git a/tests/parity/fixtures/tokenizer/4b314afee956fb6e.json b/tests/parity/fixtures/tokenizer/4b314afee956fb6e.json new file mode 100644 index 0000000000000000000000000000000000000000..fa35e29581b0cdb4b7dd5b6b8638938e43f8a595 --- /dev/null +++ b/tests/parity/fixtures/tokenizer/4b314afee956fb6e.json @@ -0,0 +1,8 @@ +{ + "config": {}, + "input": "You are a helpful assistant. The date is . Request id 7.", + "input_sha256": "4b314afee956fb6e7ead4858e1bc7e05348074dc13bd900df19e011a2639018c", + "output": 15, + "recorded_at": "2026-04-23T23:51:37.781120+00:00", + "transform": "tokenizer" +} diff --git a/tests/parity/fixtures/tokenizer/5e301439bac11b94.json b/tests/parity/fixtures/tokenizer/5e301439bac11b94.json new file mode 100644 index 0000000000000000000000000000000000000000..09251077d5701b49be40226c8678ae01e6c794b5 --- /dev/null +++ b/tests/parity/fixtures/tokenizer/5e301439bac11b94.json @@ -0,0 +1,8 @@ +{ + "config": {}, + "input": "You are a helpful assistant. The date is . Request id 16.", + "input_sha256": "5e301439bac11b945ed07ba9470cc217acbeef2832863718d87fb9dd9b47ea4a", + "output": 15, + "recorded_at": "2026-04-23T23:51:37.791614+00:00", + "transform": "tokenizer" +} diff --git a/tests/parity/fixtures/tokenizer/5e9e501480519ebf.json b/tests/parity/fixtures/tokenizer/5e9e501480519ebf.json new file mode 100644 index 0000000000000000000000000000000000000000..082417f944ae2df4be626dccc0066d67c7266452 --- /dev/null +++ b/tests/parity/fixtures/tokenizer/5e9e501480519ebf.json @@ -0,0 +1,8 @@ +{ + "config": {}, + "input": "\n[[variant 0]]", + "input_sha256": "5e9e501480519ebf466fbb8291051f74bff231145f1a82fea97e7e1923eb9500", + "output": 6, + "recorded_at": "2026-04-23T23:51:37.611642+00:00", + "transform": "tokenizer" +} diff --git a/tests/parity/fixtures/tokenizer/5ea4afd66e350044.json b/tests/parity/fixtures/tokenizer/5ea4afd66e350044.json new file mode 100644 index 0000000000000000000000000000000000000000..d798f051b482826a83a689380cd21e93e7a80722 --- /dev/null +++ b/tests/parity/fixtures/tokenizer/5ea4afd66e350044.json @@ -0,0 +1,8 @@ +{ + "config": {}, + "input": "You are a helpful assistant. The date is . Request id 3.", + "input_sha256": "5ea4afd66e35004452addc9c64791d057e80a5b3600aa3f69fc7a5ef04d6a957", + "output": 15, + "recorded_at": "2026-04-23T23:51:37.778967+00:00", + "transform": "tokenizer" +} diff --git a/tests/parity/fixtures/tokenizer/64b92a621d26bb0a.json b/tests/parity/fixtures/tokenizer/64b92a621d26bb0a.json new file mode 100644 index 0000000000000000000000000000000000000000..acb9b70e84c911049ab9b15fc9d898288737b307 --- /dev/null +++ b/tests/parity/fixtures/tokenizer/64b92a621d26bb0a.json @@ -0,0 +1,8 @@ +{ + "config": {}, + "input": "You are a helpful assistant. The date is . Request id 9.", + "input_sha256": "64b92a621d26bb0abe7bd462db3bdd79dd7e54cef82ec3ec2e9968350be3c26f", + "output": 15, + "recorded_at": "2026-04-23T23:51:37.782172+00:00", + "transform": "tokenizer" +} diff --git a/tests/parity/fixtures/tokenizer/675ed2a00f670b59.json b/tests/parity/fixtures/tokenizer/675ed2a00f670b59.json new file mode 100644 index 0000000000000000000000000000000000000000..5f5ac65c5eb6924aed8126afbe64dbc34a3928fe --- /dev/null +++ b/tests/parity/fixtures/tokenizer/675ed2a00f670b59.json @@ -0,0 +1,8 @@ +{ + "config": {}, + "input": "def foo(x):\n return x + 1\n\nclass Bar:\n pass\n[[variant 0]]", + "input_sha256": "675ed2a00f670b594c21f551f2dbf8ca15da0bcf301bfceb7187f3a3af205609", + "output": 22, + "recorded_at": "2026-04-23T23:51:37.612786+00:00", + "transform": "tokenizer" +} diff --git a/tests/parity/fixtures/tokenizer/67f44d29b70020ae.json b/tests/parity/fixtures/tokenizer/67f44d29b70020ae.json new file mode 100644 index 0000000000000000000000000000000000000000..1b37420d15f2a011ae54e188a908f8858c35aa1d --- /dev/null +++ b/tests/parity/fixtures/tokenizer/67f44d29b70020ae.json @@ -0,0 +1,8 @@ +{ + "config": {}, + "input": "You are a helpful assistant. The date is . Request id 5.", + "input_sha256": "67f44d29b70020ae835de677f7130474221674343351193674bf5a46d90fa589", + "output": 15, + "recorded_at": "2026-04-23T23:51:37.780123+00:00", + "transform": "tokenizer" +} diff --git a/tests/parity/fixtures/tokenizer/6dd3675c600e922d.json b/tests/parity/fixtures/tokenizer/6dd3675c600e922d.json new file mode 100644 index 0000000000000000000000000000000000000000..e62338f2824e4ffbbe52117d0e7a42ec3b3c2c99 --- /dev/null +++ b/tests/parity/fixtures/tokenizer/6dd3675c600e922d.json @@ -0,0 +1,8 @@ +{ + "config": {}, + "input": "You are a helpful assistant. The date is . Request id 15.", + "input_sha256": "6dd3675c600e922d6273a899ab3892468c7a631ed3b7fbde864fe0482b19d7cf", + "output": 15, + "recorded_at": "2026-04-23T23:51:37.786949+00:00", + "transform": "tokenizer" +} diff --git a/tests/parity/fixtures/tokenizer/7421cdb88c6f14df.json b/tests/parity/fixtures/tokenizer/7421cdb88c6f14df.json new file mode 100644 index 0000000000000000000000000000000000000000..374b8b3b97395a26b7c68c8e5ebae7ff9bb7db98 --- /dev/null +++ b/tests/parity/fixtures/tokenizer/7421cdb88c6f14df.json @@ -0,0 +1,8 @@ +{ + "config": {}, + "input": "hello\n[[variant 0]]", + "input_sha256": "7421cdb88c6f14dff6ddaa082b792d4d44d6cd3c929f5178c5b85a722966fd1a", + "output": 7, + "recorded_at": "2026-04-23T23:51:37.612029+00:00", + "transform": "tokenizer" +} diff --git a/tests/parity/fixtures/tokenizer/7b0e5eb9ea094adc.json b/tests/parity/fixtures/tokenizer/7b0e5eb9ea094adc.json new file mode 100644 index 0000000000000000000000000000000000000000..a1154c29993f8ab1c5a78a155a5e322d6483e084 --- /dev/null +++ b/tests/parity/fixtures/tokenizer/7b0e5eb9ea094adc.json @@ -0,0 +1,8 @@ +{ + "config": {}, + "input": "You are a helpful assistant. The date is . Request id 18.", + "input_sha256": "7b0e5eb9ea094adc5f5559d23e79b6833dd55f2c7642d0b16381e7d0e78b0d5c", + "output": 15, + "recorded_at": "2026-04-23T23:51:37.796726+00:00", + "transform": "tokenizer" +} diff --git a/tests/parity/fixtures/tokenizer/8d2a28d938e958e6.json b/tests/parity/fixtures/tokenizer/8d2a28d938e958e6.json new file mode 100644 index 0000000000000000000000000000000000000000..a0a432ec56d8405f9a3d40a839525601e08d037d --- /dev/null +++ b/tests/parity/fixtures/tokenizer/8d2a28d938e958e6.json @@ -0,0 +1,8 @@ +{ + "config": {}, + "input": "Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n[[variant 0]]", + "input_sha256": "8d2a28d938e958e634effce9c014bac79728fb114ae434e948e1bf78a2d21aa8", + "output": 15, + "recorded_at": "2026-04-23T23:51:37.612604+00:00", + "transform": "tokenizer" +} diff --git a/tests/parity/fixtures/tokenizer/ac459b9dbc008e75.json b/tests/parity/fixtures/tokenizer/ac459b9dbc008e75.json new file mode 100644 index 0000000000000000000000000000000000000000..dc58cab44203f710656965b481cc6d7f419d33cc --- /dev/null +++ b/tests/parity/fixtures/tokenizer/ac459b9dbc008e75.json @@ -0,0 +1,8 @@ +{ + "config": {}, + "input": "\n[[variant 1]]", + "input_sha256": "ac459b9dbc008e753fe345de5bd12ebc38c0d293baa4fef86ce14e2973121c16", + "output": 6, + "recorded_at": "2026-04-23T23:51:37.614360+00:00", + "transform": "tokenizer" +} diff --git a/tests/parity/fixtures/tokenizer/ae10c17413b82252.json b/tests/parity/fixtures/tokenizer/ae10c17413b82252.json new file mode 100644 index 0000000000000000000000000000000000000000..a461df1eb34c82087df766be5a3d77710ad8c48e --- /dev/null +++ b/tests/parity/fixtures/tokenizer/ae10c17413b82252.json @@ -0,0 +1,8 @@ +{ + "config": {}, + "input": "def foo(x):\n return x + 1\n\nclass Bar:\n pass\n[[variant 1]]", + "input_sha256": "ae10c17413b822526610538d35fd4c397522fc3b46d1705bbe5ae3071901d9c1", + "output": 22, + "recorded_at": "2026-04-23T23:51:37.615459+00:00", + "transform": "tokenizer" +} diff --git a/tests/parity/fixtures/tokenizer/b5fcd690c3829eba.json b/tests/parity/fixtures/tokenizer/b5fcd690c3829eba.json new file mode 100644 index 0000000000000000000000000000000000000000..75cfe47abe141ef8ea41ad1e200e1bc670a3b924 --- /dev/null +++ b/tests/parity/fixtures/tokenizer/b5fcd690c3829eba.json @@ -0,0 +1,8 @@ +{ + "config": {}, + "input": "You are a helpful assistant. The date is . Request id 2.", + "input_sha256": "b5fcd690c3829eba25604f3535219c46d370c3290c5311a3a4442763911b7415", + "output": 15, + "recorded_at": "2026-04-23T23:51:37.776880+00:00", + "transform": "tokenizer" +} diff --git a/tests/parity/fixtures/tokenizer/b714e53084605087.json b/tests/parity/fixtures/tokenizer/b714e53084605087.json new file mode 100644 index 0000000000000000000000000000000000000000..1ab9a5611023770c9fe6148f9d065be4d05119cc --- /dev/null +++ b/tests/parity/fixtures/tokenizer/b714e53084605087.json @@ -0,0 +1,8 @@ +{ + "config": {}, + "input": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n[[variant 0]]", + "input_sha256": "b714e53084605087fccc704d6fb7299641d0f3f2c37dda853281cc31539dc30e", + "output": 131, + "recorded_at": "2026-04-23T23:51:37.613511+00:00", + "transform": "tokenizer" +} diff --git a/tests/parity/fixtures/tokenizer/c2ddce4c6eeb637d.json b/tests/parity/fixtures/tokenizer/c2ddce4c6eeb637d.json new file mode 100644 index 0000000000000000000000000000000000000000..2266c7335ae7fe240f01d7687d06ea929c16aa61 --- /dev/null +++ b/tests/parity/fixtures/tokenizer/c2ddce4c6eeb637d.json @@ -0,0 +1,8 @@ +{ + "config": {}, + "input": "a\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\na\nb\nc\n\n[[variant 1]]", + "input_sha256": "c2ddce4c6eeb637d12b32653e2ffe1946f3335b0f79be9ff06fbbdb58cc623a8", + "output": 605, + "recorded_at": "2026-04-23T23:51:37.616571+00:00", + "transform": "tokenizer" +} diff --git a/tests/parity/fixtures/tokenizer/c787dbaa804482e4.json b/tests/parity/fixtures/tokenizer/c787dbaa804482e4.json new file mode 100644 index 0000000000000000000000000000000000000000..88fc04272fdfa592504dde4975b4a8cb9e7c9159 --- /dev/null +++ b/tests/parity/fixtures/tokenizer/c787dbaa804482e4.json @@ -0,0 +1,8 @@ +{ + "config": {}, + "input": "SELECT id, name FROM users WHERE email LIKE '%@example.com'\n[[variant 0]]", + "input_sha256": "c787dbaa804482e4882fa97f0a2309e5799c229df2639224df3ded5660b032aa", + "output": 19, + "recorded_at": "2026-04-23T23:51:37.612983+00:00", + "transform": "tokenizer" +} diff --git a/tests/parity/fixtures/tokenizer/d94694095d50af14.json b/tests/parity/fixtures/tokenizer/d94694095d50af14.json new file mode 100644 index 0000000000000000000000000000000000000000..15f0328208d6da16b00771b4232ca48d8348509c --- /dev/null +++ b/tests/parity/fixtures/tokenizer/d94694095d50af14.json @@ -0,0 +1,8 @@ +{ + "config": {}, + "input": "{\"role\": \"user\", \"content\": \"hi\"}\n[[variant 0]]", + "input_sha256": "d94694095d50af149351f14d1473ed010cfb78a1d360037347184f0cc088e6ac", + "output": 17, + "recorded_at": "2026-04-23T23:51:37.614059+00:00", + "transform": "tokenizer" +} diff --git a/tests/parity/fixtures/tokenizer/db97c1684b0d0b4f.json b/tests/parity/fixtures/tokenizer/db97c1684b0d0b4f.json new file mode 100644 index 0000000000000000000000000000000000000000..a0175ea0d87fd1d8d4a8869c6c8968d58f3b5311 --- /dev/null +++ b/tests/parity/fixtures/tokenizer/db97c1684b0d0b4f.json @@ -0,0 +1,8 @@ +{ + "config": {}, + "input": "Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n[[variant 1]]", + "input_sha256": "db97c1684b0d0b4fd1f342888e6a2cc8916a05555ce7ab7792f5951390d348fa", + "output": 15, + "recorded_at": "2026-04-23T23:51:37.615100+00:00", + "transform": "tokenizer" +} diff --git a/tests/parity/fixtures/tokenizer/dba3a3310f24e9fd.json b/tests/parity/fixtures/tokenizer/dba3a3310f24e9fd.json new file mode 100644 index 0000000000000000000000000000000000000000..215e60d140eaa2af9b0e89d25182ee4a7f44a79f --- /dev/null +++ b/tests/parity/fixtures/tokenizer/dba3a3310f24e9fd.json @@ -0,0 +1,8 @@ +{ + "config": {}, + "input": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n[[variant 1]]", + "input_sha256": "dba3a3310f24e9fdd63ea91a69c55bf7b09fd07c791bf86150868dac3955f44d", + "output": 131, + "recorded_at": "2026-04-23T23:51:37.616175+00:00", + "transform": "tokenizer" +} diff --git a/tests/parity/fixtures/tokenizer/ddb0c93fe06909c7.json b/tests/parity/fixtures/tokenizer/ddb0c93fe06909c7.json new file mode 100644 index 0000000000000000000000000000000000000000..dd19ba9e8cafda569eba39b9f2f05853f35e1695 --- /dev/null +++ b/tests/parity/fixtures/tokenizer/ddb0c93fe06909c7.json @@ -0,0 +1,8 @@ +{ + "config": {}, + "input": "You are a helpful assistant. The date is . Request id 12.", + "input_sha256": "ddb0c93fe06909c7270a7381d28cd02fb5a87d3fe3b9744a94ea3f0bad66e61d", + "output": 15, + "recorded_at": "2026-04-23T23:51:37.783567+00:00", + "transform": "tokenizer" +} diff --git a/tests/parity/fixtures/tokenizer/df1c3d2b4cea38a5.json b/tests/parity/fixtures/tokenizer/df1c3d2b4cea38a5.json new file mode 100644 index 0000000000000000000000000000000000000000..6d0712fe561cb9cb5db58fafcc3760537e66268e --- /dev/null +++ b/tests/parity/fixtures/tokenizer/df1c3d2b4cea38a5.json @@ -0,0 +1,8 @@ +{ + "config": {}, + "input": "You are a helpful assistant. The date is . Request id 1.", + "input_sha256": "df1c3d2b4cea38a5357c953e2015aa3b7880350db1ac209f6c693b7c6933bded", + "output": 15, + "recorded_at": "2026-04-23T23:51:37.775255+00:00", + "transform": "tokenizer" +} diff --git a/tests/parity/fixtures/tokenizer/dfab157ab6ff0bf9.json b/tests/parity/fixtures/tokenizer/dfab157ab6ff0bf9.json new file mode 100644 index 0000000000000000000000000000000000000000..ca5cc3d333b0291dcf5351fb2c7be7ec460e0223 --- /dev/null +++ b/tests/parity/fixtures/tokenizer/dfab157ab6ff0bf9.json @@ -0,0 +1,8 @@ +{ + "config": {}, + "input": "SELECT id, name FROM users WHERE email LIKE '%@example.com'\n[[variant 1]]", + "input_sha256": "dfab157ab6ff0bf9acd5f7ef0bac3ca7621f77da0550caa8aa3d89076b9b44a0", + "output": 19, + "recorded_at": "2026-04-23T23:51:37.615652+00:00", + "transform": "tokenizer" +} diff --git a/tests/parity/fixtures/tokenizer/e9ff95fb7cdd0236.json b/tests/parity/fixtures/tokenizer/e9ff95fb7cdd0236.json new file mode 100644 index 0000000000000000000000000000000000000000..97c89a06092067172ec0db1dae0c03423444a686 --- /dev/null +++ b/tests/parity/fixtures/tokenizer/e9ff95fb7cdd0236.json @@ -0,0 +1,8 @@ +{ + "config": {}, + "input": "hello\n[[variant 1]]", + "input_sha256": "e9ff95fb7cdd023626aa919c9194975d3a26e95ad0ef2c766ab649bdf9c198f6", + "output": 7, + "recorded_at": "2026-04-23T23:51:37.614484+00:00", + "transform": "tokenizer" +} diff --git a/tests/parity/fixtures/tokenizer/ef39f30c64707d97.json b/tests/parity/fixtures/tokenizer/ef39f30c64707d97.json new file mode 100644 index 0000000000000000000000000000000000000000..7c55f8f3a1df5526cb84dd6e7357ad7a4f1533d9 --- /dev/null +++ b/tests/parity/fixtures/tokenizer/ef39f30c64707d97.json @@ -0,0 +1,8 @@ +{ + "config": {}, + "input": "{\"role\": \"user\", \"content\": \"hi\"}\n[[variant 1]]", + "input_sha256": "ef39f30c64707d975d94bad0cc188755e40bdd2ea926366e74f606ed6cbb1ebd", + "output": 17, + "recorded_at": "2026-04-23T23:51:37.616732+00:00", + "transform": "tokenizer" +} diff --git a/tests/parity/fixtures/tokenizer/f2890229e85eace8.json b/tests/parity/fixtures/tokenizer/f2890229e85eace8.json new file mode 100644 index 0000000000000000000000000000000000000000..3260832d0e2efbc209a7971f2d963f34167d170e --- /dev/null +++ b/tests/parity/fixtures/tokenizer/f2890229e85eace8.json @@ -0,0 +1,8 @@ +{ + "config": {}, + "input": "You are a helpful assistant. The date is . Request id 17.", + "input_sha256": "f2890229e85eace8ed595e178f748698d6405429d3f65eeae8ddd79c5f7313f4", + "output": 15, + "recorded_at": "2026-04-23T23:51:37.793496+00:00", + "transform": "tokenizer" +} diff --git a/tests/parity/fixtures/tokenizer/f689763e8643cc5e.json b/tests/parity/fixtures/tokenizer/f689763e8643cc5e.json new file mode 100644 index 0000000000000000000000000000000000000000..285d181b67f7dd7add6243abc2f099efe16927d5 --- /dev/null +++ b/tests/parity/fixtures/tokenizer/f689763e8643cc5e.json @@ -0,0 +1,8 @@ +{ + "config": {}, + "input": "You are a helpful assistant. The date is . Request id 6.", + "input_sha256": "f689763e8643cc5e34ce65cd627306b15911b7edf711c13a40c3879a42f4a6b1", + "output": 15, + "recorded_at": "2026-04-23T23:51:37.780641+00:00", + "transform": "tokenizer" +} diff --git a/tests/parity/recorder.py b/tests/parity/recorder.py new file mode 100644 index 0000000000000000000000000000000000000000..f167ebf952b3883b51a83908d017df1d87979ee4 --- /dev/null +++ b/tests/parity/recorder.py @@ -0,0 +1,552 @@ +"""Fixture recorder for the Rust-vs-Python parity harness. + +This module is new (no modifications to existing Python under `headroom/`). +It provides a decorator that captures `(input, config, output)` triples and +writes them as JSON fixtures under +`tests/parity/fixtures//.json`. + +`record_all()` monkey-patches the Phase-1 transform classes in-process so +that every call made during a workload produces a fixture. The +`scripts/record_fixtures.py` entry point drives a minimal synthetic workload +and does not require network access or real LLM calls. + +Schema of each fixture file: + +```json +{ + "transform": "log_compressor", + "input": "", + "config": { "max_total_lines": 100, ... }, + "output": "", + "recorded_at": "2026-04-23T00:00:00Z", + "input_sha256": "" +} +``` +""" + +from __future__ import annotations + +import datetime as _dt +import functools +import hashlib +import json +import logging +from collections.abc import Callable +from dataclasses import asdict, is_dataclass +from pathlib import Path +from typing import Any + +LOG = logging.getLogger("headroom.parity.recorder") + +# tests/parity/recorder.py -> repo root -> tests/parity/fixtures +_REPO_ROOT = Path(__file__).resolve().parent.parent.parent +_FIXTURES_ROOT = _REPO_ROOT / "tests" / "parity" / "fixtures" + + +# --------------------------------------------------------------------------- +# JSON-safe serialization helpers +# --------------------------------------------------------------------------- + + +def _json_default(obj: Any) -> Any: + """Best-effort JSON fallback for dataclasses/enums/bytes.""" + if is_dataclass(obj) and not isinstance(obj, type): + return asdict(obj) + if hasattr(obj, "value") and hasattr(obj, "name"): # enum.Enum + return obj.value + if isinstance(obj, set | frozenset): + return sorted(obj) + if isinstance(obj, bytes): + return obj.decode("utf-8", errors="replace") + if hasattr(obj, "__dict__"): + return vars(obj) + return repr(obj) + + +def _to_jsonable(obj: Any) -> Any: + """Round-trip obj through json to guarantee it's JSON-safe.""" + return json.loads(json.dumps(obj, default=_json_default, sort_keys=True)) + + +def _canonical_digest(payload: Any) -> str: + blob = json.dumps(payload, default=_json_default, sort_keys=True).encode("utf-8") + return hashlib.sha256(blob).hexdigest() + + +# --------------------------------------------------------------------------- +# Core `@record` decorator +# --------------------------------------------------------------------------- + + +def record( + transform_name: str, + *, + root: Path | None = None, + input_arg: int = 0, + input_kw: str | None = None, + config_attr: str = "config", +) -> Callable[[Callable[..., Any]], Callable[..., Any]]: + """Wrap a `(input, ...) -> output` callable so every call writes a fixture. + + Args: + transform_name: Name of the transform (matches Rust comparator name + and the fixture subdirectory). + root: Override the fixtures root (mostly for tests). + input_arg: Positional index of the primary input. Default 0; use + `input_kw` for keyword-only callables. + input_kw: Keyword name of the primary input. Takes precedence over + `input_arg` when the kwarg is present. + config_attr: Attribute on `self` whose value should be serialized as + the fixture config. Defaults to `"config"`; methods that have no + such attribute record an empty config. + """ + fixtures_root = Path(root) if root is not None else _FIXTURES_ROOT + out_dir = fixtures_root / transform_name + out_dir.mkdir(parents=True, exist_ok=True) + + def decorator(fn: Callable[..., Any]) -> Callable[..., Any]: + @functools.wraps(fn) + def wrapper(*args: Any, **kwargs: Any) -> Any: + result = fn(*args, **kwargs) + try: + _write_fixture( + fn=fn, + transform_name=transform_name, + out_dir=out_dir, + args=args, + kwargs=kwargs, + input_arg=input_arg, + input_kw=input_kw, + config_attr=config_attr, + result=result, + ) + except Exception as e: # pragma: no cover - best effort + LOG.warning("recorder: failed to write fixture for %s: %s", transform_name, e) + return result + + wrapper.__wrapped__ = fn # type: ignore[attr-defined] + return wrapper + + return decorator + + +def _write_fixture( + *, + fn: Callable[..., Any], + transform_name: str, + out_dir: Path, + args: tuple[Any, ...], + kwargs: dict[str, Any], + input_arg: int, + input_kw: str | None, + config_attr: str, + result: Any, +) -> None: + # Resolve the primary input. If this is a bound method, args[0] is self + # and the actual input lives at args[input_arg + 1]. + is_method = bool(args) and not isinstance( + args[0], str | bytes | list | dict | int | float | bool | type(None) + ) + self_obj = args[0] if is_method else None + positional_inputs = args[1:] if is_method else args + + if input_kw and input_kw in kwargs: + primary_input = kwargs[input_kw] + elif len(positional_inputs) > input_arg: + primary_input = positional_inputs[input_arg] + else: + primary_input = None + + config_obj: Any = {} + if self_obj is not None and hasattr(self_obj, config_attr): + config_obj = getattr(self_obj, config_attr) + + payload_input = _to_jsonable(primary_input) + payload_config = _to_jsonable(config_obj) + payload_output = _to_jsonable(result) + + digest_source = { + "transform": transform_name, + "input": payload_input, + "config": payload_config, + "fn": f"{fn.__module__}.{fn.__qualname__}", + } + digest = _canonical_digest(digest_source) + + fixture = { + "transform": transform_name, + "input": payload_input, + "config": payload_config, + "output": payload_output, + "recorded_at": _dt.datetime.now(tz=_dt.timezone.utc).isoformat(), + "input_sha256": digest, + } + + target = out_dir / f"{digest[:16]}.json" + target.write_text(json.dumps(fixture, indent=2, sort_keys=True) + "\n") + + +# --------------------------------------------------------------------------- +# record_all(): monkey-patch the Phase-1 transforms +# --------------------------------------------------------------------------- + + +def record_all(root: Path | None = None) -> dict[str, str]: + """Monkey-patch the Phase-1 transform classes so every call writes a + fixture. Returns a dict mapping transform name -> status ("patched", + "blocked:"). + + Safe to call repeatedly; wrappers are idempotent (they tag themselves). + """ + statuses: dict[str, str] = {} + + # --- log_compressor ---------------------------------------------------- + try: + from headroom.transforms.log_compressor import LogCompressor + + _wrap_method(LogCompressor, "compress", "log_compressor", root=root) + statuses["log_compressor"] = "patched" + except Exception as e: + statuses["log_compressor"] = f"blocked:{e.__class__.__name__}:{e}" + + # --- diff_compressor --------------------------------------------------- + try: + from headroom.transforms.diff_compressor import DiffCompressor + + _wrap_method(DiffCompressor, "compress", "diff_compressor", root=root) + statuses["diff_compressor"] = "patched" + except Exception as e: + statuses["diff_compressor"] = f"blocked:{e.__class__.__name__}:{e}" + + # --- tokenizer --------------------------------------------------------- + try: + from headroom.tokenizer import Tokenizer + + _wrap_method(Tokenizer, "count_text", "tokenizer", root=root) + statuses["tokenizer"] = "patched" + except Exception as e: + statuses["tokenizer"] = f"blocked:{e.__class__.__name__}:{e}" + + # --- cache_aligner ----------------------------------------------------- + # CacheAligner.apply() takes a Tokenizer argument — recording its output + # requires building a tokenizer. We do that in the workload driver, but + # still install the patch here so calls made elsewhere are captured. + try: + from headroom.transforms.cache_aligner import CacheAligner + + _wrap_method( + CacheAligner, + "apply", + "cache_aligner", + root=root, + input_arg=0, # first non-self positional is `messages` + ) + statuses["cache_aligner"] = "patched" + except Exception as e: + statuses["cache_aligner"] = f"blocked:{e.__class__.__name__}:{e}" + + # --- ccr --------------------------------------------------------------- + # Phase 0: the Python CCR implementation is split across a tool injector + # (encoder-side) and a response handler (decoder-side). We record the + # deterministic synchronous entry point on the injector. The + # response-handler decode path is async + requires a batch store, so it + # is recorded only from the workload driver with a real injected call. + try: + from headroom.ccr.tool_injection import CCRToolInjector + + _wrap_method( + CCRToolInjector, + "inject_tool_definition", + "ccr", + root=root, + ) + statuses["ccr"] = "patched" + except Exception as e: + statuses["ccr"] = f"blocked:{e.__class__.__name__}:{e}" + + return statuses + + +def _wrap_method( + cls: type, + method_name: str, + transform_name: str, + *, + root: Path | None = None, + input_arg: int = 0, + input_kw: str | None = None, +) -> None: + original = getattr(cls, method_name) + if getattr(original, "_parity_recorder_wrapped", False): + return # idempotent + + decorator = record( + transform_name, + root=root, + input_arg=input_arg, + input_kw=input_kw, + ) + wrapped = decorator(original) + wrapped._parity_recorder_wrapped = True # type: ignore[attr-defined] + setattr(cls, method_name, wrapped) + + +# --------------------------------------------------------------------------- +# Minimal workload helpers — callable independently of the scripts entry. +# --------------------------------------------------------------------------- + + +def _varied_log_inputs() -> list[str]: + """20 varied log-compressor inputs: short, medium, long; pytest/npm/cargo/etc.""" + base_short = [ + "INFO starting\nERROR database connection failed\nINFO shutting down", + "PASSED test_foo\nPASSED test_bar\nFAILED test_baz\nassert 1 == 2", + ] + pytest_output = "\n".join( + [ + "============================= test session starts ==============================", + "collected 42 items", + *[f"tests/test_mod_{i}.py::test_case PASSED [{i * 2}%]" for i in range(25)], + "tests/test_mod_25.py::test_bad FAILED", + "=================================== FAILURES ===================================", + "___________________________________ test_bad ___________________________________", + " def test_bad():", + "> assert compute(1, 2) == 4", + "E assert 3 == 4", + "tests/test_mod_25.py:17: AssertionError", + "=========================== short test summary info ============================", + "FAILED tests/test_mod_25.py::test_bad", + "1 failed, 25 passed in 0.42s", + ] + ) + npm_output = "\n".join( + [ + "npm WARN deprecated foo@1.0.0: use bar", + *[f"added {i} packages in 3s" for i in range(5)], + "npm ERR! code ERESOLVE", + "npm ERR! ERESOLVE unable to resolve dependency tree", + "npm ERR! While resolving: project@1.0.0", + "npm ERR! Found: react@17.0.2", + ] + ) + cargo_output = "\n".join( + [ + *[f" Compiling crate_{i} v0.1.{i}" for i in range(10)], + "error[E0308]: mismatched types", + " --> src/lib.rs:42:9", + " |", + "42 | return x;", + " | ^^^^^^^^^ expected `i32`, found `u64`", + "error: aborting due to previous error", + ] + ) + make_output = "make: *** [Makefile:12: all] Error 2\ngcc -c foo.c -o foo.o\nfoo.c:5:3: error: 'undeclared' undeclared" + big = "\n".join( + [f"line {i}: INFO processing request" for i in range(300)] + + [ + "ERROR something broke at step 42", + "Traceback (most recent call last):", + ' File "a.py", line 5', + "RuntimeError: boom", + ] + ) + + out = list(base_short) + out.extend([pytest_output] * 3) + out.extend([npm_output] * 3) + out.extend([cargo_output] * 4) + out.append(make_output) + out.append(big) + # pad variants so we're at >= 20 unique inputs + for i in range(20 - len(out)): + out.append(f"INFO iteration {i}\nERROR error {i}\nWARN warn {i}\nINFO done {i}") + # tweak each to guarantee uniqueness + return [f"{s}\n# variant {i}" for i, s in enumerate(out)] + + +def _varied_diff_inputs() -> list[str]: + tiny = """diff --git a/a.py b/a.py +--- a/a.py ++++ b/a.py +@@ -1,3 +1,3 @@ +-x = 1 ++x = 2 + y = 3 + z = 4 +""" + medium = "\n".join( + [ + "diff --git a/src/main.rs b/src/main.rs", + "--- a/src/main.rs", + "+++ b/src/main.rs", + "@@ -10,7 +10,7 @@", + *[f" unchanged_line_{i}" for i in range(5)], + "- let x = 1;", + "+ let x = 2;", + *[f" unchanged_after_{i}" for i in range(5)], + ] + ) + big = [] + for f in range(8): + big.append(f"diff --git a/file_{f}.py b/file_{f}.py") + big.append(f"--- a/file_{f}.py") + big.append(f"+++ b/file_{f}.py") + big.append("@@ -1,10 +1,12 @@") + big.extend([f" context_{i}_{f}" for i in range(5)]) + big.extend([f"-removed_{i}_{f}" for i in range(3)]) + big.extend([f"+added_{i}_{f}" for i in range(5)]) + big.extend([f" tail_{i}_{f}" for i in range(5)]) + big_diff = "\n".join(big) + + new_file = """diff --git a/new.py b/new.py +new file mode 100644 +--- /dev/null ++++ b/new.py +@@ -0,0 +1,4 @@ ++def hello(): ++ return 'world' ++ ++x = hello() +""" + out: list[str] = [] + for i in range(7): + out.append(f"{tiny}# variant {i}") + for i in range(6): + out.append(f"{medium}\n# variant {i}") + for i in range(4): + out.append(f"{big_diff}\n# variant {i}") + for i in range(3): + out.append(f"{new_file}# variant {i}") + return out[:20] if len(out) >= 20 else out + [tiny] * (20 - len(out)) + + +def _varied_text_inputs() -> list[str]: + base = [ + "", + "hello", + "The quick brown fox jumps over the lazy dog.", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", + "def foo(x):\n return x + 1\n\nclass Bar:\n pass", + "SELECT id, name FROM users WHERE email LIKE '%@example.com'", + "a" * 1000, + "a\nb\nc\n" * 100, + '{"role": "user", "content": "hi"}', + "🚀 unicode ✨ emoji 中文 русский", + ] + out: list[str] = [] + for i in range(2): + for b in base: + out.append(f"{b}\n[[variant {i}]]") + return out[:20] + + +def _varied_message_batches() -> list[list[dict[str, Any]]]: + today = _dt.date.today().isoformat() + out: list[list[dict[str, Any]]] = [] + for i in range(20): + out.append( + [ + { + "role": "system", + "content": f"You are a helpful assistant. The date is {today}. Request id {i}.", + }, + {"role": "user", "content": f"Question number {i}: what is 2+2?"}, + ] + ) + return out + + +def run_default_workload(root: Path | None = None) -> dict[str, int]: + """Drive synthetic inputs through every patched transform. Returns a + dict mapping transform name to count of fixtures produced.""" + counts: dict[str, int] = { + "log_compressor": 0, + "diff_compressor": 0, + "tokenizer": 0, + "cache_aligner": 0, + "ccr": 0, + } + + # log_compressor + try: + from headroom.transforms.log_compressor import LogCompressor + + lc = LogCompressor() + for s in _varied_log_inputs(): + lc.compress(s) + counts["log_compressor"] += 1 + except Exception as e: + LOG.warning("log_compressor workload failed: %s", e) + + # diff_compressor + try: + from headroom.transforms.diff_compressor import DiffCompressor + + dc = DiffCompressor() + for s in _varied_diff_inputs(): + dc.compress(s) + counts["diff_compressor"] += 1 + except Exception as e: + LOG.warning("diff_compressor workload failed: %s", e) + + # tokenizer + try: + from headroom.providers.openai import OpenAITokenCounter + from headroom.tokenizer import Tokenizer + + tok = Tokenizer(OpenAITokenCounter("gpt-4o-mini"), model="gpt-4o-mini") + for s in _varied_text_inputs(): + tok.count_text(s) + counts["tokenizer"] += 1 + except Exception as e: + LOG.warning("tokenizer workload failed: %s", e) + + # cache_aligner — needs Tokenizer; reuse the one above + try: + from headroom.providers.openai import OpenAITokenCounter + from headroom.tokenizer import Tokenizer + from headroom.transforms.cache_aligner import CacheAligner + + tok = Tokenizer(OpenAITokenCounter("gpt-4o-mini"), model="gpt-4o-mini") + aligner = CacheAligner() + for batch in _varied_message_batches(): + aligner.apply(batch, tok) + counts["cache_aligner"] += 1 + except Exception as e: + LOG.warning("cache_aligner workload failed: %s", e) + + # ccr — CCRToolInjector is a dataclass whose `inject_tool_definition` + # takes `tools: list[dict] | None` and returns `(tools, was_injected)`. + # It only mutates state when it has already scanned messages with + # compression markers, so we force `has_compressed_content` by planting + # a hash in the detected set directly. + try: + from headroom.ccr.tool_injection import CCRToolInjector + + for i in range(25): + injector = CCRToolInjector(provider="anthropic" if i % 2 == 0 else "openai") + # Plant a unique 24-hex-char hash per iteration so the injector + # treats each call as having compressed content. + planted_hash = hashlib.sha256(f"planted-{i}".encode()).hexdigest()[:24] + injector._detected_hashes.append(planted_hash) # noqa: SLF001 + # Always include a unique marker tool in the list so input + # hashes never collide across iterations. + existing_tools: list[dict[str, Any]] | None = [ + {"name": f"other_tool_{i}", "description": f"desc {i}"} + ] + try: + injector.inject_tool_definition(existing_tools) + counts["ccr"] += 1 + except Exception as e: + LOG.debug("ccr inject failed on input %d: %s", i, e) + except Exception as e: + LOG.warning("ccr workload failed: %s", e) + + return counts + + +__all__ = [ + "record", + "record_all", + "run_default_workload", +]