From 102ecec3cd448db954fb78ed21b4e7acaa2cb762 Mon Sep 17 00:00:00 2001 From: Nick Ruest <127058086+nicholas-ruest@users.noreply.github.com> Date: Sat, 10 Oct 2026 14:16:55 -0400 Subject: [PATCH 1/3] fix: enforce exact Cloud Function CORS allowlist --- .github/workflows/ci.yml | 26 +++- .../2026-10-10-cloud-function-cors.md | 46 +++++++ index.js | 11 +- package.json | 2 +- test/cloud-function.test.js | 121 ++++++++++++++++++ 5 files changed, 200 insertions(+), 6 deletions(-) create mode 100644 docs/metaharness/2026-10-10-cloud-function-cors.md create mode 100644 test/cloud-function.test.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5040443..e5ddb3b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,27 @@ env: RUSTFLAGS: "-Dwarnings" jobs: + cloud-function: + name: Cloud Function + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Set up Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: 20 + cache: npm + + - name: Install dependencies + run: npm ci --ignore-scripts + + - name: Test Cloud Function + run: npm run test:cf + + - name: Audit production dependencies + run: npm audit --omit=dev --audit-level=high + lint: name: Lint runs-on: ubuntu-latest @@ -190,12 +211,13 @@ jobs: all-checks: name: All Checks Pass runs-on: ubuntu-latest - needs: [lint, test, build, security-audit] + needs: [cloud-function, lint, test, build, security-audit] if: always() steps: - name: Check all jobs passed run: | - if [ "${{ needs.lint.result }}" != "success" ] || \ + if [ "${{ needs.cloud-function.result }}" != "success" ] || \ + [ "${{ needs.lint.result }}" != "success" ] || \ [ "${{ needs.test.result }}" != "success" ] || \ [ "${{ needs.build.result }}" != "success" ] || \ [ "${{ needs.security-audit.result }}" != "success" ]; then diff --git a/docs/metaharness/2026-10-10-cloud-function-cors.md b/docs/metaharness/2026-10-10-cloud-function-cors.md new file mode 100644 index 0000000..17b846d --- /dev/null +++ b/docs/metaharness/2026-10-10-cloud-function-cors.md @@ -0,0 +1,46 @@ +# MetaHarness evidence: Cloud Function CORS allowlist + +## Scope and frozen contract + +- Baseline: `main` at `7e0ec19c3035e98176db0d31cdef2d3ea9616b31`. +- Source candidate: restrict `Access-Control-Allow-Origin` to an exact configured origin, normalize comma-separated configuration, and emit `Vary: Origin` for dynamic allowlists. +- Compatibility anchors: explicit wildcard behavior, handler export, unknown-route 404, missing-backend 503, and unchanged Rust core/Cargo manifests. +- Project-source optimization and harness optimization were evaluated separately. The project candidate was eligible for Flywheel promotion; Darwin did not promote a harness mutation. + +## Pinned tooling + +Upstream MetaHarness revision: `e0dfd44da72b7adfb7c57fdd42d124be58ed7086`. + +| Component | Version | +| --- | --- | +| `metaharness` | `0.4.17` | +| `@metaharness/darwin` | `0.10.3` | +| `@metaharness/flywheel` | `0.1.12` | +| Node.js | `24.19.0` locally; CI pins Node.js 20 | +| Rust | `1.99.0` (`stable`, 2026-10-01 release manifest) | + +## Reproducible results + +| Gate | Baseline | Candidate | Status | +| --- | ---: | ---: | --- | +| Cloud Function contract (`npm run test:cf`) | 2/7 | 7/7 | PASS candidate | +| npm production audit, high threshold | 0 vulnerabilities | 0 vulnerabilities | PASS | +| Flywheel CORS holdout | 1/6 (0.1667) | 6/6 (1.0000) | PROMOTED | +| Flywheel frozen anchor | 5/5 | 5/5 | PASS, no regression | +| Darwin real-sandbox score | 0.435 | 0.435 | NO_IMPROVEMENT | +| `cargo fmt --all -- --check` | Existing workspace formatting drift | Unchanged | BLOCKED | +| `cargo check --workspace` | Local environment lacks `pkg-config`/OpenSSL development metadata | Unchanged | BLOCKED locally | + +The untouched implementation returned the first configured trusted origin for both a disallowed request origin and a request without an `Origin` header. The candidate withholds the allow-origin header in both cases. The test suite also covers whitespace normalization, allowed-origin reflection, wildcard compatibility, preflight behavior, and routing fail-closed anchors. + +MetaHarness repository scoring completed with exit 0: harness fit 71, compile confidence 100, task coverage 100, tool safety 95, memory usefulness 47, estimated cost per run USD 0.048, and all six hard constraints satisfied. + +Darwin was invoked with a fixed deterministic mutator, seed `2026101014`, one generation, two children, concurrency one, and the real sandbox. The baseline and both children scored 0.435 with test pass rate 0; the baseline remained the winner with delta 0.000. No harness mutation was copied into project source. + +Flywheel used a deterministic evidence proposer with a frozen six-item CORS holdout and five-item anchor. The default conjunctive gate promoted the exact candidate with primary delta `+0.8333333333333334`, no-op rate `0.8333 -> 0`, cost per win `1 -> 0.1667`, and no regression. Gate fingerprint: `c5942e7c9bf8f28fe7af47a6ec3887d11eeb3f8c8e8601cc6a7a54969ea0b981`. Independent replay passed Ed25519 receipts, root reconstruction, contiguous parents, promotion verdicts, the full diagnostic ledger, sealed score fields, and the pinned gate fingerprint. + +## CI and rollback + +The CI addition runs a clean npm install, the Cloud Function contract suite, and the production dependency audit on Linux with commit-pinned checkout and Node setup actions. It is included in the existing `All Checks Pass` aggregate and does not remove or relax any Rust gate. + +If a post-merge regression is discovered, revert the PR merge commit. This restores the previous CORS behavior, test script, and CI job as one atomic rollback. No deployment or package publication is part of this change. diff --git a/index.js b/index.js index a151f8c..606a080 100644 --- a/index.js +++ b/index.js @@ -8,7 +8,7 @@ const crypto = require('crypto'); const SERVICE_NAME = 'research-lab-agents'; const BACKEND_URL = process.env.RESEARCH_LAB_BACKEND_URL; const ALLOWED_ORIGINS = process.env.ALLOWED_ORIGINS - ? process.env.ALLOWED_ORIGINS.split(',') + ? process.env.ALLOWED_ORIGINS.split(',').map((origin) => origin.trim()).filter(Boolean) : ['*']; const AGENT_ROUTES = { @@ -43,12 +43,17 @@ function buildExecutionMetadata(traceId, executionId) { function resolveOrigin(reqOrigin) { if (ALLOWED_ORIGINS.includes('*')) return '*'; if (reqOrigin && ALLOWED_ORIGINS.includes(reqOrigin)) return reqOrigin; - return ALLOWED_ORIGINS[0]; + return undefined; } function setCorsHeaders(req, res) { const origin = resolveOrigin(req.headers.origin || ''); - res.set('Access-Control-Allow-Origin', origin); + if (!ALLOWED_ORIGINS.includes('*')) { + res.set('Vary', 'Origin'); + } + if (origin) { + res.set('Access-Control-Allow-Origin', origin); + } res.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.set( 'Access-Control-Allow-Headers', diff --git a/package.json b/package.json index 253cc21..91f24aa 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "build": "echo 'Cloud Function: no build step required'", "build:rust": "cargo build --workspace", "test": "cargo test --workspace", - "test:cf": "node -e \"const h = require('./index.js'); console.log('handler exported:', typeof h.handler === 'function')\"", + "test:cf": "node --test test/*.test.js", "lint": "cargo clippy --workspace", "fmt": "cargo fmt --all", "deploy": "gcloud functions deploy research-lab-agents --runtime nodejs20 --trigger-http --region us-central1 --project agentics-dev --entry-point handler --memory 512MB --timeout 120s --no-allow-unauthenticated" diff --git a/test/cloud-function.test.js b/test/cloud-function.test.js new file mode 100644 index 0000000..15602e2 --- /dev/null +++ b/test/cloud-function.test.js @@ -0,0 +1,121 @@ +'use strict'; + +const assert = require('node:assert/strict'); +const path = require('node:path'); +const test = require('node:test'); + +const modulePath = path.resolve(__dirname, '..', 'index.js'); + +function loadHandler(allowedOrigins) { + if (allowedOrigins === undefined) { + delete process.env.ALLOWED_ORIGINS; + } else { + process.env.ALLOWED_ORIGINS = allowedOrigins; + } + delete process.env.RESEARCH_LAB_BACKEND_URL; + delete require.cache[modulePath]; + return require(modulePath).handler; +} + +function createResponse() { + const headers = new Map(); + return { + headers, + statusCode: 200, + body: undefined, + set(name, value) { + headers.set(name.toLowerCase(), value); + return this; + }, + status(code) { + this.statusCode = code; + return this; + }, + json(value) { + this.body = value; + return this; + }, + send(value) { + this.body = value; + return this; + }, + }; +} + +async function invoke({ allowedOrigins, origin, method = 'GET', pathName = '/missing' }) { + const handler = loadHandler(allowedOrigins); + const req = { method, path: pathName, headers: {} }; + if (origin !== undefined) req.headers.origin = origin; + const res = createResponse(); + await handler(req, res); + return res; +} + +test.afterEach(() => { + delete process.env.ALLOWED_ORIGINS; + delete process.env.RESEARCH_LAB_BACKEND_URL; + delete require.cache[modulePath]; +}); + +test('returns an exact configured origin', async () => { + const res = await invoke({ + allowedOrigins: 'https://allowed.example,https://second.example', + origin: 'https://second.example', + }); + assert.equal(res.headers.get('access-control-allow-origin'), 'https://second.example'); + assert.equal(res.headers.get('vary'), 'Origin'); +}); + +test('does not grant CORS to a disallowed origin', async () => { + const res = await invoke({ + allowedOrigins: 'https://allowed.example,https://second.example', + origin: 'https://attacker.example', + }); + assert.equal(res.headers.has('access-control-allow-origin'), false); + assert.equal(res.headers.get('vary'), 'Origin'); +}); + +test('does not emit an allow-origin header without an Origin request header', async () => { + const res = await invoke({ + allowedOrigins: 'https://allowed.example,https://second.example', + }); + assert.equal(res.headers.has('access-control-allow-origin'), false); +}); + +test('trims configured origins before exact matching', async () => { + const res = await invoke({ + allowedOrigins: ' https://allowed.example , https://second.example ', + origin: 'https://allowed.example', + }); + assert.equal(res.headers.get('access-control-allow-origin'), 'https://allowed.example'); +}); + +test('preserves explicitly configured wildcard behavior', async () => { + const res = await invoke({ allowedOrigins: '*', origin: 'https://any.example' }); + assert.equal(res.headers.get('access-control-allow-origin'), '*'); + assert.equal(res.headers.has('vary'), false); +}); + +test('preflight returns 204 while withholding CORS from a disallowed origin', async () => { + const res = await invoke({ + allowedOrigins: 'https://allowed.example', + origin: 'https://attacker.example', + method: 'OPTIONS', + }); + assert.equal(res.statusCode, 204); + assert.equal(res.headers.has('access-control-allow-origin'), false); +}); + +test('unknown route and missing backend remain fail-closed', async () => { + const notFound = await invoke({ allowedOrigins: '*', pathName: '/missing' }); + assert.equal(notFound.statusCode, 404); + assert.equal(notFound.body.error, 'Not Found'); + + const unavailable = await invoke({ + allowedOrigins: '*', + method: 'POST', + pathName: '/v1/research-lab/hypothesis', + }); + assert.equal(unavailable.statusCode, 503); + assert.equal(unavailable.body.error, 'Service Unavailable'); +}); From 0e4304aaf3e8e9ced19b680b22f7ea0f6010645d Mon Sep 17 00:00:00 2001 From: Nick Ruest <127058086+nicholas-ruest@users.noreply.github.com> Date: Sat, 10 Oct 2026 14:19:53 -0400 Subject: [PATCH 2/3] ci: restore pinned Rust toolchain setup --- .github/workflows/ci.yml | 18 +++++++++++++----- .../2026-10-10-cloud-function-cors.md | 2 ++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e5ddb3b..ddd13e6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,8 +39,9 @@ jobs: - uses: actions/checkout@v4 - name: Install Rust toolchain - uses: dtolnay/rust-action@stable + uses: dtolnay/rust-toolchain@e2a55d2ffb04f378e9626c28d38b36d230d1e12f with: + toolchain: stable components: clippy, rustfmt - name: Cache cargo registry @@ -82,7 +83,9 @@ jobs: - uses: actions/checkout@v4 - name: Install Rust toolchain - uses: dtolnay/rust-action@stable + uses: dtolnay/rust-toolchain@e2a55d2ffb04f378e9626c28d38b36d230d1e12f + with: + toolchain: stable - name: Cache cargo registry uses: actions/cache@v4 @@ -110,7 +113,9 @@ jobs: - uses: actions/checkout@v4 - name: Install Rust toolchain - uses: dtolnay/rust-action@stable + uses: dtolnay/rust-toolchain@e2a55d2ffb04f378e9626c28d38b36d230d1e12f + with: + toolchain: stable - name: Cache cargo registry uses: actions/cache@v4 @@ -139,7 +144,9 @@ jobs: - uses: actions/checkout@v4 - name: Install Rust toolchain - uses: dtolnay/rust-action@stable + uses: dtolnay/rust-toolchain@e2a55d2ffb04f378e9626c28d38b36d230d1e12f + with: + toolchain: stable - name: Install cargo-audit run: cargo install cargo-audit @@ -169,8 +176,9 @@ jobs: - uses: actions/checkout@v4 - name: Install Rust toolchain - uses: dtolnay/rust-action@stable + uses: dtolnay/rust-toolchain@e2a55d2ffb04f378e9626c28d38b36d230d1e12f with: + toolchain: stable components: llvm-tools-preview - name: Install cargo-llvm-cov diff --git a/docs/metaharness/2026-10-10-cloud-function-cors.md b/docs/metaharness/2026-10-10-cloud-function-cors.md index 17b846d..c8f05d7 100644 --- a/docs/metaharness/2026-10-10-cloud-function-cors.md +++ b/docs/metaharness/2026-10-10-cloud-function-cors.md @@ -43,4 +43,6 @@ Flywheel used a deterministic evidence proposer with a frozen six-item CORS hold The CI addition runs a clean npm install, the Cloud Function contract suite, and the production dependency audit on Linux with commit-pinned checkout and Node setup actions. It is included in the existing `All Checks Pass` aggregate and does not remove or relax any Rust gate. +The first exact GitHub run proved the Cloud Function job green but found every existing Rust job failing during job setup because the workflow referenced the nonexistent `dtolnay/rust-action`. The candidate replaces those references with the official `dtolnay/rust-toolchain` action pinned to `e2a55d2ffb04f378e9626c28d38b36d230d1e12f` and explicitly requests the stable toolchain. This repairs evaluator execution without changing a Rust test, lint rule, or aggregate requirement. + If a post-merge regression is discovered, revert the PR merge commit. This restores the previous CORS behavior, test script, and CI job as one atomic rollback. No deployment or package publication is part of this change. From f88c9b8549ce3fd501823bdc9c7bf2d2c2936b8b Mon Sep 17 00:00:00 2001 From: Nick Ruest <127058086+nicholas-ruest@users.noreply.github.com> Date: Thu, 15 Oct 2026 18:30:49 +0000 Subject: [PATCH 3/3] Recover strict Rust CI and telemetry gates Restore the optional database feature contract, make confidence scoring monotonic, emit metric telemetry, clear strict formatting/lint debt, and preserve reproducible MetaHarness/Flywheel evidence. --- .gitignore | 1 - Cargo.lock | 3860 +++++++++++++++++ .../2026-10-10-cloud-function-cors.md | 34 + llm-research-agents/src/agents/hypothesis.rs | 174 +- .../src/agents/metric_agent.rs | 183 +- llm-research-agents/src/agents/telemetry.rs | 33 +- llm-research-agents/src/agents/traits.rs | 17 +- llm-research-agents/src/clients/ruvector.rs | 95 +- llm-research-agents/src/contracts/common.rs | 5 + .../src/contracts/decision_event.rs | 75 +- .../src/contracts/hypothesis.rs | 35 +- llm-research-agents/src/contracts/metrics.rs | 25 +- llm-research-agents/src/contracts/mod.rs | 4 +- llm-research-agents/src/execution.rs | 12 +- .../src/handlers/hypothesis.rs | 85 +- llm-research-agents/src/handlers/metric.rs | 44 +- llm-research-agents/src/lib.rs | 26 +- llm-research-agents/src/telemetry/mod.rs | 6 +- .../tests/integration_tests.rs | 66 +- llm-research-core/Cargo.toml | 4 +- llm-research-core/src/domain.rs | 16 +- llm-research-core/src/domain/config.rs | 30 +- llm-research-core/src/domain/evaluation.rs | 44 +- llm-research-core/src/domain/experiment.rs | 40 +- llm-research-core/src/domain/ids.rs | 24 +- llm-research-core/src/domain/run.rs | 63 +- llm-research-core/src/traits.rs | 2 +- llm-research-core/tests/config_tests.rs | 21 +- llm-research-core/tests/domain_tests.rs | 114 +- .../tests/model_dataset_tests.rs | 128 +- .../tests/serialization_tests.rs | 39 +- llm-research-lab/src/config.rs | 35 +- llm-research-lab/src/main.rs | 119 +- 33 files changed, 4751 insertions(+), 708 deletions(-) create mode 100644 Cargo.lock diff --git a/.gitignore b/.gitignore index 12f0812..8ec2e24 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,6 @@ node_modules /target **/*.rs.bk *.pdb -Cargo.lock # IDE .vscode/ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..3374dc1 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,3860 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2fb6cfd47bf496ff64095c20eaba0c201404ee38714d4142fcfa1dc334fcc7a" + +[[package]] +name = "alloc-stdlib" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5c1865780388bfa186411ab5f247819487fc4864c6e9c3106611fa347586e1" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android_system_properties" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" +dependencies = [ + "libc", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + +[[package]] +name = "assert-json-diff" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "async-compression" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee19bd99b43e3691acbad4e840420a4881cea6c0b66a208125a824f8fd53f5a1" +dependencies = [ + "compression-codecs", + "compression-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "async-trait" +version = "0.1.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[package]] +name = "atomic" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +dependencies = [ + "async-trait", + "axum-core", + "axum-macros", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bit-set" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56d87354e4229f54a44f7bf2435906a4656dba36026ab6eaca629a2c436a691c" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5727b15fa97d4f4fee0a3b7c3d550ed0269f54329207b86388de918604e31269" +dependencies = [ + "borsh", + "serde", +] + +[[package]] +name = "bitflags" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" +dependencies = [ + "serde_core", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "borsh" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "553c5d846a6ba5150c65e3b1b8ec073bcf1abc20f9b7220de384a4443ea4e20a" +dependencies = [ + "borsh-derive", + "bytes", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12cdfe656708a01f89b451a7d36466e6fe6c414de0aa18fc54f864f6f9ca9f56" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "brotli" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8b851b75c23ca7873623d612fe49bd1989aeb03d08fb9432187eb253d3d4c6b" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "6.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "941cd9bd4ddab83cb46fa5a2d428f1c857b24ac78cb876cf7beb710840934bd7" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "cc" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6651c9ed80effdc7db0ff72512157f901af5e3549e341e24b1dd4887d836d838" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "chacha20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "rand_core 0.10.1", +] + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "compression-codecs" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98fc98460ba0ad5317075d3632b8dfc45d0be8c4a49347c2a38272019717614a" +dependencies = [ + "brotli", + "compression-core", + "flate2", + "memchr", +] + +[[package]] +name = "compression-core" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e8ccc4ea9f6acc32d102c0f6d471d11d913ad15f20c04de743374861fa1d414" + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core_detect" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" + +[[package]] +name = "crc32fast" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03e8bd762f7479489c70ed6c768ddca99d7296857de437a68dcb2a94365b3fae" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.119", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "deadpool" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0be2b1d1d6ec8d846f05e137292d0b89133caf95ef33695424c09568bdd39b1b" +dependencies = [ + "deadpool-runtime", + "lazy_static", + "num_cpus", + "tokio", +] + +[[package]] +name = "deadpool-runtime" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b" + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deunicode" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04" + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "downcast" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" + +[[package]] +name = "dummy" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abcba80bdf851db5616e27ff869399468e2d339d7c6480f5887681e6bdfc2186" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "either" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e9c71c2167ca323c882b99918929403426e2373ea17242ff5653e0d5e1058be" +dependencies = [ + "serde", +] + +[[package]] +name = "encoding_rs" +version = "0.8.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e985e0451871ad22fb8d2b6b076e2028a502a0d3950998c2c5c0a4f9b5d9679" +dependencies = [ + "cfg-if", + "core_detect", + "multiversion_no_op", + "rustversion", + "scopeguard", + "simdutf8", +] + +[[package]] +name = "equivalent" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + +[[package]] +name = "event-listener" +version = "5.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" +dependencies = [ + "parking", + "pin-project-lite", +] + +[[package]] +name = "fake" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef603df4ba9adbca6a332db7da6f614f21eafefbaf8e087844e452fdec152d0" +dependencies = [ + "chrono", + "deunicode", + "dummy", + "rand 0.8.8", + "uuid", +] + +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + +[[package]] +name = "find-msvc-tools" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" + +[[package]] +name = "flate2" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" +dependencies = [ + "crc32fast", + "miniz_oxide", + "zlib-rs", +] + +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "spin", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fragile" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8878864ba14bb86e818a412bfd6f18f9eabd4ec0f008a28e8f7eb61db532fcf9" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-executor" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[package]] +name = "futures-macro" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-timer" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", + "wasm-bindgen", +] + +[[package]] +name = "glob" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" + +[[package]] +name = "h2" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d29020232d6aa3fb1daca64c1127cf662cf97f254ae16c18c05b8ab635fc118" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "http" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3e324da4c95177d6291d4c8730197c0d1822f8a9766814a4a44fa5ab797c9c" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa8e654703247911e29c23fbeaa261834bd9bb74efba2f9acddc37bfb127f53" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots 1.0.9", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc03d96684f9226b8a787cdb71488417b53ab5ea8fdb1dac946cb9431cc8bff" +dependencies = [ + "base64 0.23.1", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "httparse", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" + +[[package]] +name = "icu_properties" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" + +[[package]] +name = "icu_provider" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", +] + +[[package]] +name = "ipnet" +version = "2.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb" +dependencies = [ + "spin", +] + +[[package]] +name = "libc" +version = "0.2.190" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61ff90caf6077a803a240f62fdbe88645a890bbca49ef8174c3cb0404362171d" +dependencies = [ + "bitflags", + "libc", + "plain", + "redox_syscall 0.9.4", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +dependencies = [ + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" + +[[package]] +name = "llm-research-agents" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "chrono", + "futures", + "hex", + "llm-research-core", + "metrics", + "mockall", + "pretty_assertions", + "reqwest", + "rstest", + "rust_decimal", + "rust_decimal_macros", + "serde", + "serde_json", + "sha2", + "thiserror", + "tokio", + "tokio-test", + "tracing", + "url", + "uuid", + "validator", + "wiremock", +] + +[[package]] +name = "llm-research-core" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "chrono", + "fake", + "hex", + "pretty_assertions", + "proptest", + "regex", + "rstest", + "rust_decimal", + "serde", + "serde_json", + "sha2", + "sqlx", + "test-case", + "thiserror", + "tokio", + "tokio-test", + "uuid", + "validator", +] + +[[package]] +name = "llm-research-lab" +version = "0.1.0" +dependencies = [ + "anyhow", + "axum", + "llm-research-agents", + "serde", + "serde_json", + "tokio", + "tower-http", + "tracing", + "tracing-subscriber", + "uuid", +] + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "lru-slab" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4050469837a6ff301cd14c1f8f24f88549e6d548f24f64e2148eb0f72cebc51f" + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "metrics" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3045b4193fbdc5b5681f32f11070da9be3609f189a79f3390706d42587f46bb5" +dependencies = [ + "ahash", + "portable-atomic", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1788edb87fdc09c7e26304471e2f5be8cdefb1b6930d6e3985fc02ff53bf86ee" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "mockall" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39a6bfcc6c8c7eed5ee98b9c3e33adc726054389233e201c95dab2d41a3839d2" +dependencies = [ + "cfg-if", + "downcast", + "fragile", + "mockall_derive", + "predicates", + "predicates-tree", +] + +[[package]] +name = "mockall_derive" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ca3004c2efe9011bd4e461bd8256445052b9615405b4f7ea43fc8ca5c20898" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "multiversion_no_op" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d" + +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.8", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-integer" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "openssl" +version = "0.10.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-sys" +version = "0.9.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "portable-atomic" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" + +[[package]] +name = "potential_utf" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +dependencies = [ + "zerovec", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "predicates" +version = "3.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe" +dependencies = [ + "anstyle", + "predicates-core", +] + +[[package]] +name = "predicates-core" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144" + +[[package]] +name = "predicates-tree" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2" +dependencies = [ + "predicates-core", + "termtree", +] + +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8530004ccb15eae51c7e40009fbe317f341f804db54dc033eec1c50be28cfa0" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags", + "chacha20", + "core_detect", + "num-traits", + "rand 0.10.3", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quinn" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4051e23e9185c255a7e33ef59cdbca87a22d359052eecd22fc6b901fb37d9d11" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e750cca55fe4f0439a15d0bb529da9651e79993e8e72c61a899a36d462befbe" +dependencies = [ + "bytes", + "getrandom 0.4.3", + "lru-slab", + "rand 0.10.3", + "rand_pcg", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af66907df18639dcf4db56ca65490cabc4b27a97dbadd96f2926cca73298f016" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.61.2", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core 0.10.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core 0.10.1", +] + +[[package]] +name = "rand_xorshift" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60aa6af80be32871323012e02e6e65f8a7cc7890931ae421d217ad8fe0df2ccf" +dependencies = [ + "rand_core 0.10.1", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_syscall" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "737970939a87c6fa31e7acad13307bccbb017a073b695b6089a2c484f929e20e" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "relative-path" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 1.0.9", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rsa" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rstest" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a2c585be59b6b5dd66a9d2084aa1d8bd52fbdb806eafdeffb52791147862035" +dependencies = [ + "futures", + "futures-timer", + "rstest_macros", + "rustc_version", +] + +[[package]] +name = "rstest_macros" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "825ea780781b15345a146be27eaefb05085e337e869bff01b4306a4fd4a9ad5a" +dependencies = [ + "cfg-if", + "glob", + "proc-macro-crate", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn 2.0.119", + "unicode-ident", +] + +[[package]] +name = "rust_decimal" +version = "1.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7653272e75dcac41dc199fbea6f5797633994fafd339943c06c9af16bf29cd3a" +dependencies = [ + "arrayvec", + "borsh", + "bytes", + "num-traits", + "rand 0.8.8", + "rand 0.9.5", + "serde", + "serde_json", + "wasm-bindgen", +] + +[[package]] +name = "rust_decimal_macros" +version = "1.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74a5a6f027e892c7a035c6fddb50435a1fbf5a734ffc0c2a9fed4d0221440519" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "rusty-fork" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "serde_json" +version = "1.0.154" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "sha1_smol" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b3dc8af474f516a851ff4bd12db780f948b9250ad37211e4eec0bccea54e01b" +dependencies = [ + "serde", +] + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "spin" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlx" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" +dependencies = [ + "base64 0.22.1", + "bytes", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown 0.15.5", + "hashlink", + "indexmap", + "log", + "memchr", + "once_cell", + "percent-encoding", + "rustls", + "serde", + "serde_json", + "sha2", + "smallvec", + "thiserror", + "tokio", + "tokio-stream", + "tracing", + "url", + "webpki-roots 0.26.11", +] + +[[package]] +name = "sqlx-macros" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 2.0.119", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" +dependencies = [ + "dotenvy", + "either", + "heck", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn 2.0.119", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" +dependencies = [ + "atoi", + "base64 0.22.1", + "bitflags", + "byteorder", + "bytes", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand 0.8.8", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" +dependencies = [ + "atoi", + "base64 0.22.1", + "bitflags", + "byteorder", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand 0.8.8", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" +dependencies = [ + "atoi", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "serde_urlencoded", + "sqlx-core", + "thiserror", + "tracing", + "url", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.3", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "termtree" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" + +[[package]] +name = "test-case" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "124953e7f67cb0b2fcfb87e899e4ae5a64fb68e2e160767933cc67b646fb0042" +dependencies = [ + "test-case-macros", +] + +[[package]] +name = "test-case-core" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd097615b407247e102046ba34426bb594923ab3554cf0f3cdfed050f1a5a3e8" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "test-case-macros" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4ce35ece947bccac166e1ded639133827a953f69a48ba134d17f91108333082" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", + "test-case-core", +] + +[[package]] +name = "thiserror" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "thread_local" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tinystr" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee" + +[[package]] +name = "tokio" +version = "1.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce3335fa71841cda333a58d7615b03901380ecf09d59b3296d21f8bbac0dde4e" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9cc2678c2cdd569ef8215e2afd7954ada2ae20b4fdd2c5fe6139a3b02d105db" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-test" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12bc5db7778a8e08ee255090861e0e7aac55e784300077e7ed293c7be36168b2" +dependencies = [ + "futures-core", + "tokio", + "tokio-stream", +] + +[[package]] +name = "tokio-util" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e464cf451ba96ebfc6f9b6542f17ee8b8956e33f1e40d9690624e59d7a7f8a4b" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "libc", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml_datetime" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b86d767906c6c42421dcba507eb9d203e779497710a47782a224bb871653053" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.17+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3641d5bbb5349a79e1020a242d251efbc546ad8048d133958323ce9c40a9c9c" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baa693a8032d7e1cada7d0041e96126df243179ff061456783ac7f12bda4744c" +dependencies = [ + "winnow", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "async-compression", + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "pin-project-lite", + "tokio", + "tokio-util", + "tower", + "tower-layer", + "tower-service", + "tracing", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-ident" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" + +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cc1186384beb7dd8eedea376413fd654937285ea6c9cfbb928dc3043ea4b606" +dependencies = [ + "atomic", + "getrandom 0.4.3", + "js-sys", + "md-5", + "serde_core", + "sha1_smol", + "wasm-bindgen", +] + +[[package]] +name = "validator" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0b4a29d8709210980a09379f27ee31549b73292c87ab9899beee1c0d3be6303" +dependencies = [ + "idna", + "once_cell", + "regex", + "serde", + "serde_derive", + "serde_json", + "url", + "validator_derive", +] + +[[package]] +name = "validator_derive" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bac855a2ce6f843beb229757e6e570a42e837bcb15e5f449dd48d5747d41bf77" +dependencies = [ + "darling", + "once_cell", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + +[[package]] +name = "want" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec4cdd0dd910afe868b7ef477227d8d538b46b3075031afee8a9f2acb0a2ed0b" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "serde", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cbab34de2d982e9b48e18d216d04c4a6f641066ff19ffb699980f591ee3610e" +dependencies = [ + "js-sys", + "tokio", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 3.0.9", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88261b9deccee56594c11a3460c462c41f58d148598fe70ad77070126a68aba4" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.9", +] + +[[package]] +name = "webpki-roots" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "whoami" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" +dependencies = [ + "libredox", + "wasite", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + +[[package]] +name = "wiremock" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08db1edfb05d9b3c1542e521aea074442088292f00b5f28e435c714a98f85031" +dependencies = [ + "assert-json-diff", + "base64 0.22.1", + "deadpool", + "futures", + "http", + "http-body-util", + "hyper", + "hyper-util", + "log", + "once_cell", + "regex", + "serde", + "serde_json", + "tokio", + "url", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "writeable" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5fe1f8f1b06191a00962174c61aa5005e0bb391a6d80d07e24d115c01a92ed8" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "863ad3ac83293fb4d740aedbfdc9240dd8d1a50c1099acd76ce80ce7c7230c7f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879" + +[[package]] +name = "zerotrie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "zlib-rs" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112" + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/docs/metaharness/2026-10-10-cloud-function-cors.md b/docs/metaharness/2026-10-10-cloud-function-cors.md index c8f05d7..e46714c 100644 --- a/docs/metaharness/2026-10-10-cloud-function-cors.md +++ b/docs/metaharness/2026-10-10-cloud-function-cors.md @@ -46,3 +46,37 @@ The CI addition runs a clean npm install, the Cloud Function contract suite, and The first exact GitHub run proved the Cloud Function job green but found every existing Rust job failing during job setup because the workflow referenced the nonexistent `dtolnay/rust-action`. The candidate replaces those references with the official `dtolnay/rust-toolchain` action pinned to `e2a55d2ffb04f378e9626c28d38b36d230d1e12f` and explicitly requests the stable toolchain. This repairs evaluator execution without changing a Rust test, lint rule, or aggregate requirement. If a post-merge regression is discovered, revert the PR merge commit. This restores the previous CORS behavior, test script, and CI job as one atomic rollback. No deployment or package publication is part of this change. + +## Strict Rust gate recovery (2026-10-15) + +The recovery cycle kept the model and six-gate evaluation contract fixed. Its baseline was PR head `0e4304aaf3e8e9ced19b680b22f7ea0f6010645d`: Cloud Function tests and npm audit passed, while rustfmt, build, tests, coverage, and lint failed. The first compiler failure was an undeclared `database` feature guarding an `sqlx::Error` conversion. Formatting drift then concealed pre-existing missing documentation, dead code, Clippy findings, and a non-monotonic metric-confidence test. + +Darwin `evolve-numeric` ran two generations with four children, concurrency one, seed `20261015`, and a frozen real evaluator: `cargo check -p llm-research-core --all-features` under `-Dwarnings`. Strategy `0` (unchanged) scored 0 and regressed; strategy `2` (restore optional `sqlx` plus `database = ["dep:sqlx"]`) scored 1 and won with delta `+1.0`. No prompt/harness mutation was copied into project source; this was bounded project-source selection. + +The retained candidate also: + +- restores and validates the optional database feature instead of declaring an empty feature; +- makes the metric confidence estimator strictly increase from its 0.1 floor to its 0.9 ceiling; +- emits successful metric-agent decisions through the existing Observatory-compatible telemetry path; +- documents the public agent API and clears all existing strict Clippy findings without suppressing lints; +- replaces an unusable nine-argument evaluation constructor with a typed `NewEvaluation` input; +- records `Cargo.lock` so Linux CI resolves the same dependency graph that was validated; +- formats the full Rust workspace with rustfmt. + +| Gate | Baseline | Candidate | Result | +| --- | ---: | ---: | --- | +| `cargo fmt --all -- --check` | exit 1 | exit 0 | PASS | +| core all-features check (`-Dwarnings`) | exit 101 | exit 0 | PASS | +| workspace Clippy, all targets/features (`-D warnings`) | exit 101 | exit 0 | PASS | +| workspace Rust tests (`-Dwarnings`) | 58/59 before first failure | 291/291 | PASS | +| release workspace build (`-Dwarnings`) | exit 101 | exit 0 | PASS | +| Cloud Function contract | 7/7 | 7/7 | PASS, anchor preserved | +| npm high-severity audit | 0 vulnerabilities | 0 vulnerabilities | PASS | +| `cargo audit` 0.22.2 | prior hosted audit exit 0 | 0 vulnerabilities; 1 allowed unmaintained warning | PASS | +| MetaHarness hard constraints | 6/6 | 6/6 | PASS | + +MetaHarness `0.4.17` at upstream revision `e0dfd44da72b7adfb7c57fdd42d124be58ed7086` scored the candidate at harness fit 71, compile confidence 100, task coverage 100, tool safety 95, memory usefulness 47, and estimated cost per run USD 0.048. Rust was bootstrapped in an isolated tool home at `rustc 1.99.1`; Darwin remained `0.10.3`, Flywheel `0.1.12`, and Ruflo `3.56.3`. + +Flywheel used a frozen six-item CI holdout (rustfmt, core all-features, Clippy, Rust tests, Node tests, audit) and a seven-item CORS anchor. It promoted `baseline -> candidate` from 2/6 to 6/6 with the anchor unchanged at 7/7. Gate fingerprint `c5942e7c9bf8f28fe7af47a6ec3887d11eeb3f8c8e8601cc6a7a54969ea0b981`; signed replay passed receipts, root reachability, contiguous parents, promotion verdict re-execution, sealed fields, full ledger, and pinned-gate verification. + +Rollback remains one atomic PR revert. Because `Evaluation::new` now accepts `NewEvaluation`, downstream Rust callers must migrate positional arguments to named fields; the repository tests cover every in-tree call site. No deployment, publishing, persistence-layer change, or database bypass is included. diff --git a/llm-research-agents/src/agents/hypothesis.rs b/llm-research-agents/src/agents/hypothesis.rs index fc869d5..c9179c0 100644 --- a/llm-research-agents/src/agents/hypothesis.rs +++ b/llm-research-agents/src/agents/hypothesis.rs @@ -45,12 +45,10 @@ use tracing::{debug, info, instrument, warn}; use uuid::Uuid; use validator::Validate; -use crate::contracts::{ - hypothesis::*, - decision_event::*, - common::*, +use super::traits::{ + Agent, BudgetViolation, ConfidenceEstimator, PerformanceBounded, PerformanceBudget, }; -use super::traits::{Agent, ConfidenceEstimator, PerformanceBounded, PerformanceBudget, BudgetViolation}; +use crate::contracts::{common::*, decision_event::*, hypothesis::*}; /// Agent version (semantic versioning). pub const HYPOTHESIS_AGENT_VERSION: &str = "1.0.0"; @@ -58,25 +56,38 @@ pub const HYPOTHESIS_AGENT_VERSION: &str = "1.0.0"; /// Agent identifier. pub const HYPOTHESIS_AGENT_ID: &str = "hypothesis-agent-v1"; +type GroupedSamples = Option<(Vec, Vec)>; + /// Errors from Hypothesis Agent operations. #[derive(Debug, Error)] pub enum HypothesisAgentError { #[error("Validation error: {0}")] + /// Represents the validation case. Validation(String), #[error("Insufficient sample size: required {required}, got {actual}")] - InsufficientSampleSize { required: u64, actual: u64 }, + /// Represents the insufficient sample size case. + InsufficientSampleSize { + #[doc = "The required minimum value."] + required: u64, + #[doc = "The observed value."] + actual: u64, + }, #[error("Statistical computation error: {0}")] + /// Represents the statistical computation case. StatisticalComputation(String), #[error("Configuration error: {0}")] + /// Represents the configuration case. Configuration(String), #[error("Internal error: {0}")] + /// Represents the internal case. Internal(String), #[error("Performance budget exceeded: {0}")] + /// Represents the budget exceeded case. BudgetExceeded(String), } @@ -152,7 +163,10 @@ impl HypothesisAgent { } /// Create a new Hypothesis Agent with custom configuration and budget. - pub fn with_config_and_budget(config: HypothesisAgentConfig, budget: PerformanceBudget) -> Self { + pub fn with_config_and_budget( + config: HypothesisAgentConfig, + budget: PerformanceBudget, + ) -> Self { Self { identity: AgentIdentity { id: HYPOTHESIS_AGENT_ID.to_string(), @@ -216,11 +230,12 @@ impl HypothesisAgent { fn extract_samples( hypothesis: &HypothesisDefinition, data: &ExperimentalData, - ) -> (Vec, Option<(Vec, Vec)>) { + ) -> (Vec, GroupedSamples) { let var_name = Self::dependent_variable_name(hypothesis); let mut flat: Vec = Vec::with_capacity(data.observations.len()); - let mut grouped: std::collections::BTreeMap> = std::collections::BTreeMap::new(); + let mut grouped: std::collections::BTreeMap> = + std::collections::BTreeMap::new(); for obs in &data.observations { if let Some(value) = Self::extract_observation_value(obs, &var_name) { @@ -281,10 +296,7 @@ impl HypothesisAgent { }); } - let alpha: f64 = hypothesis - .significance_level - .try_into() - .unwrap_or(0.05); + let alpha: f64 = hypothesis.significance_level.try_into().unwrap_or(0.05); if let Some((a, b)) = two_sample { return Ok(self.two_sample_ttest(&a, &b, alpha, config)); @@ -295,7 +307,11 @@ impl HypothesisAgent { let mean: f64 = values.iter().sum::() / n; let variance: f64 = values.iter().map(|x| (x - mean).powi(2)).sum::() / (n - 1.0); let std_dev = variance.sqrt(); - let std_error = if std_dev == 0.0 { 1e-12 } else { std_dev / n.sqrt() }; + let std_error = if std_dev == 0.0 { + 1e-12 + } else { + std_dev / n.sqrt() + }; let t_statistic = mean / std_error; let df = n - 1.0; @@ -356,9 +372,13 @@ impl HypothesisAgent { // Welch–Satterthwaite df let df_num = se_sq.powi(2); - let df_den = (var_a / na).powi(2) / (na - 1.0).max(1.0) - + (var_b / nb).powi(2) / (nb - 1.0).max(1.0); - let df = if df_den <= 0.0 { na + nb - 2.0 } else { df_num / df_den }; + let df_den = + (var_a / na).powi(2) / (na - 1.0).max(1.0) + (var_b / nb).powi(2) / (nb - 1.0).max(1.0); + let df = if df_den <= 0.0 { + na + nb - 2.0 + } else { + df_num / df_den + }; let p_value = self.approximate_t_pvalue(t_statistic.abs(), df); let null_rejected = p_value < alpha; @@ -422,10 +442,7 @@ impl HypothesisAgent { }); } - let alpha: f64 = hypothesis - .significance_level - .try_into() - .unwrap_or(0.05); + let alpha: f64 = hypothesis.significance_level.try_into().unwrap_or(0.05); // Mann-Whitney U needs two groups. Without groups, degrade to a // two-sample t-test across a median split to still return real output. @@ -530,7 +547,8 @@ impl HypothesisAgent { let (mean_b, var_b) = mean_and_variance(&b); let na = a.len() as f64; let nb = b.len() as f64; - let pooled = (((na - 1.0) * var_a + (nb - 1.0) * var_b) / (na + nb - 2.0).max(1.0)).sqrt(); + let pooled = + (((na - 1.0) * var_a + (nb - 1.0) * var_b) / (na + nb - 2.0).max(1.0)).sqrt(); if pooled == 0.0 { return None; } @@ -636,9 +654,7 @@ impl ConfidenceEstimator for HypothesisAgent { let size_confidence = (sample_size as f64 / 1000.0).min(0.9); // Adjust based on effect size if available - let effect_adjustment = effect_size - .map(|e| (e.abs() * 0.1).min(0.1)) - .unwrap_or(0.0); + let effect_adjustment = effect_size.map(|e| (e.abs() * 0.1).min(0.1)).unwrap_or(0.0); (size_confidence + effect_adjustment).min(0.99) } @@ -699,13 +715,18 @@ impl Agent for HypothesisAgent { StatisticalTest::TTest | StatisticalTest::WelchTTest => { self.evaluate_ttest(&input.hypothesis, &input.experimental_data, &input.config)? } - StatisticalTest::MannWhitneyU => { - self.evaluate_mann_whitney(&input.hypothesis, &input.experimental_data, &input.config)? - } + StatisticalTest::MannWhitneyU => self.evaluate_mann_whitney( + &input.hypothesis, + &input.experimental_data, + &input.config, + )?, _ => { // For other tests, run a t-test (one- or two-sample) rather // than silently falling through with the wrong test path. - warn!("Test method {:?} not fully implemented, using t-test", input.config.test_method); + warn!( + "Test method {:?} not fully implemented, using t-test", + input.config.test_method + ); self.evaluate_ttest(&input.hypothesis, &input.experimental_data, &input.config)? } }; @@ -722,13 +743,15 @@ impl Agent for HypothesisAgent { // Compute achieved power (post-hoc) let sample_size = input.experimental_data.sample_size; - let effect_value: Option = effect_size.as_ref().map(|e| e.value.try_into().unwrap_or(0.0)); - let achieved_power = Decimal::try_from( - self.estimate_confidence(sample_size, effect_value) - ).ok(); + let effect_value: Option = effect_size + .as_ref() + .map(|e| e.value.try_into().unwrap_or(0.0)); + let achieved_power = + Decimal::try_from(self.estimate_confidence(sample_size, effect_value)).ok(); // Build recommendations - let recommendations = self.build_recommendations(&status, &test_results, &assumption_violations); + let recommendations = + self.build_recommendations(&status, &test_results, &assumption_violations); // Assess sample adequacy let sample_adequacy = if sample_size >= 100 { @@ -790,7 +813,9 @@ impl Agent for HypothesisAgent { // Estimate confidence let sample_size = input.experimental_data.sample_size; - let effect_value: Option = output.effect_size.as_ref() + let effect_value: Option = output + .effect_size + .as_ref() .map(|e| e.value.try_into().unwrap_or(0.0)); let confidence_value = self.estimate_confidence(sample_size, effect_value); @@ -799,8 +824,16 @@ impl Agent for HypothesisAgent { .map_err(|e| HypothesisAgentError::Internal(e.to_string()))?, method: ConfidenceMethod::Heuristic, sample_size: Some(sample_size), - ci_lower: output.test_results.confidence_interval.as_ref().map(|ci| ci.lower), - ci_upper: output.test_results.confidence_interval.as_ref().map(|ci| ci.upper), + ci_lower: output + .test_results + .confidence_interval + .as_ref() + .map(|ci| ci.lower), + ci_upper: output + .test_results + .confidence_interval + .as_ref() + .map(|ci| ci.upper), }; // Build constraints @@ -867,7 +900,8 @@ impl HypothesisAgent { // Status-based recommendations match status { HypothesisStatus::Inconclusive => { - recommendations.push("Address assumption violations before drawing conclusions".to_string()); + recommendations + .push("Address assumption violations before drawing conclusions".to_string()); } HypothesisStatus::Accepted => { let p_value: f64 = results.p_value.try_into().unwrap_or(1.0); @@ -928,9 +962,19 @@ fn mann_whitney_u(a: &[f64], b: &[f64]) -> (f64, f64) { from_a: bool, } let mut combined: Vec = Vec::with_capacity(na + nb); - combined.extend(a.iter().map(|&v| Entry { value: v, from_a: true })); - combined.extend(b.iter().map(|&v| Entry { value: v, from_a: false })); - combined.sort_by(|x, y| x.value.partial_cmp(&y.value).unwrap_or(std::cmp::Ordering::Equal)); + combined.extend(a.iter().map(|&v| Entry { + value: v, + from_a: true, + })); + combined.extend(b.iter().map(|&v| Entry { + value: v, + from_a: false, + })); + combined.sort_by(|x, y| { + x.value + .partial_cmp(&y.value) + .unwrap_or(std::cmp::Ordering::Equal) + }); let n_total = combined.len(); let mut ranks = vec![0.0f64; n_total]; @@ -943,8 +987,8 @@ fn mann_whitney_u(a: &[f64], b: &[f64]) -> (f64, f64) { } let group_size = j - i; let avg_rank = ((i + 1) as f64 + j as f64) / 2.0; - for k in i..j { - ranks[k] = avg_rank; + for rank in &mut ranks[i..j] { + *rank = avg_rank; } if group_size > 1 { let t = group_size as f64; @@ -981,7 +1025,11 @@ fn mann_whitney_u(a: &[f64], b: &[f64]) -> (f64, f64) { let sigma = var_u.sqrt(); // Continuity correction let numerator = (u_stat - mean_u).abs() - 0.5; - let z = if numerator <= 0.0 { 0.0 } else { numerator / sigma }; + let z = if numerator <= 0.0 { + 0.0 + } else { + numerator / sigma + }; 2.0 * (1.0 - normal_cdf(z)) }; @@ -1085,14 +1133,14 @@ fn ln_gamma(x: f64) -> f64 { // Lanczos approximation let g = 7; let c = [ - 0.99999999999980993, + 0.999_999_999_999_809_9, 676.5203681218851, -1259.1392167224028, - 771.32342877765313, - -176.61502916214059, + 771.323_428_777_653_1, + -176.615_029_162_140_6, 12.507343278686905, -0.13857109526572012, - 9.9843695780195716e-6, + 9.984_369_578_019_572e-6, 1.5056327351493116e-7, ]; @@ -1101,8 +1149,8 @@ fn ln_gamma(x: f64) -> f64 { } else { let x = x - 1.0; let mut a = c[0]; - for i in 1..g + 2 { - a += c[i] / (x + i as f64); + for (i, coefficient) in c.iter().enumerate().take(g + 2).skip(1) { + a += coefficient / (x + i as f64); } let t = x + g as f64 + 0.5; 0.5 * (2.0 * std::f64::consts::PI).ln() + (t - 0.5) * t.ln() - t + a.ln() @@ -1116,7 +1164,7 @@ fn inverse_normal_cdf(p: f64) -> f64 { -3.969683028665376e+01, 2.209460984245205e+02, -2.759285104469687e+02, - 1.383577518672690e+02, + 1.383_577_518_672_69e2, -3.066479806614716e+01, 2.506628277459239e+00, ]; @@ -1242,7 +1290,10 @@ mod tests { let input = create_test_input(10); // Below minimum let result = agent.execute(input).await; - assert!(matches!(result, Err(HypothesisAgentError::InsufficientSampleSize { .. }))); + assert!(matches!( + result, + Err(HypothesisAgentError::InsufficientSampleSize { .. }) + )); } #[tokio::test] @@ -1250,7 +1301,7 @@ mod tests { let agent = HypothesisAgent::new(); let input = create_test_input(100); - let (output, event) = agent.invoke(input).await.unwrap(); + let (_output, event) = agent.invoke(input).await.unwrap(); assert_eq!(event.agent_id, HYPOTHESIS_AGENT_ID); assert_eq!(event.agent_version, HYPOTHESIS_AGENT_VERSION); @@ -1357,8 +1408,14 @@ mod tests { let p: f64 = output.test_results.p_value.try_into().unwrap(); let t: f64 = output.test_results.test_statistic.try_into().unwrap(); assert!(p.is_finite(), "p-value must be finite"); - assert!(p >= 0.0 && p <= 1.0, "p-value must be in [0,1], got {p}"); - assert!(t.abs() > 0.0, "two-sample statistic must reflect group difference"); + assert!( + (0.0..=1.0).contains(&p), + "p-value must be in [0,1], got {p}" + ); + assert!( + t.abs() > 0.0, + "two-sample statistic must reflect group difference" + ); } #[tokio::test] @@ -1370,7 +1427,7 @@ mod tests { let output = agent.execute(input).await.expect("should evaluate"); let p: f64 = output.test_results.p_value.try_into().unwrap(); - assert!(p.is_finite() && p >= 0.0 && p <= 1.0); + assert!(p.is_finite() && (0.0..=1.0).contains(&p)); } #[tokio::test] @@ -1399,6 +1456,9 @@ mod tests { let a: Vec = (0..30).map(|i| 0.88 + i as f64 * 0.0005).collect(); let b: Vec = (0..30).map(|i| 0.83 + i as f64 * 0.0005).collect(); let (_u, p) = mann_whitney_u(&a, &b); - assert!(p < 0.05, "non-overlapping samples should reject null, p={p}"); + assert!( + p < 0.05, + "non-overlapping samples should reject null, p={p}" + ); } } diff --git a/llm-research-agents/src/agents/metric_agent.rs b/llm-research-agents/src/agents/metric_agent.rs index 75902bf..fe5c4c2 100644 --- a/llm-research-agents/src/agents/metric_agent.rs +++ b/llm-research-agents/src/agents/metric_agent.rs @@ -52,6 +52,7 @@ //! ``` use async_trait::async_trait; +use chrono::Utc; use rust_decimal::Decimal; use rust_decimal_macros::dec; use serde_json::json; @@ -60,15 +61,12 @@ use thiserror::Error; use tracing::{debug, info, instrument, warn}; use uuid::Uuid; use validator::Validate; -use chrono::Utc; -use crate::contracts::{ - metrics::*, - decision_event::*, - common::*, -}; -use super::traits::{Agent, ConfidenceEstimator, PerformanceBounded, PerformanceBudget, BudgetViolation}; use super::telemetry::{AgentTelemetry, TelemetryEvent, TelemetryEventType}; +use super::traits::{ + Agent, BudgetViolation, ConfidenceEstimator, PerformanceBounded, PerformanceBudget, +}; +use crate::contracts::{common::*, decision_event::*, metrics::*}; // ============================================================================= // Agent Constants @@ -88,34 +86,51 @@ pub const METRIC_AGENT_VERSION: &str = "1.0.0"; #[derive(Debug, Error)] pub enum MetricAgentRuntimeError { #[error("Validation error: {0}")] + /// Represents the validation case. Validation(String), #[error("Empty data: no records provided for computation")] + /// Represents the empty data case. EmptyData, #[error("Computation error for metric '{metric}': {message}")] - Computation { metric: String, message: String }, + /// Represents the computation case. + Computation { + #[doc = "The metric."] + metric: String, + #[doc = "The human-readable message."] + message: String, + }, #[error("Missing required field: {0}")] + /// Represents the missing required case. MissingRequired(String), #[error("Type mismatch for field '{field}': expected {expected}, got {actual}")] + /// Represents the type mismatch case. TypeMismatch { + /// The field that failed validation. field: String, + /// The expected value or type. expected: String, + /// The observed value. actual: String, }, #[error("Confidence calculation error: {0}")] + /// Represents the confidence error case. ConfidenceError(String), #[error("Serialization error: {0}")] + /// Represents the serialization case. Serialization(String), #[error("Internal error: {0}")] + /// Represents the internal case. Internal(String), #[error("Performance budget exceeded: {0}")] + /// Represents the budget exceeded case. BudgetExceeded(String), } @@ -228,7 +243,9 @@ impl ExperimentalMetricAgent { id: METRIC_AGENT_ID.to_string(), version: METRIC_AGENT_VERSION.to_string(), classification: AgentClassification::ExperimentalMetrics, - description: "Computes experimental metrics for hypothesis evaluation and research outcomes".to_string(), + description: + "Computes experimental metrics for hypothesis evaluation and research outcomes" + .to_string(), }, config, telemetry: AgentTelemetry::new(METRIC_AGENT_ID.to_string()), @@ -252,7 +269,10 @@ impl ExperimentalMetricAgent { if values.is_empty() { return Err(MetricAgentRuntimeError::Computation { metric: request.name.clone(), - message: format!("No valid numeric values for variable '{}'", request.variable), + message: format!( + "No valid numeric values for variable '{}'", + request.variable + ), }); } @@ -264,10 +284,24 @@ impl ExperimentalMetricAgent { MetricType::CentralTendency => self.compute_central_tendency(&values, config)?, MetricType::Dispersion => self.compute_dispersion(&values, config)?, MetricType::DistributionShape => self.compute_distribution_shape(&values, config)?, - MetricType::Percentile => self.compute_percentile(&values, request.params.as_ref(), config)?, - MetricType::Correlation => self.compute_correlation(records, &request.variable, request.params.as_ref(), config)?, - MetricType::Regression => self.compute_regression(records, &request.variable, request.params.as_ref(), config)?, - MetricType::CustomAggregation => self.compute_custom_aggregation(&values, request.params.as_ref(), config)?, + MetricType::Percentile => { + self.compute_percentile(&values, request.params.as_ref(), config)? + } + MetricType::Correlation => self.compute_correlation( + records, + &request.variable, + request.params.as_ref(), + config, + )?, + MetricType::Regression => self.compute_regression( + records, + &request.variable, + request.params.as_ref(), + config, + )?, + MetricType::CustomAggregation => { + self.compute_custom_aggregation(&values, request.params.as_ref(), config)? + } }; // Round to configured precision @@ -312,7 +346,11 @@ impl ExperimentalMetricAgent { } /// Get nested value from JSON using dot notation (e.g., "metrics.accuracy"). - fn get_nested_value<'a>(&self, record: &'a serde_json::Value, path: &str) -> Option<&'a serde_json::Value> { + fn get_nested_value<'a>( + &self, + record: &'a serde_json::Value, + path: &str, + ) -> Option<&'a serde_json::Value> { let parts: Vec<&str> = path.split('.').collect(); let mut current = record; @@ -335,7 +373,7 @@ impl ExperimentalMetricAgent { let ci = if config.include_ci && values.len() >= 3 { let variance = values.iter().map(|x| (x - mean).powi(2)).sum::() / (n - 1.0); let std_error = (variance / n).sqrt(); - + // t-critical for 95% CI with n-1 df (approximation for large n) let t_critical = if n > 30.0 { 1.96 } else { 2.0 + 2.0 / n }; let margin = t_critical * std_error; @@ -351,8 +389,8 @@ impl ExperimentalMetricAgent { None }; - let decimal_mean = Decimal::try_from(mean) - .map_err(|e| MetricAgentRuntimeError::Computation { + let decimal_mean = + Decimal::try_from(mean).map_err(|e| MetricAgentRuntimeError::Computation { metric: "central_tendency".to_string(), message: e.to_string(), })?; @@ -371,8 +409,8 @@ impl ExperimentalMetricAgent { let variance = values.iter().map(|x| (x - mean).powi(2)).sum::() / (n - 1.0); let std_dev = variance.sqrt(); - let decimal_std = Decimal::try_from(std_dev) - .map_err(|e| MetricAgentRuntimeError::Computation { + let decimal_std = + Decimal::try_from(std_dev).map_err(|e| MetricAgentRuntimeError::Computation { metric: "dispersion".to_string(), message: e.to_string(), })?; @@ -396,11 +434,15 @@ impl ExperimentalMetricAgent { } // Compute skewness - let m3 = values.iter().map(|x| ((x - mean) / std_dev).powi(3)).sum::() / n; + let m3 = values + .iter() + .map(|x| ((x - mean) / std_dev).powi(3)) + .sum::() + / n; let skewness = m3 * (n * (n - 1.0)).sqrt() / (n - 2.0); - let decimal_skew = Decimal::try_from(skewness) - .map_err(|e| MetricAgentRuntimeError::Computation { + let decimal_skew = + Decimal::try_from(skewness).map_err(|e| MetricAgentRuntimeError::Computation { metric: "distribution_shape".to_string(), message: e.to_string(), })?; @@ -426,8 +468,8 @@ impl ExperimentalMetricAgent { let idx = ((percentile / 100.0) * (sorted.len() - 1) as f64).round() as usize; let value = sorted.get(idx).copied().unwrap_or(0.0); - let decimal_value = Decimal::try_from(value) - .map_err(|e| MetricAgentRuntimeError::Computation { + let decimal_value = + Decimal::try_from(value).map_err(|e| MetricAgentRuntimeError::Computation { metric: "percentile".to_string(), message: e.to_string(), })?; @@ -446,16 +488,20 @@ impl ExperimentalMetricAgent { let variable_y = params .and_then(|p| p.get("variable_y")) .and_then(|v| v.as_str()) - .ok_or_else(|| MetricAgentRuntimeError::MissingRequired( - "params.variable_y required for correlation".to_string() - ))?; + .ok_or_else(|| { + MetricAgentRuntimeError::MissingRequired( + "params.variable_y required for correlation".to_string(), + ) + })?; // Extract paired values let mut pairs: Vec<(f64, f64)> = Vec::new(); for record in records { if let (Some(x), Some(y)) = ( - self.get_nested_value(record, variable_x).and_then(|v| v.as_f64()), - self.get_nested_value(record, variable_y).and_then(|v| v.as_f64()), + self.get_nested_value(record, variable_x) + .and_then(|v| v.as_f64()), + self.get_nested_value(record, variable_y) + .and_then(|v| v.as_f64()), ) { if x.is_finite() && y.is_finite() { pairs.push((x, y)); @@ -474,9 +520,15 @@ impl ExperimentalMetricAgent { let mean_x = pairs.iter().map(|(x, _)| x).sum::() / n; let mean_y = pairs.iter().map(|(_, y)| y).sum::() / n; - let cov = pairs.iter().map(|(x, y)| (x - mean_x) * (y - mean_y)).sum::() / (n - 1.0); - let std_x = (pairs.iter().map(|(x, _)| (x - mean_x).powi(2)).sum::() / (n - 1.0)).sqrt(); - let std_y = (pairs.iter().map(|(_, y)| (y - mean_y).powi(2)).sum::() / (n - 1.0)).sqrt(); + let cov = pairs + .iter() + .map(|(x, y)| (x - mean_x) * (y - mean_y)) + .sum::() + / (n - 1.0); + let std_x = + (pairs.iter().map(|(x, _)| (x - mean_x).powi(2)).sum::() / (n - 1.0)).sqrt(); + let std_y = + (pairs.iter().map(|(_, y)| (y - mean_y).powi(2)).sum::() / (n - 1.0)).sqrt(); let r = if std_x > 0.0 && std_y > 0.0 { cov / (std_x * std_y) @@ -484,11 +536,12 @@ impl ExperimentalMetricAgent { 0.0 }; - let decimal_r = Decimal::try_from(r.clamp(-1.0, 1.0)) - .map_err(|e| MetricAgentRuntimeError::Computation { + let decimal_r = Decimal::try_from(r.clamp(-1.0, 1.0)).map_err(|e| { + MetricAgentRuntimeError::Computation { metric: "correlation".to_string(), message: e.to_string(), - })?; + } + })?; Ok((decimal_r, None)) } @@ -504,16 +557,20 @@ impl ExperimentalMetricAgent { let variable_y = params .and_then(|p| p.get("variable_y")) .and_then(|v| v.as_str()) - .ok_or_else(|| MetricAgentRuntimeError::MissingRequired( - "params.variable_y required for regression".to_string() - ))?; + .ok_or_else(|| { + MetricAgentRuntimeError::MissingRequired( + "params.variable_y required for regression".to_string(), + ) + })?; // Extract paired values let mut pairs: Vec<(f64, f64)> = Vec::new(); for record in records { if let (Some(x), Some(y)) = ( - self.get_nested_value(record, variable_x).and_then(|v| v.as_f64()), - self.get_nested_value(record, variable_y).and_then(|v| v.as_f64()), + self.get_nested_value(record, variable_x) + .and_then(|v| v.as_f64()), + self.get_nested_value(record, variable_y) + .and_then(|v| v.as_f64()), ) { if x.is_finite() && y.is_finite() { pairs.push((x, y)); @@ -532,7 +589,10 @@ impl ExperimentalMetricAgent { let mean_x = pairs.iter().map(|(x, _)| x).sum::() / n; let mean_y = pairs.iter().map(|(_, y)| y).sum::() / n; - let numerator = pairs.iter().map(|(x, y)| (x - mean_x) * (y - mean_y)).sum::(); + let numerator = pairs + .iter() + .map(|(x, y)| (x - mean_x) * (y - mean_y)) + .sum::(); let denominator = pairs.iter().map(|(x, _)| (x - mean_x).powi(2)).sum::(); let slope = if denominator > 0.0 { @@ -541,8 +601,8 @@ impl ExperimentalMetricAgent { 0.0 }; - let decimal_slope = Decimal::try_from(slope) - .map_err(|e| MetricAgentRuntimeError::Computation { + let decimal_slope = + Decimal::try_from(slope).map_err(|e| MetricAgentRuntimeError::Computation { metric: "regression".to_string(), message: e.to_string(), })?; @@ -575,8 +635,8 @@ impl ExperimentalMetricAgent { _ => values.iter().sum::(), }; - let decimal_value = Decimal::try_from(value) - .map_err(|e| MetricAgentRuntimeError::Computation { + let decimal_value = + Decimal::try_from(value).map_err(|e| MetricAgentRuntimeError::Computation { metric: "custom_aggregation".to_string(), message: e.to_string(), })?; @@ -625,9 +685,7 @@ impl ExperimentalMetricAgent { "Independent observations assumed".to_string(), ], limitations: warnings.to_vec(), - data_filters: vec![ - format!("handle_missing: {:?}", input.config.handle_missing), - ], + data_filters: vec![format!("handle_missing: {:?}", input.config.handle_missing)], } } } @@ -640,9 +698,10 @@ impl Default for ExperimentalMetricAgent { impl ConfidenceEstimator for ExperimentalMetricAgent { fn estimate_confidence(&self, sample_size: u64, _effect_size: Option) -> f64 { - // Base confidence from sample size - let size_confidence = (sample_size as f64 / 1000.0).min(0.9); - size_confidence.max(0.1) + // Preserve a non-zero floor while increasing smoothly with sample size. + // The previous max-based formula flattened every sample size <= 100 at + // 0.1, violating the estimator's monotonicity contract. + 0.1 + 0.8 * (sample_size as f64 / 1000.0).min(1.0) } } @@ -674,16 +733,17 @@ impl Agent for ExperimentalMetricAgent { // Check for requested metrics if input.metrics_requested.is_empty() { return Err(MetricAgentRuntimeError::MissingRequired( - "metrics_requested".to_string() + "metrics_requested".to_string(), )); } // Check max records limit if input.data.records.len() > self.config.max_records { - return Err(MetricAgentRuntimeError::Validation( - format!("Record count {} exceeds maximum {}", - input.data.records.len(), self.config.max_records) - )); + return Err(MetricAgentRuntimeError::Validation(format!( + "Record count {} exceeds maximum {}", + input.data.records.len(), + self.config.max_records + ))); } Ok(()) @@ -810,7 +870,8 @@ impl Agent for ExperimentalMetricAgent { // Compute overall confidence let total_sample_size: u64 = output.metrics.iter().map(|m| m.sample_size).sum(); - let computation_confidence = self.compute_overall_confidence(&output.metrics, total_sample_size); + let computation_confidence = + self.compute_overall_confidence(&output.metrics, total_sample_size); let confidence = Confidence { value: computation_confidence.value, @@ -994,7 +1055,7 @@ mod tests { let output = agent.execute(input).await.unwrap(); assert_eq!(output.metrics.len(), 1); - + // Since our test data has a positive relationship, correlation should be positive let corr: f64 = output.metrics[0].value.try_into().unwrap(); assert!(corr > 0.0); @@ -1009,7 +1070,9 @@ mod tests { json!({"metrics": {"accuracy": 0.95}}), ]; - let values = agent.extract_numeric_values(&records, "metrics.accuracy").unwrap(); + let values = agent + .extract_numeric_values(&records, "metrics.accuracy") + .unwrap(); assert_eq!(values.len(), 3); assert!((values[0] - 0.9).abs() < 0.001); } diff --git a/llm-research-agents/src/agents/telemetry.rs b/llm-research-agents/src/agents/telemetry.rs index cb518ea..0375d25 100644 --- a/llm-research-agents/src/agents/telemetry.rs +++ b/llm-research-agents/src/agents/telemetry.rs @@ -146,10 +146,15 @@ impl TelemetryEvent { #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum TelemetrySeverity { + /// Represents the debug case. Debug, + /// Represents the info case. Info, + /// Represents the warn case. Warn, + /// Represents the error case. Error, + /// Represents the critical case. Critical, } @@ -277,14 +282,16 @@ impl AgentTelemetry { "agent_telemetry_events_total", "agent_id" => agent_id.clone(), "event_type" => event_type - ).increment(1); + ) + .increment(1); // Emit duration metric if available if let Some(duration_ms) = event.metadata.get("duration_ms").and_then(|v| v.as_u64()) { metrics::histogram!( "agent_execution_duration_ms", "agent_id" => agent_id - ).record(duration_ms as f64); + ) + .record(duration_ms as f64); } } @@ -305,7 +312,12 @@ impl AgentTelemetry { } /// Emit execution completed telemetry. - pub fn execution_completed(&self, request_id: Uuid, duration_ms: u64, result_metadata: serde_json::Value) { + pub fn execution_completed( + &self, + request_id: Uuid, + duration_ms: u64, + result_metadata: serde_json::Value, + ) { self.emit(TelemetryEvent { event_type: TelemetryEventType::ExecutionCompleted, agent_id: self.agent_id.clone(), @@ -427,9 +439,18 @@ mod tests { #[test] fn test_telemetry_event_type_display() { - assert_eq!(TelemetryEventType::ExecutionStarted.to_string(), "execution_started"); - assert_eq!(TelemetryEventType::MetricComputed.to_string(), "metric_computed"); - assert_eq!(TelemetryEventType::Custom("test".to_string()).to_string(), "custom_test"); + assert_eq!( + TelemetryEventType::ExecutionStarted.to_string(), + "execution_started" + ); + assert_eq!( + TelemetryEventType::MetricComputed.to_string(), + "metric_computed" + ); + assert_eq!( + TelemetryEventType::Custom("test".to_string()).to_string(), + "custom_test" + ); } #[test] diff --git a/llm-research-agents/src/agents/traits.rs b/llm-research-agents/src/agents/traits.rs index 190c22d..1c469d4 100644 --- a/llm-research-agents/src/agents/traits.rs +++ b/llm-research-agents/src/agents/traits.rs @@ -65,7 +65,10 @@ pub trait Agent: Send + Sync { /// Full invocation cycle: validate, execute, build event. /// /// This is the primary entry point for agent invocation. - async fn invoke(&self, input: Self::Input) -> Result<(Self::Output, DecisionEvent), Self::Error> { + async fn invoke( + &self, + input: Self::Input, + ) -> Result<(Self::Output, DecisionEvent), Self::Error> { // Validate input self.validate_input(&input)?; @@ -84,7 +87,10 @@ pub trait Agent: Send + Sync { #[async_trait] pub trait BatchAgent: Agent { /// Execute multiple inputs in batch. - async fn execute_batch(&self, inputs: Vec) -> Result, Self::Error>; + async fn execute_batch( + &self, + inputs: Vec, + ) -> Result, Self::Error>; } /// Trait for agents that can estimate their confidence. @@ -159,8 +165,11 @@ pub struct BudgetViolation { impl std::fmt::Display for BudgetViolation { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "BudgetViolation[{}]: {} (limit={}, actual={})", - self.budget_type, self.message, self.limit, self.actual) + write!( + f, + "BudgetViolation[{}]: {} (limit={}, actual={})", + self.budget_type, self.message, self.limit, self.actual + ) } } diff --git a/llm-research-agents/src/clients/ruvector.rs b/llm-research-agents/src/clients/ruvector.rs index 978e358..5061576 100644 --- a/llm-research-agents/src/clients/ruvector.rs +++ b/llm-research-agents/src/clients/ruvector.rs @@ -71,10 +71,12 @@ impl RuVectorConfig { /// - RUVECTOR_TIMEOUT_SECS: Request timeout (default: 30) pub fn from_env() -> Result { // REQUIRED env vars - FAIL if missing (Phase 7 Layer 2 hardening) - let base_url = std::env::var("RUVECTOR_SERVICE_URL") - .map_err(|_| RuVectorError::Configuration( - "RUVECTOR_SERVICE_URL environment variable is required. ABORTING STARTUP.".to_string() - ))?; + let base_url = std::env::var("RUVECTOR_SERVICE_URL").map_err(|_| { + RuVectorError::Configuration( + "RUVECTOR_SERVICE_URL environment variable is required. ABORTING STARTUP." + .to_string(), + ) + })?; let auth_token = std::env::var("RUVECTOR_API_KEY") .map_err(|_| RuVectorError::Configuration( @@ -107,28 +109,46 @@ impl RuVectorConfig { #[derive(Debug, Error)] pub enum RuVectorError { #[error("Configuration error: {0}")] + /// Represents the configuration case. Configuration(String), #[error("Connection error: {0}")] + /// Represents the connection case. Connection(String), #[error("Request error: {0}")] + /// Represents the request case. Request(String), #[error("Response error: status={status}, message={message}")] - Response { status: u16, message: String }, + /// Represents the response case. + Response { + #[doc = "The HTTP response status code."] + status: u16, + #[doc = "The human-readable message."] + message: String, + }, #[error("Serialization error: {0}")] + /// Represents the serialization case. Serialization(String), #[error("Authentication error: {0}")] + /// Represents the authentication case. Authentication(String), #[error("Timeout error")] + /// Represents the timeout case. Timeout, #[error("Retry exhausted after {attempts} attempts: {last_error}")] - RetryExhausted { attempts: u32, last_error: String }, + /// Represents the retry exhausted case. + RetryExhausted { + #[doc = "The number of attempts made."] + attempts: u32, + #[doc = "The error returned by the final attempt."] + last_error: String, + }, } impl From for RuVectorError { @@ -168,8 +188,11 @@ pub struct RuVectorResponse { /// Error response from ruvector-service. #[derive(Debug, Serialize, Deserialize)] pub struct RuVectorErrorResponse { + /// The machine-readable error code. pub code: String, + /// The human-readable error message. pub message: String, + /// Optional structured error details. pub details: Option, } @@ -193,7 +216,10 @@ pub trait RuVectorPersistence: Send + Sync { /// /// This is the primary persistence method for all LLM-Research-Lab agents. /// Every agent invocation MUST call this exactly once. - async fn persist_decision_event(&self, event: DecisionEvent) -> Result; + async fn persist_decision_event( + &self, + event: DecisionEvent, + ) -> Result; /// Retrieve a DecisionEvent by ID. async fn get_decision_event(&self, id: Uuid) -> Result, RuVectorError>; @@ -255,7 +281,7 @@ impl RuVectorClient { let healthy = client.health_check().await?; if !healthy { return Err(RuVectorError::Connection( - "Ruvector health check failed - service unavailable. ABORTING STARTUP.".to_string() + "Ruvector health check failed - service unavailable. ABORTING STARTUP.".to_string(), )); } @@ -349,10 +375,16 @@ impl RuVectorClient { } Err(e) => { if e.is_timeout() { - warn!("Request timeout, attempt {}/{}", attempts, self.config.max_retries); + warn!( + "Request timeout, attempt {}/{}", + attempts, self.config.max_retries + ); last_error = "Timeout".to_string(); } else if e.is_connect() { - warn!("Connection error, attempt {}/{}", attempts, self.config.max_retries); + warn!( + "Connection error, attempt {}/{}", + attempts, self.config.max_retries + ); last_error = e.to_string(); } else { return Err(e.into()); @@ -362,7 +394,8 @@ impl RuVectorClient { // Exponential backoff if attempts < self.config.max_retries { - let backoff = Duration::from_millis(self.config.retry_backoff_ms * 2u64.pow(attempts - 1)); + let backoff = + Duration::from_millis(self.config.retry_backoff_ms * 2u64.pow(attempts - 1)); tokio::time::sleep(backoff).await; } } @@ -377,7 +410,10 @@ impl RuVectorClient { #[async_trait] impl RuVectorPersistence for RuVectorClient { #[instrument(skip(self, event), fields(event_id = %event.id, agent_id = %event.agent_id))] - async fn persist_decision_event(&self, event: DecisionEvent) -> Result { + async fn persist_decision_event( + &self, + event: DecisionEvent, + ) -> Result { let url = self.build_url("/api/v1/decision-events")?; info!("Persisting DecisionEvent to ruvector-service"); @@ -389,11 +425,9 @@ impl RuVectorPersistence for RuVectorClient { .await?; if response.success { - let persisted = response.data.ok_or_else(|| { - RuVectorError::Response { - status: 200, - message: "Success response without data".to_string(), - } + let persisted = response.data.ok_or_else(|| RuVectorError::Response { + status: 200, + message: "Success response without data".to_string(), })?; info!( @@ -460,20 +494,19 @@ impl RuVectorPersistence for RuVectorClient { async fn health_check(&self) -> Result { let url = self.build_url("/health")?; - let response = self.client.get(url).send().await - .map_err(|e| { - error!( - error = %e, - ruvector_url = %self.config.base_url, - phase = "phase7", - layer = "layer2", - "Ruvector health check connection failed - FATAL" - ); - RuVectorError::Connection(format!( - "Health check failed - cannot connect to Ruvector at {}: {}", - self.config.base_url, e - )) - })?; + let response = self.client.get(url).send().await.map_err(|e| { + error!( + error = %e, + ruvector_url = %self.config.base_url, + phase = "phase7", + layer = "layer2", + "Ruvector health check connection failed - FATAL" + ); + RuVectorError::Connection(format!( + "Health check failed - cannot connect to Ruvector at {}: {}", + self.config.base_url, e + )) + })?; if !response.status().is_success() { let status = response.status(); diff --git a/llm-research-agents/src/contracts/common.rs b/llm-research-agents/src/contracts/common.rs index a69af5d..9c5c97b 100644 --- a/llm-research-agents/src/contracts/common.rs +++ b/llm-research-agents/src/contracts/common.rs @@ -25,7 +25,9 @@ pub struct AgentIdentity { #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum AgentClassification { + /// Represents the hypothesis evaluation case. HypothesisEvaluation, + /// Represents the experimental metrics case. ExperimentalMetrics, } @@ -78,8 +80,11 @@ pub enum CliInput { #[serde(rename_all = "snake_case")] pub enum CliOutputFormat { #[default] + /// Represents the json case. Json, + /// Represents the yaml case. Yaml, + /// Represents the table case. Table, } diff --git a/llm-research-agents/src/contracts/decision_event.rs b/llm-research-agents/src/contracts/decision_event.rs index 7cef82c..f7a2d0a 100644 --- a/llm-research-agents/src/contracts/decision_event.rs +++ b/llm-research-agents/src/contracts/decision_event.rs @@ -21,7 +21,7 @@ use chrono::{DateTime, Utc}; use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; -use sha2::{Sha256, Digest}; +use sha2::{Digest, Sha256}; use uuid::Uuid; use validator::Validate; @@ -109,9 +109,7 @@ impl Confidence { } if let (Some(lo), Some(hi)) = (self.ci_lower, self.ci_upper) { if lo > hi { - return Err(format!( - "ci_lower ({lo}) must be ≤ ci_upper ({hi})" - )); + return Err(format!("ci_lower ({lo}) must be ≤ ci_upper ({hi})")); } } Ok(()) @@ -160,7 +158,9 @@ pub struct ConstraintsApplied { /// Temporal bounds for experimental constraints. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TemporalBounds { + /// The optional start of the time range. pub start: Option>, + /// The optional end of the time range. pub end: Option>, } @@ -270,36 +270,43 @@ pub struct EvidenceRefsBuilder { } impl EvidenceRefsBuilder { + /// Sets the pub for this builder. pub fn run_id(mut self, id: impl Into) -> Self { self.run_ids.push(id.into()); self } + /// Sets the pub for this builder. pub fn run_ids(mut self, ids: impl IntoIterator>) -> Self { self.run_ids.extend(ids.into_iter().map(Into::into)); self } + /// Sets the pub for this builder. pub fn telemetry_id(mut self, id: impl Into) -> Self { self.telemetry_ids.push(id.into()); self } + /// Sets the pub for this builder. pub fn telemetry_ids(mut self, ids: impl IntoIterator>) -> Self { self.telemetry_ids.extend(ids.into_iter().map(Into::into)); self } + /// Sets the pub for this builder. pub fn dataset_ref(mut self, ref_: impl Into) -> Self { self.dataset_refs.push(ref_.into()); self } + /// Sets the pub for this builder. pub fn dataset_refs(mut self, refs: impl IntoIterator>) -> Self { self.dataset_refs.extend(refs.into_iter().map(Into::into)); self } + /// Sets the pub for this builder. pub fn build(self) -> EvidenceRefs { EvidenceRefs { run_ids: self.run_ids, @@ -414,46 +421,55 @@ pub struct DecisionEventBuilder { } impl DecisionEventBuilder { + /// Sets the pub for this builder. pub fn agent_id(mut self, id: impl Into) -> Self { self.agent_id = Some(id.into()); self } + /// Sets the pub for this builder. pub fn agent_version(mut self, version: impl Into) -> Self { self.agent_version = Some(version.into()); self } + /// Sets the pub for this builder. pub fn decision_type(mut self, dt: DecisionType) -> Self { self.decision_type = Some(dt); self } + /// Sets the pub for this builder. pub fn inputs_hash(mut self, hash: impl Into) -> Self { self.inputs_hash = Some(hash.into()); self } + /// Sets the pub for this builder. pub fn outputs(mut self, outputs: serde_json::Value) -> Self { self.outputs = Some(outputs); self } + /// Sets the pub for this builder. pub fn confidence(mut self, confidence: Confidence) -> Self { self.confidence = Some(confidence); self } + /// Sets the pub for this builder. pub fn constraints_applied(mut self, constraints: ConstraintsApplied) -> Self { self.constraints_applied = Some(constraints); self } + /// Sets the pub for this builder. pub fn execution_ref(mut self, exec_ref: ExecutionRef) -> Self { self.execution_ref = Some(exec_ref); self } + /// Sets the pub for this builder. pub fn metadata(mut self, metadata: serde_json::Value) -> Self { self.metadata = Some(metadata); self @@ -486,12 +502,24 @@ impl DecisionEventBuilder { /// - `phase7_identity`: Defaults from environment variables if not provided /// - `evidence_refs`: Defaults to empty references if not provided pub fn build(self) -> Result { - let agent_id = self.agent_id.ok_or(DecisionEventBuildError::MissingField("agent_id"))?; - let agent_version = self.agent_version.ok_or(DecisionEventBuildError::MissingField("agent_version"))?; - let decision_type = self.decision_type.ok_or(DecisionEventBuildError::MissingField("decision_type"))?; - let inputs_hash = self.inputs_hash.ok_or(DecisionEventBuildError::MissingField("inputs_hash"))?; - let outputs = self.outputs.ok_or(DecisionEventBuildError::MissingField("outputs"))?; - let confidence = self.confidence.ok_or(DecisionEventBuildError::MissingField("confidence"))?; + let agent_id = self + .agent_id + .ok_or(DecisionEventBuildError::MissingField("agent_id"))?; + let agent_version = self + .agent_version + .ok_or(DecisionEventBuildError::MissingField("agent_version"))?; + let decision_type = self + .decision_type + .ok_or(DecisionEventBuildError::MissingField("decision_type"))?; + let inputs_hash = self + .inputs_hash + .ok_or(DecisionEventBuildError::MissingField("inputs_hash"))?; + let outputs = self + .outputs + .ok_or(DecisionEventBuildError::MissingField("outputs"))?; + let confidence = self + .confidence + .ok_or(DecisionEventBuildError::MissingField("confidence"))?; let execution_ref = self.execution_ref.unwrap_or_else(|| ExecutionRef { execution_id: Uuid::new_v4(), @@ -529,6 +557,7 @@ impl DecisionEventBuilder { #[derive(Debug, thiserror::Error)] pub enum DecisionEventBuildError { #[error("Missing required field: {0}")] + /// Represents the missing field case. MissingField(&'static str), } @@ -575,11 +604,7 @@ mod tests { ci_upper: None, }; - let phase7_identity = Phase7Identity::new( - "simulation-runner", - "simulation", - "2.0.0", - ); + let phase7_identity = Phase7Identity::new("simulation-runner", "simulation", "2.0.0"); let evidence_refs = EvidenceRefs::builder() .run_id("run-abc-123") @@ -634,11 +659,23 @@ mod tests { fn test_phase7_signal_types_serialization() { let signal_types = vec![ (DecisionType::HypothesisSignal, "\"hypothesis_signal\""), - (DecisionType::SimulationOutcomeSignal, "\"simulation_outcome_signal\""), - (DecisionType::ScenarioComparisonSignal, "\"scenario_comparison_signal\""), - (DecisionType::ConfidenceDeltaSignal, "\"confidence_delta_signal\""), + ( + DecisionType::SimulationOutcomeSignal, + "\"simulation_outcome_signal\"", + ), + ( + DecisionType::ScenarioComparisonSignal, + "\"scenario_comparison_signal\"", + ), + ( + DecisionType::ConfidenceDeltaSignal, + "\"confidence_delta_signal\"", + ), (DecisionType::UncertaintySignal, "\"uncertainty_signal\""), - (DecisionType::ResearchInsightSignal, "\"research_insight_signal\""), + ( + DecisionType::ResearchInsightSignal, + "\"research_insight_signal\"", + ), ]; for (dt, expected) in signal_types { diff --git a/llm-research-agents/src/contracts/hypothesis.rs b/llm-research-agents/src/contracts/hypothesis.rs index 5cbfc5e..4429065 100644 --- a/llm-research-agents/src/contracts/hypothesis.rs +++ b/llm-research-agents/src/contracts/hypothesis.rs @@ -180,9 +180,13 @@ pub struct HypothesisVariable { #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum VariableRole { + /// Represents the independent case. Independent, + /// Represents the dependent case. Dependent, + /// Represents the confounding case. Confounding, + /// Represents the control case. Control, } @@ -190,10 +194,15 @@ pub enum VariableRole { #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum VariableDataType { + /// Represents the continuous case. Continuous, + /// Represents the discrete case. Discrete, + /// Represents the categorical case. Categorical, + /// Represents the ordinal case. Ordinal, + /// Represents the binary case. Binary, } @@ -282,9 +291,13 @@ pub struct EvaluationConfig { #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum CorrectionMethod { + /// Represents the bonferroni case. Bonferroni, + /// Represents the holm case. Holm, + /// Represents the benjamini hochberg case. BenjaminiHochberg, + /// Represents the benjamini yekutieli case. BenjaminiYekutieli, } @@ -359,8 +372,11 @@ pub struct TestResults { /// Confidence interval. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ConfidenceInterval { + /// The lower confidence-bound value. pub lower: Decimal, + /// The upper confidence-bound value. pub upper: Decimal, + /// The confidence level. pub level: Decimal, } @@ -381,14 +397,23 @@ pub struct EffectSize { #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum EffectSizeMeasure { + /// Represents the cohens d case. CohensD, + /// Represents the hedges g case. HedgesG, + /// Represents the glass delta case. GlassDelta, + /// Represents the eta squared case. EtaSquared, + /// Represents the omega squared case. OmegaSquared, + /// Represents the cramers v case. CramersV, + /// Represents the phi coefficient case. PhiCoefficient, + /// Represents the odds ratio case. OddsRatio, + /// Represents the risk ratio case. RiskRatio, } @@ -412,9 +437,13 @@ pub struct DiagnosticInfo { #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum SampleAdequacy { + /// Represents the adequate case. Adequate, + /// Represents the marginal case. Marginal, + /// Represents the inadequate case. Inadequate, + /// Represents the unknown case. Unknown, } @@ -438,8 +467,11 @@ pub struct AssumptionViolation { #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum ViolationSeverity { + /// Represents the minor case. Minor, + /// Represents the moderate case. Moderate, + /// Represents the severe case. Severe, } @@ -494,7 +526,8 @@ mod tests { }; let json = serde_json::to_string(&input).expect("Serialization failed"); - let deserialized: HypothesisInput = serde_json::from_str(&json).expect("Deserialization failed"); + let deserialized: HypothesisInput = + serde_json::from_str(&json).expect("Deserialization failed"); assert_eq!(input.hypothesis.name, deserialized.hypothesis.name); } diff --git a/llm-research-agents/src/contracts/metrics.rs b/llm-research-agents/src/contracts/metrics.rs index 8ac56e4..3fbcb3b 100644 --- a/llm-research-agents/src/contracts/metrics.rs +++ b/llm-research-agents/src/contracts/metrics.rs @@ -152,14 +152,18 @@ pub struct MetricsData { /// Schema definition for data. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DataSchema { + /// The ordered field definitions. pub fields: Vec, } /// Field definition in schema. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct FieldDefinition { + /// The field name. pub name: String, + /// The field data type. pub data_type: String, + /// Whether the field may be null. pub nullable: bool, } @@ -183,10 +187,15 @@ pub struct MetricsConfig { #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum MissingValueStrategy { + /// Represents the skip case. Skip, + /// Represents the zero fill case. ZeroFill, + /// Represents the mean impute case. MeanImpute, + /// Represents the median impute case. MedianImpute, + /// Represents the fail case. Fail, } @@ -231,8 +240,11 @@ pub struct ComputedMetric { /// Confidence interval for a metric. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MetricConfidenceInterval { + /// The lower confidence-bound value. pub lower: Decimal, + /// The upper confidence-bound value. pub upper: Decimal, + /// The confidence level. pub level: Decimal, } @@ -255,6 +267,7 @@ pub struct MetricsMetadata { /// Agent identity for the Experimental Metric Agent. pub const AGENT_ID: &str = "experimental-metric-agent"; +/// The current agent contract version. pub const AGENT_VERSION: &str = "1.0.0"; /// Error codes for the Experimental Metric Agent. @@ -494,8 +507,14 @@ mod tests { #[test] fn test_persistence_fields() { let persistence = MetricEventPersistence::default(); - assert!(persistence.persisted_fields.contains(&"agent_id".to_string())); - assert!(persistence.persisted_fields.contains(&"decision_type".to_string())); - assert!(persistence.excluded_fields.contains(&"pii_fields".to_string())); + assert!(persistence + .persisted_fields + .contains(&"agent_id".to_string())); + assert!(persistence + .persisted_fields + .contains(&"decision_type".to_string())); + assert!(persistence + .excluded_fields + .contains(&"pii_fields".to_string())); } } diff --git a/llm-research-agents/src/contracts/mod.rs b/llm-research-agents/src/contracts/mod.rs index 31fd3b8..a7efe36 100644 --- a/llm-research-agents/src/contracts/mod.rs +++ b/llm-research-agents/src/contracts/mod.rs @@ -11,12 +11,12 @@ //! - All inputs and outputs are validated against these contracts //! - DecisionEvents follow the mandated schema +pub mod common; pub mod decision_event; pub mod hypothesis; pub mod metrics; -pub mod common; +pub use common::*; pub use decision_event::*; pub use hypothesis::*; pub use metrics::*; -pub use common::*; diff --git a/llm-research-agents/src/execution.rs b/llm-research-agents/src/execution.rs index 144fb53..e6131eb 100644 --- a/llm-research-agents/src/execution.rs +++ b/llm-research-agents/src/execution.rs @@ -319,7 +319,9 @@ pub enum ExecutionError { pub fn validate_execution_context( ctx: &Option, ) -> Result<&ExecutionContext, ExecutionError> { - let ctx = ctx.as_ref().ok_or(ExecutionError::MissingExecutionContext)?; + let ctx = ctx + .as_ref() + .ok_or(ExecutionError::MissingExecutionContext)?; if ctx.parent_span_id.is_nil() { return Err(ExecutionError::MissingParentSpanId); } @@ -477,8 +479,8 @@ mod tests { result: Some(serde_json::json!({"status": "ok"})), }; - let json = serde_json::to_string(&result) - .expect("ExecutionResult must be JSON-serializable"); + let json = + serde_json::to_string(&result).expect("ExecutionResult must be JSON-serializable"); assert!(json.contains("execution_id")); assert!(json.contains("repo_span")); assert!(json.contains("result")); @@ -486,9 +488,7 @@ mod tests { #[test] fn test_execution_result_rejected() { - let result = ExecutionResult::::rejected( - "no context".to_string(), - ); + let result = ExecutionResult::::rejected("no context".to_string()); assert!(result.execution_id.is_nil()); assert_eq!(result.repo_span.status, SpanStatus::Failed); diff --git a/llm-research-agents/src/handlers/hypothesis.rs b/llm-research-agents/src/handlers/hypothesis.rs index 9c5a5b4..ebe3fb4 100644 --- a/llm-research-agents/src/handlers/hypothesis.rs +++ b/llm-research-agents/src/handlers/hypothesis.rs @@ -27,7 +27,7 @@ use uuid::Uuid; use crate::agents::{Agent, HypothesisAgent, HypothesisAgentError, HYPOTHESIS_AGENT_ID}; use crate::clients::{RuVectorClient, RuVectorError, RuVectorPersistence}; -use crate::contracts::{HypothesisInput, HypothesisOutput, AgentError, DecisionEvent}; +use crate::contracts::{AgentError, DecisionEvent, HypothesisInput, HypothesisOutput}; use crate::execution::{ExecutionArtifact, ExecutionContext, ExecutionSpan}; use crate::telemetry::TelemetryEmitter; @@ -48,8 +48,11 @@ pub struct HypothesisEvaluateRequest { /// Trace context for distributed tracing. #[derive(Debug, Clone, Deserialize, Serialize)] pub struct TraceContext { + /// The distributed trace identifier. pub trace_id: String, + /// The current span identifier. pub span_id: String, + /// The optional parent span identifier. pub parent_span_id: Option, } @@ -75,7 +78,9 @@ pub struct HypothesisEvaluateResponse { /// Reference to persisted decision event. #[derive(Debug, Serialize)] pub struct DecisionEventRef { + /// The event id. pub event_id: Uuid, + /// The optional RuVector storage reference. pub storage_ref: String, } @@ -83,15 +88,19 @@ pub struct DecisionEventRef { #[derive(Debug, thiserror::Error)] pub enum HandlerError { #[error("Agent error: {0}")] + /// Represents the agent case. Agent(#[from] HypothesisAgentError), #[error("Persistence error: {0}")] + /// Represents the persistence case. Persistence(#[from] RuVectorError), #[error("Serialization error: {0}")] + /// Represents the serialization case. Serialization(#[from] serde_json::Error), #[error("Configuration error: {0}")] + /// Represents the configuration case. Configuration(String), } @@ -116,8 +125,8 @@ impl HypothesisHandler { /// Create a new handler with default configuration. pub fn new() -> Result { let agent = HypothesisAgent::new(); - let ruvector_client = RuVectorClient::from_env() - .map_err(|e| HandlerError::Configuration(e.to_string()))?; + let ruvector_client = + RuVectorClient::from_env().map_err(|e| HandlerError::Configuration(e.to_string()))?; let telemetry = TelemetryEmitter::new(); Ok(Self { @@ -162,16 +171,14 @@ impl HypothesisHandler { info!("Handling hypothesis evaluation request"); // Create trace span if context provided - let _span = if let Some(ref ctx) = request.trace_context { - Some(span!( + let _span = request.trace_context.as_ref().map(|ctx| { + span!( Level::INFO, "hypothesis_evaluation", trace_id = %ctx.trace_id, span_id = %ctx.span_id - )) - } else { - None - }; + ) + }); // Execute agent with full lifecycle match self.execute_evaluation(request.input).await { @@ -184,10 +191,9 @@ impl HypothesisHandler { ); // Emit success telemetry - self.telemetry.emit_success( - &event, - request.trace_context.as_ref(), - ).await; + self.telemetry + .emit_success(&event, request.trace_context.as_ref()) + .await; // Attach decision event as artifact to agent span agent_span.add_artifact(ExecutionArtifact { @@ -238,11 +244,9 @@ impl HypothesisHandler { error!(error = %e, "Hypothesis evaluation failed"); // Emit failure telemetry - self.telemetry.emit_failure( - &e.to_string(), - request_id, - request.trace_context.as_ref(), - ).await; + self.telemetry + .emit_failure(&e.to_string(), request_id, request.trace_context.as_ref()) + .await; agent_span.fail(e.to_string()); @@ -276,7 +280,11 @@ impl HypothesisHandler { ) -> Result<(HypothesisOutput, DecisionEvent, Option), HandlerError> { let (output, event) = self.agent.invoke(input).await?; - let storage_ref = match self.ruvector_client.persist_decision_event(event.clone()).await { + let storage_ref = match self + .ruvector_client + .persist_decision_event(event.clone()) + .await + { Ok(persisted) => Some(persisted.storage_ref), Err(e) => { tracing::warn!( @@ -295,12 +303,22 @@ impl HypothesisHandler { /// Map errors to error codes. fn error_code(error: &HandlerError) -> String { match error { - HandlerError::Agent(HypothesisAgentError::Validation(_)) => "HYPOTHESIS_INPUT_INVALID".to_string(), - HandlerError::Agent(HypothesisAgentError::InsufficientSampleSize { .. }) => "HYPOTHESIS_SAMPLE_SIZE".to_string(), - HandlerError::Agent(HypothesisAgentError::StatisticalComputation(_)) => "HYPOTHESIS_COMPUTATION".to_string(), - HandlerError::Agent(HypothesisAgentError::Configuration(_)) => "HYPOTHESIS_CONFIG".to_string(), + HandlerError::Agent(HypothesisAgentError::Validation(_)) => { + "HYPOTHESIS_INPUT_INVALID".to_string() + } + HandlerError::Agent(HypothesisAgentError::InsufficientSampleSize { .. }) => { + "HYPOTHESIS_SAMPLE_SIZE".to_string() + } + HandlerError::Agent(HypothesisAgentError::StatisticalComputation(_)) => { + "HYPOTHESIS_COMPUTATION".to_string() + } + HandlerError::Agent(HypothesisAgentError::Configuration(_)) => { + "HYPOTHESIS_CONFIG".to_string() + } HandlerError::Agent(HypothesisAgentError::Internal(_)) => "HYPOTHESIS_INTERNAL".to_string(), - HandlerError::Agent(HypothesisAgentError::BudgetExceeded(_)) => "HYPOTHESIS_BUDGET_EXCEEDED".to_string(), + HandlerError::Agent(HypothesisAgentError::BudgetExceeded(_)) => { + "HYPOTHESIS_BUDGET_EXCEEDED".to_string() + } HandlerError::Persistence(_) => "HYPOTHESIS_PERSISTENCE".to_string(), HandlerError::Serialization(_) => "HYPOTHESIS_SERIALIZATION".to_string(), HandlerError::Configuration(_) => "HYPOTHESIS_CONFIG".to_string(), @@ -309,10 +327,7 @@ fn error_code(error: &HandlerError) -> String { /// Health check handler. pub async fn health_check(ruvector_client: &RuVectorClient) -> bool { - match ruvector_client.health_check().await { - Ok(healthy) => healthy, - Err(_) => false, - } + ruvector_client.health_check().await.unwrap_or_default() } #[cfg(test)] @@ -387,7 +402,11 @@ mod tests { #[test] fn test_error_code_mapping() { - let validation_error = HandlerError::Agent(HypothesisAgentError::Validation("test".to_string())); + let request = create_test_request(); + assert_eq!(request.input.experimental_data.sample_size, 100); + + let validation_error = + HandlerError::Agent(HypothesisAgentError::Validation("test".to_string())); assert_eq!(error_code(&validation_error), "HYPOTHESIS_INPUT_INVALID"); let sample_error = HandlerError::Agent(HypothesisAgentError::InsufficientSampleSize { @@ -396,12 +415,4 @@ mod tests { }); assert_eq!(error_code(&sample_error), "HYPOTHESIS_SAMPLE_SIZE"); } - - #[test] - fn test_agent_span_included_in_handle_result() { - // Verify the handle signature returns an ExecutionSpan - // (compile-time check via type annotation) - let _: fn(&HypothesisHandler, HypothesisEvaluateRequest, Uuid) - -> std::pin::Pin + Send + '_>>; - } } diff --git a/llm-research-agents/src/handlers/metric.rs b/llm-research-agents/src/handlers/metric.rs index 94f5dd4..e2d2bd7 100644 --- a/llm-research-agents/src/handlers/metric.rs +++ b/llm-research-agents/src/handlers/metric.rs @@ -51,13 +51,17 @@ use crate::clients::{RuVectorClient, RuVectorPersistence}; use crate::contracts::metrics::{MetricsInput, MetricsOutput}; use crate::contracts::DecisionEvent; use crate::execution::{ExecutionArtifact, ExecutionContext, ExecutionSpan}; +use crate::handlers::hypothesis::TraceContext; use crate::telemetry::TelemetryEmitter; /// Trace context for distributed tracing. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MetricTraceContext { + /// The distributed trace identifier. pub trace_id: String, + /// The current span identifier. pub span_id: String, + /// The optional parent span identifier. pub parent_span_id: Option, } @@ -104,11 +108,17 @@ pub struct MetricComputeResponse { /// Summary of decision event (without full outputs). #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DecisionEventSummary { + /// The unique identifier. pub id: Uuid, + /// The stable agent identifier. pub agent_id: String, + /// The agent implementation version. pub agent_version: String, + /// The classified decision type. pub decision_type: String, + /// The serialized confidence value. pub confidence: String, + /// The optional RuVector storage reference. pub storage_ref: Option, } @@ -162,6 +172,11 @@ impl MetricHandler { ) -> (MetricComputeResponse, ExecutionSpan) { let start_time = Instant::now(); let request_id = request.input.request_id; + let telemetry_trace = request.trace_context.as_ref().map(|context| TraceContext { + trace_id: context.trace_id.clone(), + span_id: context.span_id.clone(), + parent_span_id: context.parent_span_id.clone(), + }); let mut agent_span = ExecutionSpan::new_agent(repo_span_id, METRIC_AGENT_ID); info!("Handling metric computation request"); @@ -201,6 +216,10 @@ impl MetricHandler { } }; + self.telemetry + .emit_success(&event, telemetry_trace.as_ref()) + .await; + // Persist decision event let storage_ref = self.persist_decision_event(&event).await; @@ -309,9 +328,13 @@ impl Default for MetricHandler { /// Agent information response. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AgentInfo { + /// The unique identifier. pub id: String, + /// The version. pub version: String, + /// The agent classification. pub classification: String, + /// The HTTP endpoint exposed by the agent. pub endpoint: String, } @@ -335,15 +358,13 @@ mod tests { input: MetricsInput { request_id: Uuid::new_v4(), context_id: "test-experiment".to_string(), - metrics_requested: vec![ - MetricRequest { - name: "mean_accuracy".to_string(), - metric_type: MetricType::CentralTendency, - variable: "accuracy".to_string(), - group_by: None, - params: None, - }, - ], + metrics_requested: vec![MetricRequest { + name: "mean_accuracy".to_string(), + metric_type: MetricType::CentralTendency, + variable: "accuracy".to_string(), + group_by: None, + params: None, + }], data: MetricsData { source: "test".to_string(), records, @@ -400,7 +421,10 @@ mod tests { assert!(!response.success); assert!(response.error.is_some()); - assert_eq!(response.error_code, Some("METRIC_COMPUTATION_FAILED".to_string())); + assert_eq!( + response.error_code, + Some("METRIC_COMPUTATION_FAILED".to_string()) + ); // Verify agent span records failure assert_eq!(agent_span.status, crate::execution::SpanStatus::Failed); diff --git a/llm-research-agents/src/lib.rs b/llm-research-agents/src/lib.rs index 94270bb..f57d02a 100644 --- a/llm-research-agents/src/lib.rs +++ b/llm-research-agents/src/lib.rs @@ -102,22 +102,20 @@ pub mod telemetry; // Re-export commonly used types pub use agents::{ - Agent, - HypothesisAgent, HypothesisAgentError, HYPOTHESIS_AGENT_ID, HYPOTHESIS_AGENT_VERSION, - ExperimentalMetricAgent, MetricAgentRuntimeError, METRIC_AGENT_ID, METRIC_AGENT_VERSION, - AgentTelemetry, + Agent, AgentTelemetry, ExperimentalMetricAgent, HypothesisAgent, HypothesisAgentError, + MetricAgentRuntimeError, HYPOTHESIS_AGENT_ID, HYPOTHESIS_AGENT_VERSION, METRIC_AGENT_ID, + METRIC_AGENT_VERSION, }; pub use clients::{RuVectorClient, RuVectorConfig, RuVectorError, RuVectorPersistence}; +pub use contracts::metrics::{ComputedMetric, MetricType, MetricsInput, MetricsOutput}; pub use contracts::{DecisionEvent, DecisionType, HypothesisInput, HypothesisOutput}; -pub use contracts::metrics::{MetricsInput, MetricsOutput, MetricType, ComputedMetric}; -pub use handlers::{HypothesisHandler, HypothesisEvaluateRequest, HypothesisEvaluateResponse}; -pub use handlers::{MetricHandler, MetricComputeRequest, MetricComputeResponse}; -pub use telemetry::TelemetryEmitter; pub use execution::{ - ExecutionContext, ExecutionSpan, ExecutionResult, ExecutionArtifact, - ExecutionError, SpanType, SpanStatus, - validate_execution_context, REPO_NAME, + validate_execution_context, ExecutionArtifact, ExecutionContext, ExecutionError, + ExecutionResult, ExecutionSpan, SpanStatus, SpanType, REPO_NAME, }; +pub use handlers::{HypothesisEvaluateRequest, HypothesisEvaluateResponse, HypothesisHandler}; +pub use handlers::{MetricComputeRequest, MetricComputeResponse, MetricHandler}; +pub use telemetry::TelemetryEmitter; /// Type alias for MetricAgent (for backward compatibility with CLI) pub type MetricAgent = ExperimentalMetricAgent; @@ -150,7 +148,11 @@ pub fn get_agent_registrations() -> Vec { version: HYPOTHESIS_AGENT_VERSION.to_string(), classification: "HYPOTHESIS_EVALUATION".to_string(), cli_command: "hypothesis".to_string(), - cli_subcommands: vec!["evaluate".to_string(), "inspect".to_string(), "validate".to_string()], + cli_subcommands: vec![ + "evaluate".to_string(), + "inspect".to_string(), + "validate".to_string(), + ], endpoint_path: "/api/v1/agents/hypothesis".to_string(), }, AgentRegistration { diff --git a/llm-research-agents/src/telemetry/mod.rs b/llm-research-agents/src/telemetry/mod.rs index 513a5e8..5423665 100644 --- a/llm-research-agents/src/telemetry/mod.rs +++ b/llm-research-agents/src/telemetry/mod.rs @@ -70,8 +70,11 @@ pub struct TelemetryEvent { /// Serializable trace context. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct SerializableTraceContext { + /// The distributed trace identifier. pub trace_id: String, + /// The current span identifier. pub span_id: String, + /// The optional parent span identifier. pub parent_span_id: Option, } @@ -135,7 +138,8 @@ impl TelemetryEmitter { timestamp: Utc::now(), agent_id: decision_event.agent_id.clone(), agent_version: decision_event.agent_version.clone(), - request_id: decision_event.metadata + request_id: decision_event + .metadata .as_ref() .and_then(|m| m.get("request_id")) .and_then(|v| v.as_str()) diff --git a/llm-research-agents/tests/integration_tests.rs b/llm-research-agents/tests/integration_tests.rs index 030877e..9265b46 100644 --- a/llm-research-agents/tests/integration_tests.rs +++ b/llm-research-agents/tests/integration_tests.rs @@ -19,19 +19,15 @@ use uuid::Uuid; use llm_research_agents::{ agents::Agent, contracts::{ - hypothesis::*, decision_event::*, + hypothesis::*, metrics::{ - MetricsInput, MetricRequest, MetricType, MetricsData, - MetricsConfig, MissingValueStrategy, + MetricRequest, MetricType, MetricsConfig, MetricsData, MetricsInput, + MissingValueStrategy, }, }, - HypothesisAgent, - ExperimentalMetricAgent, - HYPOTHESIS_AGENT_ID, - HYPOTHESIS_AGENT_VERSION, - METRIC_AGENT_ID, - METRIC_AGENT_VERSION, + ExperimentalMetricAgent, HypothesisAgent, HYPOTHESIS_AGENT_ID, HYPOTHESIS_AGENT_VERSION, + METRIC_AGENT_ID, METRIC_AGENT_VERSION, }; // ============================================================================ @@ -97,11 +93,13 @@ fn create_valid_hypothesis_input(sample_size: u64) -> HypothesisInput { /// Create a valid MetricsInput for testing. fn create_valid_metrics_input() -> MetricsInput { let records: Vec = (0..100) - .map(|i| json!({ - "metric_a": (i as f64) * 0.5, - "metric_b": (i as f64) * 0.25 + 10.0, - "group": if i % 2 == 0 { "A" } else { "B" }, - })) + .map(|i| { + json!({ + "metric_a": (i as f64) * 0.5, + "metric_b": (i as f64) * 0.25 + 10.0, + "group": if i % 2 == 0 { "A" } else { "B" }, + }) + }) .collect(); MetricsInput { @@ -163,7 +161,9 @@ mod hypothesis_agent { let output = result.unwrap(); assert!(matches!( output.status, - HypothesisStatus::Accepted | HypothesisStatus::Rejected | HypothesisStatus::Inconclusive + HypothesisStatus::Accepted + | HypothesisStatus::Rejected + | HypothesisStatus::Inconclusive )); } @@ -181,7 +181,11 @@ mod hypothesis_agent { assert_eq!(event.agent_id, HYPOTHESIS_AGENT_ID); assert_eq!(event.agent_version, HYPOTHESIS_AGENT_VERSION); assert_eq!(event.decision_type, DecisionType::HypothesisEvaluation); - assert_eq!(event.inputs_hash.len(), 64, "inputs_hash should be SHA256 (64 hex chars)"); + assert_eq!( + event.inputs_hash.len(), + 64, + "inputs_hash should be SHA256 (64 hex chars)" + ); assert!(!event.outputs.is_null(), "outputs should be populated"); assert!(event.confidence.value >= dec!(0) && event.confidence.value <= dec!(1)); @@ -205,8 +209,6 @@ mod hypothesis_agent { #[tokio::test] async fn test_determinism() { - let agent = HypothesisAgent::new(); - // Create identical inputs with same seed let input1 = create_valid_hypothesis_input(100); let input2 = HypothesisInput { @@ -283,7 +285,10 @@ mod experimental_metric_agent { assert!(result.is_ok(), "Execution should succeed with valid input"); let output = result.unwrap(); - assert!(!output.metrics.is_empty(), "Should produce computed metrics"); + assert!( + !output.metrics.is_empty(), + "Should produce computed metrics" + ); } #[tokio::test] @@ -323,7 +328,10 @@ mod experimental_metric_agent { // Verify mean computation (values are 0, 0.5, 1.0, ... 49.5) // Mean should be around 24.75 let mean: f64 = metric.value.try_into().unwrap(); - assert!(mean > 20.0 && mean < 30.0, "Mean should be approximately 24.75"); + assert!( + mean > 20.0 && mean < 30.0, + "Mean should be approximately 24.75" + ); } #[tokio::test] @@ -352,7 +360,11 @@ mod experimental_metric_agent { let result = agent.execute(input).await.unwrap(); - assert_eq!(result.metrics.len(), 2, "Should compute all requested metrics"); + assert_eq!( + result.metrics.len(), + 2, + "Should compute all requested metrics" + ); } #[tokio::test] @@ -363,7 +375,7 @@ mod experimental_metric_agent { let result = agent.execute(input).await.unwrap(); assert_eq!(result.metadata.records_processed, 100); - assert!(result.metadata.processing_time_ms >= 0); + assert!(result.metadata.computed_at <= Utc::now()); } } @@ -427,8 +439,14 @@ mod decision_event { #[test] fn test_decision_type_serialization() { let types = vec![ - (DecisionType::HypothesisEvaluation, "\"hypothesis_evaluation\""), - (DecisionType::ExperimentalMetrics, "\"experimental_metrics\""), + ( + DecisionType::HypothesisEvaluation, + "\"hypothesis_evaluation\"", + ), + ( + DecisionType::ExperimentalMetrics, + "\"experimental_metrics\"", + ), ]; for (dt, expected) in types { diff --git a/llm-research-core/Cargo.toml b/llm-research-core/Cargo.toml index 459f3d5..b19869d 100644 --- a/llm-research-core/Cargo.toml +++ b/llm-research-core/Cargo.toml @@ -36,11 +36,11 @@ validator.workspace = true regex = "1.11" # Optional database support (not used in Cloud Run deployment) -# sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "postgres"], optional = true } +sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "postgres"], optional = true } [features] default = [] -# database = ["sqlx"] +database = ["dep:sqlx"] [dev-dependencies] tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } diff --git a/llm-research-core/src/domain.rs b/llm-research-core/src/domain.rs index 9ef0518..bb3e75f 100644 --- a/llm-research-core/src/domain.rs +++ b/llm-research-core/src/domain.rs @@ -1,17 +1,17 @@ -pub mod ids; pub mod config; -pub mod run; +pub mod dataset; +pub mod evaluation; pub mod experiment; +pub mod ids; pub mod model; -pub mod evaluation; pub mod prompt; -pub mod dataset; +pub mod run; -pub use ids::*; pub use config::*; -pub use run::*; +pub use dataset::*; +pub use evaluation::*; pub use experiment::*; +pub use ids::*; pub use model::*; -pub use evaluation::*; pub use prompt::*; -pub use dataset::*; +pub use run::*; diff --git a/llm-research-core/src/domain/config.rs b/llm-research-core/src/domain/config.rs index 3a98f7c..e0bf271 100644 --- a/llm-research-core/src/domain/config.rs +++ b/llm-research-core/src/domain/config.rs @@ -231,23 +231,13 @@ pub enum GpuType { Custom(String), } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, Hash)] pub struct GpuRequirements { pub gpu_type: Option, pub gpu_count: u32, pub gpu_memory_gb: Option, } -impl Default for GpuRequirements { - fn default() -> Self { - Self { - gpu_type: None, - gpu_count: 0, - gpu_memory_gb: None, - } - } -} - #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] pub struct ComputeRequirements { pub cpu_cores: Option, @@ -315,7 +305,7 @@ impl Default for ReproducibilitySettings { // ===== Complete Experiment Configuration ===== -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Validate)] +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Validate)] pub struct ExperimentConfig { pub model_configs: Vec, pub dataset_refs: Vec, @@ -327,20 +317,6 @@ pub struct ExperimentConfig { pub metadata: HashMap, } -impl Default for ExperimentConfig { - fn default() -> Self { - Self { - model_configs: Vec::new(), - dataset_refs: Vec::new(), - metric_configs: Vec::new(), - parameters: ExperimentParameters::default(), - resource_requirements: ResourceRequirements::default(), - reproducibility_settings: ReproducibilitySettings::default(), - metadata: HashMap::new(), - } - } -} - #[cfg(test)] mod tests { use super::*; @@ -366,7 +342,7 @@ mod tests { fn test_parameter_value() { let string_val = ParameterValue::from("test".to_string()); let int_val = ParameterValue::from(42i64); - let _float_val = ParameterValue::from(3.14f64); + let _float_val = ParameterValue::from(3.125f64); let _bool_val = ParameterValue::from(true); match string_val { diff --git a/llm-research-core/src/domain/evaluation.rs b/llm-research-core/src/domain/evaluation.rs index 15ee136..c62263e 100644 --- a/llm-research-core/src/domain/evaluation.rs +++ b/llm-research-core/src/domain/evaluation.rs @@ -18,29 +18,33 @@ pub struct Evaluation { pub created_at: DateTime, } +/// Inputs used to create a new evaluation record. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NewEvaluation { + pub experiment_id: Uuid, + pub sample_id: Uuid, + pub input: String, + pub output: String, + pub expected_output: Option, + pub latency_ms: i64, + pub token_count: i32, + pub cost: Option, + pub metrics: serde_json::Value, +} + impl Evaluation { - pub fn new( - experiment_id: Uuid, - sample_id: Uuid, - input: String, - output: String, - expected_output: Option, - latency_ms: i64, - token_count: i32, - cost: Option, - metrics: serde_json::Value, - ) -> Self { + pub fn new(details: NewEvaluation) -> Self { Self { id: Uuid::new_v4(), - experiment_id, - sample_id, - input, - output, - expected_output, - latency_ms, - token_count, - cost, - metrics, + experiment_id: details.experiment_id, + sample_id: details.sample_id, + input: details.input, + output: details.output, + expected_output: details.expected_output, + latency_ms: details.latency_ms, + token_count: details.token_count, + cost: details.cost, + metrics: details.metrics, created_at: Utc::now(), } } diff --git a/llm-research-core/src/domain/experiment.rs b/llm-research-core/src/domain/experiment.rs index f3218ad..fe7590a 100644 --- a/llm-research-core/src/domain/experiment.rs +++ b/llm-research-core/src/domain/experiment.rs @@ -3,8 +3,8 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; use validator::Validate; -use super::ids::{ExperimentId, UserId}; use super::config::ExperimentConfig; +use super::ids::{ExperimentId, UserId}; // ===== Experiment Status ===== @@ -355,13 +355,8 @@ mod tests { let user_id = UserId::new(); let config = ExperimentConfig::default(); - let mut experiment = Experiment::new( - "Test Experiment".to_string(), - None, - None, - user_id, - config, - ); + let mut experiment = + Experiment::new("Test Experiment".to_string(), None, None, user_id, config); // Activate experiment assert!(experiment.activate().is_ok()); @@ -395,13 +390,8 @@ mod tests { let collaborator2 = UserId::new(); let config = ExperimentConfig::default(); - let mut experiment = Experiment::new( - "Test Experiment".to_string(), - None, - None, - owner_id, - config, - ); + let mut experiment = + Experiment::new("Test Experiment".to_string(), None, None, owner_id, config); // Add collaborators experiment.add_collaborator(collaborator1); @@ -424,14 +414,9 @@ mod tests { let user_id = UserId::new(); let config = ExperimentConfig::default(); - let mut experiment = Experiment::new( - "Test Experiment".to_string(), - None, - None, - user_id, - config, - ) - .with_tags(vec!["test".to_string(), "ml".to_string()]); + let mut experiment = + Experiment::new("Test Experiment".to_string(), None, None, user_id, config) + .with_tags(vec!["test".to_string(), "ml".to_string()]); assert_eq!(experiment.tags.len(), 2); @@ -453,13 +438,8 @@ mod tests { let user_id = UserId::new(); let config = ExperimentConfig::default(); - let mut experiment = Experiment::new( - "Test Experiment".to_string(), - None, - None, - user_id, - config, - ); + let mut experiment = + Experiment::new("Test Experiment".to_string(), None, None, user_id, config); // Cannot pause from Draft assert!(experiment.pause().is_err()); diff --git a/llm-research-core/src/domain/ids.rs b/llm-research-core/src/domain/ids.rs index 1a48716..69e2e8d 100644 --- a/llm-research-core/src/domain/ids.rs +++ b/llm-research-core/src/domain/ids.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; use std::fmt; use uuid::Uuid; -use sha2::{Sha256, Digest}; /// Newtype pattern for ExperimentId #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)] @@ -404,8 +404,8 @@ impl ContentHash { Self(hex::encode(result)) } - /// Create a new content hash from a string - pub fn from_str(data: &str) -> Self { + /// Create a new content hash from string content. + pub fn from_content(data: &str) -> Self { Self::from_bytes(data.as_bytes()) } @@ -490,11 +490,14 @@ impl SemanticVersion { return Err(format!("Invalid version format: {}", s)); } - let major = parts[0].parse::() + let major = parts[0] + .parse::() .map_err(|_| format!("Invalid major version: {}", parts[0]))?; - let minor = parts[1].parse::() + let minor = parts[1] + .parse::() .map_err(|_| format!("Invalid minor version: {}", parts[1]))?; - let patch = parts[2].parse::() + let patch = parts[2] + .parse::() .map_err(|_| format!("Invalid patch version: {}", parts[2]))?; Ok(Self { @@ -566,8 +569,7 @@ mod tests { let v = SemanticVersion::new(1, 2, 3); assert_eq!(v.to_string(), "1.2.3"); - let v = SemanticVersion::new(1, 2, 3) - .with_pre_release("alpha.1".to_string()); + let v = SemanticVersion::new(1, 2, 3).with_pre_release("alpha.1".to_string()); assert_eq!(v.to_string(), "1.2.3-alpha.1"); let v = SemanticVersion::new(1, 2, 3) @@ -595,9 +597,9 @@ mod tests { #[test] fn test_content_hash() { - let hash1 = ContentHash::from_str("hello world"); - let hash2 = ContentHash::from_str("hello world"); - let hash3 = ContentHash::from_str("hello world!"); + let hash1 = ContentHash::from_content("hello world"); + let hash2 = ContentHash::from_content("hello world"); + let hash3 = ContentHash::from_content("hello world!"); assert_eq!(hash1, hash2); assert_ne!(hash1, hash3); diff --git a/llm-research-core/src/domain/run.rs b/llm-research-core/src/domain/run.rs index a80ce06..3f1cf4d 100644 --- a/llm-research-core/src/domain/run.rs +++ b/llm-research-core/src/domain/run.rs @@ -3,8 +3,8 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; use validator::Validate; -use super::ids::{ArtifactId, ExperimentId, RunId, UserId}; use super::config::ParameterValue; +use super::ids::{ArtifactId, ExperimentId, RunId, UserId}; // ===== Run Status ===== @@ -24,10 +24,7 @@ impl RunStatus { pub fn is_terminal(&self) -> bool { matches!( self, - RunStatus::Completed - | RunStatus::Failed - | RunStatus::Cancelled - | RunStatus::TimedOut + RunStatus::Completed | RunStatus::Failed | RunStatus::Cancelled | RunStatus::TimedOut ) } @@ -73,7 +70,7 @@ pub struct RuntimeInfo { #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] pub struct DependencyManifest { pub manifest_type: String, // e.g., "pip", "conda", "cargo", "npm" - pub content: String, // requirements.txt, environment.yml, Cargo.lock, etc. + pub content: String, // requirements.txt, environment.yml, Cargo.lock, etc. pub checksum: Option, } @@ -147,7 +144,7 @@ pub struct ConfusionMatrix { pub timestamp: DateTime, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] pub struct RunMetrics { pub scalars: Vec, pub distributions: Vec, @@ -155,17 +152,6 @@ pub struct RunMetrics { pub custom_metrics: HashMap, } -impl Default for RunMetrics { - fn default() -> Self { - Self { - scalars: Vec::new(), - distributions: Vec::new(), - confusion_matrices: Vec::new(), - custom_metrics: HashMap::new(), - } - } -} - // ===== Artifact Reference ===== #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Validate)] @@ -203,7 +189,7 @@ pub struct LogHighlight { pub source: Option, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, Hash)] pub struct LogSummary { pub total_lines: u64, pub error_count: u64, @@ -212,18 +198,6 @@ pub struct LogSummary { pub log_artifact_id: Option, } -impl Default for LogSummary { - fn default() -> Self { - Self { - total_lines: 0, - error_count: 0, - warning_count: 0, - highlights: Vec::new(), - log_artifact_id: None, - } - } -} - // ===== Run Error ===== #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] @@ -389,12 +363,7 @@ mod tests { fn test_run_lifecycle() { let experiment_id = ExperimentId::new(); let user_id = UserId::new(); - let mut run = ExperimentRun::new( - experiment_id, - 1, - "Test Run".to_string(), - user_id, - ); + let mut run = ExperimentRun::new(experiment_id, 1, "Test Run".to_string(), user_id); assert_eq!(run.status, RunStatus::Pending); assert!(!run.is_terminal()); @@ -423,15 +392,10 @@ mod tests { let mut params = HashMap::new(); params.insert("learning_rate".to_string(), ParameterValue::from(0.001f64)); - let run = ExperimentRun::new( - experiment_id, - 1, - "Test Run".to_string(), - user_id, - ) - .with_parameters(params.clone()) - .with_parent(parent_id) - .with_tags(vec!["test".to_string(), "experiment".to_string()]); + let run = ExperimentRun::new(experiment_id, 1, "Test Run".to_string(), user_id) + .with_parameters(params.clone()) + .with_parent(parent_id) + .with_tags(vec!["test".to_string(), "experiment".to_string()]); assert_eq!(run.parameters.len(), 1); assert_eq!(run.parent_run_id, Some(parent_id)); @@ -442,12 +406,7 @@ mod tests { fn test_run_duration() { let experiment_id = ExperimentId::new(); let user_id = UserId::new(); - let mut run = ExperimentRun::new( - experiment_id, - 1, - "Test Run".to_string(), - user_id, - ); + let mut run = ExperimentRun::new(experiment_id, 1, "Test Run".to_string(), user_id); assert!(run.duration_seconds().is_none()); diff --git a/llm-research-core/src/traits.rs b/llm-research-core/src/traits.rs index 43ec584..2b9f352 100644 --- a/llm-research-core/src/traits.rs +++ b/llm-research-core/src/traits.rs @@ -1,5 +1,5 @@ -use async_trait::async_trait; use crate::error::Result; +use async_trait::async_trait; #[async_trait] pub trait Repository { diff --git a/llm-research-core/tests/config_tests.rs b/llm-research-core/tests/config_tests.rs index 0f76cfe..eec6c23 100644 --- a/llm-research-core/tests/config_tests.rs +++ b/llm-research-core/tests/config_tests.rs @@ -151,7 +151,7 @@ fn test_gpu_requirements_default() { #[test] fn test_gpu_type_variants() { - let types = vec![ + let types = [ GpuType::T4, GpuType::V100, GpuType::A100, @@ -186,10 +186,10 @@ fn test_parameter_value_integer() { #[test] fn test_parameter_value_float() { - let value = ParameterValue::from(3.14f64); + let value = ParameterValue::from(3.125f64); match value { - ParameterValue::Float(f) => assert!((f - 3.14).abs() < 1e-10), + ParameterValue::Float(f) => assert!((f - 3.125).abs() < 1e-10), _ => panic!("Expected Float variant"), } } @@ -222,7 +222,10 @@ fn test_parameter_value_array() { #[test] fn test_parameter_value_object() { let mut obj = HashMap::new(); - obj.insert("key1".to_string(), ParameterValue::from("value1".to_string())); + obj.insert( + "key1".to_string(), + ParameterValue::from("value1".to_string()), + ); obj.insert("key2".to_string(), ParameterValue::from(42i64)); let value = ParameterValue::Object(obj); @@ -265,7 +268,7 @@ fn test_dataset_ref_creation() { #[test] fn test_dataset_version_selector() { - let selectors = vec![ + let selectors = [ DatasetVersionSelector::Latest, DatasetVersionSelector::Tag("v1.0".to_string()), DatasetVersionSelector::Specific(DatasetVersionId::new()), @@ -277,7 +280,7 @@ fn test_dataset_version_selector() { #[test] fn test_data_split_variants() { - let splits = vec![ + let splits = [ DataSplit::Train, DataSplit::Validation, DataSplit::Test, @@ -299,7 +302,7 @@ fn test_sample_config_default() { #[test] fn test_sample_strategy_variants() { - let strategies = vec![ + let strategies = [ SampleStrategy::Random, SampleStrategy::Sequential, SampleStrategy::Stratified, @@ -364,7 +367,7 @@ fn test_metric_config_with_threshold() { #[test] fn test_threshold_direction_variants() { - let directions = vec![ + let directions = [ ThresholdDirection::Above, ThresholdDirection::Below, ThresholdDirection::Equal, @@ -419,7 +422,7 @@ fn test_search_space_creation() { #[test] fn test_search_strategy_variants() { - let strategies = vec![ + let strategies = [ SearchStrategy::Grid, SearchStrategy::Random, SearchStrategy::Bayesian, diff --git a/llm-research-core/tests/domain_tests.rs b/llm-research-core/tests/domain_tests.rs index a99d1ff..be245cf 100644 --- a/llm-research-core/tests/domain_tests.rs +++ b/llm-research-core/tests/domain_tests.rs @@ -76,15 +76,13 @@ fn test_semantic_version_creation() { #[test] fn test_semantic_version_with_pre_release() { - let v = SemanticVersion::new(1, 0, 0) - .with_pre_release("alpha.1".to_string()); + let v = SemanticVersion::new(1, 0, 0).with_pre_release("alpha.1".to_string()); assert_eq!(v.pre_release, Some("alpha.1".to_string())); } #[test] fn test_semantic_version_with_build_metadata() { - let v = SemanticVersion::new(1, 0, 0) - .with_build_metadata("build.123".to_string()); + let v = SemanticVersion::new(1, 0, 0).with_build_metadata("build.123".to_string()); assert_eq!(v.build_metadata, Some("build.123".to_string())); } @@ -128,8 +126,7 @@ fn test_semantic_version_comparison() { // Pre-release versions have lower precedence let v_release = SemanticVersion::new(1, 0, 0); - let v_pre = SemanticVersion::new(1, 0, 0) - .with_pre_release("alpha".to_string()); + let v_pre = SemanticVersion::new(1, 0, 0).with_pre_release("alpha".to_string()); assert!(v_pre < v_release); } @@ -138,8 +135,7 @@ fn test_semantic_version_display() { let v1 = SemanticVersion::new(1, 2, 3); assert_eq!(v1.to_string(), "1.2.3"); - let v2 = SemanticVersion::new(1, 0, 0) - .with_pre_release("beta.1".to_string()); + let v2 = SemanticVersion::new(1, 0, 0).with_pre_release("beta.1".to_string()); assert_eq!(v2.to_string(), "1.0.0-beta.1"); let v3 = SemanticVersion::new(2, 1, 0) @@ -153,8 +149,8 @@ fn test_semantic_version_display() { #[test] fn test_content_hash_computation() { let data = "hello world"; - let hash1 = ContentHash::from_str(data); - let hash2 = ContentHash::from_str(data); + let hash1 = ContentHash::from_content(data); + let hash2 = ContentHash::from_content(data); assert_eq!(hash1, hash2); assert_eq!(hash1.as_str(), hash2.as_str()); @@ -162,8 +158,8 @@ fn test_content_hash_computation() { #[test] fn test_content_hash_different_data() { - let hash1 = ContentHash::from_str("hello"); - let hash2 = ContentHash::from_str("world"); + let hash1 = ContentHash::from_content("hello"); + let hash2 = ContentHash::from_content("world"); assert_ne!(hash1, hash2); } @@ -172,14 +168,14 @@ fn test_content_hash_different_data() { fn test_content_hash_from_bytes() { let data = b"test data"; let hash1 = ContentHash::from_bytes(data); - let hash2 = ContentHash::from_str("test data"); + let hash2 = ContentHash::from_content("test data"); assert_eq!(hash1, hash2); } #[test] fn test_content_hash_display() { - let hash = ContentHash::from_str("test"); + let hash = ContentHash::from_content("test"); let display = format!("{}", hash); assert_eq!(display, hash.as_str()); assert!(!display.is_empty()); @@ -307,13 +303,7 @@ fn test_experiment_state_transitions() { let owner_id = UserId::new(); let config = ExperimentConfig::default(); - let mut experiment = Experiment::new( - "Test".to_string(), - None, - None, - owner_id, - config, - ); + let mut experiment = Experiment::new("Test".to_string(), None, None, owner_id, config); // Activate assert!(experiment.activate().is_ok()); @@ -342,13 +332,7 @@ fn test_experiment_invalid_transitions() { let owner_id = UserId::new(); let config = ExperimentConfig::default(); - let mut experiment = Experiment::new( - "Test".to_string(), - None, - None, - owner_id, - config, - ); + let mut experiment = Experiment::new("Test".to_string(), None, None, owner_id, config); // Cannot pause from Draft assert!(experiment.pause().is_err()); @@ -367,13 +351,7 @@ fn test_experiment_collaborators() { let collab2 = UserId::new(); let config = ExperimentConfig::default(); - let mut experiment = Experiment::new( - "Test".to_string(), - None, - None, - owner_id, - config, - ); + let mut experiment = Experiment::new("Test".to_string(), None, None, owner_id, config); experiment.add_collaborator(collab1); experiment.add_collaborator(collab2); @@ -402,13 +380,7 @@ fn test_experiment_tags() { let owner_id = UserId::new(); let config = ExperimentConfig::default(); - let mut experiment = Experiment::new( - "Test".to_string(), - None, - None, - owner_id, - config, - ); + let mut experiment = Experiment::new("Test".to_string(), None, None, owner_id, config); experiment.add_tag("ml".to_string()); experiment.add_tag("nlp".to_string()); @@ -429,12 +401,7 @@ fn test_experiment_run_creation() { let experiment_id = ExperimentId::new(); let user_id = UserId::new(); - let run = ExperimentRun::new( - experiment_id, - 1, - "Run 1".to_string(), - user_id, - ); + let run = ExperimentRun::new(experiment_id, 1, "Run 1".to_string(), user_id); assert_eq!(run.experiment_id, experiment_id); assert_eq!(run.run_number, 1); @@ -451,12 +418,7 @@ fn test_experiment_run_lifecycle() { let experiment_id = ExperimentId::new(); let user_id = UserId::new(); - let mut run = ExperimentRun::new( - experiment_id, - 1, - "Test Run".to_string(), - user_id, - ); + let mut run = ExperimentRun::new(experiment_id, 1, "Test Run".to_string(), user_id); // Queue run.queue(); @@ -481,12 +443,7 @@ fn test_experiment_run_failure() { let experiment_id = ExperimentId::new(); let user_id = UserId::new(); - let mut run = ExperimentRun::new( - experiment_id, - 1, - "Test Run".to_string(), - user_id, - ); + let mut run = ExperimentRun::new(experiment_id, 1, "Test Run".to_string(), user_id); run.start(); @@ -511,12 +468,7 @@ fn test_experiment_run_cancel() { let experiment_id = ExperimentId::new(); let user_id = UserId::new(); - let mut run = ExperimentRun::new( - experiment_id, - 1, - "Test Run".to_string(), - user_id, - ); + let mut run = ExperimentRun::new(experiment_id, 1, "Test Run".to_string(), user_id); run.start(); run.cancel(); @@ -530,12 +482,7 @@ fn test_experiment_run_timeout() { let experiment_id = ExperimentId::new(); let user_id = UserId::new(); - let mut run = ExperimentRun::new( - experiment_id, - 1, - "Test Run".to_string(), - user_id, - ); + let mut run = ExperimentRun::new(experiment_id, 1, "Test Run".to_string(), user_id); run.start(); run.timeout(); @@ -549,12 +496,7 @@ fn test_experiment_run_duration() { let experiment_id = ExperimentId::new(); let user_id = UserId::new(); - let mut run = ExperimentRun::new( - experiment_id, - 1, - "Test Run".to_string(), - user_id, - ); + let mut run = ExperimentRun::new(experiment_id, 1, "Test Run".to_string(), user_id); assert!(run.duration_seconds().is_none()); @@ -579,12 +521,8 @@ fn test_experiment_run_with_parameters() { params.insert("batch_size".to_string(), ParameterValue::from(32i64)); params.insert("enabled".to_string(), ParameterValue::from(true)); - let run = ExperimentRun::new( - experiment_id, - 1, - "Test Run".to_string(), - user_id, - ).with_parameters(params); + let run = ExperimentRun::new(experiment_id, 1, "Test Run".to_string(), user_id) + .with_parameters(params); assert_eq!(run.parameters.len(), 3); } @@ -595,12 +533,8 @@ fn test_experiment_run_with_parent() { let user_id = UserId::new(); let parent_id = RunId::new(); - let run = ExperimentRun::new( - experiment_id, - 2, - "Child Run".to_string(), - user_id, - ).with_parent(parent_id); + let run = ExperimentRun::new(experiment_id, 2, "Child Run".to_string(), user_id) + .with_parent(parent_id); assert_eq!(run.parent_run_id, Some(parent_id)); } diff --git a/llm-research-core/tests/model_dataset_tests.rs b/llm-research-core/tests/model_dataset_tests.rs index 76973a9..6fd2b01 100644 --- a/llm-research-core/tests/model_dataset_tests.rs +++ b/llm-research-core/tests/model_dataset_tests.rs @@ -264,13 +264,7 @@ fn test_dataset_sample_without_expected_output() { let input = json!({"prompt": "Generate a story about..."}); let metadata = json!({}); - let sample = DatasetSample::new( - dataset_id, - 0, - input.clone(), - None, - metadata.clone(), - ); + let sample = DatasetSample::new(dataset_id, 0, input.clone(), None, metadata.clone()); assert_eq!(sample.expected_output, None); assert_eq!(sample.input, input); @@ -338,28 +332,16 @@ fn test_prompt_template_creation() { #[test] fn test_prompt_template_validation() { // Valid - let valid = PromptTemplate::new( - "Test".to_string(), - None, - "Template {{var}}".to_string(), - ); + let valid = PromptTemplate::new("Test".to_string(), None, "Template {{var}}".to_string()); assert!(valid.validate().is_ok()); // Invalid: empty name - let mut invalid = PromptTemplate::new( - "".to_string(), - None, - "Template".to_string(), - ); + let mut invalid = PromptTemplate::new("".to_string(), None, "Template".to_string()); invalid.name = "".to_string(); assert!(invalid.validate().is_err()); // Invalid: empty template - let mut invalid = PromptTemplate::new( - "Test".to_string(), - None, - "Template".to_string(), - ); + let mut invalid = PromptTemplate::new("Test".to_string(), None, "Template".to_string()); invalid.template = "".to_string(); assert!(invalid.validate().is_err()); } @@ -367,11 +349,7 @@ fn test_prompt_template_validation() { #[test] fn test_prompt_template_variable_extraction() { let template = "{{var1}} and {{var2}} but not {var3} or {{var1}} again"; - let prompt = PromptTemplate::new( - "Test".to_string(), - None, - template.to_string(), - ); + let prompt = PromptTemplate::new("Test".to_string(), None, template.to_string()); // Should extract var1 and var2, but may have duplicates assert!(prompt.variables.contains(&"var1".to_string())); @@ -382,11 +360,7 @@ fn test_prompt_template_variable_extraction() { #[test] fn test_prompt_template_render_success() { let template = "Dear {{name}},\n\nYour order #{{order_id}} is ready."; - let prompt = PromptTemplate::new( - "Order Notification".to_string(), - None, - template.to_string(), - ); + let prompt = PromptTemplate::new("Order Notification".to_string(), None, template.to_string()); let context = json!({ "name": "Alice", @@ -403,11 +377,7 @@ fn test_prompt_template_render_success() { #[test] fn test_prompt_template_render_missing_variable() { let template = "Hello {{name}}!"; - let prompt = PromptTemplate::new( - "Test".to_string(), - None, - template.to_string(), - ); + let prompt = PromptTemplate::new("Test".to_string(), None, template.to_string()); let context = json!({ "wrong_key": "value" @@ -421,11 +391,7 @@ fn test_prompt_template_render_missing_variable() { #[test] fn test_prompt_template_render_with_non_string_values() { let template = "Score: {{score}}, Passed: {{passed}}"; - let prompt = PromptTemplate::new( - "Test".to_string(), - None, - template.to_string(), - ); + let prompt = PromptTemplate::new("Test".to_string(), None, template.to_string()); let context = json!({ "score": 95, @@ -442,11 +408,7 @@ fn test_prompt_template_render_with_non_string_values() { #[test] fn test_prompt_template_no_variables() { let template = "This is a static template with no variables."; - let prompt = PromptTemplate::new( - "Static".to_string(), - None, - template.to_string(), - ); + let prompt = PromptTemplate::new("Static".to_string(), None, template.to_string()); assert_eq!(prompt.variables.len(), 0); @@ -507,17 +469,17 @@ fn test_evaluation_creation() { "recall": 0.97 }); - let eval = Evaluation::new( + let eval = Evaluation::new(NewEvaluation { experiment_id, sample_id, - "What is 2+2?".to_string(), - "4".to_string(), - Some("4".to_string()), - 150, - 10, - Some(rust_decimal::Decimal::new(15, 5)), // 0.00015 - metrics.clone(), - ); + input: "What is 2+2?".to_string(), + output: "4".to_string(), + expected_output: Some("4".to_string()), + latency_ms: 150, + token_count: 10, + cost: Some(rust_decimal::Decimal::new(15, 5)), // 0.00015 + metrics: metrics.clone(), + }); assert_eq!(eval.experiment_id, experiment_id); assert_eq!(eval.sample_id, sample_id); @@ -535,17 +497,17 @@ fn test_evaluation_without_expected_output() { let experiment_id = Uuid::new_v4(); let sample_id = Uuid::new_v4(); - let eval = Evaluation::new( + let eval = Evaluation::new(NewEvaluation { experiment_id, sample_id, - "Generate a poem".to_string(), - "Roses are red...".to_string(), - None, - 500, - 50, - None, - json!({}), - ); + input: "Generate a poem".to_string(), + output: "Roses are red...".to_string(), + expected_output: None, + latency_ms: 500, + token_count: 50, + cost: None, + metrics: json!({}), + }); assert_eq!(eval.expected_output, None); assert_eq!(eval.cost, None); @@ -556,17 +518,17 @@ fn test_evaluation_with_high_latency() { let experiment_id = Uuid::new_v4(); let sample_id = Uuid::new_v4(); - let eval = Evaluation::new( + let eval = Evaluation::new(NewEvaluation { experiment_id, sample_id, - "Complex task".to_string(), - "Result".to_string(), - None, - 30_000, // 30 seconds - 1000, - None, - json!({"latency_class": "slow"}), - ); + input: "Complex task".to_string(), + output: "Result".to_string(), + expected_output: None, + latency_ms: 30_000, // 30 seconds + token_count: 1000, + cost: None, + metrics: json!({"latency_class": "slow"}), + }); assert_eq!(eval.latency_ms, 30_000); } @@ -576,7 +538,7 @@ fn test_evaluation_metrics() { let metrics = EvaluationMetrics { accuracy: Some(rust_decimal::Decimal::new(95, 2)), // 0.95 precision: Some(rust_decimal::Decimal::new(93, 2)), // 0.93 - recall: Some(rust_decimal::Decimal::new(97, 2)), // 0.97 + recall: Some(rust_decimal::Decimal::new(97, 2)), // 0.97 f1_score: Some(rust_decimal::Decimal::new(95, 2)), // 0.95 bleu_score: Some(rust_decimal::Decimal::new(82, 2)), // 0.82 rouge_scores: Some(json!({ @@ -743,7 +705,7 @@ fn test_sample_config_custom() { fn test_parameter_value_conversions() { let string_val = ParameterValue::from("test".to_string()); let int_val = ParameterValue::from(42i64); - let float_val = ParameterValue::from(3.14f64); + let float_val = ParameterValue::from(3.125f64); let bool_val = ParameterValue::from(true); match string_val { @@ -757,7 +719,7 @@ fn test_parameter_value_conversions() { } match float_val { - ParameterValue::Float(f) => assert_eq!(f, 3.14), + ParameterValue::Float(f) => assert_eq!(f, 3.125), _ => panic!("Expected Float"), } @@ -781,7 +743,10 @@ fn test_parameter_value_nested() { } let mut map = HashMap::new(); - map.insert("key1".to_string(), ParameterValue::from("value1".to_string())); + map.insert( + "key1".to_string(), + ParameterValue::from("value1".to_string()), + ); map.insert("key2".to_string(), ParameterValue::from(42i64)); let object = ParameterValue::Object(map); @@ -1014,7 +979,10 @@ fn test_metric_config_validation() { #[test] fn test_core_error_variants() { let validation_err = CoreError::Validation("Invalid input".to_string()); - assert_eq!(validation_err.to_string(), "Validation error: Invalid input"); + assert_eq!( + validation_err.to_string(), + "Validation error: Invalid input" + ); let not_found_err = CoreError::NotFound("Resource not found".to_string()); assert_eq!(not_found_err.to_string(), "Not found: Resource not found"); @@ -1046,7 +1014,7 @@ fn test_core_error_from_serde() { if let Err(e) = parse_result { let core_err: CoreError = e.into(); match core_err { - CoreError::Serialization(_) => {}, + CoreError::Serialization(_) => {} _ => panic!("Expected Serialization error"), } } diff --git a/llm-research-core/tests/serialization_tests.rs b/llm-research-core/tests/serialization_tests.rs index 9d06d9d..dc393f9 100644 --- a/llm-research-core/tests/serialization_tests.rs +++ b/llm-research-core/tests/serialization_tests.rs @@ -168,13 +168,7 @@ fn test_dataset_sample_serialization_roundtrip() { let expected = json!({"answer": "Artificial Intelligence"}); let metadata = json!({"source": "human", "quality": 0.95}); - let original = DatasetSample::new( - dataset_id, - 42, - input, - Some(expected), - metadata, - ); + let original = DatasetSample::new(dataset_id, 42, input, Some(expected), metadata); let json = serde_json::to_string(&original).unwrap(); let deserialized: DatasetSample = serde_json::from_str(&json).unwrap(); @@ -212,11 +206,7 @@ fn test_prompt_template_serialization_roundtrip() { fn test_prompt_template_with_multiline() { let template = "System: {{system_prompt}}\n\nUser: {{user_input}}\n\nAssistant:"; - let original = PromptTemplate::new( - "Chat Template".to_string(), - None, - template.to_string(), - ); + let original = PromptTemplate::new("Chat Template".to_string(), None, template.to_string()); let json = serde_json::to_string(&original).unwrap(); let deserialized: PromptTemplate = serde_json::from_str(&json).unwrap(); @@ -255,17 +245,17 @@ fn test_evaluation_serialization_roundtrip() { "f1": 0.92 }); - let original = Evaluation::new( + let original = Evaluation::new(NewEvaluation { experiment_id, sample_id, - "Input text".to_string(), - "Output text".to_string(), - Some("Expected".to_string()), - 250, - 20, - Some(rust_decimal::Decimal::new(25, 5)), + input: "Input text".to_string(), + output: "Output text".to_string(), + expected_output: Some("Expected".to_string()), + latency_ms: 250, + token_count: 20, + cost: Some(rust_decimal::Decimal::new(25, 5)), metrics, - ); + }); let json = serde_json::to_string(&original).unwrap(); let deserialized: Evaluation = serde_json::from_str(&json).unwrap(); @@ -437,7 +427,7 @@ fn test_parameter_value_serialization() { let values = vec![ ParameterValue::String("test".to_string()), ParameterValue::Integer(42), - ParameterValue::Float(3.14), + ParameterValue::Float(3.125), ParameterValue::Boolean(true), ]; @@ -664,7 +654,7 @@ fn test_semantic_version_serialization() { #[test] fn test_content_hash_serialization() { - let original = ContentHash::from_str("test data for hashing"); + let original = ContentHash::from_content("test data for hashing"); let json = serde_json::to_string(&original).unwrap(); let deserialized: ContentHash = serde_json::from_str(&json).unwrap(); @@ -867,7 +857,10 @@ fn test_complete_experiment_config_roundtrip() { assert_eq!(config.model_configs.len(), deserialized.model_configs.len()); assert_eq!(config.dataset_refs.len(), deserialized.dataset_refs.len()); - assert_eq!(config.metric_configs.len(), deserialized.metric_configs.len()); + assert_eq!( + config.metric_configs.len(), + deserialized.metric_configs.len() + ); assert_eq!( config.model_configs[0].model_name, deserialized.model_configs[0].model_name diff --git a/llm-research-lab/src/config.rs b/llm-research-lab/src/config.rs index 8c087c4..7b764a5 100644 --- a/llm-research-lab/src/config.rs +++ b/llm-research-lab/src/config.rs @@ -30,18 +30,14 @@ use std::env; /// Platform environment. #[derive(Debug, Clone, Deserialize, PartialEq)] #[serde(rename_all = "lowercase")] +#[derive(Default)] pub enum PlatformEnv { + #[default] Dev, Staging, Prod, } -impl Default for PlatformEnv { - fn default() -> Self { - Self::Dev - } -} - /// Cloud Run service configuration. /// /// CONSTITUTION COMPLIANCE: @@ -161,11 +157,13 @@ impl AgentIdentity { /// FAILS FAST if required identity variables are missing or invalid. /// NO FALLBACKS in production mode. pub fn load(platform_env: &PlatformEnv) -> Result { - let name = env::var("AGENT_NAME") - .map_err(|_| anyhow!("AGENT_NAME environment variable is required. ABORTING STARTUP."))?; + let name = env::var("AGENT_NAME").map_err(|_| { + anyhow!("AGENT_NAME environment variable is required. ABORTING STARTUP.") + })?; - let domain = env::var("AGENT_DOMAIN") - .map_err(|_| anyhow!("AGENT_DOMAIN environment variable is required. ABORTING STARTUP."))?; + let domain = env::var("AGENT_DOMAIN").map_err(|_| { + anyhow!("AGENT_DOMAIN environment variable is required. ABORTING STARTUP.") + })?; let phase = env::var("AGENT_PHASE") .map_err(|_| anyhow!("AGENT_PHASE environment variable is required (must be 'phase7'). ABORTING STARTUP."))?; @@ -173,8 +171,9 @@ impl AgentIdentity { let layer = env::var("AGENT_LAYER") .map_err(|_| anyhow!("AGENT_LAYER environment variable is required (must be 'layer2'). ABORTING STARTUP."))?; - let version = env::var("AGENT_VERSION") - .map_err(|_| anyhow!("AGENT_VERSION environment variable is required. ABORTING STARTUP."))?; + let version = env::var("AGENT_VERSION").map_err(|_| { + anyhow!("AGENT_VERSION environment variable is required. ABORTING STARTUP.") + })?; // Validate phase and layer in production/staging if *platform_env != PlatformEnv::Dev { @@ -222,7 +221,8 @@ impl AgentIdentity { domain: env::var("AGENT_DOMAIN").unwrap_or_else(|_| "research".to_string()), phase: env::var("AGENT_PHASE").unwrap_or_else(|_| "phase7".to_string()), layer: env::var("AGENT_LAYER").unwrap_or_else(|_| "layer2".to_string()), - version: env::var("AGENT_VERSION").unwrap_or_else(|_| env!("CARGO_PKG_VERSION").to_string()), + version: env::var("AGENT_VERSION") + .unwrap_or_else(|_| env!("CARGO_PKG_VERSION").to_string()), }) } else { Self::load(platform_env) @@ -266,8 +266,7 @@ impl Config { if platform_env == PlatformEnv::Prod { "prod" } else { "staging" } ))? } else { - env::var("RUVECTOR_SERVICE_URL") - .unwrap_or_else(|_| "http://localhost:8081".to_string()) + env::var("RUVECTOR_SERVICE_URL").unwrap_or_else(|_| "http://localhost:8081".to_string()) }; // RUVECTOR_API_KEY - REQUIRED in prod, optional in dev @@ -331,12 +330,10 @@ impl Config { .unwrap_or(3), llm_observatory_endpoint, telemetry_endpoint, - service_name: env::var("SERVICE_NAME") - .unwrap_or_else(|_| default_service_name()), + service_name: env::var("SERVICE_NAME").unwrap_or_else(|_| default_service_name()), service_version: env::var("SERVICE_VERSION") .unwrap_or_else(|_| default_service_version()), - log_level: env::var("LLM_RESEARCH_LOG_LEVEL") - .unwrap_or_else(|_| default_log_level()), + log_level: env::var("LLM_RESEARCH_LOG_LEVEL").unwrap_or_else(|_| default_log_level()), telemetry_stdout: env::var("TELEMETRY_STDOUT") .unwrap_or_else(|_| "true".to_string()) .parse() diff --git a/llm-research-lab/src/main.rs b/llm-research-lab/src/main.rs index 68c0076..36f7777 100644 --- a/llm-research-lab/src/main.rs +++ b/llm-research-lab/src/main.rs @@ -47,17 +47,14 @@ use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; use llm_research_agents::{RuVectorClient, RuVectorPersistence}; // Agentics Execution System imports -use llm_research_agents::{ - ExecutionResult, ExecutionSpan, - validate_execution_context, -}; +use llm_research_agents::{validate_execution_context, ExecutionResult, ExecutionSpan}; mod config; // Agent imports use llm_research_agents::handlers::{ - HypothesisHandler, HypothesisEvaluateRequest, HypothesisEvaluateResponse, - MetricHandler, MetricComputeRequest, MetricComputeResponse, + HypothesisEvaluateRequest, HypothesisEvaluateResponse, HypothesisHandler, MetricComputeRequest, + MetricComputeResponse, MetricHandler, }; /// Application state for Cloud Run deployment. @@ -81,7 +78,7 @@ impl CloudRunState { pub fn new(config: config::Config) -> Result { let hypothesis_handler = Arc::new( HypothesisHandler::new() - .map_err(|e| anyhow::anyhow!("Failed to create hypothesis handler: {}", e))? + .map_err(|e| anyhow::anyhow!("Failed to create hypothesis handler: {}", e))?, ); let metric_handler = Arc::new(MetricHandler::new()); @@ -98,10 +95,9 @@ async fn main() -> Result<()> { // Initialize tracing tracing_subscriber::registry() .with( - tracing_subscriber::EnvFilter::try_from_default_env() - .unwrap_or_else(|_| { - "llm_research_lab=debug,llm_research_agents=debug,tower_http=debug".into() - }), + tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| { + "llm_research_lab=debug,llm_research_agents=debug,tower_http=debug".into() + }), ) .with(tracing_subscriber::fmt::layer().json()) .init(); @@ -127,28 +123,26 @@ async fn main() -> Result<()> { // ========================================================================= info!("Validating Ruvector connectivity..."); - let ruvector_client = RuVectorClient::from_env() - .map_err(|e| { - error!( - error = %e, - phase = "phase7", - layer = "layer2", - "Failed to create Ruvector client. ABORTING STARTUP." - ); - anyhow::anyhow!("Failed to create Ruvector client: {}. ABORTING STARTUP.", e) - })?; + let ruvector_client = RuVectorClient::from_env().map_err(|e| { + error!( + error = %e, + phase = "phase7", + layer = "layer2", + "Failed to create Ruvector client. ABORTING STARTUP." + ); + anyhow::anyhow!("Failed to create Ruvector client: {}. ABORTING STARTUP.", e) + })?; - let healthy = ruvector_client.health_check().await - .map_err(|e| { - error!( - error = %e, - ruvector_url = %config.ruvector_service_url, - phase = "phase7", - layer = "layer2", - "Ruvector health check failed. ABORTING STARTUP." - ); - anyhow::anyhow!("Ruvector health check failed: {}. ABORTING STARTUP.", e) - })?; + let healthy = ruvector_client.health_check().await.map_err(|e| { + error!( + error = %e, + ruvector_url = %config.ruvector_service_url, + phase = "phase7", + layer = "layer2", + "Ruvector health check failed. ABORTING STARTUP." + ); + anyhow::anyhow!("Ruvector health check failed: {}. ABORTING STARTUP.", e) + })?; if !healthy { error!( @@ -191,20 +185,18 @@ async fn main() -> Result<()> { // Health endpoints .route("/health", get(health_check)) .route("/ready", get(readiness_check)) - // Agent endpoints (per service topology) .route("/api/v1/agents/hypothesis", post(hypothesis_evaluate)) .route("/api/v1/agents/hypothesis", get(hypothesis_info)) .route("/api/v1/agents/metric", post(metric_compute)) .route("/api/v1/agents/metric", get(metric_info)) .route("/api/v1/agents", get(list_agents)) - // Middleware .layer( CorsLayer::new() .allow_origin(Any) .allow_methods(Any) - .allow_headers(Any) + .allow_headers(Any), ) .layer(TraceLayer::new_for_http()) .with_state(state); @@ -246,36 +238,37 @@ async fn health_check() -> &'static str { /// PHASE 7 LAYER 2: This endpoint now performs an ACTUAL health check /// against ruvector-service. Cloud Run will not route traffic to this /// instance if Ruvector is unhealthy or unreachable. -async fn readiness_check(State(_state): State) -> (StatusCode, &'static str) { +async fn readiness_check(State(state): State) -> (StatusCode, &'static str) { + let ruvector_url = &state.config.ruvector_service_url; + // Phase 7 Layer 2: Create a client and check Ruvector health match RuVectorClient::from_env() { - Ok(client) => { - match client.health_check().await { - Ok(true) => { - (StatusCode::OK, "READY") - } - Ok(false) => { - warn!( - phase = "phase7", - layer = "layer2", - "Readiness check: Ruvector unhealthy" - ); - (StatusCode::SERVICE_UNAVAILABLE, "RUVECTOR_UNHEALTHY") - } - Err(e) => { - warn!( - error = %e, - phase = "phase7", - layer = "layer2", - "Readiness check: Ruvector unreachable" - ); - (StatusCode::SERVICE_UNAVAILABLE, "RUVECTOR_UNREACHABLE") - } + Ok(client) => match client.health_check().await { + Ok(true) => (StatusCode::OK, "READY"), + Ok(false) => { + warn!( + ruvector_url = %ruvector_url, + phase = "phase7", + layer = "layer2", + "Readiness check: Ruvector unhealthy" + ); + (StatusCode::SERVICE_UNAVAILABLE, "RUVECTOR_UNHEALTHY") } - } + Err(e) => { + warn!( + error = %e, + ruvector_url = %ruvector_url, + phase = "phase7", + layer = "layer2", + "Readiness check: Ruvector unreachable" + ); + (StatusCode::SERVICE_UNAVAILABLE, "RUVECTOR_UNREACHABLE") + } + }, Err(e) => { error!( error = %e, + ruvector_url = %ruvector_url, phase = "phase7", layer = "layer2", "Readiness check: Configuration error" @@ -307,7 +300,10 @@ async fn readiness_check(State(_state): State) -> (StatusCode, &' async fn hypothesis_evaluate( State(state): State, Json(request): Json, -) -> (StatusCode, Json>) { +) -> ( + StatusCode, + Json>, +) { // ENFORCEMENT: Validate execution context - reject if missing let exec_ctx = match validate_execution_context(&request.execution_context) { Ok(ctx) => ctx.clone(), @@ -421,8 +417,7 @@ async fn metric_compute( } else { let reason = response .error - .as_ref() - .map(|e| e.clone()) + .clone() .unwrap_or_else(|| "Unknown failure".to_string()); repo_span.fail(reason); }