From 9108e8984b690acc4aa757792342bbd2ea6082c2 Mon Sep 17 00:00:00 2001 From: proerror Date: Thu, 16 Jul 2026 21:07:57 +0800 Subject: [PATCH] fix(research): pin static prediction policy graph --- rust_hft/prediction-markets/Cargo.lock | 1 - .../polymarket-btc-5m.example.json | 2 +- .../polymarket-sol-5m.example.json | 2 +- .../crates/ploy-research/Cargo.toml | 4 +- .../crates/ploy-research/build.rs | 351 ++++++++---------- ...rediction-policy-dependencies.linux.sha256 | 1 + .../prediction-policy-dependencies.linux.txt | 162 ++++++++ .../src/prediction_policy_identity.rs | 136 ++++++- .../docs/ALPHA_FACTOR_SEARCH_CICD.md | 15 +- rust_hft/prediction-markets/tasks/todo.md | 1 + 10 files changed, 474 insertions(+), 201 deletions(-) create mode 100644 rust_hft/prediction-markets/crates/ploy-research/prediction-policy-dependencies.linux.sha256 create mode 100644 rust_hft/prediction-markets/crates/ploy-research/prediction-policy-dependencies.linux.txt diff --git a/rust_hft/prediction-markets/Cargo.lock b/rust_hft/prediction-markets/Cargo.lock index d107d8d9d..40cc01b3a 100644 --- a/rust_hft/prediction-markets/Cargo.lock +++ b/rust_hft/prediction-markets/Cargo.lock @@ -5705,7 +5705,6 @@ dependencies = [ "sqlx", "tempfile", "tokio", - "toml 0.8.23", "tracing", "uuid", ] diff --git a/rust_hft/prediction-markets/config/research_missions/polymarket-btc-5m.example.json b/rust_hft/prediction-markets/config/research_missions/polymarket-btc-5m.example.json index 108e47ef6..95bdb3a17 100644 --- a/rust_hft/prediction-markets/config/research_missions/polymarket-btc-5m.example.json +++ b/rust_hft/prediction-markets/config/research_missions/polymarket-btc-5m.example.json @@ -14,7 +14,7 @@ ], "horizon": "5m", "prompt_snapshot_id": "sha256:2b55ba0e724dfc9f5a040911e397058a09c6229e42064e4998739d617b368dcb", - "search_policy_snapshot_id": "sha256:9d34971aee81a37c0777f747d1db1d4dfadc05090af20bb4ac2b47415dd139e2", + "search_policy_snapshot_id": "sha256:1a64d6c8da22d8fc058bd3d7ac12676ec384714e88a5bdbef43ab4ccffa055fa", "search_budget": { "max_candidates": 6, "max_llm_calls": 2, diff --git a/rust_hft/prediction-markets/config/research_missions/polymarket-sol-5m.example.json b/rust_hft/prediction-markets/config/research_missions/polymarket-sol-5m.example.json index ecf9545dc..743f23917 100644 --- a/rust_hft/prediction-markets/config/research_missions/polymarket-sol-5m.example.json +++ b/rust_hft/prediction-markets/config/research_missions/polymarket-sol-5m.example.json @@ -14,7 +14,7 @@ ], "horizon": "5m", "prompt_snapshot_id": "sha256:0816ebccf4c75ee6bdcfe315b253c84ddf1808a38687ebe3b924b87bd72a52a9", - "search_policy_snapshot_id": "sha256:9d34971aee81a37c0777f747d1db1d4dfadc05090af20bb4ac2b47415dd139e2", + "search_policy_snapshot_id": "sha256:1a64d6c8da22d8fc058bd3d7ac12676ec384714e88a5bdbef43ab4ccffa055fa", "search_budget": { "max_candidates": 6, "max_llm_calls": 2, diff --git a/rust_hft/prediction-markets/crates/ploy-research/Cargo.toml b/rust_hft/prediction-markets/crates/ploy-research/Cargo.toml index 1f3120521..b4be8324c 100644 --- a/rust_hft/prediction-markets/crates/ploy-research/Cargo.toml +++ b/rust_hft/prediction-markets/crates/ploy-research/Cargo.toml @@ -52,9 +52,7 @@ rust_decimal_macros.workspace = true tempfile.workspace = true [build-dependencies] -serde.workspace = true -serde_json.workspace = true -toml.workspace = true +sha2.workspace = true [[example]] name = "event_dataset_baseline" diff --git a/rust_hft/prediction-markets/crates/ploy-research/build.rs b/rust_hft/prediction-markets/crates/ploy-research/build.rs index 0b85fe9cf..eddfa2d8a 100644 --- a/rust_hft/prediction-markets/crates/ploy-research/build.rs +++ b/rust_hft/prediction-markets/crates/ploy-research/build.rs @@ -1,43 +1,40 @@ -use serde::Deserialize; +use sha2::{Digest, Sha256}; use std::{ - collections::{BTreeMap, BTreeSet}, env, fs, path::{Path, PathBuf}, - process::Command, }; const POLICY_TARGET: &str = "x86_64-unknown-linux-gnu"; +const POLICY_GRAPH_SCHEMA: &str = "prediction-policy-dependencies.v5"; +const POLICY_GRAPH_FILE: &str = "prediction-policy-dependencies.linux.txt"; +const CANONICAL_POLICY_DEPENDENCY_HASH_FILE: &str = "prediction-policy-dependencies.linux.sha256"; +const POLICY_INPUTS: [(&str, &str); 6] = [ + ("Cargo.lock", "Cargo.lock"), + ("Cargo.toml", "Cargo.toml"), + ( + "crates/ploy-research/Cargo.toml", + "crates/ploy-research/Cargo.toml", + ), + ( + "crates/ploy-feed-loaders/Cargo.toml", + "crates/ploy-feed-loaders/Cargo.toml", + ), + ( + "crates/ploy-market-contracts/Cargo.toml", + "crates/ploy-market-contracts/Cargo.toml", + ), + ( + "crates/ploy-market-data/Cargo.toml", + "crates/ploy-market-data/Cargo.toml", + ), +]; const FORBIDDEN_RUNTIME_PACKAGES: [&str; 3] = [ "ploy-operator-contracts", "ploy-strategy-bundles", "ploy-trading", ]; - -#[derive(Deserialize)] -struct Metadata { - packages: Vec, -} - -#[derive(Deserialize)] -struct Package { - name: String, - version: String, - source: Option, - manifest_path: PathBuf, -} - -#[derive(Deserialize)] -struct Lockfile { - package: Vec, -} - -#[derive(Deserialize)] -struct LockPackage { - name: String, - version: String, - source: Option, - checksum: Option, -} +const EXCLUDED_HOST_OR_PROC_MACRO_PACKAGES: [&str; 3] = + ["core-foundation-sys", "security-framework", "sqlx-macros"]; fn main() { println!("cargo:rerun-if-changed=Cargo.toml"); @@ -46,178 +43,158 @@ fn main() { .parent() .and_then(Path::parent) .expect("ploy-research must remain under /crates"); - println!( - "cargo:rerun-if-changed={}", - workspace_dir.join("Cargo.toml").display() - ); - println!( - "cargo:rerun-if-changed={}", - workspace_dir.join("Cargo.lock").display() - ); - - let output = Command::new(env::var_os("CARGO").unwrap_or_else(|| "cargo".into())) - .current_dir(&manifest_dir) - .args([ - "metadata", - "--manifest-path", - "Cargo.toml", - "--format-version", - "1", - "--locked", - "--features", - "db", - ]) - .output() - .expect("run cargo metadata for the governed prediction policy profile"); - if !output.status.success() { - panic!( - "cargo metadata failed for the governed prediction policy profile: {}", - String::from_utf8_lossy(&output.stderr) + let graph_path = manifest_dir.join(POLICY_GRAPH_FILE); + let canonical_hash_path = manifest_dir.join(CANONICAL_POLICY_DEPENDENCY_HASH_FILE); + println!("cargo:rerun-if-changed={}", graph_path.display()); + println!("cargo:rerun-if-changed={}", canonical_hash_path.display()); + for (_, relative_path) in POLICY_INPUTS { + println!( + "cargo:rerun-if-changed={}", + workspace_dir.join(relative_path).display() ); } - let metadata: Metadata = serde_json::from_slice(&output.stdout) - .expect("parse cargo metadata for the governed prediction policy profile"); - let tree_output = Command::new(env::var_os("CARGO").unwrap_or_else(|| "cargo".into())) - .current_dir(&manifest_dir) - .args([ - "tree", - "--manifest-path", - "Cargo.toml", - "--locked", - "--features", - "db", - "--target", - POLICY_TARGET, - "--edges", - "normal,build", - "--prefix", - "none", - "--format", - "{p}\t{f}", - ]) - .output() - .expect("run cargo tree for the governed prediction policy profile"); - if !tree_output.status.success() { + + let graph = fs::read_to_string(&graph_path).unwrap_or_else(|error| { panic!( - "cargo tree failed for the governed prediction policy profile: {}", - String::from_utf8_lossy(&tree_output.stderr) + "read checked-in Linux policy dependency graph {}: {error}", + graph_path.display() + ) + }); + validate_checked_in_graph(&graph, workspace_dir); + + let canonical_hash = read_canonical_dependency_hash(&canonical_hash_path); + let graph_hash = dependency_fingerprint_hash(&graph); + if canonical_hash != graph_hash { + panic!( + "checked-in Linux prediction-policy dependency graph hash {graph_hash} does not match {}; regenerate both reviewed policy graph artifacts together", + canonical_hash_path.display() ); } - let lockfile: Lockfile = toml::from_str( - &fs::read_to_string(workspace_dir.join("Cargo.lock")) - .expect("read Cargo.lock for the governed prediction policy profile"), - ) - .expect("parse Cargo.lock for the governed prediction policy profile"); - let fingerprint = normalized_active_graph( - &metadata, - &lockfile, - &String::from_utf8(tree_output.stdout).expect("cargo tree output must be UTF-8"), - ) - .expect("normalize governed prediction policy dependency graph"); + + let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); fs::write( - PathBuf::from(env::var_os("OUT_DIR").unwrap()).join("prediction-policy-dependencies.txt"), - fingerprint, + out_dir.join("prediction-policy-dependencies.txt"), + format!("{canonical_hash}\n"), ) - .expect("write governed prediction policy dependency fingerprint"); + .expect("write canonical governed prediction policy dependency hash"); +} - for package in &metadata.packages { - if package.source.is_none() && package.manifest_path.starts_with(workspace_dir) { - println!("cargo:rerun-if-changed={}", package.manifest_path.display()); +pub(crate) fn validate_checked_in_graph(graph: &str, workspace_dir: &Path) { + let mut lines = graph.lines(); + let headers = [ + POLICY_GRAPH_SCHEMA.to_owned(), + format!("target={POLICY_TARGET}"), + "profile=default,db".to_owned(), + ]; + for expected in headers { + let actual = lines.next().unwrap_or_else(|| { + panic!("checked-in Linux policy dependency graph is missing {expected}") + }); + if actual != expected { + panic!( + "checked-in Linux policy dependency graph expected {expected:?}, found {actual:?}" + ); + } + } + for (label, relative_path) in POLICY_INPUTS { + let expected = format!( + "input:{label}={}", + sha256_file(&workspace_dir.join(relative_path)) + ); + let actual = lines.next().unwrap_or_else(|| { + panic!("checked-in Linux policy dependency graph is missing {expected}") + }); + if actual != expected { + panic!( + "checked-in Linux policy dependency graph input {label} is stale: expected {expected:?}, found {actual:?}; regenerate the reviewed graph" + ); } } -} -fn normalized_active_graph( - metadata: &Metadata, - lockfile: &Lockfile, - tree: &str, -) -> Result { - let mut packages = BTreeMap::<(&str, &str), Vec<&Package>>::new(); - for package in &metadata.packages { - packages - .entry((package.name.as_str(), package.version.as_str())) - .or_default() - .push(package); + let mut package_count = 0usize; + let mut has_sqlx_postgres = false; + let mut sqlx_has_postgres_feature = false; + for line in lines { + let package = line.strip_prefix("package:").unwrap_or_else(|| { + panic!("checked-in Linux policy dependency graph has invalid line {line:?}") + }); + let (package_id, fields) = package.split_once('|').unwrap_or_else(|| { + panic!("checked-in Linux policy dependency graph has invalid package {line:?}") + }); + let (name, version) = package_id.split_once('@').unwrap_or_else(|| { + panic!("checked-in Linux policy dependency graph has invalid package id {package_id:?}") + }); + if name.is_empty() + || version.is_empty() + || !fields.starts_with("source=") + || !fields.contains("|checksum=") + || !fields.contains("|features=") + { + panic!("checked-in Linux policy dependency graph has invalid package {line:?}"); + } + if FORBIDDEN_RUNTIME_PACKAGES.contains(&name) { + panic!("checked-in Linux policy dependency graph includes runtime authority {name}"); + } + if EXCLUDED_HOST_OR_PROC_MACRO_PACKAGES.contains(&name) { + panic!( + "checked-in Linux policy dependency graph includes host or proc-macro dependency {name}" + ); + } + if name == "sqlx-sqlite" { + panic!("checked-in Linux policy dependency graph includes sqlx-sqlite"); + } + has_sqlx_postgres |= name == "sqlx-postgres"; + if name == "sqlx" { + let features = fields + .split_once("|features=") + .map(|(_, features)| features) + .expect("validated sqlx package must include features"); + sqlx_has_postgres_feature |= features.split(',').any(|feature| feature == "postgres"); + } + package_count += 1; } - let checksums = lockfile - .package - .iter() - .map(|package| { - ( - ( - package.name.as_str(), - package.version.as_str(), - package.source.as_deref().unwrap_or("path"), - ), - package.checksum.as_deref().unwrap_or("none"), - ) - }) - .collect::>(); - let mut normalized_packages = BTreeSet::new(); - let mut selected_names = BTreeSet::new(); - for line in tree.lines().filter(|line| !line.trim().is_empty()) { - let line = line.strip_suffix(" (*)").unwrap_or(line); - let (display, features) = line - .split_once('\t') - .ok_or_else(|| format!("cargo tree line has no feature delimiter: {line}"))?; - let mut display_parts = display.split_whitespace(); - let name = display_parts - .next() - .ok_or_else(|| format!("cargo tree line has no package name: {line}"))?; - let version = display_parts - .next() - .and_then(|value| value.strip_prefix('v')) - .ok_or_else(|| format!("cargo tree line has no package version: {line}"))?; - let candidates = packages - .get(&(name, version)) - .ok_or_else(|| format!("cargo metadata has no package for {name}@{version}"))?; - let package = match candidates.as_slice() { - [package] => *package, - _ => { - return Err(format!( - "cargo metadata package identity is ambiguous for {name}@{version}" - )) - } - }; - let source = package.source.as_deref().unwrap_or("path"); - let checksum = checksums - .get(&(package.name.as_str(), package.version.as_str(), source)) - .copied() - .ok_or_else(|| { - format!( - "Cargo.lock is missing {}@{} from {source}", - package.name, package.version - ) - })?; - let mut features = features - .split(',') - .filter(|feature| !feature.is_empty()) - .collect::>(); - features.sort(); - selected_names.insert(package.name.as_str()); - normalized_packages.insert(format!( - "{}@{}|source={}|checksum={}|features={}", - package.name, - package.version, - source, - checksum, - features.join(",") - )); + if package_count == 0 { + panic!("checked-in Linux policy dependency graph has no packages"); } - for forbidden in FORBIDDEN_RUNTIME_PACKAGES { - if selected_names.contains(forbidden) { - return Err(format!( - "governed prediction policy graph includes runtime authority {forbidden}" - )); - } + if !has_sqlx_postgres || !sqlx_has_postgres_feature { + panic!( + "checked-in Linux policy dependency graph must retain the PostgreSQL sqlx runtime profile" + ); } +} + +fn sha256_file(path: &Path) -> String { + let bytes = fs::read(path) + .unwrap_or_else(|error| panic!("read policy dependency input {}: {error}", path.display())); + format!("sha256:{:x}", Sha256::digest(bytes)) +} - let mut output = - format!("prediction-policy-dependencies.v2\ntarget={POLICY_TARGET}\nprofile=default,db\n"); - for package in normalized_packages { - output.push_str("package:"); - output.push_str(&package); - output.push('\n'); +fn read_canonical_dependency_hash(path: &Path) -> String { + let value = fs::read_to_string(path).unwrap_or_else(|error| { + panic!( + "read canonical Linux policy dependency hash {}: {error}", + path.display() + ) + }); + let value = value.trim(); + if !is_sha256_id(value) { + panic!( + "canonical Linux policy dependency hash {} must contain one sha256:<64 lowercase hexadecimal characters> value", + path.display() + ); } - Ok(output) + value.to_owned() +} + +fn dependency_fingerprint_hash(fingerprint: &str) -> String { + format!("sha256:{:x}", Sha256::digest(fingerprint)) +} + +fn is_sha256_id(value: &str) -> bool { + value.strip_prefix("sha256:").is_some_and(|digest| { + digest.len() == 64 + && digest + .bytes() + .all(|byte| byte.is_ascii_hexdigit() && !byte.is_ascii_uppercase()) + }) } diff --git a/rust_hft/prediction-markets/crates/ploy-research/prediction-policy-dependencies.linux.sha256 b/rust_hft/prediction-markets/crates/ploy-research/prediction-policy-dependencies.linux.sha256 new file mode 100644 index 000000000..7b64fc45a --- /dev/null +++ b/rust_hft/prediction-markets/crates/ploy-research/prediction-policy-dependencies.linux.sha256 @@ -0,0 +1 @@ +sha256:3be7f78980c58e79d0ed94b61dd2ec17570fd60b73c661611fc296d10fcf539f diff --git a/rust_hft/prediction-markets/crates/ploy-research/prediction-policy-dependencies.linux.txt b/rust_hft/prediction-markets/crates/ploy-research/prediction-policy-dependencies.linux.txt new file mode 100644 index 000000000..38a94c3a6 --- /dev/null +++ b/rust_hft/prediction-markets/crates/ploy-research/prediction-policy-dependencies.linux.txt @@ -0,0 +1,162 @@ +prediction-policy-dependencies.v5 +target=x86_64-unknown-linux-gnu +profile=default,db +input:Cargo.lock=sha256:38443de835a89c15d596eb434e4e6df7ed76cc89081a7416f5e2f6091b61c336 +input:Cargo.toml=sha256:fbcd5c33c246ade9700e461e9fd62c1f56f3af262ac67d8206a29b4d0bf626f9 +input:crates/ploy-research/Cargo.toml=sha256:1215f293ae9a79f63e2d88feea7ddf64b9e999a0cf974f25d49c2c6461bd29b8 +input:crates/ploy-feed-loaders/Cargo.toml=sha256:abf8f1b8adec93b1ad84a66294cb58eb65f9cbca33c701cc81c7635eb9b35e2c +input:crates/ploy-market-contracts/Cargo.toml=sha256:e086ddc8f162ff0abcabe832b1b61549f0daf7e98a78ab96f5512c596c3dc7f0 +input:crates/ploy-market-data/Cargo.toml=sha256:d50156acf961c0fb72d8dc6f22addbbd02b675996331a0e7cd8d5097ad9da5f9 +package:allocator-api2@0.2.21|source=registry+https://github.com/rust-lang/crates.io-index|checksum=683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923|features=alloc +package:anyhow@1.0.102|source=registry+https://github.com/rust-lang/crates.io-index|checksum=7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c|features=default,std +package:arrayvec@0.7.6|source=registry+https://github.com/rust-lang/crates.io-index|checksum=7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50|features=std +package:atoi@2.0.0|source=registry+https://github.com/rust-lang/crates.io-index|checksum=f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528|features=default,std +package:atomic-waker@1.1.2|source=registry+https://github.com/rust-lang/crates.io-index|checksum=1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0|features= +package:aws-lc-rs@1.17.1|source=registry+https://github.com/rust-lang/crates.io-index|checksum=4342d8937fc7e5dd9b1c60292261c0670c882a2cd1719cfc11b1af41731e32ad|features=aws-lc-sys,prebuilt-nasm +package:aws-lc-sys@0.42.0|source=registry+https://github.com/rust-lang/crates.io-index|checksum=6d9ceb1da931507a12f4fccea479dccd00da1943e1b4ae72d8e502d707361444|features=prebuilt-nasm +package:base64@0.22.1|source=registry+https://github.com/rust-lang/crates.io-index|checksum=72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6|features=alloc,default,std +package:bitflags@2.11.1|source=registry+https://github.com/rust-lang/crates.io-index|checksum=c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3|features= +package:block-buffer@0.10.4|source=registry+https://github.com/rust-lang/crates.io-index|checksum=3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71|features= +package:byteorder@1.5.0|source=registry+https://github.com/rust-lang/crates.io-index|checksum=1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b|features=std +package:bytes@1.11.1|source=registry+https://github.com/rust-lang/crates.io-index|checksum=1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33|features=default,std +package:cfg-if@1.0.4|source=registry+https://github.com/rust-lang/crates.io-index|checksum=9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801|features= +package:chrono@0.4.44|source=registry+https://github.com/rust-lang/crates.io-index|checksum=c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0|features=alloc,clock,default,iana-time-zone,js-sys,now,oldtime,serde,std,wasm-bindgen,wasmbind,winapi,windows-link +package:concurrent-queue@2.5.0|source=registry+https://github.com/rust-lang/crates.io-index|checksum=4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973|features=std +package:cpufeatures@0.2.17|source=registry+https://github.com/rust-lang/crates.io-index|checksum=59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280|features= +package:crc-catalog@2.4.0|source=registry+https://github.com/rust-lang/crates.io-index|checksum=19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5|features= +package:crc@3.4.0|source=registry+https://github.com/rust-lang/crates.io-index|checksum=5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d|features= +package:crossbeam-queue@0.3.12|source=registry+https://github.com/rust-lang/crates.io-index|checksum=0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115|features=alloc,default,std +package:crossbeam-utils@0.8.21|source=registry+https://github.com/rust-lang/crates.io-index|checksum=d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28|features=std +package:crypto-common@0.1.7|source=registry+https://github.com/rust-lang/crates.io-index|checksum=78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a|features=std +package:digest@0.10.7|source=registry+https://github.com/rust-lang/crates.io-index|checksum=9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292|features=alloc,block-buffer,core-api,default,mac,std,subtle +package:dotenvy@0.15.7|source=registry+https://github.com/rust-lang/crates.io-index|checksum=1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b|features= +package:either@1.15.0|source=registry+https://github.com/rust-lang/crates.io-index|checksum=48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719|features=default,serde,std +package:equivalent@1.0.2|source=registry+https://github.com/rust-lang/crates.io-index|checksum=877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f|features= +package:errno@0.3.14|source=registry+https://github.com/rust-lang/crates.io-index|checksum=39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb|features=default,std +package:event-listener@5.4.1|source=registry+https://github.com/rust-lang/crates.io-index|checksum=e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab|features=default,parking,std +package:fastrand@2.4.1|source=registry+https://github.com/rust-lang/crates.io-index|checksum=9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6|features=alloc,default,std +package:foldhash@0.1.5|source=registry+https://github.com/rust-lang/crates.io-index|checksum=d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2|features= +package:form_urlencoded@1.2.2|source=registry+https://github.com/rust-lang/crates.io-index|checksum=cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf|features=alloc,std +package:fs2@0.4.3|source=registry+https://github.com/rust-lang/crates.io-index|checksum=9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213|features= +package:futures-channel@0.3.32|source=registry+https://github.com/rust-lang/crates.io-index|checksum=07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d|features=alloc,default,futures-sink,sink,std +package:futures-core@0.3.32|source=registry+https://github.com/rust-lang/crates.io-index|checksum=7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d|features=alloc,default,std +package:futures-intrusive@0.5.0|source=registry+https://github.com/rust-lang/crates.io-index|checksum=1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f|features=alloc,default,parking_lot,std +package:futures-io@0.3.32|source=registry+https://github.com/rust-lang/crates.io-index|checksum=cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718|features=default,std +package:futures-sink@0.3.32|source=registry+https://github.com/rust-lang/crates.io-index|checksum=c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893|features= +package:futures-task@0.3.32|source=registry+https://github.com/rust-lang/crates.io-index|checksum=037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393|features=alloc,std +package:futures-util@0.3.32|source=registry+https://github.com/rust-lang/crates.io-index|checksum=389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6|features=alloc,futures-io,futures-sink,io,memchr,sink,slab,std +package:generic-array@0.14.7|source=registry+https://github.com/rust-lang/crates.io-index|checksum=85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a|features=more_lengths +package:getrandom@0.2.17|source=registry+https://github.com/rust-lang/crates.io-index|checksum=ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0|features=std +package:getrandom@0.4.2|source=registry+https://github.com/rust-lang/crates.io-index|checksum=0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555|features= +package:hashbrown@0.15.5|source=registry+https://github.com/rust-lang/crates.io-index|checksum=9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1|features=allocator-api2,default,default-hasher,equivalent,inline-more,raw-entry +package:hashbrown@0.17.0|source=registry+https://github.com/rust-lang/crates.io-index|checksum=4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51|features= +package:hashlink@0.10.0|source=registry+https://github.com/rust-lang/crates.io-index|checksum=7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1|features= +package:hex@0.4.3|source=registry+https://github.com/rust-lang/crates.io-index|checksum=7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70|features=alloc,default,std +package:hkdf@0.12.4|source=registry+https://github.com/rust-lang/crates.io-index|checksum=7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7|features= +package:hmac@0.12.1|source=registry+https://github.com/rust-lang/crates.io-index|checksum=6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e|features=reset +package:home@0.5.12|source=registry+https://github.com/rust-lang/crates.io-index|checksum=cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d|features= +package:http-body-util@0.1.3|source=registry+https://github.com/rust-lang/crates.io-index|checksum=b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a|features=default +package:http-body@1.0.1|source=registry+https://github.com/rust-lang/crates.io-index|checksum=1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184|features= +package:http@1.4.0|source=registry+https://github.com/rust-lang/crates.io-index|checksum=e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a|features=default,std +package:httparse@1.10.1|source=registry+https://github.com/rust-lang/crates.io-index|checksum=6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87|features=default,std +package:hyper-rustls@0.27.9|source=registry+https://github.com/rust-lang/crates.io-index|checksum=33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f|features=aws-lc-rs,http1,tls12 +package:hyper-util@0.1.20|source=registry+https://github.com/rust-lang/crates.io-index|checksum=96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0|features=client,client-legacy,client-proxy,default,http1,tokio +package:hyper@1.9.0|source=registry+https://github.com/rust-lang/crates.io-index|checksum=6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca|features=client,default,http1 +package:iana-time-zone@0.1.65|source=registry+https://github.com/rust-lang/crates.io-index|checksum=e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470|features=fallback +package:icu_collections@2.2.0|source=registry+https://github.com/rust-lang/crates.io-index|checksum=2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c|features= +package:icu_locale_core@2.2.0|source=registry+https://github.com/rust-lang/crates.io-index|checksum=92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29|features=zerovec +package:icu_normalizer@2.2.0|source=registry+https://github.com/rust-lang/crates.io-index|checksum=c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4|features=compiled_data +package:icu_normalizer_data@2.2.0|source=registry+https://github.com/rust-lang/crates.io-index|checksum=da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38|features= +package:icu_properties@2.2.0|source=registry+https://github.com/rust-lang/crates.io-index|checksum=bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de|features=compiled_data +package:icu_properties_data@2.2.0|source=registry+https://github.com/rust-lang/crates.io-index|checksum=8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14|features= +package:icu_provider@2.2.0|source=registry+https://github.com/rust-lang/crates.io-index|checksum=139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421|features=baked +package:idna@1.1.0|source=registry+https://github.com/rust-lang/crates.io-index|checksum=3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de|features=alloc,compiled_data,std +package:idna_adapter@1.2.1|source=registry+https://github.com/rust-lang/crates.io-index|checksum=3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344|features=compiled_data +package:indexmap@2.14.0|source=registry+https://github.com/rust-lang/crates.io-index|checksum=d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9|features=default,std +package:ipnet@2.12.0|source=registry+https://github.com/rust-lang/crates.io-index|checksum=d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2|features=default,std +package:iri-string@0.7.12|source=registry+https://github.com/rust-lang/crates.io-index|checksum=25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20|features=alloc,default,std +package:itoa@1.0.18|source=registry+https://github.com/rust-lang/crates.io-index|checksum=8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682|features= +package:libc@0.2.185|source=registry+https://github.com/rust-lang/crates.io-index|checksum=52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f|features=default,std +package:litemap@0.8.2|source=registry+https://github.com/rust-lang/crates.io-index|checksum=92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0|features= +package:lock_api@0.4.14|source=registry+https://github.com/rust-lang/crates.io-index|checksum=224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965|features=atomic_usize,default +package:log@0.4.29|source=registry+https://github.com/rust-lang/crates.io-index|checksum=5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897|features= +package:md-5@0.10.6|source=registry+https://github.com/rust-lang/crates.io-index|checksum=d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf|features= +package:memchr@2.8.0|source=registry+https://github.com/rust-lang/crates.io-index|checksum=f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79|features=alloc,default,std +package:mio@1.2.0|source=registry+https://github.com/rust-lang/crates.io-index|checksum=50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1|features=net,os-ext,os-poll +package:num-traits@0.2.19|source=registry+https://github.com/rust-lang/crates.io-index|checksum=071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841|features=i128,std +package:once_cell@1.21.4|source=registry+https://github.com/rust-lang/crates.io-index|checksum=9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50|features=alloc,default,race,std +package:openssl-probe@0.2.1|source=registry+https://github.com/rust-lang/crates.io-index|checksum=7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe|features= +package:optimizer@1.0.0|source=registry+https://github.com/rust-lang/crates.io-index|checksum=eae76a9cedd8a9155f2533e1ad45793496021a7c8e0fd48676442a7eee654776|features=default +package:parking@2.2.1|source=registry+https://github.com/rust-lang/crates.io-index|checksum=f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba|features= +package:parking_lot@0.12.5|source=registry+https://github.com/rust-lang/crates.io-index|checksum=93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a|features=default +package:parking_lot_core@0.9.12|source=registry+https://github.com/rust-lang/crates.io-index|checksum=2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1|features= +package:percent-encoding@2.3.2|source=registry+https://github.com/rust-lang/crates.io-index|checksum=9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220|features=alloc,default,std +package:pin-project-lite@0.2.17|source=registry+https://github.com/rust-lang/crates.io-index|checksum=a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd|features= +package:ploy-feed-loaders@0.1.0|source=path|checksum=none|features= +package:ploy-market-contracts@0.1.0|source=path|checksum=none|features= +package:ploy-market-data@0.1.0|source=path|checksum=none|features=default +package:ploy-research@0.1.0|source=path|checksum=none|features=db,default +package:potential_utf@0.1.5|source=registry+https://github.com/rust-lang/crates.io-index|checksum=0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564|features=zerovec +package:ppv-lite86@0.2.21|source=registry+https://github.com/rust-lang/crates.io-index|checksum=85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9|features=simd,std +package:rand@0.8.5|source=registry+https://github.com/rust-lang/crates.io-index|checksum=34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404|features=alloc,default,getrandom,libc,rand_chacha,std,std_rng +package:rand_chacha@0.3.1|source=registry+https://github.com/rust-lang/crates.io-index|checksum=e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88|features=std +package:rand_core@0.6.4|source=registry+https://github.com/rust-lang/crates.io-index|checksum=ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c|features=alloc,getrandom,std +package:reqwest@0.13.2|source=registry+https://github.com/rust-lang/crates.io-index|checksum=ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801|features=__rustls,__rustls-aws-lc-rs,__tls,blocking,json,rustls +package:ring@0.17.14|source=registry+https://github.com/rust-lang/crates.io-index|checksum=a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7|features=alloc,default,dev_urandom_fallback +package:rust_decimal@1.41.0|source=registry+https://github.com/rust-lang/crates.io-index|checksum=2ce901f9a19d251159075a4c37af514c3b8ef99c22e02dd8c19161cf397ee94a|features=default,maths,serde,serde-with-str,std +package:rustls-native-certs@0.8.3|source=registry+https://github.com/rust-lang/crates.io-index|checksum=612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63|features= +package:rustls-pki-types@1.14.0|source=registry+https://github.com/rust-lang/crates.io-index|checksum=be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd|features=alloc,default,std +package:rustls-platform-verifier@0.6.2|source=registry+https://github.com/rust-lang/crates.io-index|checksum=1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784|features= +package:rustls-webpki@0.103.13|source=registry+https://github.com/rust-lang/crates.io-index|checksum=61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e|features=alloc,aws-lc-rs,ring,std +package:rustls@0.23.38|source=registry+https://github.com/rust-lang/crates.io-index|checksum=69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21|features=aws-lc-rs,aws_lc_rs,ring,std,tls12 +package:scopeguard@1.2.0|source=registry+https://github.com/rust-lang/crates.io-index|checksum=94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49|features= +package:serde@1.0.228|source=registry+https://github.com/rust-lang/crates.io-index|checksum=9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e|features=alloc,default,derive,rc,serde_derive,std +package:serde_core@1.0.228|source=registry+https://github.com/rust-lang/crates.io-index|checksum=41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad|features=alloc,rc,result,std +package:serde_json@1.0.149|source=registry+https://github.com/rust-lang/crates.io-index|checksum=83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86|features=arbitrary_precision,default,raw_value,std +package:sha2@0.10.9|source=registry+https://github.com/rust-lang/crates.io-index|checksum=a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283|features=default,std +package:signal-hook-registry@1.4.8|source=registry+https://github.com/rust-lang/crates.io-index|checksum=c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b|features= +package:slab@0.4.12|source=registry+https://github.com/rust-lang/crates.io-index|checksum=0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5|features=std +package:smallvec@1.15.1|source=registry+https://github.com/rust-lang/crates.io-index|checksum=67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03|features=const_generics,const_new,serde +package:socket2@0.6.3|source=registry+https://github.com/rust-lang/crates.io-index|checksum=3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e|features=all +package:sqlx-core@0.8.6|source=registry+https://github.com/rust-lang/crates.io-index|checksum=ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6|features=_rt-tokio,_tls-rustls,_tls-rustls-ring-webpki,any,chrono,crc,default,json,migrate,offline,rust_decimal,rustls,serde,serde_json,sha2,tokio,tokio-stream,webpki-roots +package:sqlx-postgres@0.8.6|source=registry+https://github.com/rust-lang/crates.io-index|checksum=db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46|features=any,chrono,json,migrate,rust_decimal +package:sqlx@0.8.6|source=registry+https://github.com/rust-lang/crates.io-index|checksum=1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc|features=_rt-tokio,any,chrono,default,derive,json,macros,migrate,postgres,runtime-tokio,runtime-tokio-rustls,rust_decimal,sqlx-macros,sqlx-postgres,tls-rustls-ring,tls-rustls-ring-webpki +package:stable_deref_trait@1.2.1|source=registry+https://github.com/rust-lang/crates.io-index|checksum=6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596|features= +package:stringprep@0.1.5|source=registry+https://github.com/rust-lang/crates.io-index|checksum=7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1|features= +package:subtle@2.6.1|source=registry+https://github.com/rust-lang/crates.io-index|checksum=13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292|features= +package:sync_wrapper@1.0.2|source=registry+https://github.com/rust-lang/crates.io-index|checksum=0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263|features=futures,futures-core +package:thiserror@1.0.69|source=registry+https://github.com/rust-lang/crates.io-index|checksum=b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52|features= +package:thiserror@2.0.18|source=registry+https://github.com/rust-lang/crates.io-index|checksum=4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4|features=default,std +package:tinystr@0.8.3|source=registry+https://github.com/rust-lang/crates.io-index|checksum=c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d|features=zerovec +package:tinyvec@1.11.0|source=registry+https://github.com/rust-lang/crates.io-index|checksum=3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3|features=alloc,default,tinyvec_macros +package:tinyvec_macros@0.1.1|source=registry+https://github.com/rust-lang/crates.io-index|checksum=1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20|features= +package:tokio-rustls@0.26.4|source=registry+https://github.com/rust-lang/crates.io-index|checksum=1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61|features=aws-lc-rs,aws_lc_rs,tls12 +package:tokio-stream@0.1.18|source=registry+https://github.com/rust-lang/crates.io-index|checksum=32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70|features=default,fs,time +package:tokio@1.52.1|source=registry+https://github.com/rust-lang/crates.io-index|checksum=b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6|features=bytes,default,fs,io-util,libc,macros,mio,net,rt,rt-multi-thread,signal,signal-hook-registry,socket2,sync,time,tokio-macros +package:tower-http@0.6.8|source=registry+https://github.com/rust-lang/crates.io-index|checksum=d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8|features=follow-redirect,futures-util,iri-string,tower +package:tower-layer@0.3.3|source=registry+https://github.com/rust-lang/crates.io-index|checksum=121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e|features= +package:tower-service@0.3.3|source=registry+https://github.com/rust-lang/crates.io-index|checksum=8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3|features= +package:tower@0.5.3|source=registry+https://github.com/rust-lang/crates.io-index|checksum=ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4|features=futures-core,futures-util,pin-project-lite,retry,sync_wrapper,timeout,tokio,util +package:tracing-core@0.1.36|source=registry+https://github.com/rust-lang/crates.io-index|checksum=db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a|features=once_cell,std +package:tracing@0.1.44|source=registry+https://github.com/rust-lang/crates.io-index|checksum=63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100|features=attributes,default,log,std,tracing-attributes +package:try-lock@0.2.5|source=registry+https://github.com/rust-lang/crates.io-index|checksum=e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b|features= +package:typenum@1.19.0|source=registry+https://github.com/rust-lang/crates.io-index|checksum=562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb|features= +package:unicode-bidi@0.3.18|source=registry+https://github.com/rust-lang/crates.io-index|checksum=5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5|features=default,hardcoded-data,std +package:unicode-normalization@0.1.25|source=registry+https://github.com/rust-lang/crates.io-index|checksum=5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8|features=default,std +package:unicode-properties@0.1.4|source=registry+https://github.com/rust-lang/crates.io-index|checksum=7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d|features=default,emoji,general-category +package:untrusted@0.9.0|source=registry+https://github.com/rust-lang/crates.io-index|checksum=8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1|features= +package:url@2.5.8|source=registry+https://github.com/rust-lang/crates.io-index|checksum=ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed|features=default,std +package:utf8_iter@1.0.4|source=registry+https://github.com/rust-lang/crates.io-index|checksum=b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be|features= +package:uuid@1.23.1|source=registry+https://github.com/rust-lang/crates.io-index|checksum=ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76|features=default,rng,std,v4 +package:want@0.3.1|source=registry+https://github.com/rust-lang/crates.io-index|checksum=bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e|features= +package:webpki-roots@0.26.11|source=registry+https://github.com/rust-lang/crates.io-index|checksum=521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9|features= +package:webpki-roots@1.0.7|source=registry+https://github.com/rust-lang/crates.io-index|checksum=52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d|features= +package:whoami@1.6.1|source=registry+https://github.com/rust-lang/crates.io-index|checksum=5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d|features= +package:writeable@0.6.3|source=registry+https://github.com/rust-lang/crates.io-index|checksum=1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4|features= +package:yoke@0.8.2|source=registry+https://github.com/rust-lang/crates.io-index|checksum=abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca|features=derive,zerofrom +package:zerocopy@0.8.48|source=registry+https://github.com/rust-lang/crates.io-index|checksum=eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9|features=simd +package:zerofrom@0.1.7|source=registry+https://github.com/rust-lang/crates.io-index|checksum=69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df|features=derive +package:zeroize@1.8.2|source=registry+https://github.com/rust-lang/crates.io-index|checksum=b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0|features=alloc,default +package:zerotrie@0.2.4|source=registry+https://github.com/rust-lang/crates.io-index|checksum=0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf|features=yoke,zerofrom +package:zerovec@0.11.6|source=registry+https://github.com/rust-lang/crates.io-index|checksum=90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239|features=derive,yoke +package:zmij@1.0.21|source=registry+https://github.com/rust-lang/crates.io-index|checksum=b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa|features= diff --git a/rust_hft/prediction-markets/crates/ploy-research/src/prediction_policy_identity.rs b/rust_hft/prediction-markets/crates/ploy-research/src/prediction_policy_identity.rs index df03719ac..7bafeb978 100644 --- a/rust_hft/prediction-markets/crates/ploy-research/src/prediction_policy_identity.rs +++ b/rust_hft/prediction-markets/crates/ploy-research/src/prediction_policy_identity.rs @@ -3,20 +3,66 @@ const DEPENDENCY_FINGERPRINT: &str = include_str!(concat!( "/prediction-policy-dependencies.txt" )); +#[cfg(test)] +const CHECKED_IN_DEPENDENCY_GRAPH: &str = + include_str!("../prediction-policy-dependencies.linux.txt"); + +#[cfg(test)] +#[allow(dead_code)] +#[path = "../build.rs"] +mod policy_graph_build; + pub(crate) fn prediction_dependency_fingerprint() -> &'static str { DEPENDENCY_FINGERPRINT } +#[cfg(test)] +fn checked_in_prediction_dependency_graph() -> &'static str { + CHECKED_IN_DEPENDENCY_GRAPH +} + #[cfg(test)] mod tests { use super::*; + use sha2::{Digest, Sha256}; + use std::{ + any::Any, + panic::{catch_unwind, AssertUnwindSafe}, + path::Path, + }; #[test] - fn generated_profile_is_linux_db_only_and_excludes_runtime_authority() { - let fingerprint = prediction_dependency_fingerprint(); - assert!(fingerprint.starts_with("prediction-policy-dependencies.v2\n")); + fn runtime_identity_uses_a_canonical_checked_in_linux_graph_with_features() { + let canonical = prediction_dependency_fingerprint().trim(); + assert!( + canonical.strip_prefix("sha256:").is_some_and(|digest| { + digest.len() == 64 + && digest + .bytes() + .all(|byte| byte.is_ascii_hexdigit() && !byte.is_ascii_uppercase()) + }), + "the runtime policy dependency identity must be a canonical SHA-256" + ); + + let fingerprint = checked_in_prediction_dependency_graph(); + assert_eq!( + canonical, + format!("sha256:{:x}", Sha256::digest(fingerprint)), + "the runtime identity must hash the exact checked-in Linux graph" + ); + assert!(fingerprint.starts_with("prediction-policy-dependencies.v5\n")); assert!(fingerprint.contains("target=x86_64-unknown-linux-gnu\n")); assert!(fingerprint.contains("profile=default,db\n")); + for input in [ + "Cargo.lock", + "Cargo.toml", + "crates/ploy-research/Cargo.toml", + "crates/ploy-feed-loaders/Cargo.toml", + "crates/ploy-market-contracts/Cargo.toml", + "crates/ploy-market-data/Cargo.toml", + ] { + assert!(fingerprint.contains(&format!("input:{input}=sha256:"))); + } assert!(fingerprint.contains("package:sqlx-postgres@0.8.6|")); let sqlx = fingerprint .lines() @@ -34,5 +80,89 @@ mod tests { ] { assert!(!fingerprint.contains(&format!("package:{forbidden}@"))); } + for excluded_package in ["core-foundation-sys", "security-framework", "sqlx-macros"] { + assert!( + !fingerprint.contains(&format!("package:{excluded_package}@")), + "the Linux policy fingerprint must not include host or proc-macro dependency {excluded_package}" + ); + } + } + + #[test] + fn checked_in_graph_validation_rejects_stale_and_forbidden_counterexamples() { + let workspace_dir = Path::new(env!("CARGO_MANIFEST_DIR")) + .parent() + .and_then(Path::parent) + .expect("ploy-research must remain under /crates"); + let graph = checked_in_prediction_dependency_graph(); + + let lockfile_input = graph + .lines() + .find(|line| line.starts_with("input:Cargo.lock=")) + .expect("checked-in graph must pin Cargo.lock"); + let stale_lockfile_graph = graph.replacen( + lockfile_input, + "input:Cargo.lock=sha256:0000000000000000000000000000000000000000000000000000000000000000", + 1, + ); + assert_graph_is_rejected( + &stale_lockfile_graph, + workspace_dir, + "input Cargo.lock is stale", + ); + + for (package, expected) in [ + ("ploy-trading", "includes runtime authority ploy-trading"), + ("sqlx-sqlite", "includes sqlx-sqlite"), + ( + "core-foundation-sys", + "includes host or proc-macro dependency core-foundation-sys", + ), + ( + "security-framework", + "includes host or proc-macro dependency security-framework", + ), + ( + "sqlx-macros", + "includes host or proc-macro dependency sqlx-macros", + ), + ] { + let forbidden_package_graph = graph_with_package(graph, package); + assert_graph_is_rejected(&forbidden_package_graph, workspace_dir, expected); + } + } + + fn graph_with_package(graph: &str, package: &str) -> String { + let original = graph + .lines() + .find(|line| line.starts_with("package:")) + .expect("checked-in graph must contain a package"); + let (_, fields) = original + .strip_prefix("package:") + .and_then(|package| package.split_once('|')) + .expect("checked-in package must retain fields"); + graph.replacen(original, &format!("package:{package}@0.0.0|{fields}"), 1) + } + + fn assert_graph_is_rejected(graph: &str, workspace_dir: &Path, expected: &str) { + let panic = catch_unwind(AssertUnwindSafe(|| { + policy_graph_build::validate_checked_in_graph(graph, workspace_dir); + })) + .expect_err("counterexample graph must fail closed"); + let message = panic_message(panic); + assert!( + message.contains(expected), + "expected rejection {expected:?}, got {message:?}" + ); + } + + fn panic_message(panic: Box) -> String { + if let Some(message) = panic.downcast_ref::() { + return message.clone(); + } + if let Some(message) = panic.downcast_ref::<&str>() { + return (*message).to_owned(); + } + "non-string panic payload".to_owned() } } diff --git a/rust_hft/prediction-markets/docs/ALPHA_FACTOR_SEARCH_CICD.md b/rust_hft/prediction-markets/docs/ALPHA_FACTOR_SEARCH_CICD.md index f6aeef687..958f9f0e5 100644 --- a/rust_hft/prediction-markets/docs/ALPHA_FACTOR_SEARCH_CICD.md +++ b/rust_hft/prediction-markets/docs/ALPHA_FACTOR_SEARCH_CICD.md @@ -554,11 +554,16 @@ The printed values must equal `prompt_snapshot_id` and mission revision; do not rewrite an existing run's identity. There is no Python proposal compatibility path. -The policy identity binds both the curated evaluator/LoopRun sources and -Cargo's resolved `x86_64-unknown-linux-gnu` `default,db` package, checksum, and -feature set. Generation fails closed if operator, strategy-bundle, or trading -packages enter that production research graph; those compatibility packages -remain available only behind the separate `strategy-runtime` feature. +The policy identity binds both the curated evaluator/LoopRun sources and a +checked-in, reviewed `x86_64-unknown-linux-gnu` `default,db` Cargo graph of +packages, checksums, and features. The graph also pins the lockfile and every +path dependency manifest in that graph, so changing any of those inputs fails +closed until the reviewed graph and its hash are regenerated together. +Regenerate those artifacts from the pinned Linux research CI evidence rather +than from an arbitrary developer or Docker build invocation. +Operator, strategy-bundle, trading, SQLite, host, and proc-macro packages are +rejected from that production research graph; the compatibility packages remain +available only behind the separate `strategy-runtime` feature. The mission LoopRun driver is the normal BTC/SOL research entrypoint once the immutable research snapshot exists: diff --git a/rust_hft/prediction-markets/tasks/todo.md b/rust_hft/prediction-markets/tasks/todo.md index 5cce158d9..89bcdb920 100644 --- a/rust_hft/prediction-markets/tasks/todo.md +++ b/rust_hft/prediction-markets/tasks/todo.md @@ -56,6 +56,7 @@ Migrate the imported PLOY compatibility code into Monday's canonical market-fami - [x] Gate prediction OOS evidence with Brier score, log loss, calibration error, settlement PnL, and event-level capacity. - [x] Route typed probability-blend candidates into the prediction evaluator and emit candidate-specific loop feedback. - [x] Add separate governed BTC and SOL five-minute mission templates; reject mixed-symbol missions and unresolved provenance. +- [x] Pin a canonical reviewed Linux prediction-policy graph and fail closed when its lockfile or policy path-manifest inputs change; preserve package, checksum, and feature evidence, exclude host/proc-macro runtime dependencies, then pin the v5 identity in both mission templates. - [x] Add a resumable Monday prediction LoopRun with mission candidate/call/time budgets and content-addressed iteration evidence. - [x] Implement the complete authoritative prediction LoopRun and tests in `ploy-research` Rust without introducing a Python runner; retire the Binance-to-opening-reference backfill helper. - [x] Recompute terminal provisional models from typed Brier, log-loss, ECE, settlement-PnL, and conservative-capacity metrics recorded in append-only feedback; require a separate sealed snapshot for final keep.