diff --git a/diagnostic/build-c0ddfd5c.json b/diagnostic/build-c0ddfd5c.json new file mode 100644 index 00000000..9c2f7d62 --- /dev/null +++ b/diagnostic/build-c0ddfd5c.json @@ -0,0 +1,86 @@ +{ + "generated_at": "2026-06-19T20:13:57.422522+00:00", + "commit": "c0ddfd5c", + "diagnostic_logd": "diagnostic/build-c0ddfd5c.logd", + "diagnostic_logd_error": null, + "chunked": false, + "chunk_size_bytes": null, + "password": "d2e50de9546d036294a5", + "decrypt_command": "encryptly unpack diagnostic/build-c0ddfd5c.logd --password d2e50de9546d036294a5", + "total_modules": 10, + "passed": 7, + "failed": 3, + "modules": [ + { + "name": "backend", + "status": "PASS", + "elapsed_seconds": 140.897, + "artifact": "/workspace/zeroeye/backend/target", + "output": "\u001b[1m\u001b[92m Updating\u001b[0m crates.io index\n\u001b[1m\u001b[92m Downloading\u001b[0m crates ...\n\u001b[1m\u001b[92m Downloaded\u001b[0m bitflags v2.13.0\n\u001b[1m\u001b[92m Downloaded\u001b[0m futures v0.3.32\n\u001b[1m\u001b[92m Downloaded\u001b[0m aho-corasick v1.1.4\n\u001b[1m\u001b[92m Downloaded\u001b[0m icu_locale_core v2.2.0\n\u001b[1m\u001b[92m Downloaded\u001b[0m dashmap v6.2.1\n\u001b[1m\u001b[92m Downloaded\u001b[0m futures-executor v0.3.32\n\u001b[1m\u001b[92m Downloaded\u001b[0m fixedbitset v0.5.7\n\u001b[1m\u001b[92m Downloaded\u001b[0m equivalent v1.0.2\n\u001b[1m\u001b[92m Downloaded\u001b[0m itoa v1.0.18\n\u001b[1m\u001b[92m Downloaded\u001b[0m bytes v1.11.1\n\u001b[1m\u001b[92m Downloaded\u001b[0m foreign-types v0.3.2\n\u001b[1m\u001b[92m Downloaded\u001b[0m fnv v1.0.7\n\u001b[1m\u001b[92m Downloaded\u001b[0m clap_builder v4.6.0\n\u001b[1m\u001b[92m Downloaded\u001b[0m block-buffer v0.10.4\n\u001b[1m\u001b[92m Downloaded\u001b[0m pin-project-lite v0.2.17\n\u001b[1m\u001b[92m Downloaded\u001b[0m colorchoice v1.0.5\n\u001b[1m\u001b[92m Downloaded\u001b[0m anstyle-parse v1.0.0\n\u001b[1m\u001b[92m Downloaded\u001b[0m prost-derive v0.13.5\n\u001b[1m\u001b[92m Downloaded\u001b[0m clap_lex v1.1.0\n\u001b[1m\u001b[92m Downloaded\u001b[0m autocfg v1.5.1\n\u001b[1m\u001b[92m Downloaded\u001b[0m futures-sink v0.3.32\n\u001b[1m\u001b[92m Downloaded\u001b[0m futures-macro v0.3.32\n\u001b[1m\u001b[92m Downloaded\u001b[0m futures-channel v0.3.32\n\u001b[1m\u001b[92m Downloaded\u001b[0m digest v0.10.7\n\u001b[1m\u001b[92m Downloaded\u001b[0m either v1.16.0\n\u001b[1m\u001b[92m Downloaded\u001b[0m anstyle v1.0.14\n\u001b[1m\u001b[92m Downloaded\u001b[0m clap v4.6.1\n\u001b[1m\u001b[92m Downloaded\u001b[0m proc-macro2 v1.0.106\n\u001b[1m\u001b[92m Downloaded\u001b[0m clap_derive v4.6.1\n\u001b[1m\u001b[92m Downloaded\u001b[0m cfg-if v1.0.4\n\u001b[1m\u001b[92m Downloaded\u001b[0m anstyle-query v1.1.5\n\u001b[1m\u001b[92m Downloaded\u001b[0m anyhow v1.0.102\n\u001b[1m\u001b[92m Downloaded\u001b[0m scopeguard v1.2.0\n\u001b[1m\u001b[92m Downloaded\u001b[0m rustls-pki-types v1.14.1\n\u001b[1m\u001b[92m Downloaded\u001b[0m prost v0.13.5\n\u001b[1m\u001b[92m Downloaded\u001b[0m prettyplease v0.2.37\n\u001b[1m\u001b[92m Downloaded\u001b[0m icu_properties_data v2.2.0\n\u001b[1m\u001b[92m Downloaded\u001b[0m hyper-util v0.1.20\n\u001b[1m\u001b[92m Downloaded\u001b[0m thiserror-impl v2.0.18\n\u001b[1m\u001b[92m Downloaded\u001b[0m httparse v1.10.1\n\u001b[1m\u001b[92m Downloaded\u001b[0m futures-io v0.3.32\n\u001b[1m\u001b[92m Downloaded\u001b[0m idna v1.1.0\n\u001b[1m\u001b[92m Downloaded\u001b[0m find-msvc-tools v0.1.9\n\u001b[1m\u001b[92m Downloaded\u001b[0m fastrand v2.4.1\n\u001b[1m\u001b[92m Downloaded\u001b[0m displaydoc v0.2.6\n\u001b[1m\u001b[92m Downloaded\u001b[0m icu_normalizer_data v2.2.0\n\u001b[1m\u001b[92m Downloaded\u001b[0m cc v1.2.64\n\u001b[1m\u001b[92m Downloaded\u001b[0m icu_properties v2.2.0\n\u001b[1m\u001b[92m Downloaded\u001b[0m icu_normalizer v2.2.0\n\u001b[1m\u001b[92m Downloaded\u001b[0m icu_collections v2.2.0\n\u001b[1m\u001b[92m Downloaded\u001b[0m parking_lot v0.12.5\n\u001b[1m\u001b[92m Downloaded\u001b[0m yoke-derive v0.8.2\n\u001b[1m\u001b[92m Downloaded\u001b[0m hashbrown v0.14.5\n\u001b[1m\u001b[92m Downloaded\u001b[0m sync_wrapper v1.0.2\n\u001b[1m\u001b[92m Downloaded\u001b[0m is_terminal_polyfill v1.70.2\n\u001b[1m\u001b[92m Downloaded\u001b[0m slab v0.4.12\n\u001b[1m\u001b[92m Downloaded\u001b[0m chrono v0.4.45\n\u001b[1m\u001b[92m Downloaded\u001b[0m utf8_iter v1.0.4\n\u001b[1m\u001b[92m Downloaded\u001b[0m tracing-serde v0.2.0\n\u001b[1m\u001b[92m Downloaded\u001b[0m tower-layer v0.3.3\n\u001b[1m\u001b[92m Downloaded\u001b[0m tokio-native-tls v0.3.1\n\u001b[1m\u001b[92m Downloaded\u001b[0m tracing-log v0.2.0\n\u001b[1m\u001b[92m Downloaded\u001b[0m untrusted v0.9.0\n\u001b[1m\u001b[92m Downloaded\u001b[0m zerofrom-derive v0.1.7\n\u001b[1m\u001b[92m Downloaded\u001b[0m utf8parse v0.2.2\n\u001b[1m\u001b[92m Downloaded\u001b[0m want v0.3.1\n\u001b[1m\u001b[92m Downloaded\u001b[0m smallvec v1.15.2\n\u001b[1m\u001b[92m Downloaded\u001b[0m tempfile v3.27.0\n\u001b[1m\u001b[92m Downloaded\u001b[0m zerofrom v0.1.8\n\u001b[1m\u001b[92m Downloaded\u001b[0m tonic-build v0.12.3\n\u001b[1m\u001b[92m Downloaded\u001b[0m log v0.4.32\n\u001b[1m\u001b[92m Downloaded\u001b[0m uuid v1.23.3\n\u001b[1m\u001b[92m Downloaded\u001b[0m toml v0.8.23\n\u001b[1m\u001b[92m Downloaded\u001b[0m writeable v0.6.3\n\u001b[1m\u001b[92m Downloaded\u001b[0m mio v1.2.1\n\u001b[1m\u001b[92m Downloaded\u001b[0m indexmap v2.14.0\n\u001b[1m\u001b[92m Downloaded\u001b[0m itertools v0.14.0\n\u001b[1m\u001b[92m Downloaded\u001b[0m zmij v1.0.21\n\u001b[1m\u001b[92m Downloaded\u001b[0m sharded-slab v0.1.7\n\u001b[1m\u001b[92m Downloaded\u001b[0m serde v1.0.228\n\u001b[1m\u001b[92m Downloaded\u001b[0m openssl v0.10.81\n\u001b[1m\u001b[92m Downloaded\u001b[0m url v2.5.8\n\u001b[1m\u001b[92m Downloaded\u001b[0m toml_edit v0.22.27\n\u001b[1m\u001b[92m Downloaded\u001b[0m zerotrie v0.2.4\n\u001b[1m\u001b[92m Downloaded\u001b[0m typenum v1.20.1\n\u001b[1m\u001b[92m Downloaded\u001b[0m tower v0.5.3\n\u001b[1m\u001b[92m Downloaded\u001b[0m zerovec v0.11.6\n\u001b[1m\u001b[92m Downloaded\u001b[0m tokio-util v0.7.18\n\u001b[1m\u001b[92m Downloaded\u001b[0m tower-http v0.6.11\n\u001b[1m\u001b[92m Downloaded\u001b[0m serde_json v1.0.150\n\u001b[1m\u001b[92m Downloaded\u001b[0m winnow v0.7.15\n\u001b[1m\u001b[92m Downloaded\u001b[0m vcpkg v0.2.15\n\u001b[1m\u001b[92m Downloaded\u001b[0m potential_utf v0.1.5\n\u001b[1m\u001b[92m Downloaded\u001b[0m tracing-subscriber v0.3.23\n\u001b[1m\u001b[92m Downloaded\u001b[0m pkg-config v0.3.33\n\u001b[1m\u001b[92m Downloaded\u001b[0m regex-syntax v0.8.11\n\u001b[1m\u001b[92m Downloaded\u001b[0m syn v2.0.117\n\u001b[1m\u001b[92m Downloaded\u001b[0m petgraph v0.7.1\n\u001b[1m\u001b[92m Downloaded\u001b[0m rustls v0.23.40\n\u001b[1m\u001b[92m Downloaded\u001b[0m tracing v0.1.44\n\u001b[1m\u001b[92m Downloaded\u001b[0m reqwest v0.12.28\n\u001b[1m\u001b[92m Downloaded\u001b[0m rustix v1.1.4\n\u001b[1m\u001b[92m Downloaded\u001b[0m libc v0.2.186\n\u001b[1m\u001b[92m Downloaded\u001b[0m regex v1.12.4\n\u001b[1m\u001b[92m Downloaded\u001b[0m tracing-core v0.1.36\n\u001b[1m\u001b[92m Downloaded\u001b[0m zerovec-derive v0.11.3\n\u001b[1m\u001b[92m Downloaded\u001b[0m zeroize v1.9.0\n\u001b[1m\u001b[92m Downloaded\u001b[0m yoke v0.8.3\n\u001b[1m\u001b[92m Downloaded\u001b[0m unicode-ident v1.0.24\n\u001b[1m\u001b[92m Downloaded\u001b[0m hashbrown v0.17.1\n\u001b[1m\u001b[92m Downloaded\u001b[0m hyper v1.10.1\n\u001b[1m\u001b[92m Downloaded\u001b[0m h2 v0.4.14\n\u001b[1m\u001b[92m Downloaded\u001b[0m tracing-attributes v0.1.31\n\u001b[1m\u001b[92m Downloaded\u001b[0m regex-automata v0.4.14\n\u001b[1m\u001b[92m Downloaded\u001b[0m serde_derive v1.0.228\n\u001b[1m\u001b[92m Downloaded\u001b[0m serde_core v1.0.228\n\u001b[1m\u001b[92m Downloaded\u001b[0m tokio-rustls v0.26.4\n\u001b[1m\u001b[92m Downloaded\u001b[0m thiserror v2.0.18\n\u001b[1m\u001b[92m Downloaded\u001b[0m socket2 v0.6.4\n\u001b[1m\u001b[92m Downloaded\u001b[0m base64 v0.22.1\n\u001b[1m\u001b[92m Downloaded\u001b[0m rustls-webpki v0.103.13\n\u001b[1m\u001b[92m Downloaded\u001b[0m shlex v2.0.1\n\u001b[1m\u001b[92m Downloaded\u001b[0m ryu v1.0.23\n\u001b[1m\u001b[92m Downloaded\u001b[0m openssl-sys v0.9.117\n\u001b[1m\u001b[92m Downloaded\u001b[0m litemap v0.8.2\n\u001b[1m\u001b[92m Downloaded\u001b[0m iana-time-zone v0.1.65\n\u001b[1m\u001b[92m Downloaded\u001b[0m once_cell v1.21.4\n\u001b[1m\u001b[92m Downloaded\u001b[0m tokio v1.52.3\n\u001b[1m\u001b[92m Downloaded\u001b[0m nu-ansi-term v0.50.3\n\u001b[1m\u001b[92m Downloaded\u001b[0m ipnet v2.12.0\n\u001b[1m\u001b[92m Downloaded\u001b[0m tinystr v0.8.3\n\u001b[1m\u001b[92m Downloaded\u001b[0m synstructure v0.13.2\n\u001b[1m\u001b[92m Downloaded\u001b[0m native-tls v0.2.18\n\u001b[1m\u001b[92m Downloaded\u001b[0m lock_api v0.4.14\n\u001b[1m\u001b[92m Downloaded\u001b[0m lazy_static v1.5.0\n\u001b[1m\u001b[92m Downloaded\u001b[0m http-body-util v0.1.3\n\u001b[1m\u001b[92m Downloaded\u001b[0m try-lock v0.2.5\n\u001b[1m\u001b[92m Downloaded\u001b[0m percent-encoding v2.3.2\n\u001b[1m\u001b[92m Downloaded\u001b[0m openssl-probe v0.2.1\n\u001b[1m\u001b[92m Downloaded\u001b[0m sha2 v0.10.9\n\u001b[1m\u001b[92m Downloaded\u001b[0m mime v0.3.17\n\u001b[1m\u001b[92m Downloaded\u001b[0m matchers v0.2.0\n\u001b[1m\u001b[92m Downloaded\u001b[0m idna_adapter v1.2.2\n\u001b[1m\u001b[92m Downloaded\u001b[0m generic-array v0.14.7\n\u001b[1m\u001b[92m Downloaded\u001b[0m form_urlencoded v1.2.2\n\u001b[1m\u001b[92m Downloaded\u001b[0m thread_local v1.1.9\n\u001b[1m\u001b[92m Downloaded\u001b[0m openssl-macros v0.1.1\n\u001b[1m\u001b[92m Downloaded\u001b[0m encoding_rs v0.8.35\n\u001b[1m\u001b[92m Downloaded\u001b[0m http-body v1.0.1\n\u001b[1m\u001b[92m Downloaded\u001b[0m memchr v2.8.2\n\u001b[1m\u001b[92m Downloaded\u001b[0m tower-service v0.3.3\n\u001b[1m\u001b[92m Downloaded\u001b[0m toml_write v0.1.2\n\u001b[1m\u001b[92m Downloaded\u001b[0m toml_datetime v0.6.11\n\u001b[1m\u001b[92m Downloaded\u001b[0m signal-hook-registry v1.4.8\n\u001b[1m\u001b[92m Downloaded\u001b[0m parking_lot_core v0.9.12\n\u001b[1m\u001b[92m Downloaded\u001b[0m num-traits v0.2.19\n\u001b[1m\u001b[92m Downloaded\u001b[0m icu_provider v2.2.0\n\u001b[1m\u001b[92m Downloaded\u001b[0m heck v0.5.0\n\u001b[1m\u001b[92m Downloaded\u001b[0m ring v0.17.14\n\u001b[1m\u001b[92m Downloaded\u001b[0m getrandom v0.2.17\n\u001b[1m\u001b[92m Downloaded\u001b[0m futures-task v0.3.32\n\u001b[1m\u001b[92m Downloaded\u001b[0m prost-build v0.13.5\n\u001b[1m\u001b[92m Downloaded\u001b[0m foreign-types-shared v0.1.1\n\u001b[1m\u001b[92m Downloaded\u001b[0m crypto-common v0.1.7\n\u001b[1m\u001b[92m Downloaded\u001b[0m multimap v0.10.1\n\u001b[1m\u001b[92m Downloaded\u001b[0m errno v0.3.14\n\u001b[1m\u001b[92m Downloaded\u001b[0m version_check v0.9.5\n\u001b[1m\u001b[92m Downloaded\u001b[0m tokio-macros v2.7.0\n\u001b[1m\u001b[92m Downloaded\u001b[0m http v1.4.2\n\u001b[1m\u001b[92m Downloaded\u001b[0m futures-core v0.3.32\n\u001b[1m\u001b[92m Downloaded\u001b[0m hyper-tls v0.6.0\n\u001b[1m\u001b[92m Downloaded\u001b[0m hyper-rustls v0.27.9\n\u001b[1m\u001b[92m Downloaded\u001b[0m getrandom v0.4.2\n\u001b[1m\u001b[92m Downloaded\u001b[0m futures-util v0.3.32\n\u001b[1m\u001b[92m Downloaded\u001b[0m crossbeam-utils v0.8.21\n\u001b[1m\u001b[92m Downloaded\u001b[0m cpufeatures v0.2.17\n\u001b[1m\u001b[92m Downloaded\u001b[0m atomic-waker v1.1.2\n\u001b[1m\u001b[92m Downloaded\u001b[0m subtle v2.6.1\n\u001b[1m\u001b[92m Downloaded\u001b[0m strsim v0.11.1\n\u001b[1m\u001b[92m Downloaded\u001b[0m stable_deref_trait v1.2.1\n\u001b[1m\u001b[92m Downloaded\u001b[0m serde_urlencoded v0.7.1\n\u001b[1m\u001b[92m Downloaded\u001b[0m serde_spanned v0.6.9\n\u001b[1m\u001b[92m Downloaded\u001b[0m quote v1.0.45\n\u001b[1m\u001b[92m Downloaded\u001b[0m prost-types v0.13.5\n\u001b[1m\u001b[92m Downloaded\u001b[0m async-trait v0.1.89\n\u001b[1m\u001b[92m Downloaded\u001b[0m anstream v1.0.0\n\u001b[1m\u001b[92m Downloaded\u001b[0m linux-raw-sys v0.12.1\n\u001b[1m\u001b[92m Compiling\u001b[0m proc-macro2 v1.0.106\n\u001b[1m\u001b[92m Compiling\u001b[0m quote v1.0.45\n\u001b[1m\u001b[92m Compiling\u001b[0m unicode-ident v1.0.24\n\u001b[1m\u001b[92m Compiling\u001b[0m libc v0.2.186\n\u001b[1m\u001b[92m Compiling\u001b[0m cfg-if v1.0.4\n\u001b[1m\u001b[92m Compiling\u001b[0m smallvec v1.15.2\n\u001b[1m\u001b[92m Compiling\u001b[0m pin-project-lite v0.2.17\n\u001b[1m\u001b[92m Compiling\u001b[0m stable_deref_trait v1.2.1\n\u001b[1m\u001b[92m Compiling\u001b[0m serde_core v1.0.228\n\u001b[1m\u001b[92m Compiling\u001b[0m memchr v2.8.2\n\u001b[1m\u001b[92m Compiling\u001b[0m futures-core v0.3.32\n\u001b[1m\u001b[92m Compiling\u001b[0m parking_lot_core v0.9.12\n\u001b[1m\u001b[92m Compiling\u001b[0m scopeguard v1.2.0\n\u001b[1m\u001b[92m Compiling\u001b[0m bytes v1.11.1\n\u001b[1m\u001b[92m Compiling\u001b[0m itoa v1.0.18\n\u001b[1m\u001b[92m Compiling\u001b[0m futures-sink v0.3.32\n\u001b[1m\u001b[92m Compiling\u001b[0m lock_api v0.4.14\n\u001b[1m\u001b[92m Compiling\u001b[0m once_cell v1.21.4\n\u001b[1m\u001b[92m Compiling\u001b[0m find-msvc-tools v0.1.9\n\u001b[1m\u001b[92m Compiling\u001b[0m shlex v2.0.1\n\u001b[1m\u001b[92m Compiling\u001b[0m serde v1.0.228\n\u001b[1m\u001b[92m Compiling\u001b[0m pkg-config v0.3.33\n\u001b[1m\u001b[92m Compiling\u001b[0m vcpkg v0.2.15\n\u001b[1m\u001b[92m Compiling\u001b[0m slab v0.4.12\n\u001b[1m\u001b[92m Compiling\u001b[0m futures-channel v0.3.32\n\u001b[1m\u001b[92m Compiling\u001b[0m cc v1.2.64\n\u001b[1m\u001b[92m Compiling\u001b[0m tracing-core v0.1.36\n\u001b[1m\u001b[92m Compiling\u001b[0m writeable v0.6.3\n\u001b[1m\u001b[92m Compiling\u001b[0m litemap v0.8.2\n\u001b[1m\u001b[92m Compiling\u001b[0m http v1.4.2\n\u001b[1m\u001b[92m Compiling\u001b[0m utf8_iter v1.0.4\n\u001b[1m\u001b[92m Compiling\u001b[0m futures-io v0.3.32\n\u001b[1m\u001b[92m Compiling\u001b[0m icu_properties_data v2.2.0\n\u001b[1m\u001b[92m Compiling\u001b[0m futures-task v0.3.32\n\u001b[1m\u001b[92m Compiling\u001b[0m equivalent v1.0.2\n\u001b[1m\u001b[92m Compiling\u001b[0m hashbrown v0.17.1\n\u001b[1m\u001b[92m Compiling\u001b[0m icu_normalizer_data v2.2.0\n\u001b[1m\u001b[92m Compiling\u001b[0m percent-encoding v2.3.2\n\u001b[1m\u001b[92m Compiling\u001b[0m version_check v0.9.5\n\u001b[1m\u001b[92m Compiling\u001b[0m httparse v1.10.1\n\u001b[1m\u001b[92m Compiling\u001b[0m bitflags v2.13.0\n\u001b[1m\u001b[92m Compiling\u001b[0m log v0.4.32\n\u001b[1m\u001b[92m Compiling\u001b[0m openssl v0.10.81\n\u001b[1m\u001b[92m Compiling\u001b[0m http-body v1.0.1\n\u001b[1m\u001b[92m Compiling\u001b[0m foreign-types-shared v0.1.1\n\u001b[1m\u001b[92m Compiling\u001b[0m zmij v1.0.21\n\u001b[1m\u001b[92m Compiling\u001b[0m indexmap v2.14.0\n\u001b[1m\u001b[92m Compiling\u001b[0m generic-array v0.14.7\n\u001b[1m\u001b[92m Compiling\u001b[0m errno v0.3.14\n\u001b[1m\u001b[92m Compiling\u001b[0m mio v1.2.1\n\u001b[1m\u001b[92m Compiling\u001b[0m socket2 v0.6.4\n\u001b[1m\u001b[92m Compiling\u001b[0m syn v2.0.117\n\u001b[1m\u001b[92m Compiling\u001b[0m signal-hook-registry v1.4.8\n\u001b[1m\u001b[92m Compiling\u001b[0m parking_lot v0.12.5\n\u001b[1m\u001b[92m Compiling\u001b[0m foreign-types v0.3.2\n\u001b[1m\u001b[92m Compiling\u001b[0m atomic-waker v1.1.2\n\u001b[1m\u001b[92m Compiling\u001b[0m try-lock v0.2.5\n\u001b[1m\u001b[92m Compiling\u001b[0m native-tls v0.2.18\n\u001b[1m\u001b[92m Compiling\u001b[0m fnv v1.0.7\n\u001b[1m\u001b[92m Compiling\u001b[0m openssl-sys v0.9.117\n\u001b[1m\u001b[92m Compiling\u001b[0m tower-service v0.3.3\n\u001b[1m\u001b[92m Compiling\u001b[0m typenum v1.20.1\n\u001b[1m\u001b[92m Compiling\u001b[0m want v0.3.1\n\u001b[1m\u001b[92m Compiling\u001b[0m form_urlencoded v1.2.2\n\u001b[1m\u001b[92m Compiling\u001b[0m aho-corasick v1.1.4\n\u001b[1m\u001b[92m Compiling\u001b[0m regex-syntax v0.8.11\n\u001b[1m\u001b[92m Compiling\u001b[0m openssl-probe v0.2.1\n\u001b[1m\u001b[92m Compiling\u001b[0m serde_json v1.0.150\n\u001b[1m\u001b[92m Compiling\u001b[0m utf8parse v0.2.2\n\u001b[1m\u001b[92m Compiling\u001b[0m autocfg v1.5.1\n\u001b[1m\u001b[92m Compiling\u001b[0m anstyle-parse v1.0.0\n\u001b[1m\u001b[92m Compiling\u001b[0m sync_wrapper v1.0.2\n\u001b[1m\u001b[92m Compiling\u001b[0m tower-layer v0.3.3\n\u001b[1m\u001b[92m Compiling\u001b[0m crossbeam-utils v0.8.21\n\u001b[1m\u001b[92m Compiling\u001b[0m anstyle-query v1.1.5\n\u001b[1m\u001b[92m Compiling\u001b[0m colorchoice v1.0.5\n\u001b[1m\u001b[92m Compiling\u001b[0m getrandom v0.4.2\n\u001b[1m\u001b[92m Compiling\u001b[0m is_terminal_polyfill v1.70.2\n\u001b[1m\u001b[92m Compiling\u001b[0m anstyle v1.0.14\n\u001b[1m\u001b[92m Compiling\u001b[0m num-traits v0.2.19\n\u001b[1m\u001b[92m Compiling\u001b[0m base64 v0.22.1\n\u001b[1m\u001b[92m Compiling\u001b[0m ipnet v2.12.0\n\u001b[1m\u001b[92m Compiling\u001b[0m http-body-util v0.1.3\n\u001b[1m\u001b[92m Compiling\u001b[0m clap_lex v1.1.0\n\u001b[1m\u001b[92m Compiling\u001b[0m zeroize v1.9.0\n\u001b[1m\u001b[92m Compiling\u001b[0m block-buffer v0.10.4\n\u001b[1m\u001b[92m Compiling\u001b[0m crypto-common v0.1.7\n\u001b[1m\u001b[92m Compiling\u001b[0m anstream v1.0.0\n\u001b[1m\u001b[92m Compiling\u001b[0m lazy_static v1.5.0\n\u001b[1m\u001b[92m Compiling\u001b[0m ryu v1.0.23\n\u001b[1m\u001b[92m Compiling\u001b[0m heck v0.5.0\n\u001b[1m\u001b[92m Compiling\u001b[0m toml_write v0.1.2\n\u001b[1m\u001b[92m Compiling\u001b[0m strsim v0.11.1\n\u001b[1m\u001b[92m Compiling\u001b[0m winnow v0.7.15\n\u001b[1m\u001b[92m Compiling\u001b[0m anyhow v1.0.102\n\u001b[1m\u001b[92m Compiling\u001b[0m thiserror v2.0.18\n\u001b[1m\u001b[92m Compiling\u001b[0m regex-automata v0.4.14\n\u001b[1m\u001b[92m Compiling\u001b[0m clap_builder v4.6.0\n\u001b[1m\u001b[92m Compiling\u001b[0m sharded-slab v0.1.7\n\u001b[1m\u001b[92m Compiling\u001b[0m digest v0.10.7\n\u001b[1m\u001b[92m Compiling\u001b[0m rustls-pki-types v1.14.1\n\u001b[1m\u001b[92m Compiling\u001b[0m synstructure v0.13.2\n\u001b[1m\u001b[92m Compiling\u001b[0m tracing-log v0.2.0\n\u001b[1m\u001b[92m Compiling\u001b[0m encoding_rs v0.8.35\n\u001b[1m\u001b[92m Compiling\u001b[0m thread_local v1.1.9\n\u001b[1m\u001b[92m Compiling\u001b[0m nu-ansi-term v0.50.3\n\u001b[1m\u001b[92m Compiling\u001b[0m mime v0.3.17\n\u001b[1m\u001b[92m Compiling\u001b[0m iana-time-zone v0.1.65\n\u001b[1m\u001b[92m Compiling\u001b[0m cpufeatures v0.2.17\n\u001b[1m\u001b[92m Compiling\u001b[0m hashbrown v0.14.5\n\u001b[1m\u001b[92m Compiling\u001b[0m uuid v1.23.3\n\u001b[1m\u001b[92m Compiling\u001b[0m sha2 v0.10.9\n\u001b[1m\u001b[92m Compiling\u001b[0m dashmap v6.2.1\n\u001b[1m\u001b[92m Compiling\u001b[0m zerofrom-derive v0.1.7\n\u001b[1m\u001b[92m Compiling\u001b[0m yoke-derive v0.8.2\n\u001b[1m\u001b[92m Compiling\u001b[0m zerovec-derive v0.11.3\n\u001b[1m\u001b[92m Compiling\u001b[0m displaydoc v0.2.6\n\u001b[1m\u001b[92m Compiling\u001b[0m serde_derive v1.0.228\n\u001b[1m\u001b[92m Compiling\u001b[0m tokio-macros v2.7.0\n\u001b[1m\u001b[92m Compiling\u001b[0m futures-macro v0.3.32\n\u001b[1m\u001b[92m Compiling\u001b[0m tracing-attributes v0.1.31\n\u001b[1m\u001b[92m Compiling\u001b[0m openssl-macros v0.1.1\n\u001b[1m\u001b[92m Compiling\u001b[0m thiserror-impl v2.0.18\n\u001b[1m\u001b[92m Compiling\u001b[0m clap_derive v4.6.1\n\u001b[1m\u001b[92m Compiling\u001b[0m async-trait v0.1.89\n\u001b[1m\u001b[92m Compiling\u001b[0m matchers v0.2.0\n\u001b[1m\u001b[92m Compiling\u001b[0m regex v1.12.4\n\u001b[1m\u001b[92m Compiling\u001b[0m tokio v1.52.3\n\u001b[1m\u001b[92m Compiling\u001b[0m futures-util v0.3.32\n\u001b[1m\u001b[92m Compiling\u001b[0m zerofrom v0.1.8\n\u001b[1m\u001b[92m Compiling\u001b[0m tracing v0.1.44\n\u001b[1m\u001b[92m Compiling\u001b[0m clap v4.6.1\n\u001b[1m\u001b[92m Compiling\u001b[0m yoke v0.8.3\n\u001b[1m\u001b[92m Compiling\u001b[0m zerovec v0.11.6\n\u001b[1m\u001b[92m Compiling\u001b[0m zerotrie v0.2.4\n\u001b[1m\u001b[92m Compiling\u001b[0m tinystr v0.8.3\n\u001b[1m\u001b[92m Compiling\u001b[0m potential_utf v0.1.5\n\u001b[1m\u001b[92m Compiling\u001b[0m icu_collections v2.2.0\n\u001b[1m\u001b[92m Compiling\u001b[0m icu_locale_core v2.2.0\n\u001b[1m\u001b[92m Compiling\u001b[0m toml_datetime v0.6.11\n\u001b[1m\u001b[92m Compiling\u001b[0m serde_spanned v0.6.9\n\u001b[1m\u001b[92m Compiling\u001b[0m tracing-serde v0.2.0\n\u001b[1m\u001b[92m Compiling\u001b[0m serde_urlencoded v0.7.1\n\u001b[1m\u001b[92m Compiling\u001b[0m chrono v0.4.45\n\u001b[1m\u001b[92m Compiling\u001b[0m toml_edit v0.22.27\n\u001b[1m\u001b[92m Compiling\u001b[0m tracing-subscriber v0.3.23\n\u001b[1m\u001b[92m Compiling\u001b[0m icu_provider v2.2.0\n\u001b[1m\u001b[92m Compiling\u001b[0m icu_normalizer v2.2.0\n\u001b[1m\u001b[92m Compiling\u001b[0m icu_properties v2.2.0\n\u001b[1m\u001b[92m Compiling\u001b[0m futures-executor v0.3.32\n\u001b[1m\u001b[92m Compiling\u001b[0m futures v0.3.32\n\u001b[1m\u001b[92m Compiling\u001b[0m toml v0.8.23\n\u001b[1m\u001b[92m Compiling\u001b[0m idna_adapter v1.2.2\n\u001b[1m\u001b[92m Compiling\u001b[0m tokio-util v0.7.18\n\u001b[1m\u001b[92m Compiling\u001b[0m tower v0.5.3\n\u001b[1m\u001b[92m Compiling\u001b[0m tokio-native-tls v0.3.1\n\u001b[1m\u001b[92m Compiling\u001b[0m idna v1.1.0\n\u001b[1m\u001b[92m Compiling\u001b[0m h2 v0.4.14\n\u001b[1m\u001b[92m Compiling\u001b[0m url v2.5.8\n\u001b[1m\u001b[92m Compiling\u001b[0m tower-http v0.6.11\n\u001b[1m\u001b[92m Compiling\u001b[0m hyper v1.10.1\n\u001b[1m\u001b[92m Compiling\u001b[0m hyper-util v0.1.20\n\u001b[1m\u001b[92m Compiling\u001b[0m hyper-tls v0.6.0\n\u001b[1m\u001b[92m Compiling\u001b[0m reqwest v0.12.28\n\u001b[1m\u001b[92m Compiling\u001b[0m tent-backend v0.1.0 (/workspace/zeroeye/backend)\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: unused import: `warn`\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/ai/embeddings.rs:28:28\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m28\u001b[0m \u001b[1m\u001b[94m|\u001b[0m use tracing::{debug, info, warn};\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^\u001b[0m\n \u001b[1m\u001b[94m|\u001b[0m\n \u001b[1m\u001b[94m= \u001b[0m\u001b[1mnote\u001b[0m: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: unused import: `error`\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/ai/inference.rs:25:22\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m25\u001b[0m \u001b[1m\u001b[94m|\u001b[0m use tracing::{debug, error, info, warn};\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: unused imports: `error` and `warn`\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/ai/mod.rs:40:22\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m40\u001b[0m \u001b[1m\u001b[94m|\u001b[0m use tracing::{debug, error, info, warn};\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^\u001b[0m \u001b[1m\u001b[33m^^^^\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: unused imports: `c_int` and `c_uint`\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/connector/bridge.rs:38:20\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m38\u001b[0m \u001b[1m\u001b[94m|\u001b[0m use std::os::raw::{c_int, c_uint, c_ulong};\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^\u001b[0m \u001b[1m\u001b[33m^^^^^^\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: unused import: `std::ffi::CString`\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/connector/legacy.rs:35:5\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m35\u001b[0m \u001b[1m\u001b[94m|\u001b[0m use std::ffi::CString;\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^^^^^^^^^\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: unused import: `c_char`\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/connector/legacy.rs:36:20\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m36\u001b[0m \u001b[1m\u001b[94m|\u001b[0m use std::os::raw::{c_char, c_ulong};\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: unused import: `CStr`\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/connector/types.rs:27:16\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m27\u001b[0m \u001b[1m\u001b[94m|\u001b[0m use std::ffi::{CStr, CString};\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: unused imports: `c_double` and `c_long`\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/connector/types.rs:29:28\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m29\u001b[0m \u001b[1m\u001b[94m|\u001b[0m use std::os::raw::{c_char, c_double, c_int, c_uint, c_void, c_long, c_ulong};\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^\u001b[0m \u001b[1m\u001b[33m^^^^^^\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: unused import: `AtomicBool`\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/legacy/deprecations.rs:14:25\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m14\u001b[0m \u001b[1m\u001b[94m|\u001b[0m use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^^\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: unused imports: `EntityKind` and `legacy_normalize_phone_number`\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/legacy/v1_compat.rs:8:47\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m8\u001b[0m \u001b[1m\u001b[94m|\u001b[0m use crate::legacy::deprecations::{LegacyUuid, EntityKind, LegacyPagination, legacy_normalize_phone_number};\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^^\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: unused import: `super::ProtocolError`\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/protocol/validate.rs:27:5\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m27\u001b[0m \u001b[1m\u001b[94m|\u001b[0m use super::ProtocolError;\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^^^^^^^^^^^^\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: unused import: `MAX_MESSAGE_SIZE`\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/protocol/codec.rs:25:38\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m25\u001b[0m \u001b[1m\u001b[94m|\u001b[0m use crate::protocol::{ProtocolError, MAX_MESSAGE_SIZE, MIN_COMPATIBLE_VERSION, PROTOCOL_VERSION};\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^^^^^^^^\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: unused import: `Write`\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/protocol/codec.rs:26:29\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m26\u001b[0m \u001b[1m\u001b[94m|\u001b[0m use std::io::{Cursor, Read, Write};\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: unused import: `Ordering`\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/protocol/rpc.rs:25:36\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m25\u001b[0m \u001b[1m\u001b[94m|\u001b[0m use std::sync::atomic::{AtomicU64, Ordering};\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: unused imports: `Duration` and `Instant`\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/protocol/rpc.rs:27:17\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m27\u001b[0m \u001b[1m\u001b[94m|\u001b[0m use std::time::{Duration, Instant};\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^\u001b[0m \u001b[1m\u001b[33m^^^^^^^\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: unused imports: `Deserialize` and `Serialize`\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/protocol/rpc.rs:28:13\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m28\u001b[0m \u001b[1m\u001b[94m|\u001b[0m use serde::{Deserialize, Serialize};\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^^^\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: unused import: `MAX_MESSAGE_SIZE`\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/protocol/rpc.rs:31:28\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m31\u001b[0m \u001b[1m\u001b[94m|\u001b[0m use super::{ProtocolError, MAX_MESSAGE_SIZE, DEFAULT_TIMEOUT_MS};\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^^^^^^^^\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: unused imports: `FrameDecoder` and `FrameEncoder`\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/protocol/rpc.rs:32:27\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m32\u001b[0m \u001b[1m\u001b[94m|\u001b[0m use super::codec::{Frame, FrameEncoder, FrameDecoder, FLAG_REQUIRES_ACK};\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^^^^\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^^^^\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: use of deprecated unit variant `legacy::deprecations::EntityKind::Team`: Teams are now Organizations. Use Organization instead.\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/legacy/deprecations.rs:244:25\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m244\u001b[0m \u001b[1m\u001b[94m|\u001b[0m EntityKind::Team => \"org\", // Legacy mapping\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^\u001b[0m\n \u001b[1m\u001b[94m|\u001b[0m\n \u001b[1m\u001b[94m= \u001b[0m\u001b[1mnote\u001b[0m: `#[warn(deprecated)]` on by default\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: use of deprecated unit variant `legacy::deprecations::EntityKind::Project`: Projects were removed in the Platform v2 migration\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/legacy/deprecations.rs:245:25\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m245\u001b[0m \u001b[1m\u001b[94m|\u001b[0m EntityKind::Project => \"workspace\", // Legacy mapping\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: use of deprecated unit variant `legacy::deprecations::EntityKind::Team`: Teams are now Organizations. Use Organization instead.\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/legacy/deprecations.rs:266:25\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m266\u001b[0m \u001b[1m\u001b[94m|\u001b[0m EntityKind::Team\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: use of deprecated unit variant `legacy::deprecations::EntityKind::Project`: Projects were removed in the Platform v2 migration\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/legacy/deprecations.rs:267:31\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m267\u001b[0m \u001b[1m\u001b[94m|\u001b[0m | EntityKind::Project\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: variable does not need to be mutable\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/connector/bridge.rs:317:13\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m317\u001b[0m \u001b[1m\u001b[94m|\u001b[0m let mut buffer = unsafe { &mut *c_buffer };\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[94m----\u001b[0m\u001b[1m\u001b[33m^^^^^^\u001b[0m\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[94m|\u001b[0m\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[94mhelp: remove this `mut`\u001b[0m\n \u001b[1m\u001b[94m|\u001b[0m\n \u001b[1m\u001b[94m= \u001b[0m\u001b[1mnote\u001b[0m: `#[warn(unused_mut)]` (part of `#[warn(unused)]`) on by default\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: unused variable: `initialized`\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/connector/bridge.rs:440:13\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m440\u001b[0m \u001b[1m\u001b[94m|\u001b[0m let initialized = Arc::new(AtomicBool::new(true));\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^^^\u001b[0m \u001b[1m\u001b[33mhelp: if this is intentional, prefix it with an underscore: `_initialized`\u001b[0m\n \u001b[1m\u001b[94m|\u001b[0m\n \u001b[1m\u001b[94m= \u001b[0m\u001b[1mnote\u001b[0m: `#[warn(unused_variables)]` (part of `#[warn(unused)]`) on by default\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: variable does not need to be mutable\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/connector/legacy.rs:267:13\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m267\u001b[0m \u001b[1m\u001b[94m|\u001b[0m let mut buffer = unsafe { &mut *c_buffer };\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[94m----\u001b[0m\u001b[1m\u001b[33m^^^^^^\u001b[0m\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[94m|\u001b[0m\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[94mhelp: remove this `mut`\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: unused variable: `value`\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/legacy/deprecations.rs:508:15\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m508\u001b[0m \u001b[1m\u001b[94m|\u001b[0m for (key, value) in configs {\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^\u001b[0m \u001b[1m\u001b[33mhelp: if this is intentional, prefix it with an underscore: `_value`\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: unused variable: `obj`\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/protocol/validate.rs:282:25\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m282\u001b[0m \u001b[1m\u001b[94m|\u001b[0m if let Some(obj) = value.as_object() {\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^\u001b[0m \u001b[1m\u001b[33mhelp: if this is intentional, prefix it with an underscore: `_obj`\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: type `BridgeStats` is more private than the item `ConnectorBridge::stats`\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/connector/bridge.rs:415:5\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m415\u001b[0m \u001b[1m\u001b[94m|\u001b[0m pub fn stats(&self) -> BridgeStats {\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\u001b[0m \u001b[1m\u001b[33mmethod `ConnectorBridge::stats` is reachable at visibility `pub`\u001b[0m\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[92mnote\u001b[0m: but type `BridgeStats` is only usable at visibility `pub(self)`\n \u001b[1m\u001b[94m--> \u001b[0msrc/connector/bridge.rs:225:1\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m225\u001b[0m \u001b[1m\u001b[94m|\u001b[0m struct BridgeStats {\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[92m^^^^^^^^^^^^^^^^^^\u001b[0m\n \u001b[1m\u001b[94m= \u001b[0m\u001b[1mnote\u001b[0m: `#[warn(private_interfaces)]` on by default\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: type `CircuitState` is more private than the item `ConnectorBridge::circuit_state`\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/connector/bridge.rs:423:5\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m423\u001b[0m \u001b[1m\u001b[94m|\u001b[0m pub fn circuit_state(&self) -> CircuitState {\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\u001b[0m \u001b[1m\u001b[33mmethod `ConnectorBridge::circuit_state` is reachable at visibility `pub`\u001b[0m\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[92mnote\u001b[0m: but type `CircuitState` is only usable at visibility `pub(self)`\n \u001b[1m\u001b[94m--> \u001b[0msrc/connector/bridge.rs:79:1\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m 79\u001b[0m \u001b[1m\u001b[94m|\u001b[0m enum CircuitState {\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[92m^^^^^^^^^^^^^^^^^\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: constant `NCP_TEMPERATURE` is never used\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/ai/mod.rs:53:7\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m53\u001b[0m \u001b[1m\u001b[94m|\u001b[0m const NCP_TEMPERATURE: f64 = 0.42;\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^^^^^^^\u001b[0m\n \u001b[1m\u001b[94m|\u001b[0m\n \u001b[1m\u001b[94m= \u001b[0m\u001b[1mnote\u001b[0m: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: constant `MIN_CONFIDENCE_THRESHOLD` is never used\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/ai/mod.rs:61:7\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m61\u001b[0m \u001b[1m\u001b[94m|\u001b[0m const MIN_CONFIDENCE_THRESHOLD: f64 = 0.65;\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^^^^^^^^^^^^^^^^\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: constant `MAX_INFERENCE_RETRIES` is never used\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/ai/mod.rs:65:7\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m65\u001b[0m \u001b[1m\u001b[94m|\u001b[0m const MAX_INFERENCE_RETRIES: u32 = 5;\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^^^^^^^^^^^^^\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: fields `discovery`, `broker`, and `registry` are never read\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/ai/mod.rs:173:5\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m171\u001b[0m \u001b[1m\u001b[94m|\u001b[0m pub struct AiOrchestrator {\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[94m--------------\u001b[0m \u001b[1m\u001b[94mfields in this struct\u001b[0m\n\u001b[1m\u001b[94m172\u001b[0m \u001b[1m\u001b[94m|\u001b[0m /// Reference to the service discovery subsystem\n\u001b[1m\u001b[94m173\u001b[0m \u001b[1m\u001b[94m|\u001b[0m discovery: Arc>,\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^\u001b[0m\n\u001b[1m\u001b[94m174\u001b[0m \u001b[1m\u001b[94m|\u001b[0m /// Reference to the message broker subsystem\n\u001b[1m\u001b[94m175\u001b[0m \u001b[1m\u001b[94m|\u001b[0m broker: Arc>,\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^\u001b[0m\n\u001b[1m\u001b[94m176\u001b[0m \u001b[1m\u001b[94m|\u001b[0m /// Reference to the service registry subsystem\n\u001b[1m\u001b[94m177\u001b[0m \u001b[1m\u001b[94m|\u001b[0m registry: Arc>,\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: field `window_start` is never read\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/ai/embeddings.rs:661:5\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m658\u001b[0m \u001b[1m\u001b[94m|\u001b[0m pub struct ContextWindowManager {\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[94m--------------------\u001b[0m \u001b[1m\u001b[94mfield in this struct\u001b[0m\n\u001b[1m\u001b[94m...\u001b[0m\n\u001b[1m\u001b[94m661\u001b[0m \u001b[1m\u001b[94m|\u001b[0m window_start: Instant,\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^^^^\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: constant `MAX_RETRIES` is never used\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/ai/inference.rs:41:7\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m41\u001b[0m \u001b[1m\u001b[94m|\u001b[0m const MAX_RETRIES: u32 = 3;\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^^^\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: constant `RETRY_BASE_DELAY_MS` is never used\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/ai/inference.rs:44:7\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m44\u001b[0m \u001b[1m\u001b[94m|\u001b[0m const RETRY_BASE_DELAY_MS: u64 = 1000;\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^^^^^^^^^^^\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: fields `api_key`, `base_url`, and `client` are never read\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/ai/inference.rs:453:5\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m452\u001b[0m \u001b[1m\u001b[94m|\u001b[0m pub struct AnthropicClient {\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[94m---------------\u001b[0m \u001b[1m\u001b[94mfields in this struct\u001b[0m\n\u001b[1m\u001b[94m453\u001b[0m \u001b[1m\u001b[94m|\u001b[0m api_key: String,\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^\u001b[0m\n\u001b[1m\u001b[94m454\u001b[0m \u001b[1m\u001b[94m|\u001b[0m base_url: String,\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^\u001b[0m\n\u001b[1m\u001b[94m455\u001b[0m \u001b[1m\u001b[94m|\u001b[0m models: Vec,\n\u001b[1m\u001b[94m456\u001b[0m \u001b[1m\u001b[94m|\u001b[0m client: reqwest::Client,\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^\u001b[0m\n \u001b[1m\u001b[94m|\u001b[0m\n \u001b[1m\u001b[94m= \u001b[0m\u001b[1mnote\u001b[0m: `AnthropicClient` has a derived impl for the trait `Debug`, but this is intentionally ignored during dead code analysis\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: field `routing_table` is never read\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/ai/inference.rs:719:5\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m716\u001b[0m \u001b[1m\u001b[94m|\u001b[0m pub struct ModelRouter {\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[94m-----------\u001b[0m \u001b[1m\u001b[94mfield in this struct\u001b[0m\n\u001b[1m\u001b[94m...\u001b[0m\n\u001b[1m\u001b[94m719\u001b[0m \u001b[1m\u001b[94m|\u001b[0m routing_table: RwLock>,\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^^^^^\u001b[0m\n \u001b[1m\u001b[94m|\u001b[0m\n \u001b[1m\u001b[94m= \u001b[0m\u001b[1mnote\u001b[0m: `ModelRouter` has a derived impl for the trait `Debug`, but this is intentionally ignored during dead code analysis\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: field `cost_history` is never read\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/ai/inference.rs:951:5\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m947\u001b[0m \u001b[1m\u001b[94m|\u001b[0m pub struct TokenCounter {\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[94m------------\u001b[0m \u001b[1m\u001b[94mfield in this struct\u001b[0m\n\u001b[1m\u001b[94m...\u001b[0m\n\u001b[1m\u001b[94m951\u001b[0m \u001b[1m\u001b[94m|\u001b[0m cost_history: RwLock>,\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^^^^\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: constant `HEALTH_CHECK_TIMEOUT_MS` is never used\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/connector/bridge.rs:64:7\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m64\u001b[0m \u001b[1m\u001b[94m|\u001b[0m const HEALTH_CHECK_TIMEOUT_MS: u64 = 1000;\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^^^^^^^^^^^^^^^\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: field `id` is never read\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/connector/bridge.rs:152:5\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m151\u001b[0m \u001b[1m\u001b[94m|\u001b[0m struct PoolEntry {\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[94m---------\u001b[0m \u001b[1m\u001b[94mfield in this struct\u001b[0m\n\u001b[1m\u001b[94m152\u001b[0m \u001b[1m\u001b[94m|\u001b[0m id: usize,\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: method `stats` is never used\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/connector/bridge.rs:195:8\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m163\u001b[0m \u001b[1m\u001b[94m|\u001b[0m impl ConnectionPool {\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[94m-------------------\u001b[0m \u001b[1m\u001b[94mmethod in this implementation\u001b[0m\n\u001b[1m\u001b[94m...\u001b[0m\n\u001b[1m\u001b[94m195\u001b[0m \u001b[1m\u001b[94m|\u001b[0m fn stats(&self) -> PoolStats {\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: struct `PoolStats` is never constructed\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/connector/bridge.rs:203:8\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m203\u001b[0m \u001b[1m\u001b[94m|\u001b[0m struct PoolStats {\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: fields `circuit_breaker_trips` and `health_check_failures` are never read\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/connector/bridge.rs:229:5\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m225\u001b[0m \u001b[1m\u001b[94m|\u001b[0m struct BridgeStats {\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[94m-----------\u001b[0m \u001b[1m\u001b[94mfields in this struct\u001b[0m\n\u001b[1m\u001b[94m...\u001b[0m\n\u001b[1m\u001b[94m229\u001b[0m \u001b[1m\u001b[94m|\u001b[0m circuit_breaker_trips: u64,\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^^^^^^^^^^^^^\u001b[0m\n\u001b[1m\u001b[94m230\u001b[0m \u001b[1m\u001b[94m|\u001b[0m health_check_failures: u64,\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^^^^^^^^^^^^^\u001b[0m\n \u001b[1m\u001b[94m|\u001b[0m\n \u001b[1m\u001b[94m= \u001b[0m\u001b[1mnote\u001b[0m: `BridgeStats` has derived impls for the traits `Clone` and `Debug`, but these are intentionally ignored during dead code analysis\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: field `consumers` is never read\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/messaging/mod.rs:38:5\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m35\u001b[0m \u001b[1m\u001b[94m|\u001b[0m pub struct MessageBroker {\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[94m-------------\u001b[0m \u001b[1m\u001b[94mfield in this struct\u001b[0m\n\u001b[1m\u001b[94m...\u001b[0m\n\u001b[1m\u001b[94m38\u001b[0m \u001b[1m\u001b[94m|\u001b[0m consumers: DashMap>>,\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: field `version` is never read\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/protocol/messages.rs:293:5\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m291\u001b[0m \u001b[1m\u001b[94m|\u001b[0m pub struct MessageRegistry {\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[94m---------------\u001b[0m \u001b[1m\u001b[94mfield in this struct\u001b[0m\n\u001b[1m\u001b[94m292\u001b[0m \u001b[1m\u001b[94m|\u001b[0m handlers: HashMap,\n\u001b[1m\u001b[94m293\u001b[0m \u001b[1m\u001b[94m|\u001b[0m version: u32,\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: field `version` is never read\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/protocol/serialize.rs:258:5\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m255\u001b[0m \u001b[1m\u001b[94m|\u001b[0m struct Schema {\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[94m------\u001b[0m \u001b[1m\u001b[94mfield in this struct\u001b[0m\n\u001b[1m\u001b[94m...\u001b[0m\n\u001b[1m\u001b[94m258\u001b[0m \u001b[1m\u001b[94m|\u001b[0m version: u32,\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: fields `required` and `default_value` are never read\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/protocol/serialize.rs:264:5\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m261\u001b[0m \u001b[1m\u001b[94m|\u001b[0m struct SchemaField {\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[94m-----------\u001b[0m \u001b[1m\u001b[94mfields in this struct\u001b[0m\n\u001b[1m\u001b[94m...\u001b[0m\n\u001b[1m\u001b[94m264\u001b[0m \u001b[1m\u001b[94m|\u001b[0m required: bool,\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^\u001b[0m\n\u001b[1m\u001b[94m265\u001b[0m \u001b[1m\u001b[94m|\u001b[0m default_value: Option,\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^^^^^\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: variant `Custom` is never constructed\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/protocol/serialize.rs:276:5\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m269\u001b[0m \u001b[1m\u001b[94m|\u001b[0m enum FieldValidation {\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[94m---------------\u001b[0m \u001b[1m\u001b[94mvariant in this enum\u001b[0m\n\u001b[1m\u001b[94m...\u001b[0m\n\u001b[1m\u001b[94m276\u001b[0m \u001b[1m\u001b[94m|\u001b[0m Custom(String),\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: fields `next_request_id`, `pending_requests`, `serializer`, and `timeout_ms` are never read\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/protocol/rpc.rs:218:5\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m217\u001b[0m \u001b[1m\u001b[94m|\u001b[0m pub struct RpcClient {\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[94m---------\u001b[0m \u001b[1m\u001b[94mfields in this struct\u001b[0m\n\u001b[1m\u001b[94m218\u001b[0m \u001b[1m\u001b[94m|\u001b[0m next_request_id: AtomicU64,\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^^^^^^^\u001b[0m\n\u001b[1m\u001b[94m219\u001b[0m \u001b[1m\u001b[94m|\u001b[0m pending_requests: Arc, RpcError>>>>>,\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^^^^^^^^\u001b[0m\n\u001b[1m\u001b[94m220\u001b[0m \u001b[1m\u001b[94m|\u001b[0m serializer: Serializer,\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^^\u001b[0m\n\u001b[1m\u001b[94m221\u001b[0m \u001b[1m\u001b[94m|\u001b[0m timeout_ms: u64,\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^^\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: field `serializer` is never read\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/protocol/rpc.rs:271:5\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m269\u001b[0m \u001b[1m\u001b[94m|\u001b[0m pub struct RpcServer {\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[94m---------\u001b[0m \u001b[1m\u001b[94mfield in this struct\u001b[0m\n\u001b[1m\u001b[94m270\u001b[0m \u001b[1m\u001b[94m|\u001b[0m handlers: HashMap,\n\u001b[1m\u001b[94m271\u001b[0m \u001b[1m\u001b[94m|\u001b[0m serializer: Serializer,\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^^^^^\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m\u001b[1m: field `events` is never read\u001b[0m\n \u001b[1m\u001b[94m--> \u001b[0msrc/registry/mod.rs:31:5\n \u001b[1m\u001b[94m|\u001b[0m\n\u001b[1m\u001b[94m28\u001b[0m \u001b[1m\u001b[94m|\u001b[0m pub struct ServiceRegistry {\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[94m---------------\u001b[0m \u001b[1m\u001b[94mfield in this struct\u001b[0m\n\u001b[1m\u001b[94m...\u001b[0m\n\u001b[1m\u001b[94m31\u001b[0m \u001b[1m\u001b[94m|\u001b[0m events: Arc>>,\n \u001b[1m\u001b[94m|\u001b[0m \u001b[1m\u001b[33m^^^^^^\u001b[0m\n\n\u001b[1m\u001b[33mwarning\u001b[0m: `tent-backend` (lib) generated 52 warnings (run `cargo fix --lib -p tent-backend` to apply 23 suggestions)\n\u001b[1m\u001b[92m Finished\u001b[0m `dev` profile [unoptimized + debuginfo] target(s) in 2m 20s" + }, + { + "name": "frontend", + "status": "PASS", + "elapsed_seconds": 41.719, + "artifact": "/workspace/zeroeye/frontend/dist", + "output": "> tent-frontend@0.0.0 build\n> tsc -b && vite build\n\nvite v6.4.3 building for production...\ntransforming...\n\u2713 100 modules transformed.\nrendering chunks...\ncomputing gzip size...\ndist/index.html 0.63 kB \u2502 gzip: 0.35 kB\ndist/assets/state-BkjSKDbY.js 8.91 kB \u2502 gzip: 3.54 kB \u2502 map: 57.15 kB\ndist/assets/vendor-CREcWLHI.js 48.93 kB \u2502 gzip: 17.25 kB \u2502 map: 481.27 kB\ndist/assets/index-CyxcoTyU.js 231.32 kB \u2502 gzip: 72.16 kB \u2502 map: 1,045.57 kB\n\u2713 built in 6.27s" + }, + { + "name": "market", + "status": "PASS", + "elapsed_seconds": 10.176, + "artifact": "/workspace/zeroeye/market/market", + "output": "go: downloading go.uber.org/zap v1.27.0\ngo: downloading github.com/shopspring/decimal v1.4.0\ngo: downloading github.com/google/uuid v1.6.0\ngo: downloading github.com/gorilla/websocket v1.5.3\ngo: downloading go.uber.org/multierr v1.10.0" + }, + { + "name": "frailbox", + "status": "FAIL", + "elapsed_seconds": 0.281, + "artifact": null, + "output": "gcc -Wall -Wextra -Wpedantic -std=c2x -O2 -g -D_FORTIFY_SOURCE=3 -fstack-protector-strong -fPIE -Iinclude -MMD -MP -c src/arena.c -o build/src/arena.o\nsrc/arena.c: In function 'region_alloc':\nsrc/arena.c:13:36: error: 'MAP_ANONYMOUS' undeclared (first use in this function)\n 13 | int mmap_flags = MAP_PRIVATE | MAP_ANONYMOUS;\n | ^~~~~~~~~~~~~\nsrc/arena.c:13:36: note: each undeclared identifier is reported only once for each function it appears in\nsrc/arena.c:17:23: error: 'MAP_HUGETLB' undeclared (first use in this function)\n 17 | mmap_flags |= MAP_HUGETLB;\n | ^~~~~~~~~~~\nsrc/arena.c: In function 'arena_contains':\nsrc/arena.c:179:17: warning: comparison of distinct pointer types lacks a cast\n 179 | ptr < (char *)region->start + region->size) {\n | ^\nmake: *** [Makefile:27: build/src/arena.o] Error 1" + }, + { + "name": "engine", + "status": "FAIL", + "elapsed_seconds": 6.34, + "artifact": null, + "output": "[ 11%] Building CXX object CMakeFiles/trial-engine.dir/main.cpp.o\n/workspace/zeroeye/frailbox/engine/main.cpp:12:10: fatal error: format: No such file or directory\n 12 | #include \n | ^~~~~~~~\ncompilation terminated.\ngmake[2]: *** [CMakeFiles/trial-engine.dir/build.make:76: CMakeFiles/trial-engine.dir/main.cpp.o] Error 1\ngmake[1]: *** [CMakeFiles/Makefile2:83: CMakeFiles/trial-engine.dir/all] Error 2\ngmake: *** [Makefile:136: all] Error 2" + }, + { + "name": "compliance", + "status": "PASS", + "elapsed_seconds": 3.006, + "artifact": "/workspace/zeroeye/compliance/build", + "output": "" + }, + { + "name": "v2-market-stream", + "status": "PASS", + "elapsed_seconds": 0.23, + "artifact": null, + "output": "Syntax OK\nruby: warning: shebang line ending with \\r may cause problems" + }, + { + "name": "nfc-scanner", + "status": "PASS", + "elapsed_seconds": 0.057, + "artifact": null, + "output": "" + }, + { + "name": "openapi-haskell", + "status": "FAIL", + "elapsed_seconds": 0, + "artifact": null, + "output": "Command not found: [Errno 2] No such file or directory: 'ghc'" + }, + { + "name": "openapi-tools", + "status": "PASS", + "elapsed_seconds": 0.063, + "artifact": null, + "output": "" + } + ], + "pr_note": "Include the encrypted diagnostic logd artifact(s): diagnostic/build-c0ddfd5c.logd. The encrypted .logd is the required diagnostic content for PR review; this JSON file is metadata. Maintainers may ask you to remove these diagnostic artifacts before merging." +} diff --git a/diagnostic/build-c0ddfd5c.logd b/diagnostic/build-c0ddfd5c.logd new file mode 100644 index 00000000..e4c801cd Binary files /dev/null and b/diagnostic/build-c0ddfd5c.logd differ diff --git a/go.work b/go.work new file mode 100644 index 00000000..5f0fffc5 --- /dev/null +++ b/go.work @@ -0,0 +1,3 @@ +go 1.26 + +use ./market diff --git a/market/matching/delta_validation_test.go b/market/matching/delta_validation_test.go new file mode 100644 index 00000000..62f79fdf --- /dev/null +++ b/market/matching/delta_validation_test.go @@ -0,0 +1,161 @@ +package matching + +import ( + "strings" + "testing" + + "github.com/shopspring/decimal" + "github.com/tent-of-trials/market/orderbook" + "github.com/tent-of-trials/market/types" +) + +func TestMatchingOwnedBookRejectsInvalidDeltaWithoutMutation(t *testing.T) { + engine := newDeltaTestEngine() + book := engine.books[types.Symbol("BTC-USD")] + seedMatchingBook(t, book) + + beforeBids := matchingSnapshotLevels(book.GetBids()) + beforeAsks := matchingSnapshotLevels(book.GetAsks()) + beforeSequence := book.Sequence() + + err := book.ApplyDelta(orderbook.DeltaUpdate{ + Symbol: types.Symbol("BTC-USD"), + PreviousSequence: beforeSequence, + Sequence: beforeSequence + 1, + Levels: []orderbook.LevelUpdate{{ + Side: orderbook.SideAsk, + Price: matchingDec("101.00"), + Quantity: matchingDec("-0.25"), + }}, + }) + if err == nil || !strings.Contains(err.Error(), "quantity") { + t.Fatalf("ApplyDelta error = %v, want quantity validation error", err) + } + + assertMatchingBookState(t, book, beforeSequence, beforeBids, beforeAsks) +} + +func TestMatchingOwnedBookRejectsOutOfOrderDeltaWithoutMutation(t *testing.T) { + engine := newDeltaTestEngine() + book := engine.books[types.Symbol("BTC-USD")] + seedMatchingBook(t, book) + + beforeBids := matchingSnapshotLevels(book.GetBids()) + beforeAsks := matchingSnapshotLevels(book.GetAsks()) + beforeSequence := book.Sequence() + + err := book.ApplyDelta(orderbook.DeltaUpdate{ + Symbol: types.Symbol("BTC-USD"), + PreviousSequence: beforeSequence - 1, + Sequence: beforeSequence + 1, + Levels: []orderbook.LevelUpdate{{ + Side: orderbook.SideBid, + Price: matchingDec("100.00"), + Quantity: matchingDec("2.00"), + }}, + }) + if err == nil || !strings.Contains(err.Error(), "previous_sequence") { + t.Fatalf("ApplyDelta error = %v, want previous_sequence validation error", err) + } + + assertMatchingBookState(t, book, beforeSequence, beforeBids, beforeAsks) +} + +func TestMatchingOwnedBookAcceptsSnapshotThenDeltaPath(t *testing.T) { + engine := newDeltaTestEngine() + book := engine.books[types.Symbol("BTC-USD")] + + bids := []types.Level{matchingLevel("100.00", "1.00")} + asks := []types.Level{matchingLevel("101.00", "1.00")} + snapshot := orderbook.SnapshotUpdate{ + Symbol: types.Symbol("BTC-USD"), + Sequence: 42, + Bids: bids, + Asks: asks, + } + snapshot.Checksum = orderbook.ComputeBookChecksum(snapshot.Symbol, snapshot.Sequence, bids, asks) + if err := book.ApplySnapshot(snapshot); err != nil { + t.Fatalf("ApplySnapshot error = %v", err) + } + + wantBids := []types.Level{matchingLevel("100.00", "1.50")} + wantAsks := []types.Level{matchingLevel("102.00", "0.75")} + delta := orderbook.DeltaUpdate{ + Symbol: types.Symbol("BTC-USD"), + PreviousSequence: 42, + Sequence: 43, + Levels: []orderbook.LevelUpdate{ + {Side: orderbook.SideBid, Price: matchingDec("100.00"), Quantity: matchingDec("1.50")}, + {Side: orderbook.SideAsk, Price: matchingDec("101.00"), Quantity: decimal.Zero}, + {Side: orderbook.SideAsk, Price: matchingDec("102.00"), Quantity: matchingDec("0.75")}, + }, + } + delta.Checksum = orderbook.ComputeBookChecksum(delta.Symbol, delta.Sequence, wantBids, wantAsks) + if err := book.ApplyDelta(delta); err != nil { + t.Fatalf("ApplyDelta error = %v", err) + } + + assertMatchingBookState(t, book, 43, wantBids, wantAsks) +} + +func newDeltaTestEngine() *MatchingEngine { + config := orderbook.Config{MaxDepth: 10, PriceDecimals: 8, VolumeDecimals: 8} + return NewMatchingEngine(EngineConfig{EnableShorting: true}, map[types.Symbol]*orderbook.OrderBook{ + types.Symbol("BTC-USD"): orderbook.NewOrderBook(types.Symbol("BTC-USD"), config), + }) +} + +func seedMatchingBook(t *testing.T, book *orderbook.OrderBook) { + t.Helper() + bids := []types.Level{matchingLevel("100.00", "1.00")} + asks := []types.Level{matchingLevel("101.00", "1.00")} + snapshot := orderbook.SnapshotUpdate{ + Symbol: types.Symbol("BTC-USD"), + Sequence: 10, + Bids: bids, + Asks: asks, + } + snapshot.Checksum = orderbook.ComputeBookChecksum(snapshot.Symbol, snapshot.Sequence, bids, asks) + if err := book.ApplySnapshot(snapshot); err != nil { + t.Fatalf("ApplySnapshot error = %v", err) + } +} + +func assertMatchingBookState(t *testing.T, book *orderbook.OrderBook, wantSequence uint64, wantBids, wantAsks []types.Level) { + t.Helper() + if got := book.Sequence(); got != wantSequence { + t.Fatalf("sequence = %d, want %d", got, wantSequence) + } + assertMatchingLevels(t, "bids", matchingSnapshotLevels(book.GetBids()), wantBids) + assertMatchingLevels(t, "asks", matchingSnapshotLevels(book.GetAsks()), wantAsks) +} + +func assertMatchingLevels(t *testing.T, name string, got, want []types.Level) { + t.Helper() + if len(got) != len(want) { + t.Fatalf("%s length = %d, want %d: got %#v", name, len(got), len(want), got) + } + for i := range want { + if !got[i].Price.Equal(want[i].Price) || !got[i].Quantity.Equal(want[i].Quantity) || got[i].Count != want[i].Count { + t.Fatalf("%s[%d] = %#v, want %#v", name, i, got[i], want[i]) + } + } +} + +func matchingSnapshotLevels(levels []*types.Level) []types.Level { + result := make([]types.Level, 0, len(levels)) + for _, level := range levels { + if level != nil { + result = append(result, *level) + } + } + return result +} + +func matchingLevel(price, quantity string) types.Level { + return types.Level{Price: matchingDec(price), Quantity: matchingDec(quantity), Count: 1} +} + +func matchingDec(value string) decimal.Decimal { + return decimal.RequireFromString(value) +} diff --git a/market/orderbook/delta.go b/market/orderbook/delta.go new file mode 100644 index 00000000..0dbace60 --- /dev/null +++ b/market/orderbook/delta.go @@ -0,0 +1,283 @@ +package orderbook + +import ( + "crypto/sha256" + "encoding/hex" + "fmt" + "sort" + "strings" + "time" + + "github.com/shopspring/decimal" + "github.com/tent-of-trials/market/types" +) + +type UpdateSide string + +const ( + SideBid UpdateSide = "bid" + SideAsk UpdateSide = "ask" +) + +type LevelUpdate struct { + Side UpdateSide + Price decimal.Decimal + Quantity decimal.Decimal +} + +type SnapshotUpdate struct { + Symbol types.Symbol + Sequence uint64 + Bids []types.Level + Asks []types.Level + Checksum string +} + +type DeltaUpdate struct { + Symbol types.Symbol + PreviousSequence uint64 + Sequence uint64 + Levels []LevelUpdate + Checksum string +} + +type DeltaValidationError struct { + Field string + Message string +} + +func (e *DeltaValidationError) Error() string { + if e.Field == "" { + return e.Message + } + return fmt.Sprintf("%s: %s", e.Field, e.Message) +} + +func (ob *OrderBook) Sequence() uint64 { + ob.mu.RLock() + defer ob.mu.RUnlock() + return ob.sequence +} + +func (ob *OrderBook) ApplySnapshot(snapshot SnapshotUpdate) error { + ob.mu.Lock() + defer ob.mu.Unlock() + + if ob.closed { + return ErrBookClosed + } + if err := validateSymbol(snapshot.Symbol, ob.symbol); err != nil { + return err + } + if snapshot.Sequence == 0 { + return fieldError("sequence", "snapshot sequence must be greater than zero") + } + + bids, err := normalizeLevels(snapshot.Bids, "bids", true, false, ob.config.MaxDepth) + if err != nil { + return err + } + asks, err := normalizeLevels(snapshot.Asks, "asks", false, false, ob.config.MaxDepth) + if err != nil { + return err + } + if snapshot.Checksum != "" { + expected := ComputeBookChecksum(snapshot.Symbol, snapshot.Sequence, bids, asks) + if !strings.EqualFold(strings.TrimSpace(snapshot.Checksum), expected) { + return fieldError("checksum", "snapshot checksum mismatch") + } + } + + ob.bids = levelsToPointers(bids) + ob.asks = levelsToPointers(asks) + ob.sequence = snapshot.Sequence + ob.updatedAt = time.Now() + return nil +} + +func (ob *OrderBook) ApplyDelta(delta DeltaUpdate) error { + ob.mu.Lock() + defer ob.mu.Unlock() + + if ob.closed { + return ErrBookClosed + } + if err := validateSymbol(delta.Symbol, ob.symbol); err != nil { + return err + } + if delta.Sequence <= ob.sequence { + return fieldError("sequence", "stale delta sequence") + } + if delta.PreviousSequence != ob.sequence { + return fieldError("previous_sequence", "out-of-order delta sequence") + } + if len(delta.Levels) == 0 { + return fieldError("levels", "at least one level update is required") + } + + bids := clonePointerLevels(ob.bids) + asks := clonePointerLevels(ob.asks) + + for i, level := range delta.Levels { + field := fmt.Sprintf("levels[%d]", i) + if err := validateLevelUpdate(level, field); err != nil { + return err + } + switch level.Side { + case SideBid: + bids = applyLevelUpdate(bids, level, true, ob.config.MaxDepth) + case SideAsk: + asks = applyLevelUpdate(asks, level, false, ob.config.MaxDepth) + default: + return fieldError(field+".side", "side must be bid or ask") + } + } + + if delta.Checksum != "" { + expected := ComputeBookChecksum(delta.Symbol, delta.Sequence, bids, asks) + if !strings.EqualFold(strings.TrimSpace(delta.Checksum), expected) { + return fieldError("checksum", "delta checksum mismatch") + } + } + + ob.bids = levelsToPointers(bids) + ob.asks = levelsToPointers(asks) + ob.sequence = delta.Sequence + ob.updatedAt = time.Now() + return nil +} + +func ComputeBookChecksum(symbol types.Symbol, sequence uint64, bids, asks []types.Level) string { + hasher := sha256.New() + fmt.Fprintf(hasher, "%s|%d", symbol, sequence) + writeLevelsForChecksum(hasher, "B", bids) + writeLevelsForChecksum(hasher, "A", asks) + return hex.EncodeToString(hasher.Sum(nil)) +} + +type checksumWriter interface { + Write([]byte) (int, error) +} + +func writeLevelsForChecksum(w checksumWriter, side string, levels []types.Level) { + fmt.Fprintf(w, "|%s", side) + for _, level := range levels { + fmt.Fprintf(w, "|%s:%s:%d", level.Price.String(), level.Quantity.String(), level.Count) + } +} + +func validateSymbol(got, want types.Symbol) error { + if strings.TrimSpace(string(got)) == "" { + return fieldError("symbol", "symbol is required") + } + if got != want { + return fieldError("symbol", fmt.Sprintf("symbol %q does not match book %q", got, want)) + } + return nil +} + +func validateLevelUpdate(level LevelUpdate, field string) error { + if level.Side != SideBid && level.Side != SideAsk { + return fieldError(field+".side", "side must be bid or ask") + } + if !level.Price.GreaterThan(decimal.Zero) { + return fieldError(field+".price", "price must be positive") + } + if level.Quantity.LessThan(decimal.Zero) { + return fieldError(field+".quantity", "quantity must be zero or positive") + } + return nil +} + +func normalizeLevels(levels []types.Level, field string, desc bool, allowZero bool, maxDepth int) ([]types.Level, error) { + result := make([]types.Level, 0, len(levels)) + for i, level := range levels { + itemField := fmt.Sprintf("%s[%d]", field, i) + if !level.Price.GreaterThan(decimal.Zero) { + return nil, fieldError(itemField+".price", "price must be positive") + } + if allowZero { + if level.Quantity.LessThan(decimal.Zero) { + return nil, fieldError(itemField+".quantity", "quantity must be zero or positive") + } + } else if !level.Quantity.GreaterThan(decimal.Zero) { + return nil, fieldError(itemField+".quantity", "quantity must be positive") + } + if level.Count <= 0 { + level.Count = 1 + } + result = append(result, level) + } + sortLevels(result, desc) + if maxDepth > 0 && len(result) > maxDepth { + result = result[:maxDepth] + } + return result, nil +} + +func clonePointerLevels(levels []*types.Level) []types.Level { + result := make([]types.Level, 0, len(levels)) + for _, level := range levels { + if level != nil { + result = append(result, *level) + } + } + return result +} + +func levelsToPointers(levels []types.Level) []*types.Level { + result := make([]*types.Level, 0, len(levels)) + for i := range levels { + level := levels[i] + result = append(result, &level) + } + return result +} + +func applyLevelUpdate(levels []types.Level, update LevelUpdate, desc bool, maxDepth int) []types.Level { + for i, level := range levels { + if level.Price.Equal(update.Price) { + if update.Quantity.IsZero() { + return append(levels[:i], levels[i+1:]...) + } + levels[i].Quantity = update.Quantity + if levels[i].Count <= 0 { + levels[i].Count = 1 + } + sortLevels(levels, desc) + return trimDepth(levels, maxDepth) + } + } + + if update.Quantity.IsZero() { + return levels + } + + levels = append(levels, types.Level{ + Price: update.Price, + Quantity: update.Quantity, + Count: 1, + }) + sortLevels(levels, desc) + return trimDepth(levels, maxDepth) +} + +func sortLevels(levels []types.Level, desc bool) { + sort.SliceStable(levels, func(i, j int) bool { + if desc { + return levels[i].Price.GreaterThan(levels[j].Price) + } + return levels[i].Price.LessThan(levels[j].Price) + }) +} + +func trimDepth(levels []types.Level, maxDepth int) []types.Level { + if maxDepth > 0 && len(levels) > maxDepth { + return levels[:maxDepth] + } + return levels +} + +func fieldError(field, message string) error { + return &DeltaValidationError{Field: field, Message: message} +} diff --git a/market/orderbook/delta_test.go b/market/orderbook/delta_test.go new file mode 100644 index 00000000..291729a7 --- /dev/null +++ b/market/orderbook/delta_test.go @@ -0,0 +1,271 @@ +package orderbook + +import ( + "strings" + "testing" + + "github.com/shopspring/decimal" + "github.com/tent-of-trials/market/types" +) + +const testSymbol = types.Symbol("BTC-USD") + +func TestApplySnapshotThenValidDeltas(t *testing.T) { + book := newTestBook() + snapshot := SnapshotUpdate{ + Symbol: testSymbol, + Sequence: 10, + Bids: []types.Level{ + level("100.00", "1.5"), + level("99.50", "2.0"), + }, + Asks: []types.Level{ + level("101.00", "1.25"), + level("102.00", "3.0"), + }, + } + snapshot.Checksum = ComputeBookChecksum(snapshot.Symbol, snapshot.Sequence, snapshot.Bids, snapshot.Asks) + + if err := book.ApplySnapshot(snapshot); err != nil { + t.Fatalf("ApplySnapshot returned error: %v", err) + } + assertBookState(t, book, 10, []types.Level{ + level("100.00", "1.5"), + level("99.50", "2.0"), + }, []types.Level{ + level("101.00", "1.25"), + level("102.00", "3.0"), + }) + + delta := DeltaUpdate{ + Symbol: testSymbol, + PreviousSequence: 10, + Sequence: 11, + Levels: []LevelUpdate{ + {Side: SideBid, Price: dec("100.00"), Quantity: dec("1.75")}, + {Side: SideAsk, Price: dec("101.00"), Quantity: decimal.Zero}, + {Side: SideAsk, Price: dec("100.75"), Quantity: dec("0.5")}, + }, + } + expectedBids := []types.Level{ + level("100.00", "1.75"), + level("99.50", "2.0"), + } + expectedAsks := []types.Level{ + level("100.75", "0.5"), + level("102.00", "3.0"), + } + delta.Checksum = ComputeBookChecksum(delta.Symbol, delta.Sequence, expectedBids, expectedAsks) + + if err := book.ApplyDelta(delta); err != nil { + t.Fatalf("ApplyDelta returned error: %v", err) + } + assertBookState(t, book, 11, expectedBids, expectedAsks) +} + +func TestApplyDeltaRejectsMalformedFieldsWithoutMutation(t *testing.T) { + tests := []struct { + name string + delta DeltaUpdate + wantErr string + }{ + { + name: "invalid price", + delta: baseDelta(LevelUpdate{ + Side: SideBid, + Price: decimal.Zero, + Quantity: dec("1"), + }), + wantErr: "levels[0].price", + }, + { + name: "invalid quantity", + delta: baseDelta(LevelUpdate{ + Side: SideBid, + Price: dec("100"), + Quantity: dec("-1"), + }), + wantErr: "levels[0].quantity", + }, + { + name: "invalid side", + delta: baseDelta(LevelUpdate{ + Side: UpdateSide("middle"), + Price: dec("100"), + Quantity: dec("1"), + }), + wantErr: "levels[0].side", + }, + { + name: "wrong symbol", + delta: DeltaUpdate{ + Symbol: types.Symbol("ETH-USD"), + PreviousSequence: 10, + Sequence: 11, + Levels: []LevelUpdate{{ + Side: SideBid, + Price: dec("100"), + Quantity: dec("1"), + }}, + }, + wantErr: "symbol", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + book := seededBook(t) + err := book.ApplyDelta(tt.delta) + if err == nil { + t.Fatalf("ApplyDelta returned nil error") + } + if !strings.Contains(err.Error(), tt.wantErr) { + t.Fatalf("error %q does not contain %q", err.Error(), tt.wantErr) + } + assertSeededBookUnchanged(t, book) + }) + } +} + +func TestApplyDeltaRejectsStaleAndOutOfOrderWithoutMutation(t *testing.T) { + tests := []struct { + name string + delta DeltaUpdate + wantErr string + }{ + { + name: "stale sequence", + delta: DeltaUpdate{ + Symbol: testSymbol, + PreviousSequence: 9, + Sequence: 10, + Levels: []LevelUpdate{{ + Side: SideBid, + Price: dec("100"), + Quantity: dec("2"), + }}, + }, + wantErr: "sequence", + }, + { + name: "out of order previous sequence", + delta: DeltaUpdate{ + Symbol: testSymbol, + PreviousSequence: 8, + Sequence: 11, + Levels: []LevelUpdate{{ + Side: SideBid, + Price: dec("100"), + Quantity: dec("2"), + }}, + }, + wantErr: "previous_sequence", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + book := seededBook(t) + err := book.ApplyDelta(tt.delta) + if err == nil { + t.Fatalf("ApplyDelta returned nil error") + } + if !strings.Contains(err.Error(), tt.wantErr) { + t.Fatalf("error %q does not contain %q", err.Error(), tt.wantErr) + } + assertSeededBookUnchanged(t, book) + }) + } +} + +func TestApplyDeltaRejectsChecksumMismatchWithoutMutation(t *testing.T) { + book := seededBook(t) + delta := baseDelta(LevelUpdate{ + Side: SideBid, + Price: dec("100.00"), + Quantity: dec("2.00"), + }) + delta.Checksum = "bad-checksum" + + err := book.ApplyDelta(delta) + if err == nil { + t.Fatalf("ApplyDelta returned nil error") + } + if !strings.Contains(err.Error(), "checksum") { + t.Fatalf("error %q does not contain checksum", err.Error()) + } + assertSeededBookUnchanged(t, book) +} + +func seededBook(t *testing.T) *OrderBook { + t.Helper() + book := newTestBook() + snapshot := SnapshotUpdate{ + Symbol: testSymbol, + Sequence: 10, + Bids: []types.Level{level("100.00", "1.00")}, + Asks: []types.Level{level("101.00", "1.00")}, + } + snapshot.Checksum = ComputeBookChecksum(snapshot.Symbol, snapshot.Sequence, snapshot.Bids, snapshot.Asks) + if err := book.ApplySnapshot(snapshot); err != nil { + t.Fatalf("ApplySnapshot returned error: %v", err) + } + return book +} + +func assertSeededBookUnchanged(t *testing.T, book *OrderBook) { + t.Helper() + assertBookState(t, book, 10, []types.Level{level("100.00", "1.00")}, []types.Level{level("101.00", "1.00")}) +} + +func baseDelta(level LevelUpdate) DeltaUpdate { + return DeltaUpdate{ + Symbol: testSymbol, + PreviousSequence: 10, + Sequence: 11, + Levels: []LevelUpdate{level}, + } +} + +func newTestBook() *OrderBook { + return NewOrderBook(testSymbol, Config{MaxDepth: 10, PriceDecimals: 8, VolumeDecimals: 8}) +} + +func assertBookState(t *testing.T, book *OrderBook, wantSequence uint64, wantBids, wantAsks []types.Level) { + t.Helper() + if got := book.Sequence(); got != wantSequence { + t.Fatalf("sequence = %d, want %d", got, wantSequence) + } + assertLevels(t, "bids", pointerLevelsToValues(book.GetBids()), wantBids) + assertLevels(t, "asks", pointerLevelsToValues(book.GetAsks()), wantAsks) +} + +func assertLevels(t *testing.T, name string, got, want []types.Level) { + t.Helper() + if len(got) != len(want) { + t.Fatalf("%s length = %d, want %d: got %#v", name, len(got), len(want), got) + } + for i := range want { + if !got[i].Price.Equal(want[i].Price) || !got[i].Quantity.Equal(want[i].Quantity) || got[i].Count != want[i].Count { + t.Fatalf("%s[%d] = %#v, want %#v", name, i, got[i], want[i]) + } + } +} + +func pointerLevelsToValues(levels []*types.Level) []types.Level { + result := make([]types.Level, 0, len(levels)) + for _, level := range levels { + if level != nil { + result = append(result, *level) + } + } + return result +} + +func level(price, quantity string) types.Level { + return types.Level{Price: dec(price), Quantity: dec(quantity), Count: 1} +} + +func dec(value string) decimal.Decimal { + return decimal.RequireFromString(value) +} diff --git a/market/ws/orderbook_delta.go b/market/ws/orderbook_delta.go new file mode 100644 index 00000000..e5a6944c --- /dev/null +++ b/market/ws/orderbook_delta.go @@ -0,0 +1,155 @@ +package ws + +import ( + "bytes" + "encoding/json" + "fmt" + "strings" + + "github.com/shopspring/decimal" + "github.com/tent-of-trials/market/orderbook" + "github.com/tent-of-trials/market/types" +) + +type orderBookDeltaMessage struct { + Type string `json:"type"` + Symbol string `json:"symbol"` + PreviousSequence uint64 `json:"previous_sequence"` + Sequence uint64 `json:"sequence"` + Bids [][]string `json:"bids,omitempty"` + Asks [][]string `json:"asks,omitempty"` + Changes []bookLevelMessage `json:"changes,omitempty"` + Checksum string `json:"checksum,omitempty"` +} + +type bookLevelMessage struct { + Side string `json:"side"` + Price string `json:"price"` + Quantity string `json:"quantity"` +} + +func ParseOrderBookDeltaMessage(payload []byte) (orderbook.DeltaUpdate, error) { + var msg orderBookDeltaMessage + decoder := json.NewDecoder(bytes.NewReader(payload)) + decoder.DisallowUnknownFields() + if err := decoder.Decode(&msg); err != nil { + return orderbook.DeltaUpdate{}, fmt.Errorf("json: invalid orderbook delta payload: %w", err) + } + + if msg.Type != "" && msg.Type != "depth_delta" && msg.Type != "orderbook_delta" { + return orderbook.DeltaUpdate{}, fmt.Errorf("type: must be depth_delta or orderbook_delta") + } + if strings.TrimSpace(msg.Symbol) == "" { + return orderbook.DeltaUpdate{}, fmt.Errorf("symbol: symbol is required") + } + if msg.Sequence == 0 { + return orderbook.DeltaUpdate{}, fmt.Errorf("sequence: sequence must be greater than zero") + } + + levels := make([]orderbook.LevelUpdate, 0, len(msg.Bids)+len(msg.Asks)+len(msg.Changes)) + bids, err := parseBookSideLevels(msg.Bids, orderbook.SideBid, "bids") + if err != nil { + return orderbook.DeltaUpdate{}, err + } + levels = append(levels, bids...) + + asks, err := parseBookSideLevels(msg.Asks, orderbook.SideAsk, "asks") + if err != nil { + return orderbook.DeltaUpdate{}, err + } + levels = append(levels, asks...) + + changes, err := parseBookChangeLevels(msg.Changes) + if err != nil { + return orderbook.DeltaUpdate{}, err + } + levels = append(levels, changes...) + + if len(levels) == 0 { + return orderbook.DeltaUpdate{}, fmt.Errorf("levels: at least one bid, ask, or change is required") + } + + return orderbook.DeltaUpdate{ + Symbol: types.Symbol(msg.Symbol), + PreviousSequence: msg.PreviousSequence, + Sequence: msg.Sequence, + Levels: levels, + Checksum: msg.Checksum, + }, nil +} + +func parseBookSideLevels(raw [][]string, side orderbook.UpdateSide, field string) ([]orderbook.LevelUpdate, error) { + levels := make([]orderbook.LevelUpdate, 0, len(raw)) + for i, pair := range raw { + if len(pair) != 2 { + return nil, fmt.Errorf("%s[%d]: level must contain price and quantity", field, i) + } + price, err := parsePositiveDecimal(pair[0], fmt.Sprintf("%s[%d].price", field, i), false) + if err != nil { + return nil, err + } + quantity, err := parsePositiveDecimal(pair[1], fmt.Sprintf("%s[%d].quantity", field, i), true) + if err != nil { + return nil, err + } + levels = append(levels, orderbook.LevelUpdate{ + Side: side, + Price: price, + Quantity: quantity, + }) + } + return levels, nil +} + +func parseBookChangeLevels(raw []bookLevelMessage) ([]orderbook.LevelUpdate, error) { + levels := make([]orderbook.LevelUpdate, 0, len(raw)) + for i, item := range raw { + side, err := parseBookSide(item.Side, fmt.Sprintf("changes[%d].side", i)) + if err != nil { + return nil, err + } + price, err := parsePositiveDecimal(item.Price, fmt.Sprintf("changes[%d].price", i), false) + if err != nil { + return nil, err + } + quantity, err := parsePositiveDecimal(item.Quantity, fmt.Sprintf("changes[%d].quantity", i), true) + if err != nil { + return nil, err + } + levels = append(levels, orderbook.LevelUpdate{ + Side: side, + Price: price, + Quantity: quantity, + }) + } + return levels, nil +} + +func parseBookSide(value string, field string) (orderbook.UpdateSide, error) { + switch strings.ToLower(strings.TrimSpace(value)) { + case "bid", "buy": + return orderbook.SideBid, nil + case "ask", "sell": + return orderbook.SideAsk, nil + default: + return "", fmt.Errorf("%s: side must be bid or ask", field) + } +} + +func parsePositiveDecimal(value string, field string, allowZero bool) (decimal.Decimal, error) { + if strings.TrimSpace(value) == "" { + return decimal.Zero, fmt.Errorf("%s: decimal value is required", field) + } + parsed, err := decimal.NewFromString(value) + if err != nil { + return decimal.Zero, fmt.Errorf("%s: invalid decimal value", field) + } + if allowZero { + if parsed.LessThan(decimal.Zero) { + return decimal.Zero, fmt.Errorf("%s: value must be zero or positive", field) + } + } else if !parsed.GreaterThan(decimal.Zero) { + return decimal.Zero, fmt.Errorf("%s: value must be positive", field) + } + return parsed, nil +} diff --git a/market/ws/orderbook_delta_test.go b/market/ws/orderbook_delta_test.go new file mode 100644 index 00000000..0c0297c1 --- /dev/null +++ b/market/ws/orderbook_delta_test.go @@ -0,0 +1,275 @@ +package ws + +import ( + "fmt" + "strings" + "testing" + + "github.com/shopspring/decimal" + "github.com/tent-of-trials/market/orderbook" + "github.com/tent-of-trials/market/types" +) + +func TestParseOrderBookDeltaMessageValidBidAskPayload(t *testing.T) { + payload := []byte(`{ + "type":"depth_delta", + "symbol":"BTC-USD", + "previous_sequence":10, + "sequence":11, + "bids":[["100.00","1.25"]], + "asks":[["101.00","0"]], + "checksum":"abc123" + }`) + + delta, err := ParseOrderBookDeltaMessage(payload) + if err != nil { + t.Fatalf("ParseOrderBookDeltaMessage returned error: %v", err) + } + if delta.Symbol != types.Symbol("BTC-USD") { + t.Fatalf("symbol = %q", delta.Symbol) + } + if delta.PreviousSequence != 10 || delta.Sequence != 11 { + t.Fatalf("sequence = (%d, %d)", delta.PreviousSequence, delta.Sequence) + } + if delta.Checksum != "abc123" { + t.Fatalf("checksum = %q", delta.Checksum) + } + if len(delta.Levels) != 2 { + t.Fatalf("levels length = %d, want 2", len(delta.Levels)) + } + if delta.Levels[0].Side != orderbook.SideBid || delta.Levels[1].Side != orderbook.SideAsk { + t.Fatalf("unexpected sides: %#v", delta.Levels) + } +} + +func TestParseOrderBookDeltaMessageValidChangePayload(t *testing.T) { + payload := []byte(`{ + "type":"orderbook_delta", + "symbol":"ETH-USD", + "previous_sequence":7, + "sequence":8, + "changes":[ + {"side":"buy","price":"200.50","quantity":"3.0"}, + {"side":"sell","price":"201.00","quantity":"0"} + ] + }`) + + delta, err := ParseOrderBookDeltaMessage(payload) + if err != nil { + t.Fatalf("ParseOrderBookDeltaMessage returned error: %v", err) + } + if len(delta.Levels) != 2 { + t.Fatalf("levels length = %d, want 2", len(delta.Levels)) + } + if delta.Levels[0].Side != orderbook.SideBid || delta.Levels[1].Side != orderbook.SideAsk { + t.Fatalf("unexpected sides: %#v", delta.Levels) + } +} + +func TestParseOrderBookDeltaMessageRejectsMalformedPayloads(t *testing.T) { + tests := []struct { + name string + payload string + wantErr string + }{ + { + name: "invalid bid price", + payload: `{ + "type":"depth_delta", + "symbol":"BTC-USD", + "previous_sequence":10, + "sequence":11, + "bids":[["bad-price","1"]] + }`, + wantErr: "bids[0].price", + }, + { + name: "invalid ask quantity", + payload: `{ + "type":"depth_delta", + "symbol":"BTC-USD", + "previous_sequence":10, + "sequence":11, + "asks":[["101","-1"]] + }`, + wantErr: "asks[0].quantity", + }, + { + name: "invalid side", + payload: `{ + "type":"depth_delta", + "symbol":"BTC-USD", + "previous_sequence":10, + "sequence":11, + "changes":[{"side":"middle","price":"100","quantity":"1"}] + }`, + wantErr: "changes[0].side", + }, + { + name: "missing symbol", + payload: `{ + "type":"depth_delta", + "previous_sequence":10, + "sequence":11, + "bids":[["100","1"]] + }`, + wantErr: "symbol", + }, + { + name: "invalid symbol payload type", + payload: `{ + "type":"depth_delta", + "symbol":123, + "previous_sequence":10, + "sequence":11, + "bids":[["100","1"]] + }`, + wantErr: "symbol", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := ParseOrderBookDeltaMessage([]byte(tt.payload)) + if err == nil { + t.Fatalf("ParseOrderBookDeltaMessage returned nil error") + } + if !strings.Contains(err.Error(), tt.wantErr) { + t.Fatalf("error %q does not contain %q", err.Error(), tt.wantErr) + } + }) + } +} + +func TestParsedOrderBookDeltaAppliesThenRejectsInvalidWithoutMutation(t *testing.T) { + book := orderbook.NewOrderBook(types.Symbol("BTC-USD"), orderbook.Config{ + MaxDepth: 10, + PriceDecimals: 2, + VolumeDecimals: 8, + }) + initialBids := []types.Level{wsLevel("100.00", "1.00")} + initialAsks := []types.Level{wsLevel("101.00", "1.00")} + snapshot := orderbook.SnapshotUpdate{ + Symbol: types.Symbol("BTC-USD"), + Sequence: 10, + Bids: initialBids, + Asks: initialAsks, + Checksum: orderbook.ComputeBookChecksum(types.Symbol("BTC-USD"), 10, initialBids, initialAsks), + } + if err := book.ApplySnapshot(snapshot); err != nil { + t.Fatalf("ApplySnapshot returned error: %v", err) + } + + expectedBids := []types.Level{wsLevel("100.00", "2.50")} + expectedAsks := []types.Level{wsLevel("102.00", "1.00")} + checksum := orderbook.ComputeBookChecksum(types.Symbol("BTC-USD"), 11, expectedBids, expectedAsks) + payload := fmt.Sprintf(`{ + "type":"orderbook_delta", + "symbol":"BTC-USD", + "previous_sequence":10, + "sequence":11, + "changes":[ + {"side":"buy","price":"100.00","quantity":"2.50"}, + {"side":"sell","price":"101.00","quantity":"0"}, + {"side":"sell","price":"102.00","quantity":"1.00"} + ], + "checksum":%q + }`, checksum) + + delta, err := ParseOrderBookDeltaMessage([]byte(payload)) + if err != nil { + t.Fatalf("ParseOrderBookDeltaMessage returned error: %v", err) + } + if err := book.ApplyDelta(delta); err != nil { + t.Fatalf("ApplyDelta returned error: %v", err) + } + assertWSBookState(t, book, 11, expectedBids, expectedAsks) + + beforeBids := snapshotLevels(book.GetBids()) + beforeAsks := snapshotLevels(book.GetAsks()) + stale, err := ParseOrderBookDeltaMessage([]byte(`{ + "type":"orderbook_delta", + "symbol":"BTC-USD", + "previous_sequence":10, + "sequence":11, + "changes":[{"side":"buy","price":"99.00","quantity":"9.00"}] + }`)) + if err != nil { + t.Fatalf("ParseOrderBookDeltaMessage stale payload returned error: %v", err) + } + err = book.ApplyDelta(stale) + if err == nil || !strings.Contains(err.Error(), "sequence") { + t.Fatalf("ApplyDelta stale error = %v, want sequence error", err) + } + assertWSBookState(t, book, 11, beforeBids, beforeAsks) + + badChecksum, err := ParseOrderBookDeltaMessage([]byte(`{ + "type":"orderbook_delta", + "symbol":"BTC-USD", + "previous_sequence":11, + "sequence":12, + "changes":[{"side":"buy","price":"100.00","quantity":"3.00"}], + "checksum":"wrong" + }`)) + if err != nil { + t.Fatalf("ParseOrderBookDeltaMessage checksum payload returned error: %v", err) + } + err = book.ApplyDelta(badChecksum) + if err == nil || !strings.Contains(err.Error(), "checksum") { + t.Fatalf("ApplyDelta checksum error = %v, want checksum error", err) + } + assertWSBookState(t, book, 11, beforeBids, beforeAsks) +} + +func TestParseOrderBookDeltaMessageRejectsUnknownFields(t *testing.T) { + _, err := ParseOrderBookDeltaMessage([]byte(`{ + "type":"orderbook_delta", + "symbol":"BTC-USD", + "previous_sequence":10, + "sequence":11, + "changes":[{"side":"buy","price":"100.00","quantity":"2.50"}], + "unexpected":"field" + }`)) + if err == nil || !strings.Contains(err.Error(), "unknown field") { + t.Fatalf("error = %v, want unknown field error", err) + } +} + +func assertWSBookState(t *testing.T, book *orderbook.OrderBook, wantSequence uint64, wantBids, wantAsks []types.Level) { + t.Helper() + if got := book.Sequence(); got != wantSequence { + t.Fatalf("sequence = %d, want %d", got, wantSequence) + } + assertWSLevels(t, "bids", snapshotLevels(book.GetBids()), wantBids) + assertWSLevels(t, "asks", snapshotLevels(book.GetAsks()), wantAsks) +} + +func assertWSLevels(t *testing.T, name string, got, want []types.Level) { + t.Helper() + if len(got) != len(want) { + t.Fatalf("%s length = %d, want %d: got %#v", name, len(got), len(want), got) + } + for i := range want { + if !got[i].Price.Equal(want[i].Price) || !got[i].Quantity.Equal(want[i].Quantity) || got[i].Count != want[i].Count { + t.Fatalf("%s[%d] = %#v, want %#v", name, i, got[i], want[i]) + } + } +} + +func snapshotLevels(levels []*types.Level) []types.Level { + result := make([]types.Level, 0, len(levels)) + for _, level := range levels { + if level != nil { + result = append(result, *level) + } + } + return result +} + +func wsLevel(price, quantity string) types.Level { + return types.Level{ + Price: decimal.RequireFromString(price), + Quantity: decimal.RequireFromString(quantity), + Count: 1, + } +}